timecat/polyglot/
book_hashmap.rs

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