use std::collections::HashSet;
use std::ops::Index;
use rand::prelude::*;
use crate::boardrepr::PieceSets;
use crate::coretypes::{Castling, Color, File, Piece, PieceKind, Rank, Square, SquareIndexable};
use crate::coretypes::{MoveInfo, MoveKind, Square::*};
use crate::coretypes::{NUM_FILES, NUM_PIECE_KINDS, NUM_SQUARES};
use crate::position::{Cache, Position};
pub type HashKind = u64;
pub type Key<'a> = (&'a PieceSets, &'a Color, &'a Castling, &'a Option<Square>);
impl<'a> From<&'a Position> for Key<'a> {
fn from(pos_ref: &'a Position) -> Self {
(
pos_ref.pieces(),
pos_ref.player(),
pos_ref.castling(),
pos_ref.en_passant(),
)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ZobristTable {
piece_hash: [[HashKind; NUM_SQUARES]; NUM_PIECE_KINDS],
ep_hash: [HashKind; NUM_FILES],
castling_hash: [HashKind; Castling::ENUMERATIONS],
pub(crate) player_hash: HashKind,
}
impl ZobristTable {
const TOGGLE_PLAYER: Color = Color::Black;
pub fn new() -> Self {
Self::with_rng(StdRng::from_entropy())
}
pub fn with_seed(seed: u64) -> Self {
Self::with_rng(StdRng::seed_from_u64(seed))
}
fn with_rng(mut rng: StdRng) -> Self {
let mut used_values = HashSet::new();
let mut piece_hash = [[HashKind::default(); NUM_SQUARES]; NUM_PIECE_KINDS];
let mut ep_hash = [HashKind::default(); NUM_FILES];
let mut castling_hash = [HashKind::default(); Castling::ENUMERATIONS];
let mut inf_loop_protection: usize = 10_000;
for item in piece_hash
.iter_mut()
.flatten()
.chain(ep_hash.iter_mut())
.chain(castling_hash.iter_mut())
{
let mut new_unique_value: HashKind = rng.gen();
while !used_values.insert(new_unique_value) {
new_unique_value = rng.gen();
inf_loop_protection -= 1;
if inf_loop_protection < 10 {
panic!("Encountered excessively repeated random numbers.");
}
}
*item = new_unique_value;
}
let mut player_hash: HashKind = rng.gen();
while !used_values.insert(player_hash) {
player_hash = rng.gen();
inf_loop_protection -= 1;
if inf_loop_protection < 10 {
panic!("Encountered excessively repeated random numbers.");
}
}
Self {
piece_hash,
ep_hash,
castling_hash,
player_hash,
}
}
pub fn generate_hash(&self, key: Key) -> HashKind {
let mut hash = HashKind::default();
for color in Color::iter() {
for piece_kind in PieceKind::iter() {
let piece = Piece::new(color, piece_kind);
let squares = key.0[piece];
for square in squares {
hash ^= self[(piece, square)];
}
}
}
if let Some(ep_square) = key.3 {
hash ^= self[ep_square.file()];
}
hash ^= self[*key.2];
if *key.1 == ZobristTable::TOGGLE_PLAYER {
hash ^= self.player_hash;
}
hash
}
pub fn update_hash(&self, hash: &mut HashKind, key: Key, move_info: MoveInfo, cache: Cache) {
let moved_player = !key.1;
let passive_player = *key.1;
*hash ^= self.player_hash;
*hash ^= self[cache.castling];
*hash ^= self[*key.2];
let moved_piece = Piece::new(moved_player, move_info.piece_kind);
*hash ^= self[(moved_piece, move_info.from)];
let old_ep = cache.en_passant;
let new_ep = key.3;
if let Some(ep_square) = old_ep {
*hash ^= self[ep_square.file()];
}
if let Some(ep_square) = new_ep {
*hash ^= self[ep_square.file()];
}
let to_piece_kind: PieceKind = match move_info.promotion {
Some(promoted_pk) => promoted_pk,
None => move_info.piece_kind,
};
let to_piece = Piece::new(moved_player, to_piece_kind);
*hash ^= self[(to_piece, move_info.to)];
match move_info.move_kind {
MoveKind::Capture(captured_pk) => {
let captured_piece = Piece::new(passive_player, captured_pk);
*hash ^= self[(captured_piece, move_info.to)];
}
MoveKind::EnPassant => {
let ep_square = cache.en_passant.unwrap();
let pawn_square = match ep_square.rank() {
Rank::R3 => ep_square.increment_rank().unwrap(),
_ => ep_square.decrement_rank().unwrap(),
};
let captured_pawn = Piece::new(passive_player, PieceKind::Pawn);
*hash ^= self[(captured_pawn, pawn_square)];
}
MoveKind::Castle => {
let (rook_from, rook_to) = match move_info.to {
G1 => (H1, F1),
C1 => (A1, D1),
G8 => (H8, F8),
C8 => (A8, D8),
_ => panic!("Processing Castling Move, but to square was not valid."),
};
let castled_rook = Piece::new(moved_player, PieceKind::Rook);
*hash ^= self[(castled_rook, rook_from)];
*hash ^= self[(castled_rook, rook_to)];
}
MoveKind::Quiet => (),
};
}
}
impl Default for ZobristTable {
fn default() -> Self {
Self::new()
}
}
impl Index<(Piece, Square)> for ZobristTable {
type Output = HashKind;
fn index(&self, index: (Piece, Square)) -> &Self::Output {
let (piece, square) = index;
&self.piece_hash[piece.zobrist_offset()][square.idx()]
}
}
impl Index<File> for ZobristTable {
type Output = HashKind;
fn index(&self, index: File) -> &Self::Output {
&self.ep_hash[index as usize]
}
}
impl Index<Castling> for ZobristTable {
type Output = HashKind;
fn index(&self, index: Castling) -> &Self::Output {
&self.castling_hash[index.bits() as usize]
}
}
impl Color {
#[inline(always)]
const fn zobrist_offset_block(&self) -> usize {
match self {
Color::White => 0,
Color::Black => 6,
}
}
}
impl PieceKind {
#[inline(always)]
const fn zobrist_offset_pk(&self) -> usize {
match self {
PieceKind::King => 0,
PieceKind::Pawn => 1,
PieceKind::Knight => 2,
PieceKind::Queen => 3,
PieceKind::Rook => 4,
PieceKind::Bishop => 5,
}
}
}
impl Piece {
#[inline(always)]
const fn zobrist_offset(&self) -> usize {
self.color.zobrist_offset_block() + self.piece_kind.zobrist_offset_pk()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coretypes::Move;
use crate::fen::Fen;
use crate::Position;
fn test_before_and_after(
table: ZobristTable,
before: Position,
after: Position,
legal_move: Move,
) {
let hash_before = table.generate_hash(Key::from(&before));
let hash_after = table.generate_hash(Key::from(&after));
let hash_extra_before = table.generate_hash(Key::from(&before));
let hash_extra_after = table.generate_hash(Key::from(&after));
assert_eq!(hash_before, hash_extra_before);
assert_eq!(hash_after, hash_extra_after);
let mut pos = before.clone();
let mut hash = table.generate_hash(Key::from(&pos));
assert_eq!(pos, before);
assert_eq!(hash, hash_before);
let cache = pos.cache();
let move_info = pos.do_move(legal_move);
table.update_hash(&mut hash, Key::from(&pos), move_info, cache);
assert_eq!(pos, after);
assert_eq!(hash, hash_after);
table.update_hash(&mut hash, Key::from(&pos), move_info, cache);
assert_eq!(hash, hash_before);
table.update_hash(&mut hash, Key::from(&pos), move_info, cache);
assert_eq!(hash, hash_after);
}
#[test]
fn hash_start_position() {
let table = ZobristTable::new();
let legal_move = Move::new(D2, D4, None);
let start_position = Position::start_position();
let queens_pawn_game = start_position.make_move(legal_move);
test_before_and_after(table, start_position, queens_pawn_game, legal_move);
}
#[test]
fn hash_en_passant_position() {
let table = ZobristTable::new();
let legal_move = Move::new(D5, E6, None);
let pos_before =
Position::parse_fen("rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 3")
.unwrap();
let pos_after =
Position::parse_fen("rnbqkbnr/pp1p1ppp/4P3/2p5/8/8/PPP1PPPP/RNBQKBNR b KQkq - 0 3")
.unwrap();
test_before_and_after(table, pos_before, pos_after, legal_move);
}
#[test]
fn hash_castling_position() {
let table = ZobristTable::new();
let legal_move = Move::new(E1, G1, None);
let pos_before = Position::parse_fen(
"rnb1k1nr/pp3ppp/3bp3/q2p4/2Pp4/2NBPN2/PP3PPP/R1BQK2R w KQkq - 0 7",
)
.unwrap();
let pos_after =
Position::parse_fen("rnb1k1nr/pp3ppp/3bp3/q2p4/2Pp4/2NBPN2/PP3PPP/R1BQ1RK1 b kq - 1 7")
.unwrap();
test_before_and_after(table, pos_before, pos_after, legal_move);
}
}