diff --git a/src/interface/uci.rs b/src/interface/uci.rs index fb190bf..8b37fe5 100644 --- a/src/interface/uci.rs +++ b/src/interface/uci.rs @@ -207,7 +207,7 @@ fn uci_option(option_iter: &mut SplitWhitespace, game: &mut Game) -> anyhow::Res let size = value .parse::() .map_err(|_| anyhow!("Invalid value for Hash"))?; - game.tt = TranspositionTable::new_with_mb_size(size); + game.tt = TranspositionTable::with_capacity(size); } } diff --git a/src/search/negamax.rs b/src/search/negamax.rs index 2507258..affc8ec 100644 --- a/src/search/negamax.rs +++ b/src/search/negamax.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, Result}; +use anyhow::{bail, Result}; use crate::{ board::game::Game, @@ -38,8 +38,19 @@ pub fn negamax( } if depth == 0 { - let q_score = quiescence(game, alpha, beta, time, nodes).map_err(|e| anyhow!("{e}"))?; - return Ok(q_score); + return quiescence(game, alpha, beta, time, nodes); + } + + let entry = game.tt.lookup(game.hash); + + if let Some(entry) = entry { + if plies > 0 + && entry.hash == game.hash + && entry.depth >= depth + && entry.node_type.cutoff_eligible(entry.score, alpha, beta) + { + return Ok(entry.score); + } } if plies != 0 && !in_check && depth >= 3 && do_nmp && game.board.non_pawn_materials() > 0 { @@ -61,32 +72,20 @@ pub fn negamax( } } - let mut legal_moves = 0; - let mut best_score = MIN_SCORE; - let mut best_move = None; let mut moves = game.board.pseudo_moves_all(); - let tt_entry = game.tt.lookup(game.hash); - - 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, game.killer[plies as usize], - tt_entry.and_then(|entry| entry.mv), + entry.and_then(|entry| entry.mv), ) }); + let mut legal_moves = 0; + let mut best_move = entry.and_then(|entry| entry.mv); + let mut best_score = MIN_SCORE; + for mv in moves { game.make_move(&mv); diff --git a/src/search/quiescence.rs b/src/search/quiescence.rs index b8dec45..6cafb90 100644 --- a/src/search/quiescence.rs +++ b/src/search/quiescence.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use crate::{board::game::Game, evaluation::pesto::pesto}; -use super::{move_ordering::score_move, time::TimeInfo, transposition_table::NodeType}; +use super::{move_ordering::score_move, time::TimeInfo}; pub fn quiescence( game: &mut Game, @@ -25,29 +25,20 @@ pub fn quiescence( alpha = stand_pat; } - let color = game.current_player(); - let mut best_score = stand_pat; - let mut moves = game.board.pseudo_moves_all_captures(); - let tt_entry = game.tt.lookup(game.hash); + let entry = game.tt.lookup(game.hash); - 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), - _ => (), - } + if let Some(entry) = entry { + if entry.hash == game.hash && entry.node_type.cutoff_eligible(entry.score, alpha, beta) { + return Ok(entry.score); } } + let color = game.current_player(); + let mut best_score = stand_pat; + let mut moves = game.board.pseudo_moves_all_captures(); + moves.sort_unstable_by_key(|mv| { - score_move( - &game.mailbox, - *mv, - None, - tt_entry.and_then(|entry| entry.mv), - ) + score_move(&game.mailbox, *mv, None, entry.and_then(|entry| entry.mv)) }); for mv in moves { @@ -59,7 +50,7 @@ pub fn quiescence( } *nodes += 1; - let score = -quiescence(game, -beta, -alpha, time, &mut 0)?; + let score = -quiescence(game, -beta, -alpha, time, nodes)?; game.unmake_move(); if score > best_score { diff --git a/src/search/transposition_table.rs b/src/search/transposition_table.rs index f7419a9..06b93fc 100644 --- a/src/search/transposition_table.rs +++ b/src/search/transposition_table.rs @@ -10,11 +10,11 @@ pub struct TranspositionTable { impl TranspositionTable { pub fn new() -> Self { - Self::new_with_mb_size(TT_SIZE_IN_MB) + Self::with_capacity(TT_SIZE_IN_MB) } - pub fn new_with_mb_size(size_in_mega_bytes: usize) -> Self { - let size_in_bytes = size_in_mega_bytes * 1024 * 1024; + pub fn with_capacity(size_in_mb: usize) -> Self { + let size_in_bytes = size_in_mb * 1024 * 1024; let num_of_entries = size_in_bytes / std::mem::size_of::(); Self { @@ -23,19 +23,19 @@ impl TranspositionTable { } } - pub fn lookup(&self, zobrist_hash: ZobristHash) -> Option<&TTEntry> { + pub fn lookup(&self, zobrist_hash: ZobristHash) -> Option { self.positions .get((zobrist_hash.0 % self.size) as usize) - .and_then(|entry| entry.as_ref()) + .and_then(|entry| *entry) } - pub fn insert(&mut self, tt_entry: TTEntry) { - let idx = (tt_entry.hash.0 % self.size) as usize; - self.positions[idx] = Some(tt_entry); + pub fn insert(&mut self, entry: TTEntry) { + let idx = (entry.hash.0 % self.size) as usize; + self.positions[idx] = Some(entry); } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TTEntry { pub hash: ZobristHash, pub mv: Option, @@ -62,13 +62,21 @@ impl TTEntry { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum NodeType { Exact, LowerBound, UpperBound, } +impl NodeType { + pub fn cutoff_eligible(self, score: i16, alpha: i16, beta: i16) -> bool { + self == Self::Exact + || self == Self::LowerBound && score >= beta + || self == Self::UpperBound && score <= alpha + } +} + #[cfg(test)] mod tests { use crate::{ @@ -96,14 +104,14 @@ mod tests { dbg!(time_info.instant.elapsed()); let will_be_hash = from_fen(FEN_POSSIBLE).unwrap().hash; - let tt_entry = game.tt.lookup(will_be_hash); + let entry = game.tt.lookup(will_be_hash); - assert!(tt_entry.is_some()); + assert!(entry.is_some()); let wont_be_hash = from_fen(FEN_IMPOSSIBLE).unwrap().hash; - let tt_entry = game.tt.lookup(wont_be_hash); + let entry = game.tt.lookup(wont_be_hash); - assert!(tt_entry.is_none()); + assert!(entry.is_none()); Ok(()) }