diff --git a/Cargo.lock b/Cargo.lock index 07cc37a..2ea8a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,74 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "tic-tac-toe" version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index f587244..20f4cae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rand = "0.8.5" diff --git a/src/main.rs b/src/main.rs index e73bb04..4f50876 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use rand::prelude::*; use std::str::FromStr; /// Represents the `item` on the TTT board (X or O) @@ -29,21 +30,12 @@ impl TryFrom for Item { } } -/// Two `GameType` availables: -/// * TwoPlayers: no bot -/// * Bot: player against the bot -enum GameType { - TwoPlayers, - Bot, - Unknown, -} - -impl From for GameType { - fn from(game_type: String) -> Self { - match &game_type[..] { - "0" => Self::TwoPlayers, - "1" => Self::Bot, - _ => Self::Unknown, +impl Item { + fn next(&self) -> Self { + match self { + Item::X => Item::O, + Item::O => Item::X, + Item::Empty => Item::Empty, } } } @@ -91,21 +83,27 @@ impl Board { true } - fn set_item(&mut self, item: Item, pos: String) -> Result { - let idx = if let Ok(v) = usize::from_str(&pos) { - v - } else { - return Err("unable to parse the input, retry!"); - }; + /// returns the available/free board indexes + fn get_available_indexes(&self) -> Vec { + let mut availables = vec![]; + for (idx, item) in self.items.iter().enumerate() { + if item == &Item::Empty { + availables.push(idx); + } + } + availables + } + + fn set_item(&mut self, item: Item, idx: usize) -> Result<(), &str> { if idx == 0 || idx > 9 { - return Err("can't be greater than 8"); + return Err("invalid input, must be between 1 <= x <= 9"); } match self.items[idx - 1] { Item::X => Err("already set, choose another one!"), Item::O => Err("already set, choose another one!"), Item::Empty => { self.items[idx - 1] = item; - return Ok(idx - 1); + return Ok(()); } } } @@ -200,8 +198,9 @@ impl Displayer for Board { } }) .collect(); + for item in items { - group_items.push(item.clone()); + group_items.push(item); if group_items.len() != 3 { continue; } @@ -214,10 +213,68 @@ impl Displayer for Board { } } +#[derive(Copy, Clone)] +struct Player { + item: Item, + is_bot: bool, +} + +impl Player { + fn new(item: Item, is_bot: bool) -> Self { + Player { item, is_bot } + } +} + +impl Default for Player { + fn default() -> Self { + Player { + item: Item::Empty, + is_bot: false, + } + } +} + +impl Player { + /// init players with the game type + fn from_game_type(game_type: GameType) -> Vec { + match game_type { + GameType::TwoPlayers => vec![ + Player::new(Item::Empty, false), + Player::new(Item::Empty, false), + ], + GameType::Bot => vec![ + Player::new(Item::Empty, false), + Player::new(Item::Empty, true), + ], + GameType::Unknown => vec![], + } + } +} + +/// Two `GameType` availables: +/// * TwoPlayers: no bot +/// * Bot: player against the bot +#[derive(Copy, Clone)] +enum GameType { + TwoPlayers, + Bot, + Unknown, +} + +impl From for GameType { + fn from(game_type: String) -> Self { + match &game_type[..] { + "0" => Self::TwoPlayers, + "1" => Self::Bot, + _ => Self::Unknown, + } + } +} + #[allow(dead_code)] struct TicTacToe { board: Board, - current_item: Item, + players: Vec, game_type: GameType, turn: u8, } @@ -226,14 +283,15 @@ impl Default for TicTacToe { fn default() -> Self { TicTacToe { board: Board::new(), - game_type: GameType::Bot, - current_item: Item::Empty, + game_type: GameType::Unknown, + players: vec![], turn: 1, } } } impl TicTacToe { + /// associated func in order to get user input fn get_user_input(message: &str) -> String { info(message.to_string()); let mut input = String::new(); @@ -243,12 +301,40 @@ impl TicTacToe { input.trim().to_string() } - fn set_current_item(&mut self, item: Item) { - self.current_item = item; + fn set_game_type(&mut self, game_type: GameType) { + self.game_type = game_type; } - /// tries to get the first item (the one that starts) - fn select_current_item(&mut self) { + fn set_players_item(&mut self, first_item: Item) -> Result<(), String> { + if self.players.len() != 2 { + return Err( + "unable to initialize players item, 2 players must be initialized".to_string(), + ); + } + + self.players[0].item = first_item; + self.players[1].item = first_item.next(); + + Ok(()) + } + + /// on each turn, set the next player who has to play (players vec swapped) + fn get_next_player(&mut self) -> Result { + if self.players.len() != 2 { + return Err("unable to get next player, 2 players must be initialized".to_string()); + } + + // on first turn, no need to swap + if self.turn > 1 { + self.players.swap(0, 1); + } + self.turn += 1; + + Ok(self.players[0]) + } + + /// set the item for the first player (the one that starts) + fn select_first_player_item(&mut self) { let mut attempt = 0; loop { if attempt == 2 { @@ -256,12 +342,14 @@ impl TicTacToe { std::process::exit(1); } - let item_start = - TicTacToe::get_user_input("first player, choose your item: ('X' or 'O')"); - let item = Item::try_from(item_start); - match item { - Ok(i) => { - self.set_current_item(i); + let input = TicTacToe::get_user_input("first player, choose your item: ('X' or 'O')"); + let item_start = Item::try_from(input); + match item_start { + Ok(item) => { + match self.set_players_item(item) { + Ok(()) => break, + Err(e) => fatal(e), + } break; } Err(e) => error(e.to_string()), @@ -270,19 +358,60 @@ impl TicTacToe { } } - /// on each turn, set the next item who has to play - fn next_item(&mut self) { - match self.current_item { - Item::X => self.set_current_item(Item::O), - Item::O => self.set_current_item(Item::X), - _ => return, + /// select game type and init players + fn select_game_type(&mut self) { + let mut attempt = 0; + loop { + if attempt == 2 { + error("come on... you can do better!".to_string()); + std::process::exit(1); + } + + let input = TicTacToe::get_user_input("choose your game type: ('0': versus, '1': bot)"); + let game_type = GameType::from(input); + match game_type { + GameType::Unknown => error("incorrect game type selected, retry!".to_string()), + _ => { + self.set_game_type(game_type); + break; + } + } + attempt += 1; } - self.turn += 1; + // init players + self.players = Player::from_game_type(self.game_type); + } + + fn select_player_item(&self) -> Result { + if self.players.len() != 2 { + return Err( + "unable to get player item input, 2 players must be initialized".to_string(), + ); + } + + let player = self.players[0]; + let item_icon: String = player.item.into(); + + if !player.is_bot { + let turn_message = format!("{} turn, which case do you want to fill ?", item_icon); + let idx = TicTacToe::get_user_input(&turn_message); + match usize::from_str(&idx) { + Ok(v) => return Ok(v), + Err(e) => return Err(e.to_string()), + } + } + + // for now, use a dummy bot + let mut rng = rand::thread_rng(); + let mut available_indexes = self.board.get_available_indexes(); + available_indexes.shuffle(&mut rng); + + Ok(available_indexes[0] + 1) } fn run(&mut self) { - self.select_current_item(); - self.board.show(); + self.select_game_type(); + self.select_first_player_item(); loop { if self.board.is_full() { @@ -291,39 +420,57 @@ impl TicTacToe { break; } - let item_icon: String = self.current_item.into(); - let turn_message = format!( - "{} turn, which case do you wan to fill ?", - item_icon.clone() - ); - let idx = TicTacToe::get_user_input(&turn_message); - match self.board.set_item(self.current_item.clone(), idx) { - Ok(i) => { - // no need to check for a winner until the turn 5 - if self.turn > 4 && self.board.check_win(self.current_item.clone(), i) { - info(format!("{} wins!", item_icon.clone())); - self.board.show(); - break; + let mut player = Player::default(); + match self.get_next_player() { + Ok(p) => player = p, + Err(e) => fatal(e), + } + + if !player.is_bot { + self.board.show(); + } + + let item_icon: String = player.item.into(); + let mut item_position; + + loop { + match self.select_player_item() { + Ok(pos) => { + item_position = pos; + match self.board.set_item(player.item, pos) { + Ok(_) => break, + Err(e) => { + error(e.to_string()); + } + } + } + Err(e) => { + error(e.to_string()); } - } - Err(e) => { - error(e.to_string()); - continue; } } - self.next_item(); - self.board.show(); + // check after the 5th round if there's a winner + if self.turn > 4 && self.board.check_win(player.item, item_position - 1) { + info(format!("{} wins!", item_icon)); + self.board.show(); + break; + } } } } fn info(msg: String) { - println!("{}", format!("[TIC TAC TOE] [INFO] => {}", msg)) + println!("{}", format!("[tic-tac-toe] [info] => {}", msg)) } fn error(msg: String) { - eprintln!("{}", format!("[TIC TAC TOE] [ERROR] => {}", msg)) + eprintln!("{}", format!("[tic-tac-toe] [error] => {}", msg)) +} + +fn fatal(msg: String) { + eprintln!("{}", format!("[tic-tac-toe] [fatal] => {}", msg)); + std::process::exit(1); } fn main() {