154 lines
3.9 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|