diff --git a/src/board.rs b/src/board.rs index b495f61..a532311 100644 --- a/src/board.rs +++ b/src/board.rs @@ -118,6 +118,12 @@ impl fmt::Display for Board { } } +impl fmt::Debug for Board { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + impl Board { /// Create a new empty board pub const fn new() -> Self { diff --git a/src/complexagent.rs b/src/complexagent.rs index d91f99f..a1e0259 100644 --- a/src/complexagent.rs +++ b/src/complexagent.rs @@ -5,7 +5,7 @@ use either::Either; use indicatif::{ProgressBar, ProgressIterator, ProgressStyle}; use num::Integer; -#[derive(Clone)] +#[derive(Clone, Debug)] struct Move { /// `i` position of move i: usize, @@ -32,7 +32,7 @@ struct Move { } impl Move { - fn compute_self_value(&self, agent_color: Piece, depth: usize) -> i64 { + fn compute_self_value(&self, agent_color: Piece, depth: i64) -> i64 { let mut self_value = self.value; if self.winner == Some(!agent_color) { @@ -45,14 +45,14 @@ impl Move { self_value = i64::MAX; } - self_value / depth as i64 + self_value / depth } } struct FutureMoves { arena: Vec, current_root: Option, - current_depth: usize, + current_depth: isize, max_depth: usize, @@ -77,7 +77,7 @@ impl FutureMoves { .filter(|&idx| self.arena[idx].children.is_empty()) .collect(); - for _ in (self.current_depth..self.max_depth).progress() { + for _ in self.current_depth..=(self.max_depth as isize) { let arena_len = self.arena.len(); next_nodes = next_nodes .into_iter() @@ -98,6 +98,7 @@ impl FutureMoves { }) .collect(); } + self.current_depth = self.max_depth as isize; } /// Creates children for a parent (`parent`), returns an iterator it's children's indexes @@ -143,6 +144,19 @@ impl FutureMoves { new_indices } + /// Given an index from `self.arena`, what depth is it at? 1-indexed (ROOT IS AT INDEX 1) + fn depth_of(&self, node_idx: usize) -> NonZero { + let mut depth = 0; + let mut current = Some(node_idx); + while let Some(parent_idx) = current { + depth += 1; + current = self.arena[parent_idx].parent; + } + + // SAFETY! because `node_idx` is of type `usize`, depth will never be 0 + unsafe { NonZero::new_unchecked(depth) } + } + fn compute_values(&mut self) { // PERF! doing this filtering here previously visited moves reduces time // spent in `depth_of` from 27.79% -> 2.9% @@ -158,12 +172,12 @@ impl FutureMoves { true } }) - .filter(|&idx| self.depth_of(idx).get() == depth) + .filter(|&idx| self.depth_of(idx).get() == depth as usize) .collect(); for idx in nodes_at_depth { let self_value = self.arena[idx] - .compute_self_value(self.agent_color, self.current_depth - depth + 1); + .compute_self_value(self.agent_color, (self.current_depth - depth + 1) as i64); let children_value = self.arena[idx] .children @@ -177,70 +191,72 @@ impl FutureMoves { } } } - - /// Given an index from `self.arena`, what depth is it at? 1-indexed (ROOT IS AT INDEX 1) - fn depth_of(&self, node_idx: usize) -> NonZero { - let mut depth = 0; - let mut current = Some(node_idx); - while let Some(parent_idx) = current { - depth += 1; - current = self.arena[parent_idx].parent; - } - - // SAFETY! because `node_idx` is of type `usize`, depth will never be 0 - unsafe { NonZero::new_unchecked(depth) } - } - pub fn best_move(&self) -> Option<(usize, usize)> { - (match self.current_root { - Some(root) => Either::Left(vec![root].into_iter()), - None => { - Either::Right((0..self.arena.len()).filter(|&idx| self.depth_of(idx).get() == 1)) - } - }) - .map(|idx| &self.arena[idx]) - .max_by_key(|x| x.value) - .map(|x| (x.i, x.j)) + self.current_root + .and_then(|x| { + self.arena[x] + .children + .iter() + .max_by_key(|&&idx| self.arena[idx].value) + }) + .inspect(|&&x| { + assert_eq!( + self.arena[x].color, self.agent_color, + "selected move color should be the same as the color of the agent" + ); + }) + .map(|&x| (self.arena[x].i, self.arena[x].j)) } + /// Updates `FutureMoves` based on the current state of the board + /// The board is supposed to be after the opposing move pub fn update(&mut self, board: &Board) { - self.current_root = Some(self.current_root.unwrap_or_else(|| { - self.arena - .iter() - .enumerate() - .find_map(|(idx, m)| (&m.board == board).then_some(idx)) - .unwrap_or_else(|| { - self.arena.push(Move { - i: 0, - j: 0, - board: *board, - winner: None, - parent: None, - children: Vec::new(), - value: 0, - color: !self.agent_color, - }); - 0 - }) - })); + let curr_board = self + .arena + .iter() + .enumerate() + .find(|(_, m)| { + &m.board == board && (m.parent == self.current_root) && self.current_root.is_some() + }) + .map(|(idx, _)| idx); - self.extend_layers(); + if let Some(curr_board_idx) = curr_board { + self.current_root = Some(curr_board_idx); + self.update_root_idx(curr_board_idx); + } else { + println!("Generating root of FutureMoves"); + self.arena.clear(); + self.arena.push(Move { + i: 0, + j: 0, + board: *board, + winner: None, + parent: None, + children: Vec::new(), + value: 0, + color: !self.agent_color, + }); + self.update_root_idx(0); + } } - pub fn update_root(&mut self, i: usize, j: usize) -> bool { + pub fn update_root_coord(&mut self, i: usize, j: usize) -> bool { self.arena .iter() .enumerate() .find_map(|(idx, node)| { (node.parent == self.current_root && node.i == i && node.j == j).then_some(idx) }) - .is_some_and(|root| { - self.current_root = Some(root); - self.current_depth = self.max_depth - self.depth_of(root).get(); - self.prune_unrelated(); - self.extend_layers(); - true - }) + .inspect(|&root| self.update_root_idx(root)) + .is_some() + } + + fn update_root_idx(&mut self, idx: usize) { + self.current_root = Some(idx); + self.current_depth -= self.depth_of(idx).get() as isize; + self.prune_unrelated(); + self.extend_layers(); + self.compute_values(); } fn prune_unrelated(&mut self) { @@ -298,7 +314,7 @@ pub struct ComplexAgent { impl ComplexAgent { pub const fn new(color: Piece) -> Self { - const MAX_DEPTH: usize = 17; + const MAX_DEPTH: usize = 10; Self { color, future_moves: FutureMoves::new(color, MAX_DEPTH), @@ -309,12 +325,11 @@ impl ComplexAgent { impl Agent for ComplexAgent { fn next_move(&mut self, board: &Board) -> Option<(usize, usize)> { self.future_moves.update(board); - self.future_moves.compute_values(); println!("# of moves stored: {}", self.future_moves.arena.len()); self.future_moves.best_move().inspect(|&(i, j)| { - self.future_moves.update_root(i, j); + self.future_moves.update_root_coord(i, j); }) } diff --git a/src/game.rs b/src/game.rs index 39ff315..a6943e3 100644 --- a/src/game.rs +++ b/src/game.rs @@ -60,7 +60,10 @@ impl Game { // Lets the player try again if the move is invalid // Now we dont need to restart the game if we mess up Err(err) => { - panic!("Invalid move by Player {}: {}. Try again.", player_i, err); + panic!( + "Invalid move by Player {}: {}. Try again. Move: ({}, {})", + player_i, err, i, j + ); } } } else { diff --git a/src/main.rs b/src/main.rs index 37db162..a0b4902 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,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 = agent::RandomAgent::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(); }