diff --git a/src/elo.rs b/src/elo.rs index e8106bf..708f2eb 100644 --- a/src/elo.rs +++ b/src/elo.rs @@ -29,7 +29,7 @@ pub fn run() { children_eval_method: ChildrenEvalMethod::AverageDivDepth, }; - let configs = [6] + let configs = [4, 6, 7] .into_iter() .map(move |d| FutureMoveConfig { max_depth: d, @@ -41,9 +41,9 @@ pub fn run() { }) .filter(move |move_c| { if move_c.do_prune { - move_c.max_depth >= 8 + move_c.max_depth >= 7 } else { - move_c.max_depth < 8 + move_c.max_depth < 7 } }) // .flat_map(move |prev_c| { diff --git a/src/logic/future_moves.rs b/src/logic/future_moves.rs index 79770db..51d1bca 100644 --- a/src/logic/future_moves.rs +++ b/src/logic/future_moves.rs @@ -104,7 +104,9 @@ impl FutureMoves { // we want to select all nodes that don't have children, or are lazy (need to maybe be regenerated) .filter(|&idx| { let got = &self.arena[idx]; - !got.is_trimmed && !got.tried_children && got.winner == Winner::None + !got.data.get_trimmed() + && !got.data.get_tried_children() + && got.data.get_winner() == Winner::None }) .filter(|&idx| self.is_connected_to_root(idx)) .collect::>(); @@ -150,7 +152,7 @@ impl FutureMoves { .progress_with_style(ProgressStyle::with_template(pstyle_inner).unwrap()) .try_for_each(|node_idx| { self.generate_children(node_idx); - self.arena[node_idx].tried_children = true; + self.arena[node_idx].data.set_tried_children(true); if self.arena_len() >= self.config.max_arena_size { ControlFlow::Break(()) @@ -199,7 +201,7 @@ impl FutureMoves { fn generate_children(&mut self, parent_idx: usize) { let parent = &self.arena[parent_idx]; - let new_color = !parent.color; + let new_color = !parent.data.get_piece(); let parent_board = self .get_board_from_idx(parent_idx) .expect("unable to get board"); @@ -314,7 +316,7 @@ impl FutureMoves { } let n = &self.arena[parent_idx]; - hist.push((n.coord, n.color)); + hist.push((n.coord, n.data.get_piece())); current = n.parent; } hist.reverse(); @@ -355,7 +357,8 @@ impl FutureMoves { }) .inspect(|&&x| { assert_eq!( - self.arena[x].color, self.agent_color, + self.arena[x].data.get_piece(), + self.agent_color, "selected move color should be the same as the color of the agent" ); }) @@ -371,7 +374,7 @@ impl FutureMoves { // match the agent_color usually root or great-grand child .filter(|&idx| self.depth_of(idx) % 2 == 0) .find(|&idx| { - self.arena[idx].color == !self.agent_color + self.arena[idx].data.get_piece() == !self.agent_color && self.get_board_from_idx(idx).as_ref() == Some(board) }); @@ -470,16 +473,16 @@ impl FutureMoves { } // only prune moves of the agent - if indexes.first().map(|&i| self.arena[i].color) != Some(self.agent_color) { + if indexes.first().map(|&i| self.arena[i].data.get_piece()) != Some(self.agent_color) { continue; } for idx in indexes { let mut m = self.arena[idx].clone(); - if m.is_trimmed { + if m.data.get_trimmed() { continue; } - m.is_trimmed = true; + m.data.set_trimmed(true); m.sort_children(&self.arena); if m.children.len() > self.config.top_k_children { let drained = m.children.drain(self.config.top_k_children..); diff --git a/src/logic/move.rs b/src/logic/move.rs index f014948..7d89848 100644 --- a/src/logic/move.rs +++ b/src/logic/move.rs @@ -7,29 +7,67 @@ pub struct Move { /// Coordinates (i, j) of the move (if it exists) pub coord: Option, - /// Current winner of the match - pub winner: Winner, - /// Index of this move's parent pub parent: Option, /// Indices of this Move's Children pub children: Vec, - /// Has this [`Move`] already attempted to create children? - pub tried_children: bool, - /// Value of this move (including children) pub value: Option, /// What is the inherit value of this move (not including children) pub self_value: i16, - /// Which color made a move on this move? - pub color: Piece, + pub data: MoveData, +} - /// Was this move's children previously trimmed? - pub is_trimmed: bool, +#[derive(Clone, Debug, Copy)] +pub struct MoveData(u8); + +impl MoveData { + const WINNER_LOC: usize = 0; // uses 2 bits + + pub const fn set_winner(&mut self, winner: Winner) { + self.0 &= !(0b11 << Self::WINNER_LOC); + self.0 |= winner.raw() << Self::WINNER_LOC; + } + + pub const fn get_winner(&self) -> Winner { + Winner::from_raw(((self.0 >> Self::WINNER_LOC) & 0b11) as u8) + } + + const PIECE_LOC: usize = 2; + pub const fn set_piece(&mut self, piece: Piece) { + self.0 &= !(0b1 << Self::PIECE_LOC); + self.0 |= (unsafe { std::mem::transmute::(piece) }) << Self::PIECE_LOC; + } + + pub const fn get_piece(&self) -> Piece { + unsafe { std::mem::transmute(((self.0 >> Self::PIECE_LOC) & 0b1) as u8) } + } + + const TRIMMED_LOC: usize = 3; + + pub const fn set_trimmed(&mut self, trimmed: bool) { + self.0 &= !(0b1 << Self::TRIMMED_LOC); + self.0 |= (trimmed as u8) << Self::TRIMMED_LOC; + } + + pub const fn get_trimmed(&self) -> bool { + ((self.0 >> Self::TRIMMED_LOC) & 0b1) != 0 + } + + const TRIED_CHILDREN_LOC: usize = 4; + + pub const fn set_tried_children(&mut self, tried_children: bool) { + self.0 &= !(0b1 << Self::TRIED_CHILDREN_LOC); + self.0 |= (tried_children as u8) << Self::TRIED_CHILDREN_LOC; + } + + pub const fn get_tried_children(&self) -> bool { + ((self.0 >> Self::TRIED_CHILDREN_LOC) & 0b1) != 0 + } } static BVM: LazyLock = LazyLock::new(BoardValueMap::new); @@ -38,26 +76,28 @@ impl Move { pub fn new(coord: Option, board: Board, color: Piece, agent_color: Piece) -> Self { let mut m = Move { coord, - winner: board.game_winner(), parent: None, children: Vec::new(), value: None, - color, - is_trimmed: false, self_value: 0, - tried_children: false, + data: MoveData(0), }; + let winner = board.game_winner(); + m.data.set_winner(winner); + m.data.set_piece(color); + m.data.set_trimmed(false); + m.data.set_tried_children(false); m.self_value = m.compute_self_value(agent_color, &board); m } fn compute_self_value(&self, agent_color: Piece, board: &Board) -> i16 { - if self.winner == Winner::Player(!agent_color) { + if self.data.get_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 return i16::MIN + 1; - } else if self.winner == Winner::Player(agent_color) { + } else if self.data.get_winner() == Winner::Player(agent_color) { // results in a win for the agent return i16::MAX - 1; } @@ -78,3 +118,32 @@ impl Move { }); } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn move_data_winner() { + let mut m = MoveData(0); + macro_rules! test_winner { + ($winner:expr) => { + m.set_winner($winner); + assert_eq!(m.get_winner(), $winner); + }; + } + test_winner!(Winner::Player(Piece::Black)); + test_winner!(Winner::Player(Piece::White)); + test_winner!(Winner::Tie); + test_winner!(Winner::None); + } + + #[test] + fn move_data_trimmed() { + let mut m = MoveData(0); + m.set_trimmed(false); + assert!(!m.get_trimmed()); + m.set_trimmed(true); + assert!(m.get_trimmed()); + } +} diff --git a/src/main.rs b/src/main.rs index 709efc8..cb14744 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,8 @@ pub mod repr; // TODO! make this agent configuration a config option via `clap-rs` fn main() { - // elo::run(); - // return; + elo::run(); + return; let player1 = complexagent::ComplexAgent::new( Piece::Black, FutureMoveConfig { diff --git a/src/repr/board.rs b/src/repr/board.rs index ba9e76f..39ed6ab 100644 --- a/src/repr/board.rs +++ b/src/repr/board.rs @@ -46,6 +46,16 @@ pub enum Winner { None, } +impl Winner { + pub const fn raw(self) -> u8 { + unsafe { std::mem::transmute(self) } + } + + pub const fn from_raw(value: u8) -> Self { + unsafe { std::mem::transmute(value) } + } +} + macro_rules! get_board { // Immutable static access ($self:expr, Piece::White) => { @@ -539,4 +549,17 @@ mod test { assert_eq!(board.get((6, 0).into()), Some(Piece::Black), "\n{}", board); } + + #[test] + fn winner_repr() { + assert_eq!(Winner::Player(Piece::Black).raw(), 0b00); + assert_eq!(Winner::Player(Piece::White).raw(), 0b01); + assert_eq!(Winner::Tie.raw(), 0b10); + assert_eq!(Winner::None.raw(), 0b11); + + assert_eq!(Winner::from_raw(0b00), Winner::Player(Piece::Black)); + assert_eq!(Winner::from_raw(0b01), Winner::Player(Piece::White)); + assert_eq!(Winner::from_raw(0b10), Winner::Tie); + assert_eq!(Winner::from_raw(0b11), Winner::None); + } }