Last active
May 31, 2025 05:45
-
-
Save analytik/a809a50b90272aed8faf5ceb2149e165 to your computer and use it in GitHub Desktop.
Tic Tac Toe in console in Rust with no dependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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