Move: bit madness
This commit is contained in:
parent
31e88e3313
commit
afa80ac597
@ -29,7 +29,7 @@ pub fn run() {
|
|||||||
children_eval_method: ChildrenEvalMethod::AverageDivDepth,
|
children_eval_method: ChildrenEvalMethod::AverageDivDepth,
|
||||||
};
|
};
|
||||||
|
|
||||||
let configs = [6]
|
let configs = [4, 6, 7]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |d| FutureMoveConfig {
|
.map(move |d| FutureMoveConfig {
|
||||||
max_depth: d,
|
max_depth: d,
|
||||||
@ -41,9 +41,9 @@ pub fn run() {
|
|||||||
})
|
})
|
||||||
.filter(move |move_c| {
|
.filter(move |move_c| {
|
||||||
if move_c.do_prune {
|
if move_c.do_prune {
|
||||||
move_c.max_depth >= 8
|
move_c.max_depth >= 7
|
||||||
} else {
|
} else {
|
||||||
move_c.max_depth < 8
|
move_c.max_depth < 7
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// .flat_map(move |prev_c| {
|
// .flat_map(move |prev_c| {
|
||||||
|
|||||||
@ -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)
|
// we want to select all nodes that don't have children, or are lazy (need to maybe be regenerated)
|
||||||
.filter(|&idx| {
|
.filter(|&idx| {
|
||||||
let got = &self.arena[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))
|
.filter(|&idx| self.is_connected_to_root(idx))
|
||||||
.collect::<Vec<usize>>();
|
.collect::<Vec<usize>>();
|
||||||
@ -150,7 +152,7 @@ impl FutureMoves {
|
|||||||
.progress_with_style(ProgressStyle::with_template(pstyle_inner).unwrap())
|
.progress_with_style(ProgressStyle::with_template(pstyle_inner).unwrap())
|
||||||
.try_for_each(|node_idx| {
|
.try_for_each(|node_idx| {
|
||||||
self.generate_children(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 {
|
if self.arena_len() >= self.config.max_arena_size {
|
||||||
ControlFlow::Break(())
|
ControlFlow::Break(())
|
||||||
@ -199,7 +201,7 @@ impl FutureMoves {
|
|||||||
fn generate_children(&mut self, parent_idx: usize) {
|
fn generate_children(&mut self, parent_idx: usize) {
|
||||||
let parent = &self.arena[parent_idx];
|
let parent = &self.arena[parent_idx];
|
||||||
|
|
||||||
let new_color = !parent.color;
|
let new_color = !parent.data.get_piece();
|
||||||
let parent_board = self
|
let parent_board = self
|
||||||
.get_board_from_idx(parent_idx)
|
.get_board_from_idx(parent_idx)
|
||||||
.expect("unable to get board");
|
.expect("unable to get board");
|
||||||
@ -314,7 +316,7 @@ impl FutureMoves {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let n = &self.arena[parent_idx];
|
let n = &self.arena[parent_idx];
|
||||||
hist.push((n.coord, n.color));
|
hist.push((n.coord, n.data.get_piece()));
|
||||||
current = n.parent;
|
current = n.parent;
|
||||||
}
|
}
|
||||||
hist.reverse();
|
hist.reverse();
|
||||||
@ -355,7 +357,8 @@ impl FutureMoves {
|
|||||||
})
|
})
|
||||||
.inspect(|&&x| {
|
.inspect(|&&x| {
|
||||||
assert_eq!(
|
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"
|
"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
|
// match the agent_color usually root or great-grand child
|
||||||
.filter(|&idx| self.depth_of(idx) % 2 == 0)
|
.filter(|&idx| self.depth_of(idx) % 2 == 0)
|
||||||
.find(|&idx| {
|
.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)
|
&& self.get_board_from_idx(idx).as_ref() == Some(board)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -470,16 +473,16 @@ impl FutureMoves {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only prune moves of the agent
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx in indexes {
|
for idx in indexes {
|
||||||
let mut m = self.arena[idx].clone();
|
let mut m = self.arena[idx].clone();
|
||||||
if m.is_trimmed {
|
if m.data.get_trimmed() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
m.is_trimmed = true;
|
m.data.set_trimmed(true);
|
||||||
m.sort_children(&self.arena);
|
m.sort_children(&self.arena);
|
||||||
if m.children.len() > self.config.top_k_children {
|
if m.children.len() > self.config.top_k_children {
|
||||||
let drained = m.children.drain(self.config.top_k_children..);
|
let drained = m.children.drain(self.config.top_k_children..);
|
||||||
|
|||||||
@ -7,29 +7,67 @@ pub struct Move {
|
|||||||
/// Coordinates (i, j) of the move (if it exists)
|
/// Coordinates (i, j) of the move (if it exists)
|
||||||
pub coord: Option<CoordPair>,
|
pub coord: Option<CoordPair>,
|
||||||
|
|
||||||
/// Current winner of the match
|
|
||||||
pub winner: Winner,
|
|
||||||
|
|
||||||
/// Index of this move's parent
|
/// Index of this move's parent
|
||||||
pub parent: Option<usize>,
|
pub parent: Option<usize>,
|
||||||
|
|
||||||
/// Indices of this Move's Children
|
/// Indices of this Move's Children
|
||||||
pub children: Vec<usize>,
|
pub children: Vec<usize>,
|
||||||
|
|
||||||
/// Has this [`Move`] already attempted to create children?
|
|
||||||
pub tried_children: bool,
|
|
||||||
|
|
||||||
/// Value of this move (including children)
|
/// Value of this move (including children)
|
||||||
pub value: Option<i32>,
|
pub value: Option<i32>,
|
||||||
|
|
||||||
/// What is the inherit value of this move (not including children)
|
/// What is the inherit value of this move (not including children)
|
||||||
pub self_value: i16,
|
pub self_value: i16,
|
||||||
|
|
||||||
/// Which color made a move on this move?
|
pub data: MoveData,
|
||||||
pub color: Piece,
|
}
|
||||||
|
|
||||||
/// Was this move's children previously trimmed?
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub is_trimmed: bool,
|
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, u8>(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<BoardValueMap> = LazyLock::new(BoardValueMap::new);
|
static BVM: LazyLock<BoardValueMap> = LazyLock::new(BoardValueMap::new);
|
||||||
@ -38,26 +76,28 @@ impl Move {
|
|||||||
pub fn new(coord: Option<CoordPair>, board: Board, color: Piece, agent_color: Piece) -> Self {
|
pub fn new(coord: Option<CoordPair>, board: Board, color: Piece, agent_color: Piece) -> Self {
|
||||||
let mut m = Move {
|
let mut m = Move {
|
||||||
coord,
|
coord,
|
||||||
winner: board.game_winner(),
|
|
||||||
parent: None,
|
parent: None,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
value: None,
|
value: None,
|
||||||
color,
|
|
||||||
is_trimmed: false,
|
|
||||||
self_value: 0,
|
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.self_value = m.compute_self_value(agent_color, &board);
|
||||||
m
|
m
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_self_value(&self, agent_color: Piece, board: &Board) -> i16 {
|
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
|
// 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.
|
// 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
|
// We shouldn't prune branches because we still need to always react to the opponent's moves
|
||||||
return i16::MIN + 1;
|
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
|
// results in a win for the agent
|
||||||
return i16::MAX - 1;
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ pub mod repr;
|
|||||||
|
|
||||||
// TODO! make this agent configuration a config option via `clap-rs`
|
// TODO! make this agent configuration a config option via `clap-rs`
|
||||||
fn main() {
|
fn main() {
|
||||||
// elo::run();
|
elo::run();
|
||||||
// return;
|
return;
|
||||||
let player1 = complexagent::ComplexAgent::new(
|
let player1 = complexagent::ComplexAgent::new(
|
||||||
Piece::Black,
|
Piece::Black,
|
||||||
FutureMoveConfig {
|
FutureMoveConfig {
|
||||||
|
|||||||
@ -46,6 +46,16 @@ pub enum Winner {
|
|||||||
None,
|
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 {
|
macro_rules! get_board {
|
||||||
// Immutable static access
|
// Immutable static access
|
||||||
($self:expr, Piece::White) => {
|
($self:expr, Piece::White) => {
|
||||||
@ -539,4 +549,17 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(board.get((6, 0).into()), Some(Piece::Black), "\n{}", board);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user