From cd28790d803a6788a4ed7154b88b2a1956141294 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 18 Feb 2025 02:02:50 -0500 Subject: [PATCH] improvements --- Cargo.lock | 18 ++++----- Cargo.toml | 2 +- src/board.rs | 4 ++ src/complexagent.rs | 95 ++++++++++++++++++++++++++++++--------------- 4 files changed, 77 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53e3416..63edff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha", "rand_core", - "zerocopy 0.8.17", + "zerocopy 0.8.18", ] [[package]] @@ -295,12 +295,12 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" dependencies = [ "getrandom", - "zerocopy 0.8.17", + "zerocopy 0.8.18", ] [[package]] @@ -517,11 +517,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +checksum = "79386d31a42a4996e3336b0919ddb90f81112af416270cff95b5f5af22b839c2" dependencies = [ - "zerocopy-derive 0.8.17", + "zerocopy-derive 0.8.18", ] [[package]] @@ -537,9 +537,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.17" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 172f288..187420b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ lto = true [dependencies] arrayvec = "0.7" -bitvec = "1.0.1" +bitvec = "1" either = "1.13" indicatif = "0.17" lazy_static = "1.5" diff --git a/src/board.rs b/src/board.rs index 88f302d..038badf 100644 --- a/src/board.rs +++ b/src/board.rs @@ -264,6 +264,10 @@ impl Board { .count() } + pub fn net_score(&self, piece: Piece) -> isize { + self.count(piece) as isize - self.count(!piece) as isize + } + /// Returns (White score, Black score) pub fn get_score(&self) -> (usize, usize) { (self.count(Piece::White), self.count(Piece::Black)) diff --git a/src/complexagent.rs b/src/complexagent.rs index 43db389..e8e2de4 100644 --- a/src/complexagent.rs +++ b/src/complexagent.rs @@ -3,7 +3,7 @@ use crate::{ board::{Board, Winner}, piece::Piece, }; -use indicatif::{ProgressBar, ProgressIterator, ProgressStyle}; +use indicatif::{ProgressIterator, ProgressStyle}; #[derive(Clone, Debug)] struct Move { @@ -32,8 +32,12 @@ struct Move { } impl Move { - fn compute_self_value(&self, agent_color: Piece, depth: i64) -> i64 { - let mut self_value = self.value; + pub const fn coords(&self) -> (usize, usize) { + (self.i, self.j) + } + + fn compute_self_value(&self, agent_color: Piece) -> i64 { + let mut self_value = self.board.net_score(agent_color) as i64; if self.winner == Winner::Player(!agent_color) { // if this board results in the opponent winning, MAJORLY negatively weigh this move @@ -44,17 +48,23 @@ impl Move { // results in a win for the agent self_value = i64::MAX; } - // TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2`? + // TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2` or 0? - self_value / depth + self_value } } struct FutureMoves { + /// Arena containing all [`Move`] arena: Vec, + + /// Index of the [`Move`] tree's root node current_root: Option, + + /// Current generated depth of the Arena current_depth: isize, + /// Target depth of children to generate max_depth: usize, /// Color w.r.t @@ -76,23 +86,23 @@ impl FutureMoves { fn extend_layers(&mut self) { let mut next_nodes: Vec = (0..self.arena.len()) .filter(|&idx| self.arena[idx].children.is_empty()) + .filter(|&idx| self.is_connected_to_root(idx)) // put here so this will not extend needlessly before prunes .collect(); for _ in self.current_depth..=(self.max_depth as isize) { - let prog_len = next_nodes.len(); + // TODO! use `i` in order to prune along-the-way + // i.e, every 4 moves of color `self.agent_color`, do a pruning step, + // only keeping the top [`Move`] + next_nodes = next_nodes .into_iter() - .progress_with( - ProgressBar::new(prog_len as u64).with_style( - ProgressStyle::with_template( - "Generating children: ({pos}/{len}) {per_sec}", - ) + .progress_with_style( + ProgressStyle::with_template("Generating children: ({pos}/{len}) {per_sec}") .unwrap(), - ), ) .flat_map(|node_idx| { self.generate_children( - Some(node_idx), + node_idx, &self.arena[node_idx].board.clone(), !self.arena[node_idx].color, ) @@ -102,44 +112,60 @@ impl FutureMoves { self.current_depth = self.max_depth as isize; } + /// Determines if a [`Move`] at index `idx` is connected to `self.current_root` + /// Returns `false` if `self.current_root` is None + fn is_connected_to_root(&self, idx: usize) -> bool { + if let Some(root) = self.current_root { + let mut current = Some(idx); + while let Some(parent_idx) = current { + if parent_idx == root { + return true; + } + current = self.arena[parent_idx].parent; + } + } + false + } + /// Creates children for a parent (`parent`), returns an iterator it's children's indexes fn generate_children( &mut self, - parent: Option, + parent_idx: usize, board: &Board, color: Piece, ) -> impl Iterator { - let start_idx = self.arena.len(); let mut new: Vec = // use [`Board::all_positions`] here instead of [`Board::possible_moves`] // because we use [`Board::what_if`] later and we want to reduce calls to [`Board::propegate_from_dry`] Board::all_positions() - .flat_map(|(i, j)| board.what_if(i, j, color).map(|x| (i, j, x))) + .flat_map(|(i, j)| board.what_if(i, j, !self.arena[parent_idx].color).map(|x| (i, j, x))) .map(|(i, j, new_board)| Move { i, j, board: new_board, winner: new_board.game_winner(color), - parent, + parent: Some(parent_idx), children: Vec::new(), - value: new_board.count(self.agent_color) as i64 - new_board.count(!self.agent_color) as i64, - color: parent.map(|idx| !self.arena[idx].color).unwrap_or(self.agent_color) + value: 0, + color: !self.arena[parent_idx].color }).collect(); + // keep the TOP_K children `self.agent_color`-color moves + const TOP_K_CHILDREN: usize = 1; + // we want to keep only the best move of the agent - if color == self.agent_color && new.len() > 1 { + if color == self.agent_color && new.len() > TOP_K_CHILDREN { + // TODO! Move this to `extend_layers` so we can prune based on recursive [`Move`] value // 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..); + new.sort_by_key(|x| -x.compute_self_value(self.agent_color)); + new.drain(TOP_K_CHILDREN..); } + let start_idx = self.arena.len(); self.arena.extend(new); - let new_indices = start_idx..self.arena.len(); - if let Some(parent_idx) = parent { - self.arena[parent_idx].children.extend(new_indices.clone()); - } + self.arena[parent_idx].children.extend(new_indices.clone()); new_indices } @@ -168,12 +194,13 @@ impl FutureMoves { } else { *was_visited = true; } + if self.depth_of(idx) != depth as usize { continue; } - let self_value = self.arena[idx] - .compute_self_value(self.agent_color, (self.current_depth - depth + 1) as i64); + let self_value = self.arena[idx].compute_self_value(self.agent_color) + / (self.current_depth - depth + 1) as i64; let children_value = self.arena[idx] .children @@ -187,6 +214,7 @@ impl FutureMoves { } } } + pub fn best_move(&self) -> Option<(usize, usize)> { self.current_root .and_then(|x| { @@ -201,7 +229,7 @@ impl FutureMoves { "selected move color should be the same as the color of the agent" ); }) - .map(|&x| (self.arena[x].i, self.arena[x].j)) + .map(|&x| self.arena[x].coords()) } /// Updates `FutureMoves` based on the current state of the board @@ -217,7 +245,6 @@ impl FutureMoves { .map(|(idx, _)| idx); 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"); @@ -287,7 +314,11 @@ impl FutureMoves { .map(|(new_idx, (old_idx, mut node))| { index_map[old_idx] = Some(new_idx); - node.parent = node.parent.and_then(|p| index_map[p]); + if let Some(parent) = node.parent.as_mut() { + if let Some(new_parent) = index_map[*parent] { + *parent = new_parent; + } + } node.children.retain_mut(|c| { if let Some(new_c) = index_map[*c] { @@ -313,7 +344,7 @@ pub struct ComplexAgent { impl ComplexAgent { pub const fn new(color: Piece) -> Self { - const MAX_DEPTH: usize = 17; + const MAX_DEPTH: usize = 15; Self { color, future_moves: FutureMoves::new(color, MAX_DEPTH),