initial complexagent rewrite
This commit is contained in:
parent
f5d44ca9f5
commit
19039f550b
52
Cargo.lock
generated
52
Cargo.lock
generated
@ -26,6 +26,37 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.1"
|
||||
@ -123,6 +154,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"num",
|
||||
"rand",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -183,6 +215,26 @@ dependencies = [
|
||||
"zerocopy 0.8.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
|
||||
@ -6,3 +6,4 @@ edition = "2021"
|
||||
[dependencies]
|
||||
num = "0.4"
|
||||
rand = "0.9.0"
|
||||
rayon = "1.10.0"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use crate::{agent::Agent, board::Board, piece::Piece};
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub struct ComplexAgent {
|
||||
color: Piece,
|
||||
curr_move: Option<Move>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -12,28 +12,13 @@ struct Move {
|
||||
captured: usize,
|
||||
color: Piece,
|
||||
board: Board,
|
||||
next_move: Vec<Move>,
|
||||
winner: Option<Piece>,
|
||||
parent_index: Option<usize>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
impl Move {
|
||||
fn populate_next_moves(&mut self, i: usize) {
|
||||
if i == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// only compute next move if this move doesn't end the game
|
||||
if self.winner.is_none() {
|
||||
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 {
|
||||
fn compute_self_value(&self, agent_color: Piece, depth: usize) -> i64 {
|
||||
let mut self_value = self.captured as i64;
|
||||
|
||||
if self.winner == Some(!agent_color) {
|
||||
@ -42,118 +27,100 @@ impl Move {
|
||||
// We shouldn't prune branches because we still need to always react to the opponent's moves
|
||||
self_value = i64::MIN;
|
||||
} else if self.winner == Some(agent_color) {
|
||||
// this move results in a
|
||||
// results in a win for the agent
|
||||
self_value = i64::MAX;
|
||||
} else if agent_color != self.color {
|
||||
self_value = -self_value;
|
||||
}
|
||||
|
||||
// Reduce value of capture based on depth of prediction
|
||||
self_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);
|
||||
|
||||
self_value + avg_next_move_value
|
||||
}
|
||||
|
||||
/// Returns the # of moves in the entire tree
|
||||
pub fn len(&self) -> u32 {
|
||||
self.next_move.len() as u32 + self.next_move.iter().map(Move::len).sum::<u32>()
|
||||
}
|
||||
|
||||
/// Returns a tuple containing the `i` and `j` fields
|
||||
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`]'s future moves
|
||||
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(),
|
||||
winner: board.game_winner(!color),
|
||||
}],
|
||||
winner: board.game_winner(color),
|
||||
}
|
||||
self_value / depth as i64
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<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(),
|
||||
winner: board.game_winner(color),
|
||||
}),
|
||||
)
|
||||
impl ComplexAgent {
|
||||
#[allow(dead_code)]
|
||||
pub const fn new(color: Piece) -> Self {
|
||||
Self { color }
|
||||
}
|
||||
|
||||
fn generate_layers(&self, board: &Board, depth: usize) -> Vec<Vec<Move>> {
|
||||
let mut layers = Vec::with_capacity(depth);
|
||||
let initial_moves: Vec<Move> = problem_space(board, self.color)
|
||||
.map(|mut m| {
|
||||
m.parent_index = None;
|
||||
m.value = 0;
|
||||
m
|
||||
})
|
||||
.collect();
|
||||
layers.push(initial_moves);
|
||||
|
||||
for current_depth in 0..depth {
|
||||
let current_layer = &layers[current_depth];
|
||||
if current_layer.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let next_moves: Vec<Move> = current_layer
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.flat_map(|(parent_idx, parent_move)| {
|
||||
let opponent_color = !parent_move.color;
|
||||
problem_space(&parent_move.board, opponent_color)
|
||||
.map(|mut m| {
|
||||
m.parent_index = Some(parent_idx);
|
||||
m.value = 0;
|
||||
m
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
layers.push(next_moves);
|
||||
}
|
||||
|
||||
self.compute_values(&mut layers);
|
||||
layers
|
||||
}
|
||||
|
||||
fn compute_values(&self, layers: &mut [Vec<Move>]) {
|
||||
let agent_color = self.color;
|
||||
let layers_len = layers.len();
|
||||
for depth in (0..layers.len()).rev() {
|
||||
let layer_depth_1: Vec<Move> = if depth + 1 < layers_len {
|
||||
layers[depth + 1].clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let current_layer = &mut layers[depth];
|
||||
|
||||
current_layer.par_iter_mut().for_each(|mv| {
|
||||
let self_value = mv.compute_self_value(agent_color, depth + 1);
|
||||
let children_value = if depth + 1 < layers_len {
|
||||
layer_depth_1
|
||||
.iter()
|
||||
.filter(|child| child.parent_index == Some(mv.parent_index.unwrap_or(0)))
|
||||
.map(|child| child.value)
|
||||
.sum::<i64>()
|
||||
/ layer_depth_1.len() as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
mv.value = self_value + children_value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// determine the move the other player made via the board state
|
||||
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));
|
||||
|
||||
assert!(self.curr_move.is_some(), "ComplexAgent didn't make a move");
|
||||
|
||||
self.curr_move.as_ref().map(Move::coords)
|
||||
let layers = self.generate_layers(board, LOOPS);
|
||||
println!("len: {}", layers.iter().map(Vec::len).sum::<usize>());
|
||||
layers[0]
|
||||
.par_iter()
|
||||
.max_by_key(|m| m.value)
|
||||
.map(|m| (m.i, m.j))
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
@ -165,12 +132,20 @@ impl Agent for ComplexAgent {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComplexAgent {
|
||||
#[allow(dead_code)]
|
||||
pub const fn new(color: Piece) -> Self {
|
||||
Self {
|
||||
color,
|
||||
curr_move: None,
|
||||
}
|
||||
}
|
||||
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,
|
||||
winner: board.game_winner(color),
|
||||
parent_index: None,
|
||||
value: 0,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user