// Eryn Wells use crate::{ castle, display::DiagramFormatter, piece_sets::{PlacePieceError, PlacePieceStrategy}, zobrist::{ZobristHash, ZobristState}, PieceSet, }; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, Shape, Square, Wing}; use std::sync::Arc; pub type HalfMoveClock = u32; pub type FullMoveClock = u32; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Board { active_color: Color, pieces: PieceSet, castling_rights: castle::Rights, en_passant_target: Option, pub half_move_clock: HalfMoveClock, pub full_move_number: FullMoveClock, zobrist_hash: Option, } impl Board { /// An empty board #[must_use] pub fn empty(zobrist: Option>) -> Self { let mut board = Self { zobrist_hash: zobrist.map(ZobristHash::new), ..Default::default() }; board.recompute_zobrist_hash(); board } /// The starting position #[must_use] pub fn starting(zobrist: Option>) -> Self { const BLACK_PIECES: [BitBoard; Shape::NUM] = [ BitBoard::new(0b0000_0000_1111_1111 << 48), BitBoard::new(0b0100_0010_0000_0000 << 48), BitBoard::new(0b0010_0100_0000_0000 << 48), BitBoard::new(0b1000_0001_0000_0000 << 48), BitBoard::new(0b0000_1000_0000_0000 << 48), BitBoard::new(0b0001_0000_0000_0000 << 48), ]; const WHITE_PIECES: [BitBoard; Shape::NUM] = [ BitBoard::new(0b1111_1111_0000_0000), BitBoard::new(0b0000_0000_0100_0010), BitBoard::new(0b0000_0000_0010_0100), BitBoard::new(0b0000_0000_1000_0001), BitBoard::new(0b0000_0000_0000_1000), BitBoard::new(0b0000_0000_0001_0000), ]; let mut board = Self { pieces: PieceSet::new([WHITE_PIECES, BLACK_PIECES]), zobrist_hash: zobrist.map(ZobristHash::new), ..Default::default() }; board.recompute_zobrist_hash(); board } } impl Board { #[must_use] pub fn active_color(&self) -> Color { self.active_color } pub fn set_active_color(&mut self, color: Color) { if color == self.active_color { return; } self.active_color = color; if let Some(zobrist) = self.zobrist_hash.as_mut() { zobrist.update_setting_active_color(color); } } } impl Board { #[must_use] pub fn castling_rights(&self) -> castle::Rights { self.castling_rights } pub fn set_castling_rights(&mut self, rights: castle::Rights) { if rights == self.castling_rights { return; } let old_rights = self.castling_rights; self.castling_rights = rights; self.update_zobrist_hash_castling_rights(old_rights); } #[must_use] pub fn active_color_has_castling_right(&self, wing: Wing) -> bool { self.color_has_castling_right(self.active_color, wing) } #[must_use] pub fn color_has_castling_right(&self, color: Color, wing: Wing) -> bool { self.castling_rights.color_has_right(color, wing) } pub fn grant_castling_right(&mut self, color: Color, wing: Wing) { let old_rights = self.castling_rights; self.castling_rights.grant(color, wing); self.update_zobrist_hash_castling_rights(old_rights); } pub fn revoke_all_castling_rights(&mut self) { let old_rights = self.castling_rights; self.castling_rights.revoke_all(); self.update_zobrist_hash_castling_rights(old_rights); } pub fn revoke_castling_right(&mut self, color: Color, wing: Wing) { let old_rights = self.castling_rights; self.castling_rights.revoke(color, wing); self.update_zobrist_hash_castling_rights(old_rights); } fn update_zobrist_hash_castling_rights(&mut self, old_rights: castle::Rights) { let new_rights = self.castling_rights; if old_rights == new_rights { return; } if let Some(zobrist) = self.zobrist_hash.as_mut() { zobrist.update_modifying_castling_rights(new_rights, old_rights); } } } impl Board { /// Returns a copy of the current en passant square, if one exists. #[must_use] pub fn en_passant_target(&self) -> Option { self.en_passant_target } pub fn set_en_passant_target(&mut self, square: Square) { self.set_en_passant_target_option(Some(square)); } pub fn set_en_passant_target_option(&mut self, square: Option) { let old_target = self.en_passant_target; self.en_passant_target = square; self.update_zobrist_hash_en_passant_target(old_target); } pub fn clear_en_passant_target(&mut self) { let old_target = self.en_passant_target; self.en_passant_target = None; self.update_zobrist_hash_en_passant_target(old_target); } fn update_zobrist_hash_en_passant_target(&mut self, old_target: Option) { let new_target = self.en_passant_target; if old_target == new_target { return; } if let Some(zobrist) = self.zobrist_hash.as_mut() { zobrist.update_setting_en_passant_target(old_target, new_target); } } } impl Board { #[must_use] pub fn get_piece(&self, square: Square) -> Option { self.pieces.get(square) } pub fn find_pieces(&self, piece: Piece) -> BitBoard { self.pieces.find_pieces(piece) } /// Place a piece on the board. /// /// ## Errors /// /// When is called with [`PlacePieceStrategy::PreserveExisting`], and a /// piece already exists on `square`, this method returns a /// [`PlacePieceError::ExistingPiece`] error. /// pub fn place_piece( &mut self, piece: Piece, square: Square, strategy: PlacePieceStrategy, ) -> Result, PlacePieceError> { let place_result = self.pieces.place(piece, square, strategy); if let Ok(Some(existing_piece)) = place_result.as_ref() { if let Some(zobrist) = self.zobrist_hash.as_mut() { zobrist.update_removing_piece(square, *existing_piece); zobrist.update_adding_piece(square, piece); } } place_result } pub fn remove_piece(&mut self, square: Square) -> Option { let removed_piece = self.pieces.remove(square); if let Some(piece) = removed_piece { if let Some(zobrist) = self.zobrist_hash.as_mut() { zobrist.update_removing_piece(square, piece); } } removed_piece } } impl Board { pub fn iter(&self) -> impl Iterator { self.pieces.iter() } /// A [`BitBoard`] of squares occupied by pieces of all colors. pub fn occupancy(&self) -> BitBoard { self.pieces.occpuancy() } /// A [`BitBoard`] of squares that are vacant. pub fn vacancy(&self) -> BitBoard { !self.occupancy() } pub fn friendly_occupancy(&self, color: Color) -> BitBoard { self.pieces.friendly_occupancy(color) } pub fn opposing_occupancy(&self, color: Color) -> BitBoard { self.pieces.opposing_occupancy(color) } pub fn enemies(&self, color: Color) -> BitBoard { self.pieces.opposing_occupancy(color) } /// Return a [`BitBoard`] of all pawns of a given color. pub fn pawns(&self, color: Color) -> BitBoard { self.pieces.find_pieces(Piece::pawn(color)) } pub fn knights(&self, color: Color) -> BitBoard { self.find_pieces(Piece::knight(color)) } pub fn bishops(&self, color: Color) -> BitBoard { self.find_pieces(Piece::bishop(color)) } pub fn rooks(&self, color: Color) -> BitBoard { self.find_pieces(Piece::rook(color)) } pub fn queens(&self, color: Color) -> BitBoard { self.find_pieces(Piece::queen(color)) } pub fn kings(&self, color: Color) -> BitBoard { self.find_pieces(Piece::king(color)) } } impl Board { pub fn zobrist_hash(&self) -> Option { self.zobrist_hash.as_ref().map(ZobristHash::hash_value) } pub fn recompute_zobrist_hash(&mut self) { // Avoid overlapping borrows when borrowing zobrist_hash.as_mut() and // then also borrowing self to update the board hash by computing the // hash with the static function first, and then setting the hash value // on the zobrist instance. Unfortuantely this requires unwrapping // self.zobrist_hash twice. C'est la vie. let new_hash = self.zobrist_hash.as_ref().map(|zobrist| { let state = zobrist.state(); ZobristHash::compute_board_hash(self, state.as_ref()) }); if let (Some(new_hash), Some(zobrist)) = (new_hash, self.zobrist_hash.as_mut()) { zobrist.set_hash_value(new_hash); } } } impl Board { pub fn display(&self) -> DiagramFormatter<'_> { DiagramFormatter::new(self) } } impl Board { #[must_use] pub fn unwrap_color(&self, color: Option) -> Color { color.unwrap_or(self.active_color) } } #[cfg(test)] mod tests { use super::*; use crate::test_board; use chessfriend_core::{piece, random::RandomNumberGenerator}; #[test] fn get_piece_on_square() { let board = test_board![ Black Bishop on F7, ]; assert_eq!(board.get_piece(Square::F7), Some(piece!(Black Bishop))); } // MARK: - Zobrist Hashing fn test_state() -> ZobristState { let mut rng = RandomNumberGenerator::default(); ZobristState::new(&mut rng) } #[test] fn zobrist_hash_set_for_empty_board() { let state = Arc::new(test_state()); let board = Board::empty(Some(state.clone())); let hash = board.zobrist_hash(); assert_eq!(hash, Some(0)); } #[test] fn zobrist_hash_set_for_starting_position_board() { let state = Arc::new(test_state()); let board = Board::starting(Some(state.clone())); let hash = board.zobrist_hash(); assert!(hash.is_some()); } #[test] fn zobrist_hash_updated_when_changing_active_color() { let state = Arc::new(test_state()); let mut board = Board::empty(Some(state.clone())); board.set_active_color(Color::Black); // Just verify that the value is real and has changed. The actual value // computation is covered by the tests in zobrist.rs. let hash = board.zobrist_hash(); assert!(hash.is_some()); assert_ne!(hash, Some(0)); } #[test] fn zobrist_hash_updated_when_changing_en_passant_target() { let state = Arc::new(test_state()); let mut board = Board::empty(Some(state.clone())); board.set_en_passant_target(Square::C3); // Just verify that the value is real and has changed. The actual value // computation is covered by the tests in zobrist.rs. let hash = board.zobrist_hash(); assert!(hash.is_some()); assert_ne!(hash, Some(0)); } }