use std::sync::Once;
struct XorShift64 {
state: u64,
}
impl XorShift64 {
pub fn new(seed: u64) -> XorShift64 {
debug_assert!(seed != 0, "seed must be non-zero");
XorShift64 { state: seed }
}
pub fn next(&mut self) -> u64 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.state = x;
x
}
}
static INIT: Once = Once::new();
static mut ZOBRIST_PIECE_SQUARE: [[u64; 64]; 32] = [[0; 64]; 32];
static mut ZOBRIST_SIDE: u64 = 0;
static mut ZOBRIST_EP_FILE: [u64; 8] = [0; 8];
fn init_zobrist() {
let mut rng = XorShift64::new(0x1234_5678_9ABC_DEF0);
unsafe {
for pid in 0..32 {
for sq in 0..64 {
ZOBRIST_PIECE_SQUARE[pid][sq] = rng.next();
}
}
ZOBRIST_SIDE = rng.next();
for f in 0..8 {
ZOBRIST_EP_FILE[f] = rng.next();
}
}
}
#[inline]
fn piece_square_value(pid: u8, sq: u8) -> u64 {
INIT.call_once(init_zobrist);
unsafe { ZOBRIST_PIECE_SQUARE[pid as usize][sq as usize] }
}
#[inline]
fn side_value() -> u64 {
INIT.call_once(init_zobrist);
unsafe { ZOBRIST_SIDE }
}
#[inline]
fn ep_file_value(file: u8) -> u64 {
INIT.call_once(init_zobrist);
unsafe { ZOBRIST_EP_FILE[file as usize] }
}
pub fn compute_hash(
mapping: &crate::board::PieceMapping,
side_to_move: u8,
en_passant_target: Option<u8>,
) -> u64 {
let mut h: u64 = 0;
for pid in 0..32 {
if let Some(sq) = mapping.piece_square[pid as usize] {
h ^= piece_square_value(pid as u8, sq);
}
}
if side_to_move == 1 {
h ^= side_value();
}
if let Some(ep_sq) = en_passant_target {
let file = ep_sq & 7;
h ^= ep_file_value(file);
}
h
}
#[cfg(test)]
mod tests {
use super::*;
use crate::board::{init_chess_positions, PieceMapping};
#[test]
fn same_position_same_hash() {
let mut mapping1 = PieceMapping::new_empty();
let mut mapping2 = PieceMapping::new_empty();
for &(pid, sq) in &init_chess_positions() {
mapping1.place_piece(pid, sq);
mapping2.place_piece(pid, sq);
}
let h1 = compute_hash(&mapping1, 0, None);
let h2 = compute_hash(&mapping2, 0, None);
assert_eq!(h1, h2);
}
#[test]
fn different_side_different_hash() {
let mut mapping = PieceMapping::new_empty();
for &(pid, sq) in &init_chess_positions() {
mapping.place_piece(pid, sq);
}
let h_white = compute_hash(&mapping, 0, None);
let h_black = compute_hash(&mapping, 1, None);
assert_ne!(h_white, h_black);
}
#[test]
fn ep_target_changes_hash() {
let mut mapping = PieceMapping::new_empty();
for &(pid, sq) in &init_chess_positions() {
mapping.place_piece(pid, sq);
}
let h_no_ep = compute_hash(&mapping, 1, None);
let h_with_ep = compute_hash(&mapping, 1, Some(17));
assert_ne!(h_no_ep, h_with_ep);
}
}