Skip to content

Commit

Permalink
lots of improving and lots of questions
Browse files Browse the repository at this point in the history
  • Loading branch information
bananasmoothii committed Oct 8, 2023
1 parent 6a7a06e commit 91ccef3
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 87 deletions.
4 changes: 2 additions & 2 deletions src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ impl<G: Game> Bot<G> {
self.max_depth,
game_tree.expect_game().plays() as u32,
);
// println!("Tree:\n {}", game_tree.debug(3));
// println!("Into best child...");
// println!("Tree:\n {}", game_tree.debug(2));
println!("Comparing possibilities...");
self.game_tree = Some(self.game_tree.take().unwrap().into_best_child());

let game_tree = self.game_tree.as_ref().unwrap();
Expand Down
20 changes: 15 additions & 5 deletions src/game/connect4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::cmp::min;
use std::num::{NonZeroU8, NonZeroUsize};

use console::Style;
use rand::Rng;

use crate::game::connect4::count_direction::CountDirection;
use crate::game::connect4::iteration::{BoardIterator, P4IteratorType};
Expand Down Expand Up @@ -132,7 +133,7 @@ impl Power4 {
}

fn calculate_score(&self, aligns2: u16, aligns3: u16) -> i32 {
(5 * aligns2 + 25 * aligns3) as i32
(10 * aligns2 + 100 * aligns3) as i32
}

