Add Zobrist hashing to 'ChessBoard'

This commit is contained in:
Bruno BELANYI 2024-04-06 20:50:29 +01:00
parent c850529257
commit 06b5751702
2 changed files with 79 additions and 12 deletions

View file

@ -106,7 +106,7 @@ impl TryFrom<ChessBoardBuilder> for ChessBoard {
let total_plies = (turn_count - 1) * 2 + if side == Color::White { 0 } else { 1 }; let total_plies = (turn_count - 1) * 2 + if side == Color::White { 0 } else { 1 };
let board = ChessBoard { let mut board = ChessBoard {
piece_occupancy, piece_occupancy,
color_occupancy, color_occupancy,
combined_occupancy, combined_occupancy,
@ -115,7 +115,9 @@ impl TryFrom<ChessBoardBuilder> for ChessBoard {
half_move_clock, half_move_clock,
total_plies, total_plies,
side, side,
hash: 0,
}; };
board.hash = board.compute_hash();
board.validate()?; board.validate()?;
Ok(board) Ok(board)

View file

@ -1,6 +1,6 @@
use crate::movegen; use crate::movegen;
use super::{Bitboard, CastleRights, Color, File, Move, Piece, Rank, Square}; use super::{zobrist, Bitboard, CastleRights, Color, File, Move, Piece, Rank, Square};
mod builder; mod builder;
pub use builder::*; pub use builder::*;
@ -29,6 +29,8 @@ pub struct ChessBoard {
total_plies: u32, // Should be plenty. total_plies: u32, // Should be plenty.
/// The current player turn. /// The current player turn.
side: Color, side: Color,
/// Zobrist hash for the board.
hash: u64,
} }
/// The state which can't be reversed when doing/un-doing a [Move]. /// The state which can't be reversed when doing/un-doing a [Move].
@ -123,12 +125,42 @@ impl ChessBoard {
self.compute_checkers(self.current_player()) self.compute_checkers(self.current_player())
} }
/// Return the Zobrist hash for the board.
#[inline(always)]
pub fn hash(&self) -> u64 {
self.hash
}
/// Compute the Zobrist hash for the board.
fn compute_hash(&self) -> u64 {
let mut res = 0;
// The side-to-move is only toggled on Black, and otherwise 0.
if self.current_player() == Color::Black {
res ^= zobrist::side_to_move();
}
if let Some(square) = self.en_passant() {
res ^= zobrist::en_passant(square.file());
}
res ^= zobrist::castling_rights(self.castle_rights);
for piece in Piece::iter() {
for color in Color::iter() {
for square in self.occupancy(piece, color) {
res ^= zobrist::moved_piece(color, piece, square);
}
}
}
res
}
/// Quickly add/remove a piece on the [Bitboard]s that are part of the [ChessBoard] state. /// Quickly add/remove a piece on the [Bitboard]s that are part of the [ChessBoard] state.
#[inline(always)] #[inline(always)]
fn xor(&mut self, color: Color, piece: Piece, square: Square) { fn xor(&mut self, color: Color, piece: Piece, square: Square) {
*self.piece_occupancy_mut(piece) ^= square; *self.piece_occupancy_mut(piece) ^= square;
*self.color_occupancy_mut(color) ^= square; *self.color_occupancy_mut(color) ^= square;
self.combined_occupancy ^= square; self.combined_occupancy ^= square;
self.hash ^= zobrist::moved_piece(color, piece, square);
} }
/// Compute the change of [CastleRights] from moving/taking a piece. /// Compute the change of [CastleRights] from moving/taking a piece.
@ -140,9 +172,9 @@ impl ChessBoard {
(Piece::King, _) => CastleRights::NoSide, (Piece::King, _) => CastleRights::NoSide,
_ => return, _ => return,
}; };
if new_rights != original { self.hash ^= zobrist::castling_rights(self.castle_rights);
*self.castle_rights_mut(color) = new_rights; *self.castle_rights_mut(color) = new_rights;
} self.hash ^= zobrist::castling_rights(self.castle_rights);
} }
/// Play the given [Move], return a copy of the board with the resulting state. /// Play the given [Move], return a copy of the board with the resulting state.
@ -182,14 +214,26 @@ impl ChessBoard {
} else { } else {
self.half_move_clock += 1; self.half_move_clock += 1;
} }
if is_double_step { let en_passant = if is_double_step {
let target_square = Square::new( let target_square = Square::new(
chess_move.destination().file(), chess_move.destination().file(),
self.current_player().third_rank(), self.current_player().third_rank(),
); );
self.en_passant = Some(target_square); Some(target_square)
} else { } else {
self.en_passant = None; None
};
if self.en_passant() != en_passant {
self.hash ^= self
.en_passant()
.map(Square::file)
.map(zobrist::en_passant)
.unwrap_or(0);
self.hash ^= en_passant
.map(Square::file)
.map(zobrist::en_passant)
.unwrap_or(0);
self.en_passant = en_passant;
} }
self.update_castling(self.current_player(), move_piece, chess_move.start().file()); self.update_castling(self.current_player(), move_piece, chess_move.start().file());
if let Some(piece) = captured_piece { if let Some(piece) = captured_piece {
@ -203,6 +247,7 @@ impl ChessBoard {
self.xor(self.current_player(), move_piece, chess_move.destination()); self.xor(self.current_player(), move_piece, chess_move.destination());
self.total_plies += 1; self.total_plies += 1;
self.side = !self.side; self.side = !self.side;
self.hash ^= zobrist::side_to_move();
state state
} }
@ -212,8 +257,24 @@ impl ChessBoard {
#[inline(always)] #[inline(always)]
pub fn unplay_move(&mut self, chess_move: Move, previous: NonReversibleState) { pub fn unplay_move(&mut self, chess_move: Move, previous: NonReversibleState) {
// Restore non-revertible state // Restore non-revertible state
self.castle_rights = previous.castle_rights; if self.castle_rights != previous.castle_rights {
self.en_passant = previous.en_passant; self.hash ^= zobrist::castling_rights(self.castle_rights);
self.hash ^= zobrist::castling_rights(previous.castle_rights);
self.castle_rights = previous.castle_rights;
}
if self.en_passant != previous.en_passant {
self.hash ^= self
.en_passant()
.map(Square::file)
.map(zobrist::en_passant)
.unwrap_or(0);
self.hash ^= previous
.en_passant
.map(Square::file)
.map(zobrist::en_passant)
.unwrap_or(0);
self.en_passant = previous.en_passant;
}
self.half_move_clock = previous.half_move_clock; self.half_move_clock = previous.half_move_clock;
let move_piece = Piece::iter() let move_piece = Piece::iter()
@ -231,6 +292,7 @@ impl ChessBoard {
self.xor(!self.current_player(), move_piece, chess_move.start()); self.xor(!self.current_player(), move_piece, chess_move.start());
self.total_plies -= 1; self.total_plies -= 1;
self.side = !self.side; self.side = !self.side;
self.hash ^= zobrist::side_to_move();
} }
/// Return true if the current state of the board looks valid, false if something is definitely /// Return true if the current state of the board looks valid, false if something is definitely
@ -421,7 +483,7 @@ impl ChessBoard {
/// "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" FEN string /// "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" FEN string
impl Default for ChessBoard { impl Default for ChessBoard {
fn default() -> Self { fn default() -> Self {
Self { let mut res = Self {
piece_occupancy: [ piece_occupancy: [
// King // King
Square::E1 | Square::E8, Square::E1 | Square::E8,
@ -449,7 +511,10 @@ impl Default for ChessBoard {
half_move_clock: 0, half_move_clock: 0,
total_plies: 0, total_plies: 0,
side: Color::White, side: Color::White,
} hash: 0,
};
res.hash = res.compute_hash();
res
} }
} }