Skip to content

Commit

Permalink
bot players now working
Browse files Browse the repository at this point in the history
  • Loading branch information
bananasmoothii committed Oct 7, 2023
1 parent ece3191 commit 8b62b1a
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 153 deletions.
98 changes: 98 additions & 0 deletions src/bot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::game::player::Player;
use crate::game::Game;
use crate::min_max::node::GameNode;
use crate::scalar::Scalar;

pub struct Bot<G: Game> {
player: G::Player,
game_tree: Option<GameNode<G>>,
max_depth: u32,
times: Vec<u64>,
}

impl<G: Game> Bot<G> {
pub fn new(player: G::Player, max_depth: u32) -> Self {
Self {
player,
/// game_tree should never be None
game_tree: Some(GameNode::new_root(G::new(), player, 0)),
max_depth,
times: Vec::new(),
}
}

pub fn other_played(&mut self, play: G::InputCoordinate) -> Result<(), &str> {
let had_children = self
.game_tree
.as_ref()
.is_some_and(|tree| !tree.children().is_empty());
let (is_known_move, mut new_game_tree) = self
.game_tree
.take()
.unwrap_or_else(|| panic!("game_tree should never be none, object is invalid"))
.try_into_child(play);
if is_known_move {
self.game_tree = Some(new_game_tree);
debug_assert!(self
.game_tree
.as_ref()
.is_some_and(|tree| tree.game().is_some()));
} else {
// Here, new_game_tree is actually game_tree, the ownership was given back to us
let result = new_game_tree
.expect_game_mut()
.play(self.player.other(), play);
if let Err(err) = result {
self.game_tree = Some(new_game_tree);
return Err(err);
}
if had_children {
println!("Unexpected move... Maybe you are a pure genius, or a pure idiot.");
}
let depth = new_game_tree.depth() + 1;
let game = new_game_tree.into_expect_game();
self.game_tree = Some(GameNode::new_root(game, self.player, depth));
}
Ok(())
}

pub fn play(&mut self) -> G::InputCoordinate {
let start = std::time::Instant::now();
let game_tree = self
.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,
);
// println!("Tree:\n {}", game_tree.debug(3));
// println!("Into best child...");
self.game_tree = Some(self.game_tree.take().unwrap().into_best_child());

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

let time = start.elapsed().as_millis() as u64;
self.times.push(time);
println!("Done in {}ms", time);

let weight_opt = game_tree.weight();
if weight_opt.is_some_and(|it| it > G::Score::MAX().add_towards_0(1000)) {
println!("You're dead, sorry.");
} else if weight_opt.is_some_and(|it| it < G::Score::MIN().add_towards_0(1000)) {
println!("Ok I'm basically dead...");
}

debug_assert!(game_tree.game().is_some());
game_tree.game_state.get_last_play().1.unwrap()
}

pub fn average_time(&self) -> u64 {
self.times.iter().sum::<u64>() / self.times.len() as u64
}

pub fn expect_game(&self) -> &G {
self.game_tree.as_ref().unwrap().expect_game()
}
}
4 changes: 3 additions & 1 deletion src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub mod connect4;
pub mod player;
pub(crate) mod state;

