perf: alpha-beta pruning
This commit is contained in:
parent
39d5ebc2b3
commit
8d5ea3de31
@ -20,9 +20,34 @@ use std::cmp::max;
|
|||||||
|
|
||||||
// min can't be represented as positive
|
// min can't be represented as positive
|
||||||
const EVAL_WORST: EvalInt = -(EvalInt::MAX);
|
const EVAL_WORST: EvalInt = -(EvalInt::MAX);
|
||||||
|
const EVAL_BEST: EvalInt = EvalInt::MAX;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_eval_int {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eval_worst_best_symm() {
|
||||||
|
// int limits will bite you if you don't test this
|
||||||
|
assert_eq!(EVAL_WORST, -EVAL_BEST);
|
||||||
|
assert_eq!(-EVAL_WORST, EVAL_BEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Search the game tree to find the absolute (positive good) eval for the current player.
|
/// Search the game tree to find the absolute (positive good) eval for the current player.
|
||||||
fn minmax(board: &mut Board, depth: usize) -> EvalInt {
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * board: board position to analyze.
|
||||||
|
/// * depth: how deep to analyze the game tree.
|
||||||
|
/// * alpha: best score (absolute, from current player perspective) guaranteed for current player.
|
||||||
|
/// * beta: best score (absolute, from current player perspective) guaranteed for other player.
|
||||||
|
fn minmax(board: &mut Board, depth: usize, alpha: Option<EvalInt>, beta: Option<EvalInt>) -> EvalInt {
|
||||||
|
// default to worst, then gradually improve
|
||||||
|
let mut alpha = alpha.unwrap_or(EVAL_WORST);
|
||||||
|
// our best is their worst
|
||||||
|
let beta = beta.unwrap_or(EVAL_BEST);
|
||||||
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
let eval = board.eval();
|
let eval = board.eval();
|
||||||
match board.turn {
|
match board.turn {
|
||||||
@ -46,8 +71,19 @@ fn minmax(board: &mut Board, depth: usize) -> EvalInt {
|
|||||||
|
|
||||||
for mv in mvs {
|
for mv in mvs {
|
||||||
let anti_mv = mv.make(board);
|
let anti_mv = mv.make(board);
|
||||||
abs_best = max(abs_best, -minmax(board, depth - 1));
|
let abs_score = -minmax(board, depth - 1, Some(-beta), Some(-alpha));
|
||||||
|
abs_best = max(abs_best, abs_score);
|
||||||
|
alpha = max(alpha, abs_best);
|
||||||
anti_mv.unmake(board);
|
anti_mv.unmake(board);
|
||||||
|
if alpha >= beta {
|
||||||
|
// alpha-beta prune.
|
||||||
|
//
|
||||||
|
// Beta represents the best eval that the other player can get in sibling branches
|
||||||
|
// (different moves in the parent node). Alpha >= beta means the eval here is _worse_
|
||||||
|
// for the other player, so they will never make the move that leads into this branch.
|
||||||
|
// Therefore, we stop evaluating this branch at all.
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abs_best
|
abs_best
|
||||||
@ -64,7 +100,7 @@ fn search(board: &mut Board) -> Option<Move> {
|
|||||||
|
|
||||||
for mv in mvs {
|
for mv in mvs {
|
||||||
let anti_mv = mv.make(board);
|
let anti_mv = mv.make(board);
|
||||||
let abs_eval = -minmax(board, DEPTH);
|
let abs_eval = -minmax(board, DEPTH, None, None);
|
||||||
if abs_eval >= best_eval {
|
if abs_eval >= best_eval {
|
||||||
best_eval = abs_eval;
|
best_eval = abs_eval;
|
||||||
best_mv = Some(mv);
|
best_mv = Some(mv);
|
||||||
|
Loading…
Reference in New Issue
Block a user