rewrite basically done

This commit is contained in:
Simon Gardling 2025-02-17 15:02:15 -05:00
parent 87f58e5e40
commit 7049039a77
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
4 changed files with 54 additions and 43 deletions

View File

@ -59,6 +59,13 @@ fn gen_adj_lookup() -> PosMap<ChainCollection> {
.collect()
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum Winner {
Player(Piece),
Tie,
None,
}
lazy_static! {
/// Precompute all possible chains for each position on the board
pub static ref ADJ_LOOKUP: PosMap<ChainCollection> = gen_adj_lookup();
@ -263,22 +270,20 @@ impl Board {
}
/// Returns the winner of the board (if any)
pub fn game_winner(&self, turn: Piece) -> Option<Piece> {
pub fn game_winner(&self, turn: Piece) -> Winner {
// Wikipedia: `Players take alternate turns. If one player cannot make a valid move, play passes back to the other player. The game ends when the grid has filled up or if neither player can make a valid move.`
if self.possible_moves(turn).next().is_some() || self.possible_moves(!turn).next().is_some()
{
// player can still make a move, there is no winner
return None;
return Winner::None;
}
let (white_score, black_score) = self.get_score();
match white_score.cmp(&black_score) {
Ordering::Greater => Some(Piece::White), // White win
Ordering::Less => Some(Piece::Black), // Black win
// TODO! this will end up being parsed the same as a "no winner", it should be a seperate type
Ordering::Equal => None, // Tie
Ordering::Greater => Winner::Player(Piece::White), // White win
Ordering::Less => Winner::Player(Piece::Black), // Black win
Ordering::Equal => Winner::Tie,
}
}
}

View File

@ -1,9 +1,9 @@
use std::num::NonZero;
use crate::{agent::Agent, board::Board, piece::Piece};
use either::Either;
use crate::{
agent::Agent,
board::{Board, Winner},
piece::Piece,
};
use indicatif::{ProgressBar, ProgressIterator, ProgressStyle};
use num::Integer;
#[derive(Clone, Debug)]
struct Move {
@ -17,7 +17,7 @@ struct Move {
board: Board,
/// Current winner of the match
winner: Option<Piece>,
winner: Winner,
/// Index of this move's parent
parent: Option<usize>,
@ -35,15 +35,16 @@ impl Move {
fn compute_self_value(&self, agent_color: Piece, depth: i64) -> i64 {
let mut self_value = self.value;
if self.winner == Some(!agent_color) {
if self.winner == Winner::Player(!agent_color) {
// 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.
// 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) {
} else if self.winner == Winner::Player(agent_color) {
// results in a win for the agent
self_value = i64::MAX;
}
// TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2`?
self_value / depth
}
@ -78,11 +79,11 @@ impl FutureMoves {
.collect();
for _ in self.current_depth..=(self.max_depth as isize) {
let arena_len = self.arena.len();
let prog_len = next_nodes.len();
next_nodes = next_nodes
.into_iter()
.progress_with(
ProgressBar::new(arena_len as u64).with_style(
ProgressBar::new(prog_len as u64).with_style(
ProgressStyle::with_template(
"Generating children: ({pos}/{len}) {per_sec}",
)
@ -128,7 +129,8 @@ impl FutureMoves {
// we want to keep only the best move of the agent
if color == self.agent_color {
if new.len() > 1 {
new.sort_by_key(|x| x.compute_self_value(self.agent_color, 1));
// negative, because we want the max value to be at the first index
new.sort_by_key(|x| -x.compute_self_value(self.agent_color, 1));
new.drain(1..);
}
}
@ -145,7 +147,7 @@ impl FutureMoves {
}
/// Given an index from `self.arena`, what depth is it at? 1-indexed (ROOT IS AT INDEX 1)
fn depth_of(&self, node_idx: usize) -> NonZero<usize> {
fn depth_of(&self, node_idx: usize) -> usize {
let mut depth = 0;
let mut current = Some(node_idx);
while let Some(parent_idx) = current {
@ -153,8 +155,7 @@ impl FutureMoves {
current = self.arena[parent_idx].parent;
}
// SAFETY! because `node_idx` is of type `usize`, depth will never be 0
unsafe { NonZero::new_unchecked(depth) }
depth
}
fn compute_values(&mut self) {
@ -163,19 +164,16 @@ impl FutureMoves {
let mut visited = vec![false; self.arena.len()];
for depth in (0..=self.current_depth).rev() {
let nodes_at_depth: Vec<usize> = (0..self.arena.len())
.filter(|&idx| {
if visited[idx] {
false
} else {
visited[idx] = true;
true
}
})
.filter(|&idx| self.depth_of(idx).get() == depth as usize)
.collect();
for idx in 0..self.arena.len() {
if visited[idx] {
continue;
} else {
visited[idx] = true;
}
if self.depth_of(idx) != depth as usize {
continue;
}
for idx in nodes_at_depth {
let self_value = self.arena[idx]
.compute_self_value(self.agent_color, (self.current_depth - depth + 1) as i64);
@ -230,7 +228,7 @@ impl FutureMoves {
i: 0,
j: 0,
board: *board,
winner: None,
winner: Winner::None,
parent: None,
children: Vec::new(),
value: 0,
@ -253,7 +251,7 @@ impl FutureMoves {
fn update_root_idx(&mut self, idx: usize) {
self.current_root = Some(idx);
self.current_depth -= self.depth_of(idx).get() as isize;
self.current_depth -= self.depth_of(idx) as isize - 1;
self.prune_unrelated();
self.extend_layers();
self.compute_values();
@ -264,6 +262,9 @@ impl FutureMoves {
return;
};
// make sure `root` doesn't reference another node
self.arena[root].parent = None;
let mut retain = vec![false; self.arena.len()];
// stack is going to be AT MAXIMUM, the size of the array,
@ -314,7 +315,7 @@ pub struct ComplexAgent {
impl ComplexAgent {
pub const fn new(color: Piece) -> Self {
const MAX_DEPTH: usize = 10;
const MAX_DEPTH: usize = 17;
Self {
color,
future_moves: FutureMoves::new(color, MAX_DEPTH),

View File

@ -92,11 +92,16 @@ impl Game {
println!("{}", self);
if let Some(game_winner) = self.board.game_winner(self.players[current_player].color())
{
println!("{} Wins!", game_winner.text());
// end the game
break;
match self.board.game_winner(self.players[current_player].color()) {
crate::board::Winner::Player(piece) => {
println!("{} Wins!", piece.text());
break;
}
crate::board::Winner::Tie => {
println!("Game Tied!");
break;
}
crate::board::Winner::None => {}
}
self.step(current_player);

View File

@ -12,8 +12,8 @@ mod piece;
fn main() {
let player1 = complexagent::ComplexAgent::new(Piece::Black);
// let player2 = complexagent::ComplexAgent::new(Piece::White);
let player2 = agent::ManualAgent::new(Piece::White);
// let player2 = agent::RandomAgent::new(Piece::White);
// let player2 = agent::ManualAgent::new(Piece::White);
let player2 = agent::RandomAgent::new(Piece::White);
let mut game = Game::new(Box::new(player1), Box::new(player2));
game.game_loop();
}