1use std::collections::HashSet;
4use std::ops::Index;
5
6use rand::prelude::*;
7
8use crate::boardrepr::PieceSets;
9use crate::coretypes::{Castling, Color, File, Piece, PieceKind, Rank, Square, SquareIndexable};
10use crate::coretypes::{MoveInfo, MoveKind, Square::*};
11use crate::coretypes::{NUM_FILES, NUM_PIECE_KINDS, NUM_SQUARES};
12use crate::position::{Cache, Position};
13
14pub type HashKind = u64;
16
17pub type Key<'a> = (&'a PieceSets, &'a Color, &'a Castling, &'a Option<Square>);
19
20impl<'a> From<&'a Position> for Key<'a> {
22 fn from(pos_ref: &'a Position) -> Self {
23 (
24 pos_ref.pieces(),
25 pos_ref.player(),
26 pos_ref.castling(),
27 pos_ref.en_passant(),
28 )
29 }
30}
31
32#[derive(Debug, Clone, Eq, PartialEq)]
45pub struct ZobristTable {
46 piece_hash: [[HashKind; NUM_SQUARES]; NUM_PIECE_KINDS],
47 ep_hash: [HashKind; NUM_FILES],
48 castling_hash: [HashKind; Castling::ENUMERATIONS],
49 pub(crate) player_hash: HashKind,
50}
51
52impl ZobristTable {
53 const TOGGLE_PLAYER: Color = Color::Black;
54
55 pub fn new() -> Self {
57 Self::with_rng(StdRng::from_entropy())
58 }
59
60 pub fn with_seed(seed: u64) -> Self {
62 Self::with_rng(StdRng::seed_from_u64(seed))
63 }
64
65 fn with_rng(mut rng: StdRng) -> Self {
67 let mut used_values = HashSet::new();
69
70 let mut piece_hash = [[HashKind::default(); NUM_SQUARES]; NUM_PIECE_KINDS];
71 let mut ep_hash = [HashKind::default(); NUM_FILES];
72 let mut castling_hash = [HashKind::default(); Castling::ENUMERATIONS];
73
74 let mut inf_loop_protection: usize = 10_000;
76
77 for item in piece_hash
79 .iter_mut()
80 .flatten()
81 .chain(ep_hash.iter_mut())
82 .chain(castling_hash.iter_mut())
83 {
84 let mut new_unique_value: HashKind = rng.gen();
85 while !used_values.insert(new_unique_value) {
88 new_unique_value = rng.gen();
89
90 inf_loop_protection -= 1;
91 if inf_loop_protection < 10 {
92 panic!("Encountered excessively repeated random numbers.");
93 }
94 }
95 *item = new_unique_value;
96 }
97
98 let mut player_hash: HashKind = rng.gen();
100 while !used_values.insert(player_hash) {
101 player_hash = rng.gen();
102
103 inf_loop_protection -= 1;
104 if inf_loop_protection < 10 {
105 panic!("Encountered excessively repeated random numbers.");
106 }
107 }
108
109 Self {
110 piece_hash,
111 ep_hash,
112 castling_hash,
113 player_hash,
114 }
115 }
116
117 pub fn generate_hash(&self, key: Key) -> HashKind {
119 let mut hash = HashKind::default();
120
121 for color in Color::iter() {
123 for piece_kind in PieceKind::iter() {
124 let piece = Piece::new(color, piece_kind);
125 let squares = key.0[piece];
126
127 for square in squares {
128 hash ^= self[(piece, square)];
129 }
130 }
131 }
132
133 if let Some(ep_square) = key.3 {
135 hash ^= self[ep_square.file()];
136 }
137
138 hash ^= self[*key.2];
140
141 if *key.1 == ZobristTable::TOGGLE_PLAYER {
144 hash ^= self.player_hash;
145 }
146
147 hash
148 }
149
150 pub fn update_hash(&self, hash: &mut HashKind, key: Key, move_info: MoveInfo, cache: Cache) {
160 let moved_player = !key.1;
161 let passive_player = *key.1;
162
163 *hash ^= self.player_hash;
165 *hash ^= self[cache.castling];
167 *hash ^= self[*key.2];
168 let moved_piece = Piece::new(moved_player, move_info.piece_kind);
170 *hash ^= self[(moved_piece, move_info.from)];
171
172 let old_ep = cache.en_passant;
174 let new_ep = key.3;
175 if let Some(ep_square) = old_ep {
176 *hash ^= self[ep_square.file()];
177 }
178 if let Some(ep_square) = new_ep {
179 *hash ^= self[ep_square.file()];
180 }
181
182 let to_piece_kind: PieceKind = match move_info.promotion {
184 Some(promoted_pk) => promoted_pk,
185 None => move_info.piece_kind,
186 };
187 let to_piece = Piece::new(moved_player, to_piece_kind);
188 *hash ^= self[(to_piece, move_info.to)];
189
190 match move_info.move_kind {
191 MoveKind::Capture(captured_pk) => {
193 let captured_piece = Piece::new(passive_player, captured_pk);
194 *hash ^= self[(captured_piece, move_info.to)];
195 }
196
197 MoveKind::EnPassant => {
199 let ep_square = cache.en_passant.unwrap();
200 let pawn_square = match ep_square.rank() {
201 Rank::R3 => ep_square.increment_rank().unwrap(),
202 _ => ep_square.decrement_rank().unwrap(),
203 };
204 let captured_pawn = Piece::new(passive_player, PieceKind::Pawn);
205 *hash ^= self[(captured_pawn, pawn_square)];
206 }
207
208 MoveKind::Castle => {
210 let (rook_from, rook_to) = match move_info.to {
211 G1 => (H1, F1),
212 C1 => (A1, D1),
213 G8 => (H8, F8),
214 C8 => (A8, D8),
215 _ => panic!("Processing Castling Move, but to square was not valid."),
216 };
217 let castled_rook = Piece::new(moved_player, PieceKind::Rook);
218 *hash ^= self[(castled_rook, rook_from)];
219 *hash ^= self[(castled_rook, rook_to)];
220 }
221
222 MoveKind::Quiet => (),
224 };
225 }
226}
227
228impl Default for ZobristTable {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235impl Index<(Piece, Square)> for ZobristTable {
237 type Output = HashKind;
238 fn index(&self, index: (Piece, Square)) -> &Self::Output {
239 let (piece, square) = index;
240 &self.piece_hash[piece.zobrist_offset()][square.idx()]
241 }
242}
243
244impl Index<File> for ZobristTable {
246 type Output = HashKind;
247 fn index(&self, index: File) -> &Self::Output {
248 &self.ep_hash[index as usize]
249 }
250}
251
252impl Index<Castling> for ZobristTable {
254 type Output = HashKind;
255 fn index(&self, index: Castling) -> &Self::Output {
256 &self.castling_hash[index.bits() as usize]
257 }
258}
259
260impl Color {
265 #[inline(always)]
268 const fn zobrist_offset_block(&self) -> usize {
269 match self {
270 Color::White => 0,
271 Color::Black => 6,
272 }
273 }
274}
275
276impl PieceKind {
277 #[inline(always)]
280 const fn zobrist_offset_pk(&self) -> usize {
281 match self {
282 PieceKind::King => 0,
283 PieceKind::Pawn => 1,
284 PieceKind::Knight => 2,
285 PieceKind::Queen => 3,
286 PieceKind::Rook => 4,
287 PieceKind::Bishop => 5,
288 }
289 }
290}
291
292impl Piece {
293 #[inline(always)]
295 const fn zobrist_offset(&self) -> usize {
296 self.color.zobrist_offset_block() + self.piece_kind.zobrist_offset_pk()
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::coretypes::Move;
304 use crate::fen::Fen;
305 use crate::Position;
306
307 fn test_before_and_after(
308 table: ZobristTable,
309 before: Position,
310 after: Position,
311 legal_move: Move,
312 ) {
313 let hash_before = table.generate_hash(Key::from(&before));
314 let hash_after = table.generate_hash(Key::from(&after));
315
316 let hash_extra_before = table.generate_hash(Key::from(&before));
318 let hash_extra_after = table.generate_hash(Key::from(&after));
319 assert_eq!(hash_before, hash_extra_before);
320 assert_eq!(hash_after, hash_extra_after);
321
322 let mut pos = before.clone();
324 let mut hash = table.generate_hash(Key::from(&pos));
325 assert_eq!(pos, before);
326 assert_eq!(hash, hash_before);
327
328 let cache = pos.cache();
331 let move_info = pos.do_move(legal_move);
332 table.update_hash(&mut hash, Key::from(&pos), move_info, cache);
333 assert_eq!(pos, after);
334 assert_eq!(hash, hash_after);
335
336 table.update_hash(&mut hash, Key::from(&pos), move_info, cache);
338 assert_eq!(hash, hash_before);
339
340 table.update_hash(&mut hash, Key::from(&pos), move_info, cache);
342 assert_eq!(hash, hash_after);
343 }
344
345 #[test]
346 fn hash_start_position() {
347 let table = ZobristTable::new();
348 let legal_move = Move::new(D2, D4, None);
349 let start_position = Position::start_position();
350 let queens_pawn_game = start_position.make_move(legal_move);
351
352 test_before_and_after(table, start_position, queens_pawn_game, legal_move);
353 }
354
355 #[test]
356 fn hash_en_passant_position() {
357 let table = ZobristTable::new();
358 let legal_move = Move::new(D5, E6, None);
359 let pos_before =
360 Position::parse_fen("rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 3")
361 .unwrap();
362 let pos_after =
363 Position::parse_fen("rnbqkbnr/pp1p1ppp/4P3/2p5/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 3")
364 .unwrap();
365
366 test_before_and_after(table, pos_before, pos_after, legal_move);
367 }
368
369 #[test]
370 fn hash_castling_position() {
371 let table = ZobristTable::new();
372 let legal_move = Move::new(E1, G1, None);
373 let pos_before = Position::parse_fen(
374 "rnb1k1nr/pp3ppp/3bp3/q2p4/2Pp4/2NBPN2/PP3PPP/R1BQK2R w KQkq - 0 7",
375 )
376 .unwrap();
377 let pos_after =
378 Position::parse_fen("rnb1k1nr/pp3ppp/3bp3/q2p4/2Pp4/2NBPN2/PP3PPP/R1BQ1RK1 b kq - 1 7")
379 .unwrap();
380
381 test_before_and_after(table, pos_before, pos_after, legal_move);
382 }
383}