Skip to content

Commit

Permalink
fixed that f-ing hidden bug in get_score that caused alpha-beta pruni…
Browse files Browse the repository at this point in the history
…ng to do worse, hitting performance
  • Loading branch information
bananasmoothii committed Oct 9, 2023
1 parent 59917b2 commit 6c120c5
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 48 deletions.
12 changes: 7 additions & 5 deletions src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct Bot<G: Game> {
game_tree: Option<GameNode<G>>,
max_depth: u32,
times: Vec<u64>,
play_count: u32,
}

impl<G: Game> Bot<G> {
Expand All @@ -18,6 +19,7 @@ impl<G: Game> Bot<G> {
game_tree: Some(GameNode::new_root(G::new(), player, 0)),
max_depth,
times: Vec::new(),
play_count: 0,
}
}

Expand Down Expand Up @@ -53,6 +55,7 @@ impl<G: Game> Bot<G> {
let game = new_game_tree.into_expect_game();
self.game_tree = Some(GameNode::new_root(game, self.player, depth));
}
self.play_count += 1;
Ok(())
}

Expand All @@ -62,15 +65,14 @@ impl<G: Game> Bot<G> {
.game_tree
.as_mut()
.expect("Bot has not been initialized");
game_tree.explore_children(
self.player,
self.max_depth,
game_tree.expect_game().plays() as u32,
);
game_tree.explore_children(self.player, self.max_depth, self.play_count);

// println!("Tree:\n {}", game_tree.debug(2));
println!("Comparing possibilities...");
self.game_tree = Some(self.game_tree.take().unwrap().into_best_child());

self.play_count += 1;

let game_tree = self.game_tree.as_ref().unwrap();

let time = start.elapsed().as_millis() as u64;
Expand Down
5 changes: 0 additions & 5 deletions src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ pub trait Game: Clone + Send + Sync {

fn print(&self);

/**
* Number of plays made in the game
*/
fn plays(&self) -> u16;

/**
* Last play made in the game. None only if no play has been made yet.
*/
Expand Down
39 changes: 11 additions & 28 deletions src/game/connect4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ mod tests;
#[derive(Debug, Clone)]
pub struct Power4 {
board: [[Option<NonZeroU8>; 7]; 6],
plays: u16,
last_played_coords: Option<(usize, usize)>,
}

Expand Down Expand Up @@ -153,10 +152,6 @@ impl Power4 {
if self.last_played_coords.is_none() {
return None;
}
if self.plays < 7 {
// No winner before 7 plays
return None;
}
let last_coords = self.last_played_coords.unwrap();
let connect = Self::CONNECT as usize;
for mut line_iterator in self.lines_passing_at_longer_4(last_coords) {
Expand Down Expand Up @@ -239,7 +234,6 @@ impl Game for Power4 {
fn new() -> Power4 {
Power4 {
board: [[None; 7]; 6],
plays: 0,
last_played_coords: None,
}
}
Expand All @@ -260,7 +254,6 @@ impl Game for Power4 {
let y = 5 - i;
if self.board[y][column].is_none() {
self.board[y][column] = Some(player);
self.plays += 1;
self.last_played_coords = Some((y, column));
return Ok(());
}
Expand All @@ -280,15 +273,15 @@ impl Game for Power4 {
*/
fn get_score(&self, player: Self::Player) -> Self::Score {
// todo: optimize
let mut p1_aligns2: u16 = 0;
let mut p1_aligns3: u16 = 0;
let mut p2_aligns2: u16 = 0;
let mut p2_aligns3: u16 = 0;
let mut aligns2: u16 = 0;
let mut aligns3: u16 = 0;
let mut other_aligns2: u16 = 0;
let mut other_aligns3: u16 = 0;
// let debug_cell = |cell: Option<Option<NonZeroU8>>| cell.map(|c| c.map(|c| c.to_string()).unwrap_or("-".to_string())).unwrap_or("X".to_string());
let is_playable =
|cell: Option<Option<NonZeroU8>>| cell.is_some() && cell.unwrap().is_none();
for mut line_iterator in self.all_lines_longer_4() {
let mut strike_player = NonZeroU8::new(1u8).unwrap();
let mut strike_player = NonZeroU8::new(10u8).unwrap(); // this value is never used
let mut strike: u8 = 0;
let mut cell_option = line_iterator.get_with_offset(0);
while let Some(cell) = cell_option {
Expand Down Expand Up @@ -330,9 +323,9 @@ impl Game for Power4 {
// space 2 after
{
if strike_player == player {
p1_aligns2 += 1;
aligns2 += 1;
} else {
p2_aligns2 += 1;
other_aligns2 += 1;
}
}
}
Expand All @@ -342,9 +335,9 @@ impl Game for Power4 {
// space 1 after
{
if strike_player == player {
p1_aligns3 += 1;
aligns3 += 1;
} else {
p2_aligns3 += 1;
other_aligns3 += 1;
}
}
}
Expand All @@ -361,13 +354,7 @@ impl Game for Power4 {
cell_option = line_iterator.next();
}
}
let p1_score = self.calculate_score(p1_aligns2, p1_aligns3)
- self.calculate_score(p2_aligns2, p2_aligns3);
if player == NonZeroU8::new(1u8).unwrap() {
p1_score
} else {
-p1_score
}
self.calculate_score(aligns2, aligns3) - self.calculate_score(other_aligns2, other_aligns3)
}

fn get_winner(&self) -> Option<Self::Player> {
Expand Down Expand Up @@ -405,7 +392,7 @@ impl Game for Power4 {
}

fn possible_plays(&self) -> Vec<NonZeroUsize> {
let order: [usize; 7] = match rand::thread_rng().gen_range(0..=3) {
let order: [usize; 7] = match rand::thread_rng().gen_range(0..=4) {
0 => [4, 3, 5, 2, 6, 1, 7],
1 => [3, 5, 4, 2, 6, 1, 7],
2 => [2, 6, 4, 3, 5, 1, 7],
Expand Down Expand Up @@ -455,10 +442,6 @@ impl Game for Power4 {
}
}

fn plays(&self) -> u16 {
self.plays
}

fn last_play(&self) -> Option<Self::InputCoordinate> {
self.last_played_coords
.map(|(x, _)| NonZeroUsize::new(x).unwrap())
Expand Down
15 changes: 10 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ mod min_max;
mod scalar;

fn main() {
let max_depth = 10;
let bot_vs_bot = false;
let max_depth = 11;
let bot_vs_bot = true;

let p1 = NonZeroU8::new(1).unwrap();
let p2 = NonZeroU8::new(2).unwrap();
Expand All @@ -22,14 +22,18 @@ fn main() {

let mut current_player = if bot_vs_bot || ask_start() { p1 } else { p2 };

let mut bot: Bot<Power4> = Bot::new(p2, max_depth);
// TODO: WHY IS OTHER8BOT 10 TIMES FASTER THAN BOT?????
let mut other_bot: Bot<Power4> = Bot::new(p1, max_depth);
let mut bot: Bot<Power4> = Bot::new(bot_player, max_depth);
let mut other_bot: Bot<Power4> = Bot::new(bot_player.other(), max_depth);

let mut p1_score: i32 = 0;
loop {
println!();
bot.expect_game().print();
#[cfg(debug_assertions)]
{
let p2_score = other_bot.expect_game().get_score(p2);
assert_eq!(p1_score, -p2_score);
}
println!("Scores: {p1_score} for player 1");
println!();
println!("Player {current_player}'s turn");
Expand Down Expand Up @@ -65,6 +69,7 @@ fn main() {
game.print();
break;
}

current_player = current_player.other();
}
println!("Average time: {}ms", bot.average_time());
Expand Down
31 changes: 27 additions & 4 deletions src/min_max.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::{AtomicBool, AtomicI32};
use std::sync::{Arc, Mutex};

use rayon::iter::*;
Expand All @@ -24,6 +24,9 @@ impl<G: Game> GameNode<G> {

println!("Exploring possibilities...");

#[cfg(debug_assertions)]
let call_count = Arc::new(AtomicI32::new(0));

self.explore_children_recur(
bot_player,
max_depth,
Expand All @@ -35,13 +38,27 @@ impl<G: Game> GameNode<G> {
} else {
G::Score::MIN()
})),
#[cfg(debug_assertions)]
call_count.clone(),
);

// println!("Completing weights...");
// self.complete_weights(bot_player);
#[cfg(debug_assertions)]
{
let call_count = call_count.load(Relaxed) as f64;
let call_cout_str = if call_count >= 1e9 {
format!("{:.2}G", call_count / 1e9)
} else if call_count >= 1e6 {
format!("{:.2}M", call_count / 1e6)
} else if call_count >= 1e3 {
format!("{:.2}K", call_count / 1e3)
} else {
format!("{}", call_count)
};
println!("Call count: {call_cout_str}");
}
}

const FORK_DEPTH: u32 = 1;
const FORK_DEPTH: u32 = 2;

const USE_GAME_SCORE: bool = true;

Expand All @@ -66,9 +83,13 @@ impl<G: Game> GameNode<G> {
real_plays: u32,
checks: bool,
worst_sibling_score: Arc<Mutex<G::Score>>,
#[cfg(debug_assertions)] call_count: Arc<AtomicI32>,
) -> G::Score {
assert!(self.depth() >= real_plays, "Negative exploration");

#[cfg(debug_assertions)]
call_count.fetch_add(1, Relaxed);

let do_checks = checks || self.children.is_empty();

if self.check_max_depth(bot_player, max_depth, real_plays)
Expand Down Expand Up @@ -101,6 +122,8 @@ impl<G: Game> GameNode<G> {
real_plays,
check_children,
worst_child_score.clone(),
#[cfg(debug_assertions)]
call_count.clone(),
);
let mut worst_child_score = worst_child_score.lock().unwrap();
// println!("maximize: {maximize}, child: {child_score} worst child: {worst_child_score}");
Expand Down
1 change: 0 additions & 1 deletion src/min_max/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use crate::game::Game;

#[derive(Clone)]
pub struct GameNode<G: Game> {
// todo: reduce size of this struct
depth: u32,
weight: Option<G::Score>,
pub(super) children: Vec<(G::InputCoordinate, Self)>,
Expand Down

0 comments on commit 6c120c5

Please sign in to comment.