Convert minimax to negamax and implement alpha-beta pruning
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
use crate::{
|
||||
board::{
|
||||
board::Color,
|
||||
game::Game,
|
||||
history::{History, MoveParameters},
|
||||
},
|
||||
evaluation::evaluation::evaluate_position,
|
||||
movegen::r#move::Move,
|
||||
};
|
||||
|
||||
pub fn minimax(game: &mut Game, depth: u8) -> (Option<Move>, i32) {
|
||||
let color = game.current_player();
|
||||
|
||||
if depth == 0 {
|
||||
return (None, evaluate_position(&game.board, color));
|
||||
}
|
||||
|
||||
let mut history = History::new();
|
||||
let mut best_move: Option<Move> = None;
|
||||
let (mut best_score, mate_score) = match color {
|
||||
Color::White => (-100000, -49000 - depth as i32),
|
||||
Color::Black => (100000, 49000 + depth as i32),
|
||||
};
|
||||
|
||||
let moves = game.board.pseudo_moves_all(color);
|
||||
let mut legal_moves = 0;
|
||||
|
||||
for mv in moves {
|
||||
history.push_move_parameters(MoveParameters::build(&game.board, &mv));
|
||||
game.board.make_move(&mv);
|
||||
|
||||
if !game.board.king_under_check(color) {
|
||||
legal_moves += 1;
|
||||
let (_, node_score) = minimax(game, depth - 1);
|
||||
match color {
|
||||
Color::White => {
|
||||
if best_score < node_score {
|
||||
best_score = node_score;
|
||||
best_move = Some(mv);
|
||||
}
|
||||
}
|
||||
Color::Black => {
|
||||
if best_score > node_score {
|
||||
best_score = node_score;
|
||||
best_move = Some(mv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
game.board
|
||||
.unmake_move(history.pop_move_parameters().expect("Empty history stack"));
|
||||
}
|
||||
|
||||
if legal_moves == 0 {
|
||||
if game.board.king_under_check(color) {
|
||||
return (None, mate_score);
|
||||
} else {
|
||||
return (None, 0);
|
||||
}
|
||||
}
|
||||
(best_move, best_score)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::board::{fen::from_fen, square::Square};
|
||||
use crate::movegen::attack::init_attacks;
|
||||
use crate::movegen::r#move::Move;
|
||||
use crate::search::minimax::minimax;
|
||||
|
||||
const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1";
|
||||
|
||||
#[test]
|
||||
fn test_minimax() -> Result<(), String> {
|
||||
init_attacks();
|
||||
let mut game = from_fen(FEN_MATE_IN_1)?;
|
||||
|
||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||
|
||||
assert_eq!(e3f2, minimax(&mut game, 2).0.unwrap());
|
||||
assert_eq!(e3f2, minimax(&mut game, 3).0.unwrap());
|
||||
assert_eq!(e3f2, minimax(&mut game, 4).0.unwrap());
|
||||
assert_eq!(e3f2, minimax(&mut game, 5).0.unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod minimax;
|
||||
pub mod negamax;
|
||||
pub mod perft;
|
||||
|
||||
94
src/search/negamax.rs
Normal file
94
src/search/negamax.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use crate::{
|
||||
board::{game::Game, history::MoveParameters},
|
||||
evaluation::evaluation::evaluate_position,
|
||||
movegen::r#move::Move,
|
||||
};
|
||||
|
||||
pub fn negamax(
|
||||
game: &mut Game,
|
||||
mut alpha: i32,
|
||||
beta: i32,
|
||||
depth: u8,
|
||||
plies: u8,
|
||||
) -> (Option<Move>, i32) {
|
||||
let color = game.current_player();
|
||||
|
||||
if depth == 0 {
|
||||
return (None, evaluate_position(&game.board));
|
||||
}
|
||||
|
||||
let (mut best_move, mut best_score, mate_score) = (None, -100000, -50000 + plies as i32);
|
||||
let mut legal_moves = 0;
|
||||
|
||||
for mv in game.board.pseudo_moves_all(color) {
|
||||
let move_parameters = MoveParameters::build(&game.board, &mv);
|
||||
game.board.make_move(&mv);
|
||||
|
||||
if !game.board.king_under_check(color) {
|
||||
legal_moves += 1;
|
||||
let move_score = -negamax(game, -beta, -alpha, depth - 1, plies + 1).1;
|
||||
|
||||
if move_score > best_score {
|
||||
best_score = move_score;
|
||||
best_move = Some(mv)
|
||||
}
|
||||
|
||||
if move_score >= beta {
|
||||
game.board.unmake_move(move_parameters);
|
||||
return (Some(mv), beta); // fail hard beta-cutoff
|
||||
}
|
||||
|
||||
if move_score > alpha {
|
||||
alpha = move_score; // alpha acts like max in minimax
|
||||
}
|
||||
}
|
||||
game.board.unmake_move(move_parameters);
|
||||
}
|
||||
|
||||
if legal_moves == 0 {
|
||||
if game.board.king_under_check(color) {
|
||||
return (None, mate_score);
|
||||
} else {
|
||||
return (None, 0);
|
||||
}
|
||||
}
|
||||
(best_move, best_score)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::board::{fen::from_fen, square::Square};
|
||||
use crate::evaluation::evaluation::{MAX_EVAL, MIN_EVAL};
|
||||
use crate::movegen::attack::init_attacks;
|
||||
use crate::movegen::r#move::Move;
|
||||
use crate::search::negamax::negamax;
|
||||
|
||||
const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1";
|
||||
|
||||
#[test]
|
||||
fn test_negamax() -> Result<(), String> {
|
||||
init_attacks();
|
||||
let mut game = from_fen(FEN_MATE_IN_1)?;
|
||||
|
||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||
|
||||
assert_eq!(
|
||||
e3f2,
|
||||
negamax(&mut game, MIN_EVAL, MAX_EVAL, 2, 0).0.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
e3f2,
|
||||
negamax(&mut game, MIN_EVAL, MAX_EVAL, 3, 0).0.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
e3f2,
|
||||
negamax(&mut game, MIN_EVAL, MAX_EVAL, 4, 0).0.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
e3f2,
|
||||
negamax(&mut game, MIN_EVAL, MAX_EVAL, 5, 0).0.unwrap()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user