use crate::core::Move10;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TTFlag {
Exact,
LowerBound,
UpperBound,
}
#[derive(Copy, Clone, Debug)]
pub struct TTEntry {
pub key: u64,
pub depth: u8, pub value: i32, pub flag: TTFlag, pub best_move: Option<Move10>, }
pub struct TranspositionTable {
size_mask: usize, entries: Vec<Option<TTEntry>>, }
impl TranspositionTable {
pub fn new(requested_size: usize) -> TranspositionTable {
let mut cap = 1;
while cap < requested_size {
cap <<= 1;
}
TranspositionTable {
size_mask: cap - 1,
entries: vec![None; cap],
}
}
#[inline]
fn index_of(&self, key: u64) -> usize {
(key as usize) & self.size_mask
}
pub fn store(
&mut self,
key: u64,
depth: u8,
value: i32,
flag: TTFlag,
best_move: Option<Move10>,
) {
let idx = self.index_of(key);
match self.entries[idx] {
Some(existing) => {
if depth >= existing.depth {
self.entries[idx] = Some(TTEntry {
key,
depth,
value,
flag,
best_move,
});
}
}
None => {
self.entries[idx] = Some(TTEntry {
key,
depth,
value,
flag,
best_move,
});
}
}
}
pub fn lookup(&self, key: u64) -> Option<(i32, TTFlag, Option<Move10>, u8)> {
let idx = self.index_of(key);
if let Some(entry) = self.entries[idx] {
if entry.key == key {
return Some((entry.value, entry.flag, entry.best_move, entry.depth));
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::zobrist::compute_hash;
use crate::board::{init_chess_positions, PieceMapping};
#[test]
fn basic_store_and_lookup() {
let mut mapping = PieceMapping::new_empty();
for &(pid, sq) in &init_chess_positions() {
mapping.place_piece(pid, sq);
}
let key = compute_hash(&mapping, 0, None);
let mut tt = TranspositionTable::new(16);
tt.store(key, 4, 42, TTFlag::Exact, None);
if let Some((val, flag, mv_opt, depth)) = tt.lookup(key) {
assert_eq!(val, 42);
assert_eq!(flag, TTFlag::Exact);
assert!(mv_opt.is_none());
assert_eq!(depth, 4);
} else {
panic!("Expected to find key in table");
}
tt.store(key, 2, 100, TTFlag::LowerBound, Some(Move10::new(0, 0)));
if let Some((val, flag, _, depth)) = tt.lookup(key) {
assert_eq!(val, 42);
assert_eq!(flag, TTFlag::Exact);
assert_eq!(depth, 4);
} else {
panic!("Expected to find key in table");
}
tt.store(key, 6, -13, TTFlag::UpperBound, Some(Move10::new(1, 1)));
if let Some((val, flag, mv_opt, depth)) = tt.lookup(key) {
assert_eq!(val, -13);
assert_eq!(flag, TTFlag::UpperBound);
assert_eq!(depth, 6);
assert_eq!(mv_opt.unwrap(), Move10::new(1, 1));
} else {
panic!("Expected to find key in table");
}
}
#[test]
fn collision_overwrite_based_on_depth() {
let mut mapping = PieceMapping::new_empty();
for &(pid, sq) in &init_chess_positions() {
mapping.place_piece(pid, sq);
}
let key1 = compute_hash(&mapping, 0, None);
let key2 = compute_hash(&mapping, 1, None);
let mut tt = TranspositionTable::new(1);
tt.store(key1, 3, 10, TTFlag::Exact, None);
tt.store(key2, 4, 20, TTFlag::LowerBound, None);
assert!(tt.lookup(key1).is_none());
if let Some((val, _, _, depth)) = tt.lookup(key2) {
assert_eq!(val, 20);
assert_eq!(depth, 4);
} else {
panic!("Expected key2 in table");
}
}
}