use crate::{agent::Agent, board::Board, piece::Piece}; pub struct ComplexAgent { color: Piece, curr_move: Option, } #[derive(Clone)] struct Move { i: usize, j: usize, captured: usize, color: Piece, board: Board, next_move: Vec, } impl Move { fn populate_next_moves(&mut self, i: usize) { if i == 0 { return; } if self.next_move.is_empty() { self.next_move = problem_space(&self.board, !self.color).collect(); } self.next_move .iter_mut() .for_each(|x| x.populate_next_moves(i - 1)); } fn value(&self, agent_color: Piece, depth: usize) -> i64 { let mut captured_value = self.captured as i64; if agent_color != self.color { captured_value = -captured_value; } // Reduce value of capture based on depth of prediction captured_value /= depth as i64; let avg_next_move_value = (self .next_move .iter() .map(|x| x.value(agent_color, depth + 1)) .sum::() .checked_div(self.next_move.len() as i64)) .unwrap_or(0); captured_value + avg_next_move_value } pub fn len(&self) -> u32 { self.next_move.len() as u32 + self.next_move.iter().map(Move::len).sum::() } pub const fn coords(&self) -> (usize, usize) { (self.i, self.j) } /// Cursed function to create a dummy move type from a color and board /// Used to bootstrap [`ComplexAgent`] pub fn bootstrap(color: Piece, board: &Board) -> Self { Move { i: 0, j: 0, captured: 0, color, board: *board, next_move: vec![Move { i: 0, j: 0, captured: 0, color: !color, board: *board, next_move: problem_space(board, color).collect(), }], } } } /// Take a [`Board`] and a [`Piece`] color and give all possible moves for that color on the board /// Returns a Boxed iterator to be used in other logic fn problem_space(board: &Board, color: Piece) -> Box + '_> { Box::new( board .possible_moves(color) .flat_map(move |(i, j)| board.what_if(i, j, color).map(|x| (i, j, x))) .map(move |(i, j, (board, captured))| Move { i, j, captured, color, board, next_move: Vec::new(), }), ) } impl Agent for ComplexAgent { fn next_move(&mut self, board: &Board) -> Option<(usize, usize)> { const LOOPS: usize = 5; let curr_move: Move = self .curr_move .take() .unwrap_or_else(|| Move::bootstrap(self.color, board)); let mut other_player_move = curr_move .next_move .into_iter() .filter(|x| &x.board == board) .last() // handle invalid other player moves .unwrap_or_else(|| { println!("invalid board, rebuilding move tree..."); // rebuild move tree // need to start with a !self.color move, so unwrap the first level Move::bootstrap(self.color, board).next_move.remove(0) }); other_player_move.populate_next_moves(LOOPS); println!( "(depth: {}) possible board states: {}", LOOPS, other_player_move.len() ); // Take the best move and move it, don't clone the reference self.curr_move = other_player_move .next_move .into_iter() .max_by_key(|m| m.value(self.color, 1)); self.curr_move.as_ref().map(Move::coords) } fn name(&self) -> &'static str { "Complex Agent" } fn color(&self) -> Piece { self.color } } impl ComplexAgent { pub const fn new(color: Piece) -> Self { Self { color, curr_move: None, } } }