use std::{fmt, str}; // ------------- Player #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Player { Player0, Player1, } impl From for Player { fn from(item: u8) -> Self { match item { 0 => Player::Player0, 1 => Player::Player1, _ => Player::Player0, } } } impl From for u8 { fn from(player: Player) -> u8 { match player { Player::Player0 => 0, Player::Player1 => 1, } } } impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let repr = match self { Player::Player0 => "x", Player::Player1 => "o", }; let mut s = String::new(); s.push_str(repr); write!(f, "{s}") } } // ------------- CellState #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CellState { Cross, Nought, Empty, } impl From for CellState { fn from(player: Player) -> CellState { match player { Player::Player0 => CellState::Cross, Player::Player1 => CellState::Nought, } } } impl fmt::Display for CellState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let repr = match self { CellState::Cross => "x", CellState::Nought => "o", CellState::Empty => ".", }; let mut s = String::new(); s.push_str(repr); write!(f, "{s}") } } // ------------ Board const NUM_CELLS: usize = 9; const NUM_ROWS: usize = 3; const NUM_COLS: usize = 3; struct Board { cells: Vec, } impl Default for Board { fn default() -> Self { Board { cells: vec![CellState::Empty; NUM_CELLS], } } } impl Board { fn has_line(&self, player: Player) -> bool { let c: CellState = player.into(); (self.cells[0] == c && self.cells[1] == c && self.cells[2] == c) || (self.cells[3] == c && self.cells[4] == c && self.cells[5] == c) || (self.cells[6] == c && self.cells[7] == c && self.cells[8] == c) || (self.cells[0] == c && self.cells[3] == c && self.cells[6] == c) || (self.cells[1] == c && self.cells[4] == c && self.cells[7] == c) || (self.cells[2] == c && self.cells[5] == c && self.cells[8] == c) || (self.cells[0] == c && self.cells[4] == c && self.cells[8] == c) || (self.cells[2] == c && self.cells[4] == c && self.cells[6] == c) } fn set_cell_state(&mut self, position: usize, state: CellState) { self.cells[position] = state; } fn cell_at(&self, row: usize, col: usize) -> CellState { self.cells[row * NUM_COLS + col] } } // -------------- GameState type Action = usize; pub struct GameState { pub current_player: Option, pub outcome: Option, pub board: Board, pub num_moves: usize, } impl Default for GameState { fn default() -> Self { GameState { current_player: Some(Player::Player0), outcome: None, board: Board::default(), num_moves: 0, } } } impl GameState { fn is_terminal(&self) -> bool { return self.outcome != None || self.is_full(); } fn is_full(&self) -> bool { self.num_moves == NUM_CELLS } pub fn do_action(&mut self, action: Action) { if self.board.cells[action] != CellState::Empty { return; } if let Some(player) = self.current_player { self.board.cells[action] = player.into(); if self.board.has_line(player) { self.outcome = self.current_player; } self.change_player(); self.num_moves += 1; } } fn undo_action(&mut self, player: Player, action: Action) { self.board.set_cell_state(action.into(), CellState::Empty); self.current_player = Some(player); self.outcome = None; self.num_moves -= 1; } pub fn legal_actions(&self) -> Vec { // if (self.is_terminal()) return vec![]; // Can move in any empty cell. self.board .cells .iter() .enumerate() .filter(|(_, cs)| **cs == CellState::Empty) .map(|(pos, _)| pos) .collect() } fn change_player(&mut self) { self.current_player = if self.current_player == Some(Player::Player0) { Some(Player::Player1) } else { Some(Player::Player0) }; } } impl fmt::Display for GameState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut s = String::new(); for row in 0..NUM_ROWS { for col in 0..NUM_COLS { s.push_str(self.board.cell_at(row, col).to_string().as_str()); } s.push('\n'); } write!(f, "{s}") } }