Skip to content

Instantly share code, notes, and snippets.

@analytik
Last active May 31, 2025 05:45
Show Gist options
  • Select an option

  • Save analytik/a809a50b90272aed8faf5ceb2149e165 to your computer and use it in GitHub Desktop.

Select an option

Save analytik/a809a50b90272aed8faf5ceb2149e165 to your computer and use it in GitHub Desktop.
Tic Tac Toe in console in Rust with no dependencies
use std::{fmt, io};
use std::fmt::{Display, Formatter};
use std::time::SystemTime;
const SPACER: &str = " ";
impl Display for Field {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let result: char = match self {
Field::Empty => ' ',
Field::Cross => 'X',
Field::Circle => 'O',
};
write!(f, "{}", result)
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
enum Field {
Empty,
Cross,
Circle,
}
impl From<Player> for Field {
fn from(player: Player) -> Field {
match player {
Player::Crosses => Field::Cross,
Player::Circles => Field::Circle,
}
}
}
#[derive(Copy, Clone, PartialEq)]
enum Player {
Crosses,
Circles,
}
impl From<Field> for Option<Player> {
fn from(field: Field) -> Option<Player> {
match field {
Field::Cross => Some(Player::Crosses),
Field::Circle => Some(Player::Circles),
_ => None,
}
}
}
struct GameState {
crosses_name: String,
circles_name: String,
arena: [[Field; 3]; 3],
current_turn: Player,
turn: u8,
victory: bool,
}
fn main() {
println!("start of program");
let arena= [[Field::Empty; 3]; 3];
let mut game:GameState = GameState {
crosses_name: String::from("Player One"),
circles_name: String::from("Player Two"),
current_turn: Player::Circles,
turn: 1,
victory: false,
arena
};
print_intro();
ask_for_player_names(&mut game);
// randomly choose a starting player
if cheap_random() {
game.current_turn = Player::Crosses;
}
loop {
let round_text: String = format!("Round {}", game.turn);
println!("\n├──────────────────── {} ────────────────────┤", round_text);
ask_for_input(&mut game);
draw_grid(&game);
evaluate_winner(&mut game);
if game.victory {
println!("\n 🌈 You won the game! Congratulations! Please buy our DLC. ✨\n");
break;
}
else if game.turn > 9 {
println!("\nYou're out of turns! There's nowhere else to go!!!");
break;
}
continue;
}
// todo ask if play again or not
println!("end of program");
}
fn evaluate_winner(g: &mut GameState) {
// todo split into evaluate_line evaluate_column and evaluate_diagonals?
// this will be ugly and inefficient at first either way
let mut victory = false;
let mut victor:Option<Player> = None;
// check for horizontal victory
for row in g.arena {
if row[0] != Field::Empty && row[0] == row[1] && row[0] == row[2] {
victor = row[0].into();
victory = true;
}
}
if !victory {
// check for vertical victory
let column_count = g.arena[0].len();
for i in 0..column_count {
if g.arena[0][i] != Field::Empty
&& g.arena[0][i] == g.arena[1][i]
&& g.arena[0][i] == g.arena[2][i]
{
victor = g.arena[0][i].into();
victory = true;
}
}
}
if !victory {
// check for diagonal victory
let middle = g.arena[1][1];
if middle != Field::Empty {
if (middle == g.arena[0][0] && middle == g.arena[2][2])
|| (middle == g.arena[2][0] && middle == g.arena[0][2]) {
victor = middle.into();
victory = true;
}
}
}
if victory {
let victor:Player = victor.unwrap();
let winner_name:&String = if victor == Player::Circles {
&g.circles_name
} else {
&g.crosses_name
};
println!(" ୭ ˚⊹ .ᐟ‧₊˚⊹ ᰔ Congratulations to the winner, esteemed {} 。:゚૮ ˶ˆ ﻌ ˆ˶ ა ゚:。", winner_name);
g.victory = true;
}
}
fn ask_for_player_names(g: &mut GameState) {
println!("Please enter name for player 1 - Circles");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
g.circles_name = input.trim().to_string();
println!("Please enter name for player 2 - Crosses");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
g.crosses_name = input.trim().to_string();
}
fn ask_for_input(g: &mut GameState) {
let player_name = match g.current_turn {
Player::Circles => &g.circles_name,
Player::Crosses => &g.crosses_name,
};
println!("Player {} enter your move", player_name);
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let input = input.trim().to_uppercase();
if input.len() != 2 {
println!("You have entered an invalid string. I'm not playing with you.");
return;
}
let row:usize = match &input[0..1] {
"A" => 0,
"B" => 1,
"C" => 2,
_ => 99, //TODO use Option!!!! dummy
};
// this is an awkward way to silently fail if player enters something like "ww"
let column: usize = input[1..2].parse().unwrap_or(99);
let column: usize = column - 1;
if row >= 3 || column >= 3 {
println!("only A1-A3 B1-B3 C1-C3 are valid moves!!!");
return;
}
if g.arena[row][column] != Field::Empty {
println!("That field is already taken! Bad!!!!");
return;
}
g.arena[row][column] = g.current_turn.into();
// println!("Setting array element {row}x{column} to {}", g.arena[row][column]);
g.current_turn = match g.current_turn {
Player::Crosses => Player::Circles,
Player::Circles => Player::Crosses,
};
g.turn += 1;
}
fn print_intro() {
println!("Tic Tac Toe aka Crosses and Circles, version 1\n");
println!("How to play: Players take turns. I mean...");
println!("I don't have to explain the game to you.");
println!("The only thing is, due to limited budget,");
println!("we couldn't afford the cursor keys.");
println!("You have the enter fields like this:\n");
println!("{SPACER}┌──┬──┬──┐");
println!("{SPACER}│A1│A2│A3│");
println!("{SPACER}├──┼──┼──┤");
println!("{SPACER}│B1│B2│B3│");
println!("{SPACER}├──┼──┼──┤");
println!("{SPACER}│C1│C2│C3│");
println!("{SPACER}└──┴──┴──┘");
}
fn draw_grid(game: &GameState) {
let a = game.arena;
// ┼ ┴┬┤├ └┘┐┌ │─
println!("{SPACER} 1 2 3");
println!("{SPACER} ┌───┬───┬───┐");
println!("{SPACER}A │ {} │ {} │ {} │", a[0][0], a[0][1], a[0][2]);
println!("{SPACER} ├───┼───┼───┤");
println!("{SPACER}B │ {} │ {} │ {} │", a[1][0], a[1][1], a[1][2]);
println!("{SPACER} ├───┼───┼───┤");
println!("{SPACER}C │ {} │ {} │ {} │", a[2][0], a[2][1], a[2][2]);
println!("{SPACER} └───┴───┴───┘");
}
fn cheap_random() -> bool {
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
return now.subsec_millis() > 500;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment