timecat/polyglot/
book_hashmap.rs1use super::*;
2
3#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5struct PolyglotBookEntry {
6 move_: Move,
7 weight: u16,
8 learn: u32,
9}
10
11impl PolyglotBookEntry {
12 fn get_weighted_move(&self) -> WeightedMove {
13 WeightedMove::new(self.move_, self.weight as MoveWeight)
14 }
15
16 fn write_to_file(&self, file: &mut fs::File) -> Result<()> {
17 file.write_all(&move_to_polyglot_move_int(self.move_)?.to_be_bytes())?;
18 file.write_all(&self.weight.to_be_bytes())?;
19 file.write_all(&self.learn.to_be_bytes())?;
20 Ok(())
21 }
22}
23
24impl TryFrom<[u8; 8]> for PolyglotBookEntry {
25 type Error = TimecatError;
26
27 fn try_from(value: [u8; 8]) -> std::result::Result<Self, Self::Error> {
28 Ok(Self {
29 move_: polyglot_move_int_to_move(u16::from_be_bytes(
30 value[0..2]
31 .try_into()
32 .map_err(|_| TimecatError::BadPolyglotFile)?,
33 ))?,
34 weight: u16::from_be_bytes(
35 value[2..4]
36 .try_into()
37 .map_err(|_| TimecatError::BadPolyglotFile)?,
38 ),
39 learn: u32::from_be_bytes(
40 value[4..8]
41 .try_into()
42 .map_err(|_| TimecatError::BadPolyglotFile)?,
43 ),
44 })
45 }
46}
47
48impl TryFrom<&[u8]> for PolyglotBookEntry {
49 type Error = TimecatError;
50
51 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
52 <[u8; 8]>::try_from(value)?.try_into()
53 }
54}
55
56#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
57#[derive(Clone, Debug)]
58pub struct PolyglotBookHashMap {
59 entries_map: IdentityHashMap<u64, Vec<PolyglotBookEntry>>,
60}
61
62impl PolyglotBookHashMap {
63 #[inline]
64 pub fn sort_book(&mut self) {
65 self.entries_map
66 .values_mut()
67 .for_each(|entries| entries.sort_unstable_by_key(|key| Reverse(key.weight)));
68 }
69
70 #[inline]
71 pub fn len(&self) -> usize {
72 self.entries_map.len()
73 }
74
75 #[inline]
76 pub fn is_empty(&self) -> bool {
77 self.entries_map.is_empty()
78 }
79
80 pub fn get_all_weighted_moves_with_hashes(&self) -> IdentityHashMap<u64, Vec<WeightedMove>> {
81 self.entries_map
82 .iter()
83 .map(|(&hash, entries)| {
84 (
85 hash,
86 entries
87 .iter()
88 .map(|entry| entry.get_weighted_move())
89 .collect_vec(),
90 )
91 })
92 .collect()
93 }
94
95 pub fn get_all_weighted_moves(&self, board: &Board) -> Vec<WeightedMove> {
96 self.entries_map
97 .get(&board.get_hash())
98 .map_or(const { Vec::new() }, |entries| {
99 let mut weighted_moves = Vec::with_capacity(entries.len());
100 entries
101 .iter()
102 .map(|entry| entry.get_weighted_move())
103 .for_each(|weighted_move| weighted_moves.push(weighted_move));
104 weighted_moves
105 })
106 }
107
108 pub fn save_to_file<P: AsRef<Path>>(&self, file_path: P) -> Result<()> {
109 let mut data = self
110 .entries_map
111 .iter()
112 .flat_map(|(hash, entries)| entries.iter().map(move |entry| (hash, entry)))
113 .collect_vec();
114 data.sort_unstable_by_key(|&(&hash, entry)| (hash, Reverse(entry.weight)));
115 let mut file = fs::File::create(file_path)?;
116 for (hash, entry) in data {
117 file.write_all(&hash.to_be_bytes())?;
118 entry.write_to_file(&mut file)?;
119 }
120 Ok(())
121 }
122}
123
124impl PolyglotBook for PolyglotBookHashMap {
125 #[inline]
126 fn read_from_path(book_path: &str) -> Result<Self> {
127 Self::from_str(book_path)
128 }
129
130 #[inline]
131 fn get_best_weighted_move(&self, board: &Board) -> Option<WeightedMove> {
132 Some(
133 self.entries_map
134 .get(&board.get_hash())?
135 .first()?
136 .get_weighted_move(),
137 )
138 }
139}
140
141impl TryFrom<&[u8]> for PolyglotBookHashMap {
142 type Error = TimecatError;
143
144 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
145 let mut entries_map = IdentityHashMap::default();
146 let mut offset = 0;
147 while offset < value.len() {
148 let hash = u64::from_be_bytes(
149 value[offset..offset + 8]
150 .try_into()
151 .map_err(|_| TimecatError::BadPolyglotFile)?,
152 );
153 entries_map
154 .entry(hash)
155 .or_insert_with(Vec::new)
156 .push(value[offset + 8..offset + 16].try_into()?);
157 offset += 16;
158 }
159 Ok(Self { entries_map })
160 }
161}
162
163impl TryFrom<Vec<u8>> for PolyglotBookHashMap {
164 type Error = TimecatError;
165
166 #[inline]
167 fn try_from(value: Vec<u8>) -> std::result::Result<Self, Self::Error> {
168 value.as_slice().try_into()
169 }
170}
171
172impl<const N: usize> TryFrom<[u8; N]> for PolyglotBookHashMap {
173 type Error = TimecatError;
174
175 #[inline]
176 fn try_from(value: [u8; N]) -> std::result::Result<Self, Self::Error> {
177 value.as_slice().try_into()
178 }
179}
180
181impl TryFrom<&mut fs::File> for PolyglotBookHashMap {
182 type Error = TimecatError;
183
184 fn try_from(value: &mut fs::File) -> std::result::Result<Self, Self::Error> {
185 let mut entries_map = IdentityHashMap::default();
186 let mut buffer = [0; 16];
187 while value.read_exact(&mut buffer).is_ok() {
188 let hash = u64::from_be_bytes(
189 buffer[0..8]
190 .try_into()
191 .map_err(|_| TimecatError::BadPolyglotFile)?,
192 );
193 let move_int = u16::from_be_bytes(
194 buffer[8..10]
195 .try_into()
196 .map_err(|_| TimecatError::BadPolyglotFile)?,
197 );
198 let weight = u16::from_be_bytes(
199 buffer[10..12]
200 .try_into()
201 .map_err(|_| TimecatError::BadPolyglotFile)?,
202 );
203 let learn = u32::from_be_bytes(
204 buffer[12..16]
205 .try_into()
206 .map_err(|_| TimecatError::BadPolyglotFile)?,
207 );
208 let entry = PolyglotBookEntry {
209 move_: polyglot_move_int_to_move(move_int)?,
210 weight,
211 learn,
212 };
213 entries_map.entry(hash).or_insert_with(Vec::new).push(entry);
214 }
215 Ok(Self { entries_map })
216 }
217}
218
219impl TryFrom<fs::File> for PolyglotBookHashMap {
220 type Error = TimecatError;
221
222 fn try_from(mut value: fs::File) -> std::result::Result<Self, Self::Error> {
223 (&mut value).try_into()
224 }
225}
226
227impl FromStr for PolyglotBookHashMap {
228 type Err = TimecatError;
229
230 #[inline]
231 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
232 fs::File::open(s)?.try_into()
233 }
234}
235
236impl TryFrom<String> for PolyglotBookHashMap {
237 type Error = TimecatError;
238
239 #[inline]
240 fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
241 Self::from_str(&value)
242 }
243}