pub fn get_isize(&self, (row, column): (isize, isize)) -> Option<NonZeroU8> {
Expand Down Expand Up @@ -271,12 +272,14 @@ impl Game for Power4 {
* Returns the score of the player, higher is better
*
* Scores:
* - 2 aligned: 5n (n = number of 2 aligned)
* - 3 aligned: 10^n (n = number of 3 aligned)
* - 2 aligned: 20n (n = number of 2 aligned)
* - 3 aligned: 100n (n = number of 3 aligned)
* - 4 aligned: infinite
* Subtract the same score for the opponent
* Scores are invalid if the line cannot be completed
*/
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;
Expand Down Expand Up @@ -402,9 +405,16 @@ impl Game for Power4 {
}

fn possible_plays(&self) -> Vec<NonZeroUsize> {
(1..=7usize)
let order: [usize; 7] = match rand::thread_rng().gen_range(0..=3) {
0 => [4, 3, 5, 2, 6, 1, 7],
1 => [3, 5, 4, 2, 6, 1, 7],
2 => [2, 6, 4, 3, 5, 1, 7],
_ => [7, 1, 6, 2, 5, 3, 4],
};
order
.iter()
.filter(|&column| self.get((0, column - 1)).is_none())
.map(|column| NonZeroUsize::new(column).unwrap())
.map(|&column| NonZeroUsize::new(column).unwrap())
.collect::<Vec<NonZeroUsize>>()
}

Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod min_max;
mod scalar;

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

let p1 = NonZeroU8::new(1).unwrap();
Expand All @@ -23,7 +23,7 @@ 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 p1_score: i32 = 0;
Expand Down
147 changes: 79 additions & 68 deletions src/min_max.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::{Arc, Mutex};

use rayon::iter::*;
Expand Down Expand Up @@ -40,9 +41,9 @@ impl<G: Game> GameNode<G> {
// self.complete_weights(bot_player);
}

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

const USE_GAME_SCORE: bool = false;
const USE_GAME_SCORE: bool = true;

const MULTI_THREADING: bool = true;

Expand Down Expand Up @@ -73,21 +74,8 @@ impl<G: Game> GameNode<G> {
if self.check_max_depth(bot_player, max_depth, real_plays)
|| (do_checks && (self.check_winner(bot_player) || self.check_draw()))
{
let game = self.game.as_ref().expect("game was removed too early");
return if Self::USE_GAME_SCORE {
game.get_score(bot_player)
} else {
if let Some(winner) = game.get_winner() {
if winner == bot_player {
G::Score::MAX()
} else {
G::Score::MIN()
}
.add_towards_0(self.depth() as i32 - real_plays as i32)
} else {
G::Score::ZERO()
}
};
// weight should have been set by the functions above
return self.weight().unwrap();
}

let maximize = now_playing == bot_player;
Expand All @@ -103,6 +91,8 @@ impl<G: Game> GameNode<G> {

let check_children = self.fill_children_and_destroy_game(now_playing, real_plays);

let auto_destroy = AtomicBool::new(false);

let maybe_explore_children = |child: &mut Self| {
let child_score = child.explore_children_recur(
bot_player,
Expand All @@ -127,6 +117,7 @@ impl<G: Game> GameNode<G> {
if (parent_maximize && *worst_child_score < *worst_sibling_score)
|| (!parent_maximize && *worst_child_score > *worst_sibling_score)
{
auto_destroy.store(true, Relaxed);
None // abort checking childs
} else {
// parent may choose us
Expand All @@ -138,13 +129,12 @@ impl<G: Game> GameNode<G> {
//println!("{}({})Exploring {} children of depth {} (actual: {})...", spaces, self.id(), self.children.len(), self.depth(), self.depth().overflowing_sub(real_plays).0);

//println!("({} - {real_plays} = {} )", self.depth(), self.depth().overflowing_sub(real_plays).0);
let weight = if self.depth().overflowing_sub(real_plays).0 == Self::FORK_DEPTH
&& Self::MULTI_THREADING
{
let weight = if self.is_parallelize_depth(real_plays) {
// parallelize
self.children
.par_iter_mut()
.try_for_each(|(_, child)| maybe_explore_children(child));
self.children.par_iter_mut().try_for_each(|(_, child)| {
print!("F");
maybe_explore_children(child)
});
let weight = (*worst_child_score.lock().unwrap()).into();
self.set_weight(weight);
weight.unwrap()
Expand All @@ -157,9 +147,18 @@ impl<G: Game> GameNode<G> {
weight.unwrap()
};

if auto_destroy.load(Relaxed) {
self.children = Vec::with_capacity(0);
}

weight
}

fn is_parallelize_depth(&self, real_plays: u32) -> bool {
// no need to check whether it overflows as there won't be u32::MAX plays
self.depth().overflowing_sub(real_plays).0 == Self::FORK_DEPTH && Self::MULTI_THREADING
}

/// Returns true if childrens should be checked for win or draw, false if they were already checked
fn fill_children_and_destroy_game(
&mut self,
Expand All @@ -174,22 +173,22 @@ impl<G: Game> GameNode<G> {

let possible_plays = game.possible_plays();
let possibilities = possible_plays.len();
let mut map = HashMap::with_capacity(possibilities);
let mut vec = Vec::with_capacity(possibilities);

if possibilities >= 2 || !take_game {
for i in 0..(possibilities - 1) {
let input_coord = possible_plays[i];
let mut game = game.clone();
game.play(now_playing, input_coord).unwrap(); // should not panic as input_coord is a possible play
map.insert(
vec.push((
input_coord,
GameNode::new(
Some(game),
self.depth() + 1,
None,
PlayersTurn(now_playing.other(), Some(input_coord)),
),
);
));
}
}
if possibilities >= 1 {
Expand All @@ -202,18 +201,18 @@ impl<G: Game> GameNode<G> {
game.clone()
};
game.play(now_playing, input_coord).unwrap();
map.insert(
vec.push((
input_coord,
GameNode::new(
Some(game),
self.depth() + 1,
None,
PlayersTurn(now_playing.other(), Some(input_coord)),
),
);
));
}

self.children = map;
self.children = vec;
true
} else {
self.regenerate_children_games();
Expand Down Expand Up @@ -272,59 +271,69 @@ impl<G: Game> GameNode<G> {
) -> bool {
let game = self.game.as_ref().expect("game was removed too early");
if self.depth() >= max_depth + real_plays {
// I know this is a constant, but this allows me to change it easily
if Self::USE_GAME_SCORE {
let score = game.get_score(bot_player); // computing score here
if score == G::Score::MAX() {
self.game_state = self.game_state.to_win_by(bot_player);
self.set_weight(Some(
score.add_towards_0((self.depth() - real_plays) as i32),
))
} else if score == G::Score::MIN() {
self.game_state = self.game_state.to_win_by(bot_player.other());
self.set_weight(Some(
score.add_towards_0((self.depth() - real_plays) as i32),
))
}
} else {
if let Some(winner) = game.get_winner() {
self.set_weight(Some(
(if winner == bot_player {
let weight = Some(
if Self::USE_GAME_SCORE {
// I know this is a constant, but this allows me to change it easily
let score = game.get_score(bot_player); // computing score here
if score == G::Score::MAX() {
self.game_state = self.game_state.to_win_by(bot_player);
} else if score == G::Score::MIN() {
self.game_state = self.game_state.to_win_by(bot_player.other());
}
score
} else {
if let Some(winner) = game.get_winner() {
self.game_state = self.game_state.to_win_by(winner);
if winner == bot_player {
G::Score::MAX()
} else {
G::Score::MIN()
})
.add_towards_0((self.depth() - real_plays) as i32),
));
self.game_state = self.game_state.to_win_by(winner);
} else {
self.set_weight(Some(G::Score::ZERO()));
}
} else {
G::Score::ZERO()
}
}
}
.add_towards_0((self.depth() - real_plays) as i32), // we want to prioritize the fastest win
);
self.set_weight(weight);
return true;
}
false
}

pub fn into_best_child(self) -> Self {
/*
let target_weight = self.weight().unwrap();
let mut best = G::Score::MIN();
let mut best_child = None;
for child in self.children {
let child_weight = child.1.weight().unwrap();
if child_weight == target_weight {
return child.1;
}
if child_weight > best {
best = child_weight;
best_child = Some(child.1);
for (_, child) in self.children.into_iter() {
if child.weight().unwrap() == target_weight {
best_child = Some(child);
break;
}
}
best_child.unwrap()
let mut best_child = best_child.expect("No children found");
best_child.fill_play(self.game.unwrap());
best_child
/*
let mut best = G::Score::MIN();
let mut best_child = None;
for mut child in self.children.into_values() {
let child_weight = child.weight().unwrap();
if child_weight == target_weight {
child.fill_play(self.game.unwrap());
return child;
}
if child_weight > best {
best = child_weight;
best_child = Some(child);
}
}
println!("Could not find target weight {} in children", target_weight);
let mut best_child = best_child.expect("No children found");
best_child.fill_play(self.game.unwrap());
best_child
*/
let game = self
.game
/*
let game = self.game
.expect("game should not have been removed from the root node");
let mut best_child = self
Expand All @@ -336,5 +345,7 @@ impl<G: Game> GameNode<G> {
best_child.fill_play(game);
best_child
*/
}
}
Loading

0 comments on commit 91ccef3

Please sign in to comment.