From 7049039a7716c9238b967feaffa281f0604d3303 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 17 Feb 2025 15:02:15 -0500 Subject: [PATCH] rewrite basically done --- src/board.rs | 19 +++++++++------ src/complexagent.rs | 59 +++++++++++++++++++++++---------------------- src/game.rs | 15 ++++++++---- src/main.rs | 4 +-- 4 files changed, 54 insertions(+), 43 deletions(-) diff --git a/src/board.rs b/src/board.rs index a532311..88f302d 100644 --- a/src/board.rs +++ b/src/board.rs @@ -59,6 +59,13 @@ fn gen_adj_lookup() -> PosMap { .collect() } +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Winner { + Player(Piece), + Tie, + None, +} + lazy_static! { /// Precompute all possible chains for each position on the board pub static ref ADJ_LOOKUP: PosMap = gen_adj_lookup(); @@ -263,22 +270,20 @@ impl Board { } /// Returns the winner of the board (if any) - pub fn game_winner(&self, turn: Piece) -> Option { + pub fn game_winner(&self, turn: Piece) -> Winner { // Wikipedia: `Players take alternate turns. If one player cannot make a valid move, play passes back to the other player. The game ends when the grid has filled up or if neither player can make a valid move.` if self.possible_moves(turn).next().is_some() || self.possible_moves(!turn).next().is_some() { // player can still make a move, there is no winner - return None; + return Winner::None; } let (white_score, black_score) = self.get_score(); match white_score.cmp(&black_score) { - Ordering::Greater => Some(Piece::White), // White win - Ordering::Less => Some(Piece::Black), // Black win - - // TODO! this will end up being parsed the same as a "no winner", it should be a seperate type - Ordering::Equal => None, // Tie + Ordering::Greater => Winner::Player(Piece::White), // White win + Ordering::Less => Winner::Player(Piece::Black), // Black win + Ordering::Equal => Winner::Tie, } } } diff --git a/src/complexagent.rs b/src/complexagent.rs index a1e0259..938c3da 100644 --- a/src/complexagent.rs +++ b/src/complexagent.rs @@ -1,9 +1,9 @@ -use std::num::NonZero; - -use crate::{agent::Agent, board::Board, piece::Piece}; -use either::Either; +use crate::{ + agent::Agent, + board::{Board, Winner}, + piece::Piece, +}; use indicatif::{ProgressBar, ProgressIterator, ProgressStyle}; -use num::Integer; #[derive(Clone, Debug)] struct Move { @@ -17,7 +17,7 @@ struct Move { board: Board, /// Current winner of the match - winner: Option, + winner: Winner, /// Index of this move's parent parent: Option, @@ -35,15 +35,16 @@ impl Move { fn compute_self_value(&self, agent_color: Piece, depth: i64) -> i64 { let mut self_value = self.value; - if self.winner == Some(!agent_color) { + if self.winner == Winner::Player(!agent_color) { // if this board results in the opponent winning, MAJORLY negatively weigh this move // 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 self.winner == Some(agent_color) { + } else if self.winner == Winner::Player(agent_color) { // results in a win for the agent self_value = i64::MAX; } + // TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2`? self_value / depth } @@ -78,11 +79,11 @@ impl FutureMoves { .collect(); for _ in self.current_depth..=(self.max_depth as isize) { - let arena_len = self.arena.len(); + let prog_len = next_nodes.len(); next_nodes = next_nodes .into_iter() .progress_with( - ProgressBar::new(arena_len as u64).with_style( + ProgressBar::new(prog_len as u64).with_style( ProgressStyle::with_template( "Generating children: ({pos}/{len}) {per_sec}", ) @@ -128,7 +129,8 @@ impl FutureMoves { // we want to keep only the best move of the agent if color == self.agent_color { if new.len() > 1 { - new.sort_by_key(|x| x.compute_self_value(self.agent_color, 1)); + // negative, because we want the max value to be at the first index + new.sort_by_key(|x| -x.compute_self_value(self.agent_color, 1)); new.drain(1..); } } @@ -145,7 +147,7 @@ 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 { + fn depth_of(&self, node_idx: usize) -> usize { let mut depth = 0; let mut current = Some(node_idx); while let Some(parent_idx) = current { @@ -153,8 +155,7 @@ impl FutureMoves { current = self.arena[parent_idx].parent; } - // SAFETY! because `node_idx` is of type `usize`, depth will never be 0 - unsafe { NonZero::new_unchecked(depth) } + depth } fn compute_values(&mut self) { @@ -163,19 +164,16 @@ impl FutureMoves { let mut visited = vec![false; self.arena.len()]; for depth in (0..=self.current_depth).rev() { - let nodes_at_depth: Vec = (0..self.arena.len()) - .filter(|&idx| { - if visited[idx] { - false - } else { - visited[idx] = true; - true - } - }) - .filter(|&idx| self.depth_of(idx).get() == depth as usize) - .collect(); + for idx in 0..self.arena.len() { + if visited[idx] { + continue; + } else { + visited[idx] = true; + } + if self.depth_of(idx) != depth as usize { + continue; + } - for idx in nodes_at_depth { let self_value = self.arena[idx] .compute_self_value(self.agent_color, (self.current_depth - depth + 1) as i64); @@ -230,7 +228,7 @@ impl FutureMoves { i: 0, j: 0, board: *board, - winner: None, + winner: Winner::None, parent: None, children: Vec::new(), value: 0, @@ -253,7 +251,7 @@ impl FutureMoves { fn update_root_idx(&mut self, idx: usize) { self.current_root = Some(idx); - self.current_depth -= self.depth_of(idx).get() as isize; + self.current_depth -= self.depth_of(idx) as isize - 1; self.prune_unrelated(); self.extend_layers(); self.compute_values(); @@ -264,6 +262,9 @@ impl FutureMoves { return; }; + // make sure `root` doesn't reference another node + self.arena[root].parent = None; + let mut retain = vec![false; self.arena.len()]; // stack is going to be AT MAXIMUM, the size of the array, @@ -314,7 +315,7 @@ pub struct ComplexAgent { impl ComplexAgent { pub const fn new(color: Piece) -> Self { - const MAX_DEPTH: usize = 10; + const MAX_DEPTH: usize = 17; Self { color, future_moves: FutureMoves::new(color, MAX_DEPTH), diff --git a/src/game.rs b/src/game.rs index a6943e3..8626c3d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -92,11 +92,16 @@ impl Game { println!("{}", self); - if let Some(game_winner) = self.board.game_winner(self.players[current_player].color()) - { - println!("{} Wins!", game_winner.text()); - // end the game - break; + match self.board.game_winner(self.players[current_player].color()) { + crate::board::Winner::Player(piece) => { + println!("{} Wins!", piece.text()); + break; + } + crate::board::Winner::Tie => { + println!("Game Tied!"); + break; + } + crate::board::Winner::None => {} } self.step(current_player); diff --git a/src/main.rs b/src/main.rs index a0b4902..37db162 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(); }