Skip to main content

timecat/
tt.rs

1use super::*;
2
3trait ToUnsigned {
4    type Unsigned;
5    fn to_unsigned(self) -> Self::Unsigned;
6}
7
8macro_rules! to_unsigned {
9    ($from:ty, $to:ty) => {
10        impl ToUnsigned for $from {
11            type Unsigned = $to;
12
13            fn to_unsigned(self) -> Self::Unsigned {
14                self as Self::Unsigned
15            }
16        }
17    };
18}
19
20to_unsigned!(i8, u8);
21to_unsigned!(i16, u16);
22to_unsigned!(i32, u32);
23to_unsigned!(i64, u64);
24to_unsigned!(i128, u128);
25to_unsigned!(isize, usize);
26
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq, Default)]
29pub enum EntryFlagHash {
30    #[default]
31    Exact,
32    Alpha,
33    Beta,
34}
35
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
38struct TranspositionTableData {
39    depth: Depth,
40    score: Score,
41    flag: EntryFlagHash,
42}
43
44impl Default for TranspositionTableData {
45    fn default() -> Self {
46        Self {
47            depth: -1,
48            score: Default::default(),
49            flag: Default::default(),
50        }
51    }
52}
53
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55#[derive(Clone, Copy, Debug, PartialEq, Default)]
56pub struct TranspositionTableEntry {
57    optional_data: Option<TranspositionTableData>,
58    best_move: Option<Move>,
59}
60
61impl TranspositionTableEntry {
62    fn new(optional_data: Option<TranspositionTableData>, best_move: Option<Move>) -> Self {
63        Self {
64            optional_data,
65            best_move,
66        }
67    }
68}
69
70#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71#[repr(transparent)]
72#[derive(Debug, Clone)]
73pub struct TranspositionTable {
74    table: CacheTable<TranspositionTableEntry>,
75}
76
77impl TranspositionTable {
78    #[inline]
79    pub fn print_info(&self) {
80        print_cache_table_info("Hash Table", self.table.len(), self.table.get_size());
81    }
82
83    #[inline]
84    fn generate_new_table(cache_table_size: CacheTableSize) -> CacheTable<TranspositionTableEntry> {
85        CacheTable::new(cache_table_size)
86    }
87
88    pub fn new(cache_table_size: CacheTableSize) -> Self {
89        Self {
90            table: Self::generate_new_table(cache_table_size),
91        }
92    }
93
94    pub fn read(
95        &self,
96        key: u64,
97        depth: Depth,
98        ply: Ply,
99    ) -> (Option<(Score, EntryFlagHash)>, Option<Move>) {
100        let tt_entry = match self.table.get(key) {
101            Some(entry) => entry,
102            None => return (None, None),
103        };
104        let best_move = tt_entry.best_move;
105        if tt_entry.optional_data.is_none() {
106            return (None, best_move);
107        }
108        let data = tt_entry.optional_data.unwrap();
109        if data.depth < depth {
110            return (None, best_move);
111        }
112        let mut score = data.score;
113        if is_checkmate(score) {
114            score -= if score.is_positive() {
115                ply as Score
116            } else {
117                -(ply as Score)
118            };
119        }
120        (Some((score, data.flag)), best_move)
121    }
122
123    #[inline]
124    pub fn read_best_move(&self, key: u64) -> Option<Move> {
125        self.table.get(key)?.best_move
126    }
127
128    pub fn write(
129        &self,
130        key: u64,
131        depth: Depth,
132        ply: Ply,
133        mut score: Score,
134        flag: EntryFlagHash,
135        best_move: Option<Move>,
136    ) {
137        // TODO: Logic Wrong Here?
138        let save_score = !is_checkmate(score);
139        if save_score && is_checkmate(score) {
140            let mate_distance = CHECKMATE_SCORE
141                .abs_diff(score.abs())
142                .abs_diff(ply as <Score as ToUnsigned>::Unsigned)
143                as Score;
144            let mate_score = CHECKMATE_SCORE - mate_distance;
145            score = if score.is_positive() {
146                mate_score
147            } else {
148                -mate_score
149            };
150        }
151        let old_optional_entry = self.table.get(key);
152        self.table.add(
153            key,
154            TranspositionTableEntry::new(
155                save_score.then(|| {
156                    old_optional_entry
157                        .and_then(|tt_entry| tt_entry.optional_data)
158                        .filter(|data| data.depth > depth)
159                        .unwrap_or(TranspositionTableData { depth, score, flag })
160                }),
161                best_move.or_else(|| old_optional_entry.and_then(|tt_entry| tt_entry.best_move)),
162            ),
163        );
164    }
165
166    #[inline]
167    pub fn clear_best_moves(&self) {
168        self.table
169            .get_table()
170            .write()
171            .unwrap()
172            .iter_mut()
173            .flatten()
174            .for_each(|entry| {
175                entry.get_entry_mut().best_move = None;
176            });
177    }
178}
179
180impl Default for TranspositionTable {
181    fn default() -> Self {
182        Self::new(TIMECAT_DEFAULTS.t_table_size)
183    }
184}
185
186impl Deref for TranspositionTable {
187    type Target = CacheTable<TranspositionTableEntry>;
188
189    fn deref(&self) -> &Self::Target {
190        &self.table
191    }
192}