use super::*;
trait ToUnsigned {
type Unsigned;
fn to_unsigned(self) -> Self::Unsigned;
}
macro_rules! to_unsigned {
($from:ty, $to:ty) => {
impl ToUnsigned for $from {
type Unsigned = $to;
fn to_unsigned(self) -> Self::Unsigned {
self as Self::Unsigned
}
}
};
}
to_unsigned!(i8, u8);
to_unsigned!(i16, u16);
to_unsigned!(i32, u32);
to_unsigned!(i64, u64);
to_unsigned!(i128, u128);
to_unsigned!(isize, usize);
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq, Default)]
pub enum EntryFlagHash {
#[default]
Exact,
Alpha,
Beta,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
struct TranspositionTableData {
depth: Depth,
score: Score,
flag: EntryFlagHash,
}
impl Default for TranspositionTableData {
fn default() -> Self {
Self {
depth: -1,
score: Default::default(),
flag: Default::default(),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct TranspositionTableEntry {
optional_data: Option<TranspositionTableData>,
best_move: Option<Move>,
}
impl TranspositionTableEntry {
fn new(optional_data: Option<TranspositionTableData>, best_move: Option<Move>) -> Self {
Self {
optional_data,
best_move,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
#[derive(Debug, Clone)]
pub struct TranspositionTable {
table: CacheTable<TranspositionTableEntry>,
}
impl TranspositionTable {
#[inline]
pub fn print_info(&self) {
print_cache_table_info("Hash Table", self.table.len(), self.table.get_size());
}
#[inline]
fn generate_new_table(cache_table_size: CacheTableSize) -> CacheTable<TranspositionTableEntry> {
CacheTable::new(cache_table_size)
}
pub fn new(cache_table_size: CacheTableSize) -> Self {
Self {
table: Self::generate_new_table(cache_table_size),
}
}
pub fn read(
&self,
key: u64,
depth: Depth,
ply: Ply,
) -> (Option<(Score, EntryFlagHash)>, Option<Move>) {
let tt_entry = match self.table.get(key) {
Some(entry) => entry,
None => return (None, None),
};
let best_move = tt_entry.best_move;
if tt_entry.optional_data.is_none() {
return (None, best_move);
}
let data = tt_entry.optional_data.unwrap();
if data.depth < depth {
return (None, best_move);
}
let mut score = data.score;
if is_checkmate(score) {
score -= if score.is_positive() {
ply as Score
} else {
-(ply as Score)
};
}
(Some((score, data.flag)), best_move)
}
#[inline]
pub fn read_best_move(&self, key: u64) -> Option<Move> {
self.table.get(key)?.best_move
}
pub fn write(
&self,
key: u64,
depth: Depth,
ply: Ply,
mut score: Score,
flag: EntryFlagHash,
best_move: Option<Move>,
) {
let save_score = !is_checkmate(score);
if save_score && is_checkmate(score) {
let mate_distance = CHECKMATE_SCORE
.abs_diff(score.abs())
.abs_diff(ply as <Score as ToUnsigned>::Unsigned)
as Score;
let mate_score = CHECKMATE_SCORE - mate_distance;
score = if score.is_positive() {
mate_score
} else {
-mate_score
};
}
let old_optional_entry = self.table.get(key);
self.table.add(
key,
TranspositionTableEntry::new(
save_score.then(|| {
old_optional_entry
.and_then(|tt_entry| tt_entry.optional_data)
.filter(|data| data.depth > depth)
.unwrap_or(TranspositionTableData { depth, score, flag })
}),
best_move.or_else(|| old_optional_entry.and_then(|tt_entry| tt_entry.best_move)),
),
);
}
#[inline]
pub fn clear_best_moves(&self) {
self.table
.get_table()
.write()
.unwrap()
.iter_mut()
.flatten()
.for_each(|entry| {
entry.get_entry_mut().best_move = None;
});
}
}
impl Default for TranspositionTable {
fn default() -> Self {
Self::new(TIMECAT_DEFAULTS.t_table_size)
}
}
impl Deref for TranspositionTable {
type Target = CacheTable<TranspositionTableEntry>;
fn deref(&self) -> &Self::Target {
&self.table
}
}