Add tests for Zobrist hashing
This commit is contained in:
@@ -57,7 +57,7 @@ impl Game {
|
|||||||
let color = board.state.current_player();
|
let color = board.state.current_player();
|
||||||
let pawn_move = board.is_pawn_move(mv.src);
|
let pawn_move = board.is_pawn_move(mv.src);
|
||||||
let mut en_passant_square = None;
|
let mut en_passant_square = None;
|
||||||
let capture_square = match color {
|
let capture_en_passant = match color {
|
||||||
Color::White => mv.dst - 8,
|
Color::White => mv.dst - 8,
|
||||||
Color::Black => mv.dst + 8,
|
Color::Black => mv.dst + 8,
|
||||||
};
|
};
|
||||||
@@ -71,6 +71,7 @@ impl Game {
|
|||||||
MoveType::Quiet => {
|
MoveType::Quiet => {
|
||||||
board.move_piece(mv.src, mv.dst, piece_at_src);
|
board.move_piece(mv.src, mv.dst, piece_at_src);
|
||||||
hash.update_quiet(mv.src, mv.dst, piece_at_src, color);
|
hash.update_quiet(mv.src, mv.dst, piece_at_src, color);
|
||||||
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
||||||
}
|
}
|
||||||
MoveType::Capture => {
|
MoveType::Capture => {
|
||||||
@@ -78,14 +79,16 @@ impl Game {
|
|||||||
board.move_piece(mv.src, mv.dst, piece_at_src);
|
board.move_piece(mv.src, mv.dst, piece_at_src);
|
||||||
board.remove_opponent_piece(mv.dst, piece_at_dst);
|
board.remove_opponent_piece(mv.dst, piece_at_dst);
|
||||||
hash.update_capture(mv.src, mv.dst, piece_at_src, piece_at_dst, color);
|
hash.update_capture(mv.src, mv.dst, piece_at_src, piece_at_dst, color);
|
||||||
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
||||||
}
|
}
|
||||||
MoveType::EnPassant => {
|
MoveType::EnPassant => {
|
||||||
board.move_piece(mv.src, mv.dst, piece_at_src);
|
board.move_piece(mv.src, mv.dst, PieceType::Pawn);
|
||||||
board.remove_opponent_piece(capture_square, PieceType::Pawn);
|
board.remove_opponent_piece(capture_en_passant, PieceType::Pawn);
|
||||||
hash.update_en_passant(mv.src, mv.dst, piece_at_src, color);
|
hash.update_en_passant(mv.src, mv.dst, capture_en_passant, color);
|
||||||
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(capture_square, None);
|
mailbox.set_piece_at(mv.dst, Some(PieceType::Pawn));
|
||||||
|
mailbox.set_piece_at(capture_en_passant, None);
|
||||||
}
|
}
|
||||||
MoveType::DoublePush => {
|
MoveType::DoublePush => {
|
||||||
board.move_piece(mv.src, mv.dst, piece_at_src);
|
board.move_piece(mv.src, mv.dst, piece_at_src);
|
||||||
@@ -93,13 +96,15 @@ impl Game {
|
|||||||
Color::White => Some(mv.src + 8),
|
Color::White => Some(mv.src + 8),
|
||||||
Color::Black => Some(mv.src.saturating_sub(8)),
|
Color::Black => Some(mv.src.saturating_sub(8)),
|
||||||
};
|
};
|
||||||
hash.update_double_push(mv.src, mv.dst, color, board.state.en_passant_square);
|
hash.update_double_push(mv.src, mv.dst, color, en_passant_square);
|
||||||
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
||||||
}
|
}
|
||||||
MoveType::Promotion(promote) => {
|
MoveType::Promotion(promote) => {
|
||||||
board.remove_own_piece(mv.src, piece_at_src);
|
board.remove_own_piece(mv.src, piece_at_src);
|
||||||
board.promote_piece(mv.dst, promote);
|
board.promote_piece(mv.dst, promote);
|
||||||
hash.update_promotion(mv.src, mv.dst, promote, color);
|
hash.update_promotion(mv.src, mv.dst, promote, color);
|
||||||
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(mv.dst, Some(promote.into_piece_type()));
|
mailbox.set_piece_at(mv.dst, Some(promote.into_piece_type()));
|
||||||
}
|
}
|
||||||
MoveType::PromotionCapture(promote) => {
|
MoveType::PromotionCapture(promote) => {
|
||||||
@@ -108,6 +113,7 @@ impl Game {
|
|||||||
board.remove_opponent_piece(mv.dst, piece_at_dst);
|
board.remove_opponent_piece(mv.dst, piece_at_dst);
|
||||||
board.promote_piece(mv.dst, promote);
|
board.promote_piece(mv.dst, promote);
|
||||||
hash.update_promotion_capture(mv.src, mv.dst, piece_at_dst, promote, color);
|
hash.update_promotion_capture(mv.src, mv.dst, piece_at_dst, promote, color);
|
||||||
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(mv.dst, Some(promote.into_piece_type()));
|
mailbox.set_piece_at(mv.dst, Some(promote.into_piece_type()));
|
||||||
}
|
}
|
||||||
MoveType::Castle => {
|
MoveType::Castle => {
|
||||||
@@ -120,6 +126,7 @@ impl Game {
|
|||||||
board.move_piece(rook_src, rook_dst, PieceType::Rook);
|
board.move_piece(rook_src, rook_dst, PieceType::Rook);
|
||||||
board.state.set_castling_ability(color, Castle::None);
|
board.state.set_castling_ability(color, Castle::None);
|
||||||
hash.update_castle(mv.src, mv.dst, piece_at_src, rook_src, rook_dst, color);
|
hash.update_castle(mv.src, mv.dst, piece_at_src, rook_src, rook_dst, color);
|
||||||
|
hash.drop_en_passant_hash(board.state.en_passant_square());
|
||||||
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
mailbox.set_piece_at(mv.dst, Some(piece_at_src));
|
||||||
mailbox.set_piece_at(rook_src, None);
|
mailbox.set_piece_at(rook_src, None);
|
||||||
mailbox.set_piece_at(rook_dst, Some(PieceType::Rook));
|
mailbox.set_piece_at(rook_dst, Some(PieceType::Rook));
|
||||||
@@ -132,11 +139,6 @@ impl Game {
|
|||||||
.update_game_state(mv, color, pawn_move, en_passant_square);
|
.update_game_state(mv, color, pawn_move, en_passant_square);
|
||||||
|
|
||||||
hash.update_side_to_move_key();
|
hash.update_side_to_move_key();
|
||||||
|
|
||||||
if let Some(old_en_passant) = board.state.en_passant_square {
|
|
||||||
hash.update_en_passant_keys(old_en_passant);
|
|
||||||
}
|
|
||||||
|
|
||||||
hash.update_castling_ability_keys(old_castling_ability, board.state.castling_ability);
|
hash.update_castling_ability_keys(old_castling_ability, board.state.castling_ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,8 +254,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_make_move_quiet() -> Result<(), String> {
|
fn test_make_move_quiet() -> Result<(), String> {
|
||||||
let mut game = from_fen(FEN)?;
|
let mut game = from_fen(FEN)?;
|
||||||
let b1c3 = Move::new(Square::F3, Square::E3);
|
let f3e3 = Move::new(Square::F3, Square::E3);
|
||||||
game.make_move(&b1c3);
|
game.make_move(&f3e3);
|
||||||
|
|
||||||
assert_eq!(game, from_fen(FEN_QUIET)?);
|
assert_eq!(game, from_fen(FEN_QUIET)?);
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use crate::movegen::r#move::Promote;
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
bitboard::lsb,
|
bitboard::lsb,
|
||||||
board::{self, Board, Color, PieceType},
|
board::{Board, Color, PieceType},
|
||||||
square::{self, to_file, Square},
|
square::{self, Square},
|
||||||
state::Castle,
|
state::Castle,
|
||||||
};
|
};
|
||||||
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
||||||
@@ -24,7 +24,7 @@ pub struct ZobristKeys {
|
|||||||
|
|
||||||
impl ZobristKeys {
|
impl ZobristKeys {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut keys = ZobristKeys::default();
|
let mut keys = Self::default();
|
||||||
let mut state = SmallRng::seed_from_u64(1804289383);
|
let mut state = SmallRng::seed_from_u64(1804289383);
|
||||||
|
|
||||||
for square in Square::A1..=Square::H8 {
|
for square in Square::A1..=Square::H8 {
|
||||||
@@ -58,27 +58,27 @@ impl ZobristKeys {
|
|||||||
let white_pieces = &board.white_pieces;
|
let white_pieces = &board.white_pieces;
|
||||||
let black_pieces = &board.black_pieces;
|
let black_pieces = &board.black_pieces;
|
||||||
|
|
||||||
for (idx, piece) in white_pieces.iter().enumerate() {
|
for piece in white_pieces.iter() {
|
||||||
let mut bb = piece.bitboard;
|
let mut bb = piece.bitboard;
|
||||||
|
|
||||||
while bb != 0 {
|
while bb != 0 {
|
||||||
let square = lsb(bb);
|
let square = lsb(bb);
|
||||||
hash ^= self.piece_square_color[square][idx][0];
|
hash ^= self.piece_square_color[square][piece.piece_type.idx()][piece.color.idx()];
|
||||||
bb &= bb - 1;
|
bb &= bb - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx, piece) in black_pieces.iter().enumerate() {
|
for piece in black_pieces.iter() {
|
||||||
let mut bb = piece.bitboard;
|
let mut bb = piece.bitboard;
|
||||||
|
|
||||||
while bb != 0 {
|
while bb != 0 {
|
||||||
let square = lsb(bb);
|
let square = lsb(bb);
|
||||||
hash ^= self.piece_square_color[square][idx][1];
|
hash ^= self.piece_square_color[square][piece.piece_type.idx()][piece.color.idx()];
|
||||||
bb &= bb - 1;
|
bb &= bb - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if board.state.current_player().eq(&board::Color::Black) {
|
if board.state.current_player().eq(&Color::Black) {
|
||||||
hash ^= self.side_to_move
|
hash ^= self.side_to_move
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ impl ZobristKeys {
|
|||||||
hash ^= self.castling_ability[board.state.castling_ability[1].idx()][1];
|
hash ^= self.castling_ability[board.state.castling_ability[1].idx()][1];
|
||||||
|
|
||||||
if let Some(ep) = board.state.en_passant_square {
|
if let Some(ep) = board.state.en_passant_square {
|
||||||
hash ^= self.en_passant[to_file(ep)];
|
hash ^= self.en_passant[square::to_file(ep)];
|
||||||
}
|
}
|
||||||
|
|
||||||
ZobristHash::new(hash)
|
ZobristHash::new(hash)
|
||||||
@@ -119,11 +119,6 @@ impl ZobristHash {
|
|||||||
self.hash ^= keys.side_to_move
|
self.hash ^= keys.side_to_move
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_en_passant_keys(&mut self, old_en_passant: usize) {
|
|
||||||
let keys = zobrist_keys();
|
|
||||||
self.hash ^= keys.en_passant[square::to_file(old_en_passant)]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_castling_ability_keys(
|
pub fn update_castling_ability_keys(
|
||||||
&mut self,
|
&mut self,
|
||||||
old_castling_ability: [Castle; 2],
|
old_castling_ability: [Castle; 2],
|
||||||
@@ -160,13 +155,13 @@ impl ZobristHash {
|
|||||||
&mut self,
|
&mut self,
|
||||||
src: usize,
|
src: usize,
|
||||||
dst: usize,
|
dst: usize,
|
||||||
piece_at_src: PieceType,
|
capture_en_passant: usize,
|
||||||
color: Color,
|
color: Color,
|
||||||
) {
|
) {
|
||||||
let keys = zobrist_keys();
|
let keys = zobrist_keys();
|
||||||
self.hash ^= keys.piece_square_color[src][piece_at_src.idx()][color.idx()];
|
self.hash ^= keys.piece_square_color[src][PieceType::Pawn.idx()][color.idx()];
|
||||||
self.hash ^= keys.piece_square_color[dst][piece_at_src.idx()][color.idx()];
|
self.hash ^= keys.piece_square_color[dst][PieceType::Pawn.idx()][color.idx()];
|
||||||
self.hash ^= keys.piece_square_color[dst][PieceType::Pawn.idx()][color.opponent().idx()]
|
self.hash ^= keys.piece_square_color[capture_en_passant][PieceType::Pawn.idx()][color.opponent().idx()]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_double_push(
|
pub fn update_double_push(
|
||||||
@@ -220,20 +215,141 @@ impl ZobristHash {
|
|||||||
self.hash ^= keys.piece_square_color[rook_src][PieceType::Rook.idx()][color.idx()];
|
self.hash ^= keys.piece_square_color[rook_src][PieceType::Rook.idx()][color.idx()];
|
||||||
self.hash ^= keys.piece_square_color[rook_dst][PieceType::Rook.idx()][color.idx()];
|
self.hash ^= keys.piece_square_color[rook_dst][PieceType::Rook.idx()][color.idx()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn drop_en_passant_hash(&mut self, en_passant: Option<usize>) {
|
||||||
|
let keys = zobrist_keys();
|
||||||
|
if let Some(ep) = en_passant {
|
||||||
|
self.hash ^= keys.en_passant[square::to_file(ep)]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
board::{fen::from_fen, square::Square},
|
||||||
|
movegen::r#move::{Move, MoveType, Promote},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_hash() -> Result<(), String> {
|
fn test_identical_boards() -> Result<(), String> {
|
||||||
|
let game1 = from_fen(FEN)?;
|
||||||
|
let game2 = from_fen(FEN)?;
|
||||||
|
|
||||||
|
assert_eq!(game1, game2);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: how to test
|
const FEN_QUIET: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2BQ3/1P1PNPP1/R3K2R b KQk - 1 1";
|
||||||
// test if an incremental position is the same as if it would be calculated from scratch
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_hash() -> Result<(), String> {
|
fn test_update_quiet() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::new(Square::F3, Square::E3));
|
||||||
|
let from_scratch = from_fen(FEN_QUIET)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEN_CAPTURE: &str = "1r2k2r/2P1pq1p/2npb3/1p3QpP/p3P3/P2B4/1P1PNPP1/R3K2R b KQk - 0 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_capture() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::with_type(Square::F3, Square::F5, MoveType::Capture));
|
||||||
|
let from_scratch = from_fen(FEN_CAPTURE)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEN_EN_PASSANT: &str =
|
||||||
|
"1r2k2r/2P1pq1p/2npb1P1/1p3p2/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_en_passant() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::with_type(
|
||||||
|
Square::H5,
|
||||||
|
Square::G6,
|
||||||
|
MoveType::EnPassant,
|
||||||
|
));
|
||||||
|
let from_scratch = from_fen(FEN_EN_PASSANT)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEN_DOUBLE_PUSH: &str =
|
||||||
|
"1r2k2r/2P1pq1p/2npb3/1p3ppP/pP2P3/P2B1Q2/3PNPP1/R3K2R b KQk b3 0 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_double_push() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::with_type(
|
||||||
|
Square::B2,
|
||||||
|
Square::B4,
|
||||||
|
MoveType::DoublePush,
|
||||||
|
));
|
||||||
|
let from_scratch = from_fen(FEN_DOUBLE_PUSH)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEN_PROMOTION: &str = "1rQ1k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_promotion() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::with_type(
|
||||||
|
Square::C7,
|
||||||
|
Square::C8,
|
||||||
|
MoveType::Promotion(Promote::Queen),
|
||||||
|
));
|
||||||
|
let from_scratch = from_fen(FEN_PROMOTION)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEN_PROMOTION_CAPTURE: &str =
|
||||||
|
"1Q2k2r/4pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R b KQk - 0 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_promotion_capture() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::with_type(
|
||||||
|
Square::C7,
|
||||||
|
Square::B8,
|
||||||
|
MoveType::PromotionCapture(Promote::Queen),
|
||||||
|
));
|
||||||
|
let from_scratch = from_fen(FEN_PROMOTION_CAPTURE)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEN_CASTLE: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R4RK1 b k - 1 1";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_castle() -> Result<(), String> {
|
||||||
|
let mut incremental = from_fen(FEN)?;
|
||||||
|
incremental.make_move(&Move::with_type(Square::E1, Square::G1, MoveType::Castle));
|
||||||
|
let from_scratch = from_fen(FEN_CASTLE)?;
|
||||||
|
|
||||||
|
assert_eq!(incremental.hash, from_scratch.hash);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user