From ec0eb7f849aa0e18c9f8bb08970bd414f7dc6621 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Sat, 8 Feb 2025 22:47:44 -0500 Subject: [PATCH] allow the game to end in cases of a non-full board --- src/board.rs | 28 ++++++++++------------------ src/complexagent.rs | 25 +++++++++++++++---------- src/game.rs | 34 ++++------------------------------ src/main.rs | 4 ++-- 4 files changed, 31 insertions(+), 60 deletions(-) diff --git a/src/board.rs b/src/board.rs index 7ab8023..5f525af 100644 --- a/src/board.rs +++ b/src/board.rs @@ -48,14 +48,6 @@ impl fmt::Display for Board { white_score, black_score )?; - // Print game over screen - if self.game_over() { - match self.get_winner() { - Some(piece) => writeln!(f, "{} Wins", piece.text()), - None => writeln!(f, "Tie"), - }? - } - Ok(()) } } @@ -207,24 +199,24 @@ impl Board { .fold((0_usize, 0usize), |(a, b), (c, d)| (a + c, b + d)) } - // Get the winning piece (for game over screen mainly) - pub fn get_winner(&self) -> Option { + pub fn game_winner(&self, turn: Piece) -> Option { let (white_score, black_score) = self.get_score(); + let max_score = BOARD_SIZE * BOARD_SIZE; + let combined_score = black_score + white_score; + if max_score != combined_score { + return None; + } + // if current player cannot make a move + if self.possible_moves(turn).count() > 0 { + return None; + } match white_score.cmp(&black_score) { Ordering::Greater => Some(Piece::White), // White win Ordering::Less => Some(Piece::Black), // Black win Ordering::Equal => None, // Tie } } - - pub fn game_over(&self) -> bool { - let (white_score, black_score) = self.get_score(); - let max_score = BOARD_SIZE * BOARD_SIZE; - let combined_score = black_score + white_score; - - max_score == combined_score - } } #[cfg(test)] diff --git a/src/complexagent.rs b/src/complexagent.rs index 389bdd6..bdcb15a 100644 --- a/src/complexagent.rs +++ b/src/complexagent.rs @@ -30,39 +30,43 @@ impl Move { } fn value(&self, agent_color: Piece, depth: usize) -> i64 { - let mut captured_value = self.captured as i64; + let mut self_value = self.captured as i64; - if self.board.game_over() && self.board.get_winner() != Some(!agent_color) { + if self.board.game_winner(agent_color) != Some(!agent_color) { // if this board results in the opponent winning, MAJORLY negatively weigh this move - captured_value = i64::MIN; + // NOTE! this branch isn't completely deleted because if so, the bot wouldn't make a move. + // We shouldn't prune branches because we still need to always react to the opponent's moves + self_value = i64::MIN; } else if agent_color != self.color { - captured_value = -captured_value; + self_value = -self_value; } // Reduce value of capture based on depth of prediction - captured_value /= depth as i64; + self_value /= depth as i64; - let avg_next_move_value = (self + let avg_next_move_value = self .next_move .iter() .map(|x| x.value(agent_color, depth + 1)) .sum::() - .checked_div(self.next_move.len() as i64)) - .unwrap_or(0); + .checked_div(self.next_move.len() as i64) + .unwrap_or(0); - captured_value + avg_next_move_value + self_value + avg_next_move_value } + /// Returns the # of moves in the entire tree pub fn len(&self) -> u32 { self.next_move.len() as u32 + self.next_move.iter().map(Move::len).sum::() } + /// Returns a tuple containing the `i` and `j` fields pub const fn coords(&self) -> (usize, usize) { (self.i, self.j) } /// Cursed function to create a dummy move type from a color and board - /// Used to bootstrap [`ComplexAgent`] + /// Used to bootstrap [`ComplexAgent`]'s future moves pub fn bootstrap(color: Piece, board: &Board) -> Self { Move { i: 0, @@ -109,6 +113,7 @@ impl Agent for ComplexAgent { .take() .unwrap_or_else(|| Move::bootstrap(self.color, board)); + // determine the move the other player made via the board state let mut other_player_move = curr_move .next_move .into_iter() diff --git a/src/game.rs b/src/game.rs index 797b561..66798f3 100644 --- a/src/game.rs +++ b/src/game.rs @@ -51,6 +51,7 @@ impl Game { } } else { println!("Player {} did not make a move!", player_i); + // TODO! break, player should not be able to skip a move return; // No valid move available } } @@ -67,36 +68,9 @@ impl Game { println!("{}", self); - // TODO! what if the board isn't full, but one player cannot win as - // they don't have any moves to play? - // Example:: - // Player 1 placed at (1, 0) - // Players: Complex Agent (□) and Manual Agent (■) - // 0 1 2 3 4 5 6 7 - // ----------------- - // 0|□|□|□|□|□|□|□|□| - // ----------------- - // 1|■|■|■|□|□|□| |□| - // ----------------- - // 2|■|■|□|■|□|□|□|□| - // ----------------- - // 3|■|■|□|□|■|□|□|□| - // ----------------- - // 4|■|■|□|□|■|□|□|□| - // ----------------- - // 5|□|□|■|□|■|□|□|□| - // ----------------- - // 6| |□|□|□|□|□|□|□| - // ----------------- - // 7| | | |■|■| |■| | - // ----------------- - // White Score: 17 - // Black Score: 40 - - // (depth: 5) possible board states: 0 - // thread 'main' panicked at src/complexagent.rs:139:9: - // ComplexAgent didn't make a move - if self.board.game_over() { + if let Some(game_winner) = self.board.game_winner(self.players[current_player].color()) + { + println!("{} Wins!", game_winner.text()); // end the game break; } diff --git a/src/main.rs b/src/main.rs index 5c9bb14..964522b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ mod piece; fn main() { let player1 = complexagent::ComplexAgent::new(Piece::Black); - // let player2 = complexagent::ComplexAgent::new(Piece::White); - let player2 = agent::ManualAgent::new(Piece::White); + let player2 = complexagent::ComplexAgent::new(Piece::White); + // let player2 = agent::ManualAgent::new(Piece::White); // let player2 = agent::RandomAgent::new(Piece::White); let mut game = Game::new(Box::new(player1), Box::new(player2)); game.game_loop();