Add TT lookups in quiescence, move TT inside Game, remove redundant occupancy and time limit functions
This commit is contained in:
@@ -6,21 +6,19 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
negamax,
|
||||
time::{time_limit_reached, TimeInfo},
|
||||
transposition_table::TranspositionTable,
|
||||
time::{soft_limit, TimeInfo},
|
||||
};
|
||||
|
||||
pub fn iterative_deepening(
|
||||
game: &mut Game,
|
||||
max_depth: u8,
|
||||
remaining_time: u128,
|
||||
tt: &mut TranspositionTable,
|
||||
) -> anyhow::Result<Option<Move>> {
|
||||
let (mut best_move, mut best_score) = (None, MIN_SCORE);
|
||||
let time = std::time::Instant::now();
|
||||
|
||||
for depth in 1..=max_depth {
|
||||
if time_limit_reached(&time, remaining_time, best_score) {
|
||||
if soft_limit(&time, remaining_time, best_score) {
|
||||
return Ok(best_move);
|
||||
}
|
||||
|
||||
@@ -31,7 +29,6 @@ pub fn iterative_deepening(
|
||||
depth,
|
||||
0,
|
||||
&TimeInfo::new(time, remaining_time),
|
||||
tt,
|
||||
);
|
||||
|
||||
if let Ok(search_result) = search_result {
|
||||
@@ -44,6 +41,12 @@ pub fn iterative_deepening(
|
||||
}
|
||||
}
|
||||
|
||||
// tt.lookup(game.hash).map(|entry| {
|
||||
// if entry.mv.is_some() {
|
||||
// best_move = entry.mv;
|
||||
// }
|
||||
// });
|
||||
|
||||
Ok(best_move)
|
||||
}
|
||||
|
||||
@@ -52,10 +55,7 @@ mod tests {
|
||||
use crate::{
|
||||
board::fen::from_fen,
|
||||
movegen::attack_generator::init_attacks,
|
||||
search::{
|
||||
iterative_deepening, transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE,
|
||||
REMAINING_TIME_DEFAULT,
|
||||
},
|
||||
search::{iterative_deepening, MAX_DEPTH, REMAINING_TIME_DEFAULT},
|
||||
};
|
||||
|
||||
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
||||
@@ -64,15 +64,9 @@ mod tests {
|
||||
fn test_iterative_deepening() -> anyhow::Result<()> {
|
||||
init_attacks();
|
||||
let mut game = from_fen(FEN).unwrap();
|
||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
||||
let time_now = std::time::Instant::now();
|
||||
|
||||
iterative_deepening::iterative_deepening(
|
||||
&mut game,
|
||||
MAX_DEPTH,
|
||||
REMAINING_TIME_DEFAULT,
|
||||
&mut tt,
|
||||
)?;
|
||||
iterative_deepening::iterative_deepening(&mut game, MAX_DEPTH, REMAINING_TIME_DEFAULT)?;
|
||||
|
||||
dbg!(time_now.elapsed());
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
move_ordering,
|
||||
quiescence::quiescence,
|
||||
time::{hard_limit, TimeInfo},
|
||||
transposition_table::{Bound, TTEntry, TranspositionTable},
|
||||
transposition_table::TTEntry,
|
||||
SearchResult,
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@ pub fn negamax(
|
||||
depth: u8,
|
||||
plies: u8,
|
||||
time_info: &TimeInfo,
|
||||
tt: &mut TranspositionTable,
|
||||
) -> Result<SearchResult> {
|
||||
if hard_limit(&time_info.time, time_info.remaining_time_in_ms) {
|
||||
bail!("Time is up! In Negamax");
|
||||
@@ -36,10 +35,9 @@ pub fn negamax(
|
||||
let mut best_score = MIN_SCORE;
|
||||
let mate_score = -MATE_SCORE + plies as i32;
|
||||
let mut legal_moves = 0;
|
||||
let mut bound = Bound::Alpha;
|
||||
let all_moves = game.board.pseudo_moves_all();
|
||||
|
||||
let tt_move = tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||
let tt_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move);
|
||||
|
||||
for mv in moves {
|
||||
@@ -51,7 +49,7 @@ pub fn negamax(
|
||||
}
|
||||
|
||||
legal_moves += 1;
|
||||
let score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, tt)?.best_score;
|
||||
let score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info)?.best_score;
|
||||
game.unmake_move();
|
||||
|
||||
if score > best_score {
|
||||
@@ -60,14 +58,12 @@ pub fn negamax(
|
||||
}
|
||||
|
||||
if score >= beta {
|
||||
bound = Bound::Beta;
|
||||
best_score = beta;
|
||||
best_move = Some(mv);
|
||||
break;
|
||||
}
|
||||
|
||||
if score > alpha {
|
||||
bound = Bound::Exact;
|
||||
alpha = score;
|
||||
}
|
||||
}
|
||||
@@ -79,7 +75,7 @@ pub fn negamax(
|
||||
return Ok(SearchResult::new(None, 0));
|
||||
}
|
||||
|
||||
tt.insert(TTEntry::new(game.hash, depth, best_score, best_move, bound));
|
||||
game.tt.insert(TTEntry::new(game.hash, best_move));
|
||||
Ok(SearchResult::new(best_move, best_score))
|
||||
}
|
||||
|
||||
@@ -91,8 +87,6 @@ mod tests {
|
||||
use crate::movegen::r#move::Move;
|
||||
use crate::search::negamax::negamax;
|
||||
use crate::search::time::TimeInfo;
|
||||
use crate::search::transposition_table::TranspositionTable;
|
||||
use crate::search::MAX_TT_SIZE;
|
||||
|
||||
const FEN_MATE_IN_1: [&str; 2] = [
|
||||
"8/8/8/8/8/4q1k1/8/5K2 b - - 0 1",
|
||||
@@ -104,10 +98,9 @@ mod tests {
|
||||
init_attacks();
|
||||
let mut game = from_fen(FEN_MATE_IN_1[0]).unwrap();
|
||||
|
||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||
let time_info = TimeInfo::new(std::time::Instant::now(), 1000);
|
||||
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut tt)
|
||||
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info)
|
||||
.expect("Expected a search result")
|
||||
.best_move
|
||||
.expect("Expected a move");
|
||||
@@ -117,7 +110,7 @@ mod tests {
|
||||
let mut game = from_fen(FEN_MATE_IN_1[1]).unwrap();
|
||||
|
||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut tt)
|
||||
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info)
|
||||
.expect("Expected a search result")
|
||||
.best_move
|
||||
.expect("Expected a move");
|
||||
|
||||
@@ -24,7 +24,8 @@ pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32, time_info: &TimeIn
|
||||
|
||||
let color = game.current_player();
|
||||
let all_moves = game.board.pseudo_moves_all_captures();
|
||||
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, None);
|
||||
let tt_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move);
|
||||
|
||||
for mv in moves {
|
||||
game.make_move(&mv);
|
||||
@@ -33,7 +34,7 @@ pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32, time_info: &TimeIn
|
||||
game.unmake_move();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
let score = -quiescence(game, -beta, -alpha, time_info)?;
|
||||
game.unmake_move();
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@ impl TimeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn time_limit_reached(time: &Instant, remaining_time: u128, eval: i32) -> bool {
|
||||
hard_limit(time, remaining_time) || soft_limit(time, remaining_time, eval)
|
||||
}
|
||||
|
||||
pub fn hard_limit(time_now: &Instant, remaining_time: u128) -> bool {
|
||||
time_now.elapsed().as_millis() >= remaining_time / HARD_LIMIT_DIVISION
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{board::zobrist::ZobristHash, movegen::r#move::Move};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TranspositionTable {
|
||||
positions: Vec<Option<TTEntry>>,
|
||||
size: u64,
|
||||
@@ -24,60 +25,29 @@ impl TranspositionTable {
|
||||
|
||||
pub fn insert(&mut self, tt_entry: TTEntry) {
|
||||
let index = (tt_entry.hash.hash % self.size) as usize;
|
||||
|
||||
if let Some(stored) = &self.positions[index] {
|
||||
if tt_entry.depth > stored.depth || matches!(tt_entry.bound, Bound::Exact) {
|
||||
self.positions[index] = Some(tt_entry);
|
||||
}
|
||||
} else {
|
||||
self.positions[index] = Some(tt_entry);
|
||||
}
|
||||
self.positions[index] = Some(tt_entry);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TTEntry {
|
||||
pub hash: ZobristHash,
|
||||
pub depth: u8,
|
||||
pub score: i32,
|
||||
pub mv: Option<Move>,
|
||||
pub bound: Bound,
|
||||
}
|
||||
|
||||
impl TTEntry {
|
||||
pub const fn new(
|
||||
hash: ZobristHash,
|
||||
depth: u8,
|
||||
score: i32,
|
||||
mv: Option<Move>,
|
||||
bound: Bound,
|
||||
) -> Self {
|
||||
Self {
|
||||
hash,
|
||||
depth,
|
||||
score,
|
||||
mv,
|
||||
bound,
|
||||
}
|
||||
pub const fn new(hash: ZobristHash, mv: Option<Move>) -> Self {
|
||||
Self { hash, mv }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Bound {
|
||||
Exact,
|
||||
Alpha,
|
||||
Beta,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
board::fen::from_fen,
|
||||
evaluation::{MAX_SCORE, MIN_SCORE},
|
||||
movegen::attack_generator::init_attacks,
|
||||
search::{
|
||||
negamax::negamax, time::TimeInfo, transposition_table::TranspositionTable, MAX_TT_SIZE,
|
||||
},
|
||||
search::{negamax::negamax, time::TimeInfo},
|
||||
};
|
||||
|
||||
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
||||
@@ -88,10 +58,9 @@ mod tests {
|
||||
fn test_transposition_table() -> anyhow::Result<()> {
|
||||
init_attacks();
|
||||
let mut game = from_fen(FEN).unwrap();
|
||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
||||
let time_info = TimeInfo::new(std::time::Instant::now(), 30000);
|
||||
|
||||
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut tt)
|
||||
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info)
|
||||
.expect("Expected a search result")
|
||||
.best_move
|
||||
.expect("Expected a move");
|
||||
@@ -99,12 +68,12 @@ mod tests {
|
||||
dbg!(time_info.time.elapsed());
|
||||
|
||||
let will_be_hash = from_fen(FEN_POSSIBLE).unwrap().hash;
|
||||
let tt_entry = tt.lookup(will_be_hash);
|
||||
let tt_entry = game.tt.lookup(will_be_hash);
|
||||
|
||||
assert!(tt_entry.is_some());
|
||||
|
||||
let wont_be_hash = from_fen(FEN_IMPOSSIBLE).unwrap().hash;
|
||||
let tt_entry = tt.lookup(wont_be_hash);
|
||||
let tt_entry = game.tt.lookup(wont_be_hash);
|
||||
|
||||
assert!(tt_entry.is_none());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user