rewrite basically done
This commit is contained in:
parent
87f58e5e40
commit
7049039a77
19
src/board.rs
19
src/board.rs
@ -59,6 +59,13 @@ fn gen_adj_lookup() -> PosMap<ChainCollection> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
|
pub enum Winner {
|
||||||
|
Player(Piece),
|
||||||
|
Tie,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Precompute all possible chains for each position on the board
|
/// Precompute all possible chains for each position on the board
|
||||||
pub static ref ADJ_LOOKUP: PosMap<ChainCollection> = gen_adj_lookup();
|
pub static ref ADJ_LOOKUP: PosMap<ChainCollection> = gen_adj_lookup();
|
||||||
@ -263,22 +270,20 @@ impl Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the winner of the board (if any)
|
/// 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.`
|
// 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()
|
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
|
// player can still make a move, there is no winner
|
||||||
return None;
|
return Winner::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (white_score, black_score) = self.get_score();
|
let (white_score, black_score) = self.get_score();
|
||||||
match white_score.cmp(&black_score) {
|
match white_score.cmp(&black_score) {
|
||||||
Ordering::Greater => Some(Piece::White), // White win
|
Ordering::Greater => Winner::Player(Piece::White), // White win
|
||||||
Ordering::Less => Some(Piece::Black), // Black win
|
Ordering::Less => Winner::Player(Piece::Black), // Black win
|
||||||
|
Ordering::Equal => Winner::Tie,
|
||||||
// TODO! this will end up being parsed the same as a "no winner", it should be a seperate type
|
|
||||||
Ordering::Equal => None, // Tie
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
use std::num::NonZero;
|
use crate::{
|
||||||
|
agent::Agent,
|
||||||
use crate::{agent::Agent, board::Board, piece::Piece};
|
board::{Board, Winner},
|
||||||
use either::Either;
|
piece::Piece,
|
||||||
|
};
|
||||||
use indicatif::{ProgressBar, ProgressIterator, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressIterator, ProgressStyle};
|
||||||
use num::Integer;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Move {
|
struct Move {
|
||||||
@ -17,7 +17,7 @@ struct Move {
|
|||||||
board: Board,
|
board: Board,
|
||||||
|
|
||||||
/// Current winner of the match
|
/// Current winner of the match
|
||||||
winner: Option<Piece>,
|
winner: Winner,
|
||||||
|
|
||||||
/// Index of this move's parent
|
/// Index of this move's parent
|
||||||
parent: Option<usize>,
|
parent: Option<usize>,
|
||||||
@ -35,15 +35,16 @@ impl Move {
|
|||||||
fn compute_self_value(&self, agent_color: Piece, depth: i64) -> i64 {
|
fn compute_self_value(&self, agent_color: Piece, depth: i64) -> i64 {
|
||||||
let mut self_value = self.value;
|
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
|
// 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
|
||||||
self_value = i64::MIN;
|
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
|
// results in a win for the agent
|
||||||
self_value = i64::MAX;
|
self_value = i64::MAX;
|
||||||
}
|
}
|
||||||
|
// TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2`?
|
||||||
|
|
||||||
self_value / depth
|
self_value / depth
|
||||||
}
|
}
|
||||||
@ -78,11 +79,11 @@ impl FutureMoves {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for _ in self.current_depth..=(self.max_depth as isize) {
|
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
|
next_nodes = next_nodes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.progress_with(
|
.progress_with(
|
||||||
ProgressBar::new(arena_len as u64).with_style(
|
ProgressBar::new(prog_len as u64).with_style(
|
||||||
ProgressStyle::with_template(
|
ProgressStyle::with_template(
|
||||||
"Generating children: ({pos}/{len}) {per_sec}",
|
"Generating children: ({pos}/{len}) {per_sec}",
|
||||||
)
|
)
|
||||||
@ -128,7 +129,8 @@ impl FutureMoves {
|
|||||||
// we want to keep only the best move of the agent
|
// we want to keep only the best move of the agent
|
||||||
if color == self.agent_color {
|
if color == self.agent_color {
|
||||||
if new.len() > 1 {
|
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..);
|
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)
|
/// 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 depth = 0;
|
||||||
let mut current = Some(node_idx);
|
let mut current = Some(node_idx);
|
||||||
while let Some(parent_idx) = current {
|
while let Some(parent_idx) = current {
|
||||||
@ -153,8 +155,7 @@ impl FutureMoves {
|
|||||||
current = self.arena[parent_idx].parent;
|
current = self.arena[parent_idx].parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY! because `node_idx` is of type `usize`, depth will never be 0
|
depth
|
||||||
unsafe { NonZero::new_unchecked(depth) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_values(&mut self) {
|
fn compute_values(&mut self) {
|
||||||
@ -163,19 +164,16 @@ impl FutureMoves {
|
|||||||
let mut visited = vec![false; self.arena.len()];
|
let mut visited = vec![false; self.arena.len()];
|
||||||
|
|
||||||
for depth in (0..=self.current_depth).rev() {
|
for depth in (0..=self.current_depth).rev() {
|
||||||
let nodes_at_depth: Vec<usize> = (0..self.arena.len())
|
for idx in 0..self.arena.len() {
|
||||||
.filter(|&idx| {
|
|
||||||
if visited[idx] {
|
if visited[idx] {
|
||||||
false
|
continue;
|
||||||
} else {
|
} else {
|
||||||
visited[idx] = true;
|
visited[idx] = true;
|
||||||
true
|
|
||||||
}
|
}
|
||||||
})
|
if self.depth_of(idx) != depth as usize {
|
||||||
.filter(|&idx| self.depth_of(idx).get() == depth as usize)
|
continue;
|
||||||
.collect();
|
}
|
||||||
|
|
||||||
for idx in nodes_at_depth {
|
|
||||||
let self_value = self.arena[idx]
|
let self_value = self.arena[idx]
|
||||||
.compute_self_value(self.agent_color, (self.current_depth - depth + 1) as i64);
|
.compute_self_value(self.agent_color, (self.current_depth - depth + 1) as i64);
|
||||||
|
|
||||||
@ -230,7 +228,7 @@ impl FutureMoves {
|
|||||||
i: 0,
|
i: 0,
|
||||||
j: 0,
|
j: 0,
|
||||||
board: *board,
|
board: *board,
|
||||||
winner: None,
|
winner: Winner::None,
|
||||||
parent: None,
|
parent: None,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
value: 0,
|
value: 0,
|
||||||
@ -253,7 +251,7 @@ impl FutureMoves {
|
|||||||
|
|
||||||
fn update_root_idx(&mut self, idx: usize) {
|
fn update_root_idx(&mut self, idx: usize) {
|
||||||
self.current_root = Some(idx);
|
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.prune_unrelated();
|
||||||
self.extend_layers();
|
self.extend_layers();
|
||||||
self.compute_values();
|
self.compute_values();
|
||||||
@ -264,6 +262,9 @@ impl FutureMoves {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// make sure `root` doesn't reference another node
|
||||||
|
self.arena[root].parent = None;
|
||||||
|
|
||||||
let mut retain = vec![false; self.arena.len()];
|
let mut retain = vec![false; self.arena.len()];
|
||||||
|
|
||||||
// stack is going to be AT MAXIMUM, the size of the array,
|
// stack is going to be AT MAXIMUM, the size of the array,
|
||||||
@ -314,7 +315,7 @@ pub struct ComplexAgent {
|
|||||||
|
|
||||||
impl ComplexAgent {
|
impl ComplexAgent {
|
||||||
pub const fn new(color: Piece) -> Self {
|
pub const fn new(color: Piece) -> Self {
|
||||||
const MAX_DEPTH: usize = 10;
|
const MAX_DEPTH: usize = 17;
|
||||||
Self {
|
Self {
|
||||||
color,
|
color,
|
||||||
future_moves: FutureMoves::new(color, MAX_DEPTH),
|
future_moves: FutureMoves::new(color, MAX_DEPTH),
|
||||||
|
|||||||
13
src/game.rs
13
src/game.rs
@ -92,12 +92,17 @@ impl Game {
|
|||||||
|
|
||||||
println!("{}", self);
|
println!("{}", self);
|
||||||
|
|
||||||
if let Some(game_winner) = self.board.game_winner(self.players[current_player].color())
|
match self.board.game_winner(self.players[current_player].color()) {
|
||||||
{
|
crate::board::Winner::Player(piece) => {
|
||||||
println!("{} Wins!", game_winner.text());
|
println!("{} Wins!", piece.text());
|
||||||
// end the game
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
crate::board::Winner::Tie => {
|
||||||
|
println!("Game Tied!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
crate::board::Winner::None => {}
|
||||||
|
}
|
||||||
|
|
||||||
self.step(current_player);
|
self.step(current_player);
|
||||||
current_player += 1;
|
current_player += 1;
|
||||||
|
|||||||
@ -12,8 +12,8 @@ mod piece;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let player1 = complexagent::ComplexAgent::new(Piece::Black);
|
let player1 = complexagent::ComplexAgent::new(Piece::Black);
|
||||||
// let player2 = complexagent::ComplexAgent::new(Piece::White);
|
// let player2 = complexagent::ComplexAgent::new(Piece::White);
|
||||||
let player2 = agent::ManualAgent::new(Piece::White);
|
// let player2 = agent::ManualAgent::new(Piece::White);
|
||||||
// let player2 = agent::RandomAgent::new(Piece::White);
|
let player2 = agent::RandomAgent::new(Piece::White);
|
||||||
let mut game = Game::new(Box::new(player1), Box::new(player2));
|
let mut game = Game::new(Box::new(player1), Box::new(player2));
|
||||||
game.game_loop();
|
game.game_loop();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user