othello/src/complexagent.rs
2025-02-07 10:31:58 -05:00

154 lines
3.9 KiB
Rust

use crate::{agent::Agent, board::Board, piece::Piece};
pub struct ComplexAgent {
color: Piece,
curr_move: Option<Move>,
}
#[derive(Clone)]
struct Move {
i: usize,
j: usize,
captured: usize,
color: Piece,
board: Board,
next_move: Vec<Move>,
}
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::<i64>()
.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::<u32>()
}
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(),
}],
}
}
}
fn problem_space(board: &Board, color: Piece) -> Box<dyn Iterator<Item = Move> + '_> {
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 fn new(color: Piece) -> Self {
Self {
color,
curr_move: None,
}
}
}