From a01310c7b71bae238dd2c92f5761e45c91ffced9 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Sun, 26 Jan 2025 23:58:44 +0200 Subject: [PATCH] Add TT cutoffs --- src/search/iterative_deepening.rs | 5 +--- src/search/mod.rs | 2 +- src/search/negamax.rs | 42 ++++++++++++++++++++----------- src/search/quiescence.rs | 22 +++++++++++----- src/search/transposition_table.rs | 32 ++++++++++++++++++----- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/search/iterative_deepening.rs b/src/search/iterative_deepening.rs index 23bc9e7..9e6d825 100644 --- a/src/search/iterative_deepening.rs +++ b/src/search/iterative_deepening.rs @@ -7,10 +7,7 @@ use crate::{ movegen::r#move::Move, }; -use super::{ - negamax, - time::TimeInfo, -}; +use super::{negamax, time::TimeInfo}; pub fn iterative_deepening( game: &mut Game, diff --git a/src/search/mod.rs b/src/search/mod.rs index cc8b5a9..db4304a 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -11,4 +11,4 @@ pub const TIME: u128 = 1000; pub const INC: u128 = 1000; pub const HARD_LIMIT_DIVISION: u128 = 10; pub const SOFT_LIMIT_DIVISION: u128 = HARD_LIMIT_DIVISION * 2; -pub const MAX_TT_SIZE: u64 = 1500000; +pub const MAX_TT_SIZE: u64 = 750000; diff --git a/src/search/negamax.rs b/src/search/negamax.rs index edd998a..a2a17d0 100644 --- a/src/search/negamax.rs +++ b/src/search/negamax.rs @@ -9,7 +9,7 @@ use super::{ move_ordering::score_move, quiescence::quiescence, time::TimeInfo, - transposition_table::TTEntry, + transposition_table::{NodeType, TTEntry}, }; pub fn negamax( @@ -30,8 +30,7 @@ pub fn negamax( } if depth == 0 { - let q_score = - quiescence(game, alpha, beta, time, nodes).map_err(|e| anyhow!("{e}"))?; + let q_score = quiescence(game, alpha, beta, time, nodes).map_err(|e| anyhow!("{e}"))?; return Ok(q_score); } @@ -40,9 +39,22 @@ pub fn negamax( let mut best_score = MIN_SCORE; let mut best_move = None; let mut moves = game.board.pseudo_moves_all(); - let tt_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv); + let tt_entry = game.tt.lookup(game.hash); - moves.sort_unstable_by_key(|mv| score_move(&game.mailbox, *mv, tt_move)); + if let Some(tt_entry) = tt_entry { + if plies > 0 && tt_entry.hash == game.hash && tt_entry.depth >= depth { + match tt_entry.node_type { + NodeType::Exact => return Ok(tt_entry.score), + NodeType::LowerBound if tt_entry.score >= beta => return Ok(tt_entry.score), + NodeType::UpperBound if tt_entry.score <= alpha => return Ok(tt_entry.score), + _ => (), + } + } + } + + moves.sort_unstable_by_key(|mv| { + score_move(&game.mailbox, *mv, tt_entry.and_then(|entry| entry.mv)) + }); for mv in moves { game.make_move(&mv); @@ -57,15 +69,7 @@ pub fn negamax( let score = if legal_moves == 1 { -negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes)? } 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)?; if score > alpha && score < beta { score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes)?; } @@ -95,7 +99,15 @@ pub fn negamax( return Ok(0); } - game.tt.insert(TTEntry::new(game.hash, best_move)); + let node_type = match best_score { + s if s >= beta => NodeType::LowerBound, + s if s <= alpha => NodeType::UpperBound, + _ => NodeType::Exact, + }; + + game.tt.insert(TTEntry::new( + game.hash, best_move, depth, best_score, node_type, + )); Ok(best_score) } diff --git a/src/search/quiescence.rs b/src/search/quiescence.rs index 6f66a26..dbc7817 100644 --- a/src/search/quiescence.rs +++ b/src/search/quiescence.rs @@ -2,10 +2,7 @@ use anyhow::{bail, Result}; use crate::{board::game::Game, evaluation::pesto::pesto}; -use super::{ - move_ordering::score_move, - time::TimeInfo, -}; +use super::{move_ordering::score_move, time::TimeInfo, transposition_table::NodeType}; pub fn quiescence( game: &mut Game, @@ -31,9 +28,22 @@ pub fn quiescence( let color = game.current_player(); let mut best_score = stand_pat; let mut moves = game.board.pseudo_moves_all_captures(); - let tt_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv); + let tt_entry = game.tt.lookup(game.hash); - moves.sort_unstable_by_key(|mv| score_move(&game.mailbox, *mv, tt_move)); + if let Some(tt_entry) = tt_entry { + if tt_entry.hash == game.hash { + match tt_entry.node_type { + NodeType::Exact => return Ok(tt_entry.score), + NodeType::LowerBound if tt_entry.score >= beta => return Ok(tt_entry.score), + NodeType::UpperBound if tt_entry.score <= alpha => return Ok(tt_entry.score), + _ => (), + } + } + } + + moves.sort_unstable_by_key(|mv| { + score_move(&game.mailbox, *mv, tt_entry.and_then(|entry| entry.mv)) + }); for mv in moves { game.make_move(&mv); diff --git a/src/search/transposition_table.rs b/src/search/transposition_table.rs index ec8aae2..877740f 100644 --- a/src/search/transposition_table.rs +++ b/src/search/transposition_table.rs @@ -15,12 +15,10 @@ impl TranspositionTable { } pub fn lookup(&self, zobrist_hash: ZobristHash) -> Option<&TTEntry> { - let entry = self + self .positions .get((zobrist_hash.0 % self.size) as usize) - .and_then(|entry| entry.as_ref()); - - entry + .and_then(|entry| entry.as_ref()) } pub fn insert(&mut self, tt_entry: TTEntry) { @@ -33,14 +31,36 @@ impl TranspositionTable { pub struct TTEntry { pub hash: ZobristHash, pub mv: Option, + pub depth: u8, + pub score: i32, + pub node_type: NodeType, } impl TTEntry { - pub const fn new(hash: ZobristHash, mv: Option) -> Self { - Self { hash, mv } + pub const fn new( + hash: ZobristHash, + mv: Option, + depth: u8, + score: i32, + node_type: NodeType, + ) -> Self { + Self { + hash, + mv, + depth, + score, + node_type, + } } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum NodeType { + Exact, + LowerBound, + UpperBound, +} + #[cfg(test)] mod tests { use crate::{