mirror of
https://github.com/Noratrieb/minmax.git
synced 2026-01-14 15:25:08 +01:00
minmax
This commit is contained in:
parent
a643d11021
commit
36fb1a5d3b
3 changed files with 37 additions and 12 deletions
|
|
@ -148,7 +148,7 @@ impl IndexMut<usize> for Connect4 {
|
||||||
impl Game for Connect4 {
|
impl Game for Connect4 {
|
||||||
type Move = usize;
|
type Move = usize;
|
||||||
|
|
||||||
const REASONABLE_SEARCH_DEPTH: Option<usize> = Some(7);
|
const REASONABLE_SEARCH_DEPTH: Option<usize> = Some(11);
|
||||||
|
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,14 @@ pub trait Game: Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Score(i32);
|
pub struct Score(i32);
|
||||||
|
|
||||||
impl Score {
|
impl Score {
|
||||||
const MIN: Self = Self(i32::MIN);
|
// Due to the nature of two's completement, we can't actually negate this properly, so add 1.
|
||||||
const LOST: Self = Self(-100);
|
const LOST: Self = Self(i32::MIN + 1);
|
||||||
const TIE: Self = Self(0);
|
const TIE: Self = Self(0);
|
||||||
const WON: Self = Self(100);
|
const WON: Self = Self(i32::MAX);
|
||||||
|
|
||||||
pub fn new(int: i32) -> Self {
|
pub fn new(int: i32) -> Self {
|
||||||
Self(int)
|
Self(int)
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,23 @@ impl<G: Game> PerfectPlayer<G> {
|
||||||
self.best_move.expect("no move made yet")
|
self.best_move.expect("no move made yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn minmax(&mut self, board: &mut G, player: Player, depth: usize) -> Score {
|
fn minmax(
|
||||||
|
&mut self,
|
||||||
|
board: &mut G,
|
||||||
|
maximizing_player: Player,
|
||||||
|
alpha: Score,
|
||||||
|
beta: Score,
|
||||||
|
depth: usize,
|
||||||
|
) -> Score {
|
||||||
|
// FIXME: Make depth decrease not increase.
|
||||||
if let Some(max_depth) = self.max_depth && depth >= max_depth {
|
if let Some(max_depth) = self.max_depth && depth >= max_depth {
|
||||||
return board.rate(player);
|
// FIXME: Why do we have rate and result?
|
||||||
|
return board.rate(maximizing_player);
|
||||||
}
|
}
|
||||||
|
|
||||||
match board.result() {
|
match board.result() {
|
||||||
State::Winner(winner) => {
|
State::Winner(winner) => {
|
||||||
if winner == player {
|
if winner == maximizing_player {
|
||||||
Score::WON
|
Score::WON
|
||||||
} else {
|
} else {
|
||||||
Score::LOST
|
Score::LOST
|
||||||
|
|
@ -48,11 +57,12 @@ impl<G: Game> PerfectPlayer<G> {
|
||||||
}
|
}
|
||||||
State::Draw => Score::TIE,
|
State::Draw => Score::TIE,
|
||||||
State::InProgress => {
|
State::InProgress => {
|
||||||
let mut max_value = Score::MIN;
|
let mut max_value = alpha;
|
||||||
|
|
||||||
for pos in board.possible_moves() {
|
for pos in board.possible_moves() {
|
||||||
board.make_move(pos, player);
|
board.make_move(pos, maximizing_player);
|
||||||
let value = -self.minmax(board, player.opponent(), depth + 1);
|
let value =
|
||||||
|
-self.minmax(board, maximizing_player.opponent(), -beta, -max_value, depth + 1);
|
||||||
|
|
||||||
board.undo_move(pos);
|
board.undo_move(pos);
|
||||||
|
|
||||||
|
|
@ -62,6 +72,21 @@ impl<G: Game> PerfectPlayer<G> {
|
||||||
self.best_move = Some(pos);
|
self.best_move = Some(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Imagine a game tree like this
|
||||||
|
// P( )
|
||||||
|
// / \
|
||||||
|
// A(10) B( ) <- we are here in the loop for the first child that returned 11.
|
||||||
|
// / \
|
||||||
|
// C(11) D( )
|
||||||
|
//
|
||||||
|
// Our beta parameter is 10, because that's the current max_value of our parent.
|
||||||
|
// If P plays B, we know that B will pick something _at least_ as good as C. This means
|
||||||
|
// that B will be -11 or worse. -11 is definitly worse than -10, so playing B is definitly
|
||||||
|
// a very bad idea, no matter the value of D. So don't even bother calculating the value of D
|
||||||
|
// and just break out.
|
||||||
|
if max_value >= beta {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
max_value
|
max_value
|
||||||
|
|
@ -74,7 +99,7 @@ impl<G: Game> GamePlayer<G> for PerfectPlayer<G> {
|
||||||
fn next_move(&mut self, board: &mut G, this_player: Player) {
|
fn next_move(&mut self, board: &mut G, this_player: Player) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
self.best_move = None;
|
self.best_move = None;
|
||||||
self.minmax(board, this_player, 0);
|
self.minmax(board, this_player, Score::LOST, Score::WON, 0);
|
||||||
|
|
||||||
board.make_move(self.best_move.expect("could not make move"), this_player);
|
board.make_move(self.best_move.expect("could not make move"), this_player);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue