Add Null-move pruning
This commit is contained in:
@@ -119,6 +119,13 @@ impl Board {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn non_pawn_materials(&self) -> u64 {
|
||||
self.pieces[PieceType::Knight]
|
||||
| self.pieces[PieceType::Bishop]
|
||||
| self.pieces[PieceType::Rook]
|
||||
| self.pieces[PieceType::Queen]
|
||||
}
|
||||
|
||||
pub fn set_piece(&mut self, bb: Bitboard, piece_type: PieceType, color: Color) {
|
||||
self.pieces[piece_type] |= bb;
|
||||
self.color[color] |= bb;
|
||||
|
||||
@@ -249,6 +249,41 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_null_move(&mut self) {
|
||||
let move_params = MoveParameters::build_null(self);
|
||||
self.history.push_move_parameters(move_params);
|
||||
self.hash.update_side_to_move_key();
|
||||
|
||||
self.board
|
||||
.state
|
||||
.update_null_game_state(self.current_player());
|
||||
}
|
||||
|
||||
pub fn unmake_null_move(&mut self) {
|
||||
let board = &mut self.board;
|
||||
let move_parameters = &mut self
|
||||
.history
|
||||
.pop_move_parameters()
|
||||
.expect("History stack is empty");
|
||||
|
||||
let color_before_move = board.state.change_side();
|
||||
board.state.revert_full_move(color_before_move);
|
||||
|
||||
board.state.en_passant_square = move_parameters.en_passant_square;
|
||||
|
||||
if let Some(hash) = move_parameters.zobrist_hash {
|
||||
self.hash = hash;
|
||||
}
|
||||
|
||||
if let Some(new_castling_ability) = move_parameters.castling_ability {
|
||||
board.state.castling_ability = new_castling_ability;
|
||||
}
|
||||
|
||||
if let Some(new_halfmove_clock) = move_parameters.halfmove_clock {
|
||||
board.state.halfmove_clock = new_halfmove_clock;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_repetition(&self) -> bool {
|
||||
if self.board.state.halfmove_clock < 4 {
|
||||
return false;
|
||||
@@ -462,4 +497,22 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const FEN_W: &str = "rnbqkbnr/pppppp1p/8/5Pp1/8/8/PPPPP1PP/RNBQKBNR w KQkq g6 0 1";
|
||||
const FEN_B: &str = "rnbqkbnr/pppppp1p/8/5Pp1/8/8/PPPPP1PP/RNBQKBNR b KQkq - 1 1";
|
||||
|
||||
#[test]
|
||||
fn test_make_and_unmake_null_move() -> Result<(), String> {
|
||||
let mut game = from_fen(FEN_W)?;
|
||||
|
||||
game.make_null_move();
|
||||
|
||||
assert_eq!(game, from_fen(FEN_B)?);
|
||||
|
||||
game.unmake_null_move();
|
||||
|
||||
assert!(game == from_fen(FEN_W)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,14 @@ impl MoveParameters {
|
||||
move_parameters
|
||||
}
|
||||
|
||||
pub fn build_null(game: &Game) -> Self {
|
||||
let mut move_parameters = Self::new();
|
||||
move_parameters.add_irreversible_parameters(&game.board.state);
|
||||
move_parameters.add_zobrist_hash(&game.hash);
|
||||
|
||||
move_parameters
|
||||
}
|
||||
|
||||
fn add_move(&mut self, mv: Move) {
|
||||
self.mv = Some(mv);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,13 @@ impl State {
|
||||
self.change_side();
|
||||
}
|
||||
|
||||
pub fn update_null_game_state(&mut self, color: Color) {
|
||||
self.set_en_passant_square(None);
|
||||
self.update_half_move(MoveType::Quiet, false);
|
||||
self.update_full_move(color);
|
||||
self.change_side();
|
||||
}
|
||||
|
||||
pub const fn en_passant_square(&self) -> Option<usize> {
|
||||
self.en_passant_square
|
||||
}
|
||||
|
||||
@@ -18,12 +18,15 @@ pub fn iterative_deepening(
|
||||
|
||||
for depth in 1..=max_depth {
|
||||
if time.exceed_soft_limit() {
|
||||
write_response(&mut io::stdout(), &Response::Info("Soft limit exceeded in negamax".to_string()))?;
|
||||
write_response(
|
||||
&mut io::stdout(),
|
||||
&Response::Info("Soft limit exceeded in negamax".to_string()),
|
||||
)?;
|
||||
return Ok(best_move);
|
||||
}
|
||||
|
||||
let mut nodes = 0;
|
||||
let score = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, time, &mut nodes);
|
||||
let score = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, time, &mut nodes, true);
|
||||
|
||||
if let Err(e) = score {
|
||||
write_response(&mut io::stdout(), &Response::Info(format!("{e}")))?;
|
||||
@@ -36,7 +39,7 @@ pub fn iterative_deepening(
|
||||
&mut io::stdout(),
|
||||
&log_depth_results(
|
||||
depth,
|
||||
time.instant.elapsed().as_millis() as u64,
|
||||
time.instant.elapsed().as_secs(),
|
||||
nodes,
|
||||
time.nps(nodes),
|
||||
score?,
|
||||
|
||||
@@ -20,6 +20,7 @@ pub fn negamax(
|
||||
plies: u8,
|
||||
time: &TimeInfo,
|
||||
nodes: &mut u64,
|
||||
do_nmp: bool,
|
||||
) -> Result<i32> {
|
||||
if time.exceed_hard_limit() {
|
||||
bail!("Hard limit exceeded in negamax");
|
||||
@@ -41,6 +42,25 @@ pub fn negamax(
|
||||
return Ok(q_score);
|
||||
}
|
||||
|
||||
if plies != 0 && !in_check && depth >= 3 && do_nmp && game.board.non_pawn_materials() > 0 {
|
||||
game.make_null_move();
|
||||
let score = -negamax(
|
||||
game,
|
||||
-beta,
|
||||
-beta + 1,
|
||||
depth - 3,
|
||||
plies + 1,
|
||||
time,
|
||||
nodes,
|
||||
false,
|
||||
)?;
|
||||
game.unmake_null_move();
|
||||
|
||||
if score >= beta {
|
||||
return Ok(score);
|
||||
}
|
||||
}
|
||||
|
||||
let mut legal_moves = 0;
|
||||
let mut best_score = MIN_SCORE;
|
||||
let mut best_move = None;
|
||||
@@ -78,11 +98,20 @@ pub fn negamax(
|
||||
*nodes += 1;
|
||||
|
||||
let score = if legal_moves == 1 {
|
||||
-negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes)?
|
||||
-negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes, true)?
|
||||
} else {
|
||||
let mut score = -negamax(game, -alpha - 1, -alpha, depth - 1, plies + 1, time, nodes)?;
|
||||
let mut score = -negamax(
|
||||
game,
|
||||
-alpha - 1,
|
||||
-alpha,
|
||||
depth - 1,
|
||||
plies + 1,
|
||||
time,
|
||||
nodes,
|
||||
true,
|
||||
)?;
|
||||
if score > alpha && score < beta {
|
||||
score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes)?;
|
||||
score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes, true)?;
|
||||
}
|
||||
score
|
||||
};
|
||||
@@ -147,16 +176,20 @@ mod tests {
|
||||
|
||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||
let time_info = TimeInfo::new(std::time::Instant::now(), TIME, INC);
|
||||
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut 0)
|
||||
.expect("Expected a search result");
|
||||
negamax(
|
||||
&mut game, MIN_SCORE, MAX_SCORE, 5, 0, &time_info, &mut 0, true,
|
||||
)
|
||||
.expect("Expected a search result");
|
||||
|
||||
assert_eq!(e3f2, game.tt.lookup(game.hash).unwrap().mv.unwrap());
|
||||
|
||||
let mut game = from_fen(FEN_MATE_IN_1[1]).unwrap();
|
||||
|
||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut 0)
|
||||
.expect("Expected a search result");
|
||||
negamax(
|
||||
&mut game, MIN_SCORE, MAX_SCORE, 5, 0, &time_info, &mut 0, true,
|
||||
)
|
||||
.expect("Expected a search result");
|
||||
|
||||
assert_eq!(e3f2, game.tt.lookup(game.hash).unwrap().mv.unwrap());
|
||||
|
||||
|
||||
@@ -79,8 +79,10 @@ mod tests {
|
||||
let mut game = from_fen(FEN).unwrap();
|
||||
let time_info = TimeInfo::new(std::time::Instant::now(), 30000, 100);
|
||||
|
||||
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut 0)
|
||||
.expect("Expected a search result");
|
||||
negamax(
|
||||
&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut 0, true,
|
||||
)
|
||||
.expect("Expected a search result");
|
||||
|
||||
dbg!(time_info.instant.elapsed());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user