allow the game to end in cases of a non-full board

This commit is contained in:
Simon Gardling 2025-02-08 22:47:44 -05:00
parent 5c27572f9f
commit ec0eb7f849
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
4 changed files with 31 additions and 60 deletions

View File

@ -48,14 +48,6 @@ impl fmt::Display for Board {
white_score, black_score 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(()) Ok(())
} }
} }
@ -207,24 +199,24 @@ impl Board {
.fold((0_usize, 0usize), |(a, b), (c, d)| (a + c, b + d)) .fold((0_usize, 0usize), |(a, b), (c, d)| (a + c, b + d))
} }
// Get the winning piece (for game over screen mainly) pub fn game_winner(&self, turn: Piece) -> Option<Piece> {
pub fn get_winner(&self) -> Option<Piece> {
let (white_score, black_score) = self.get_score(); 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) { match white_score.cmp(&black_score) {
Ordering::Greater => Some(Piece::White), // White win Ordering::Greater => Some(Piece::White), // White win
Ordering::Less => Some(Piece::Black), // Black win Ordering::Less => Some(Piece::Black), // Black win
Ordering::Equal => None, // Tie 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)] #[cfg(test)]

View File

@ -30,39 +30,43 @@ impl Move {
} }
fn value(&self, agent_color: Piece, depth: usize) -> i64 { 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 // 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 { } else if agent_color != self.color {
captured_value = -captured_value; self_value = -self_value;
} }
// Reduce value of capture based on depth of prediction // 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 .next_move
.iter() .iter()
.map(|x| x.value(agent_color, depth + 1)) .map(|x| x.value(agent_color, depth + 1))
.sum::<i64>() .sum::<i64>()
.checked_div(self.next_move.len() as i64)) .checked_div(self.next_move.len() as i64)
.unwrap_or(0); .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 { pub fn len(&self) -> u32 {
self.next_move.len() as u32 + self.next_move.iter().map(Move::len).sum::<u32>() self.next_move.len() as u32 + self.next_move.iter().map(Move::len).sum::<u32>()
} }
/// Returns a tuple containing the `i` and `j` fields
pub const fn coords(&self) -> (usize, usize) { pub const fn coords(&self) -> (usize, usize) {
(self.i, self.j) (self.i, self.j)
} }
/// Cursed function to create a dummy move type from a color and board /// 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 { pub fn bootstrap(color: Piece, board: &Board) -> Self {
Move { Move {
i: 0, i: 0,
@ -109,6 +113,7 @@ impl Agent for ComplexAgent {
.take() .take()
.unwrap_or_else(|| Move::bootstrap(self.color, board)); .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 let mut other_player_move = curr_move
.next_move .next_move
.into_iter() .into_iter()

View File

@ -51,6 +51,7 @@ impl Game {
} }
} else { } else {
println!("Player {} did not make a move!", player_i); 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 return; // No valid move available
} }
} }
@ -67,36 +68,9 @@ impl Game {
println!("{}", self); println!("{}", self);
// TODO! what if the board isn't full, but one player cannot win as if let Some(game_winner) = self.board.game_winner(self.players[current_player].color())
// they don't have any moves to play? {
// Example:: println!("{} Wins!", game_winner.text());
// 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() {
// end the game // end the game
break; break;
} }

View File

@ -10,8 +10,8 @@ mod piece;
fn main() { fn main() {
let player1 = complexagent::ComplexAgent::new(Piece::Black); let player1 = complexagent::ComplexAgent::new(Piece::Black);
// let player2 = complexagent::ComplexAgent::new(Piece::White); let player2 = complexagent::ComplexAgent::new(Piece::White);
let player2 = agent::ManualAgent::new(Piece::White); // let player2 = agent::ManualAgent::new(Piece::White);
// let player2 = agent::RandomAgent::new(Piece::White); // let player2 = agent::RandomAgent::new(Piece::White);
let mut game = Game::new(Box::new(player1), Box::new(player2)); let mut game = Game::new(Box::new(player1), Box::new(player2));
game.game_loop(); game.game_loop();