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 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}