use crate::types::{Color, Piece, PieceType, Square};
use std::sync::LazyLock;
pub struct Zobrist {
pub side: u64,
pub psq: [[u64; Square::NUM + 1]; 32],
pub hand: [[u64; PieceType::HAND_NUM]; Color::NUM],
pub no_pawns: u64,
}
impl Zobrist {
pub const fn init() -> Self {
let mut zobrist = Zobrist {
side: 0,
psq: [[0; Square::NUM + 1]; 32],
hand: [[0; PieceType::HAND_NUM]; Color::NUM],
no_pawns: 0,
};
let mut seed = 0x123456789ABCDEF0u64;
seed = xorshift64(seed);
zobrist.side = seed;
seed = xorshift64(seed);
zobrist.no_pawns = seed;
let mut pc = 1;
while pc < 32 {
let mut sq = 0;
while sq < Square::NUM {
seed = xorshift64(seed);
zobrist.psq[pc][sq] = seed;
sq += 1;
}
pc += 1;
}
let mut c = 0;
while c < Color::NUM {
let mut pt = 0;
while pt < PieceType::HAND_NUM {
seed = xorshift64(seed);
zobrist.hand[c][pt] = seed;
pt += 1;
}
c += 1;
}
zobrist
}
}
const fn xorshift64(mut x: u64) -> u64 {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x
}
pub static ZOBRIST: Zobrist = Zobrist::init();
#[inline]
pub fn zobrist_psq(pc: Piece, sq: Square) -> u64 {
ZOBRIST.psq[pc.index()][sq.index()]
}
#[inline]
pub fn zobrist_no_pawns() -> u64 {
ZOBRIST.no_pawns
}
#[inline]
pub fn zobrist_hand(color: Color, pt: PieceType) -> u64 {
let idx = hand_index(pt).expect("zobrist_hand called with non-hand piece");
ZOBRIST.hand[color.index()][idx]
}
const fn hand_index(pt: PieceType) -> Option<usize> {
match pt {
PieceType::Pawn => Some(0),
PieceType::Lance => Some(1),
PieceType::Knight => Some(2),
PieceType::Silver => Some(3),
PieceType::Gold => Some(4),
PieceType::Bishop => Some(5),
PieceType::Rook => Some(6),
_ => None,
}
}
#[inline]
pub fn zobrist_side() -> u64 {
ZOBRIST.side
}
const PASS_RIGHTS_ZOBRIST_SEED: u64 = 0x5A55_0000_0000_0001;
static PASS_RIGHTS_KEYS: LazyLock<[[u64; 16]; 16]> = LazyLock::new(|| {
let mut keys = [[0u64; 16]; 16];
let mut seed = PASS_RIGHTS_ZOBRIST_SEED;
for (black, row) in keys.iter_mut().enumerate() {
for (white, key) in row.iter_mut().enumerate() {
if black == 0 && white == 0 {
continue;
}
seed = xorshift64(seed);
*key = seed;
}
}
keys
});
#[inline]
pub fn zobrist_pass_rights(black_rights: u8, white_rights: u8) -> u64 {
PASS_RIGHTS_KEYS[black_rights.min(15) as usize][white_rights.min(15) as usize]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{File, Rank};
#[test]
fn test_zobrist_init() {
assert_ne!(ZOBRIST.side, 0);
let sq11 = Square::new(File::File1, Rank::Rank1);
let sq12 = Square::new(File::File1, Rank::Rank2);
assert_ne!(zobrist_psq(Piece::B_PAWN, sq11), zobrist_psq(Piece::B_PAWN, sq12));
assert_ne!(zobrist_psq(Piece::B_PAWN, sq11), zobrist_psq(Piece::W_PAWN, sq11));
}
#[test]
fn test_zobrist_hand() {
assert_ne!(
zobrist_hand(Color::Black, PieceType::Pawn),
zobrist_hand(Color::Black, PieceType::Lance)
);
assert_ne!(
zobrist_hand(Color::Black, PieceType::Pawn),
zobrist_hand(Color::White, PieceType::Pawn)
);
}
#[test]
fn test_zobrist_xor_property() {
let sq = Square::new(File::File5, Rank::Rank5);
let h1 = zobrist_psq(Piece::B_PAWN, sq);
let h2 = zobrist_psq(Piece::B_GOLD, sq);
let combined = h1 ^ h2;
assert_eq!(combined ^ h2, h1);
assert_eq!(combined ^ h1, h2);
}
#[test]
fn test_zobrist_pass_rights_zero_compatible() {
assert_eq!(zobrist_pass_rights(0, 0), 0);
}
#[test]
fn test_zobrist_pass_rights_nonzero() {
assert_ne!(zobrist_pass_rights(1, 0), 0);
assert_ne!(zobrist_pass_rights(0, 1), 0);
assert_ne!(zobrist_pass_rights(2, 2), 0);
assert_ne!(zobrist_pass_rights(15, 15), 0);
}
#[test]
fn test_zobrist_pass_rights_uniqueness() {
assert_ne!(zobrist_pass_rights(1, 0), zobrist_pass_rights(0, 1));
assert_ne!(zobrist_pass_rights(2, 2), zobrist_pass_rights(3, 3));
assert_ne!(zobrist_pass_rights(1, 1), zobrist_pass_rights(2, 2));
}
#[test]
fn test_zobrist_pass_rights_clamp() {
let key15 = zobrist_pass_rights(15, 15);
let key20 = zobrist_pass_rights(20, 20);
assert_eq!(key15, key20);
}
#[test]
fn test_zobrist_pass_rights_xor_property() {
let key1 = zobrist_pass_rights(2, 2);
let key2 = zobrist_pass_rights(3, 3);
let combined = key1 ^ key2;
assert_eq!(combined ^ key2, key1);
assert_eq!(combined ^ key1, key2);
}
}