pub trait Game: Clone + Send {
pub trait Game: Clone + Send + Sync {
type Coordinate;

type InputCoordinate: Copy + Eq + Ord + Hash + Display + Send + Sync;
Expand All @@ -17,6 +17,8 @@ pub trait Game: Clone + Send {

type Score: Scalar;

fn new() -> Self;

fn get(&self, coordinate: Self::Coordinate) -> Option<&Self::Player>;

fn play<'a>(
Expand Down
96 changes: 36 additions & 60 deletions src/game/connect4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ pub struct Power4 {
}

impl Power4 {
pub fn new() -> Power4 {
Power4 {
board: [[None; 7]; 6],
plays: 0,
last_played_coords: None,
}
}

/**
* Returns all iterators for all lines having 4 or more cells
*/
Expand Down Expand Up @@ -150,7 +142,9 @@ impl Power4 {
self.board[row as usize][column as usize]
}

pub fn get_winner_coords(&self) -> Option<[<Self as Game>::Coordinate; 4]> {
pub fn get_winner_coords(
&self,
) -> Option<[<Self as Game>::Coordinate; Self::CONNECT as usize]> {
if self.last_played_coords.is_none() {
return None;
}
Expand All @@ -159,8 +153,9 @@ impl Power4 {
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) {
let mut winner_coords: Vec<(isize, isize)> = Vec::with_capacity(7);
let mut winner_coords: Vec<(isize, isize)> = Vec::with_capacity(2 * connect - 1);
let mut strike_player = NonZeroU8::new(1u8).unwrap();
let mut strike: u8 = 0;
let mut cell_option = line_iterator.get_with_offset(0);
Expand All @@ -170,11 +165,11 @@ impl Power4 {
if strike_player == cell_player {
strike += 1;

if strike == 4 {
let mut result = [(0usize, 0usize); 4];
if strike == Self::CONNECT {
let mut result = [(0usize, 0usize); Self::CONNECT as usize];
let winner_coords_size = winner_coords.len();
for i in 0..4 {
let (y, x) = winner_coords[winner_coords_size - 4 + i];
for i in 0..connect {
let (y, x) = winner_coords[winner_coords_size - connect + i];
result[i] = (y as usize, x as usize);
}
return Some(result);
Expand Down Expand Up @@ -219,6 +214,8 @@ impl Power4 {
}
count
}

const CONNECT: u8 = 4; // should be 4 for connect-4
}

impl Game for Power4 {
Expand All @@ -234,6 +231,14 @@ impl Game for Power4 {

type Score = i32;

fn new() -> Power4 {
Power4 {
board: [[None; 7]; 6],
plays: 0,
last_played_coords: None,
}
}

fn get(&self, (row, column): (usize, usize)) -> Option<&NonZeroU8> {
if row >= 6 || column >= 7 {
return None;
Expand Down Expand Up @@ -304,6 +309,13 @@ impl Game for Power4 {
*/

match strike {
Self::CONNECT => {
return if strike_player == player {
i32::MAX
} else {
i32::MIN
};
}
2 => {
if (is_playable(before3()) && is_playable(before2())) // space 2 before
|| (is_playable(after1()) && is_playable(after2()))
Expand All @@ -328,13 +340,6 @@ impl Game for Power4 {
}
}
}
4 => {
return if strike_player == player {
i32::MAX
} else {
i32::MIN
};
}
_ => {}
}
} else {
Expand All @@ -357,62 +362,33 @@ impl Game for Power4 {
}
}

/*
fn get_winner(&self) -> Option<Self::Player> {
if self.last_played_coords.is_none() {
return None;
}
let last_coords = self.last_played_coords.unwrap();
for mut line_iterator in self.lines_passing_at_longer_4(last_coords) {
let mut strike_player = NonZeroU8::new(1u8).unwrap();
let mut strike: u8 = 0;
let mut cell_option = line_iterator.get_with_offset(0);
while let Some(cell) = cell_option {
if let Some(cell_player) = cell {
if strike_player == cell_player {
strike += 1;
if strike == 4 {
return Some(cell_player);
}
} else {
strike_player = cell_player;
strike = 1;
}
} else {
strike = 0;
}
cell_option = line_iterator.next();
}
}
None
}
*/

fn get_winner(&self) -> Option<Self::Player> {
if self.last_played_coords.is_none() {
return None;
}
let last_coords = self.last_played_coords.unwrap();
let counting_player = *self.get(last_coords).unwrap();
let connect_minus1 = Self::CONNECT - 1;
for count_direction in CountDirection::half_side() {
// max is 3 because we don't count the middle/start cell
let count = self.count_in_direction(last_coords, count_direction, 3);
if count == 3 {
let count = self.count_in_direction(last_coords, count_direction, connect_minus1);
if count == connect_minus1 {
return Some(counting_player);
}
let count_opposite =
self.count_in_direction(last_coords, count_direction.opposite(), 3 - count);
if count + count_opposite == 3 {
let count_opposite = self.count_in_direction(
last_coords,
count_direction.opposite(),
connect_minus1 - count,
);
if count + count_opposite == connect_minus1 {
return Some(counting_player);
}
}
None
}

fn is_full(&self) -> bool {
for i in 0..6 {
for i in 0..7 {
if self.board[0][i].is_none() {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/game/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl<G: Game> GameState<G> {
current_player.other(),
last_input.expect("Cannot draw when no play has been made"),
),
_ => panic!("game is not at playing state"),
_ => panic!("game is not at playing state, but at {}", self),
}
}

Expand Down
Loading

0 comments on commit 8b62b1a

Please sign in to comment.