// Eryn Wells use crate::{ display::DiagramFormatter, piece_sets::PlacePieceError, Castle, EnPassant, Flags, PieceSet, Pieces, }; use chessfriend_bitboard::BitBoard; use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square}; #[derive(Clone, Debug, Eq)] pub struct Board { player_to_move: Color, flags: Flags, pieces: PieceSet, en_passant: Option, half_move_counter: u16, full_move_number: u16, } impl Board { /// An empty board #[must_use] pub fn empty() -> Self { Board::default() } /// The starting position #[must_use] pub fn starting() -> 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), ]; Self { player_to_move: Color::White, pieces: PieceSet::new([WHITE_PIECES, BLACK_PIECES]), ..Default::default() } } pub(crate) fn new( player_to_move: Color, flags: Flags, pieces: PieceSet, en_passant: Option, half_move_counter: u16, full_move_number: u16, ) -> Self { Self { player_to_move, flags, pieces, en_passant, half_move_counter, full_move_number, } } #[must_use] pub fn player_to_move(&self) -> Color { self.player_to_move } #[must_use] pub fn move_number(&self) -> u16 { self.full_move_number } #[must_use] pub fn ply_counter(&self) -> u16 { self.half_move_counter } #[must_use] pub(crate) fn flags(&self) -> &Flags { &self.flags } /// Returns `true` if the player has the right to castle on the given side of the board. /// /// The right to castle on a particular side of the board is retained as long as the player has /// not moved their king, or the rook on that side of the board. #[must_use] pub fn player_has_right_to_castle(&self, color: Color, castle: Castle) -> bool { self.flags.player_has_right_to_castle(color, castle) } /// The rook to use for a castling move. #[must_use] pub fn rook_for_castle(&self, player: Color, castle: Castle) -> Option { let square = castle.parameters(player).rook_origin_square(); self.piece_on_square(square) } /// A [`BitBoard`] representing the set of squares containing a piece. #[inline] #[must_use] pub fn occupied_squares(&self) -> &BitBoard { self.pieces.all_pieces() } #[inline] #[must_use] pub fn friendly_pieces(&self) -> &BitBoard { self.pieces.all_pieces_of_color(self.player_to_move) } #[inline] #[must_use] pub fn opposing_pieces(&self) -> &BitBoard { self.pieces.all_pieces_of_color(self.player_to_move.other()) } #[inline] #[must_use] pub fn all_pieces(&self) -> (&BitBoard, &BitBoard) { (self.friendly_pieces(), self.opposing_pieces()) } /// A [`BitBoard`] representing the set of squares containing a piece. This set is the inverse of /// `Board::occupied_squares`. #[inline] #[must_use] pub fn empty_squares(&self) -> BitBoard { !self.occupied_squares() } #[must_use] pub fn piece_on_square(&self, sq: Square) -> Option { for color in Color::iter() { for shape in Shape::iter() { let piece = Piece::new(*color, *shape); if self.pieces.bitboard_for_piece(&piece).is_set(sq) { return Some(PlacedPiece::new(piece, sq)); } } } None } #[must_use] pub fn pieces(&self, color: Color) -> Pieces { Pieces::new(self, color) } #[must_use] pub fn has_en_passant_square(&self) -> bool { self.en_passant.is_some() } #[must_use] pub fn en_passant(&self) -> Option { self.en_passant } fn king_bitboard(&self, player: Color) -> &BitBoard { self.pieces.bitboard_for_piece(&Piece::king(player)) } pub(crate) fn king_square(&self, player: Color) -> Square { self.king_bitboard(player) .occupied_squares() .next() .unwrap() } #[must_use] pub fn display(&self) -> DiagramFormatter<'_> { DiagramFormatter::new(self) } #[must_use] pub fn bitboard_for_color(&self, color: Color) -> &BitBoard { self.pieces.bitboard_for_color(color) } #[must_use] pub fn bitboard_for_piece(&self, piece: Piece) -> &BitBoard { self.pieces.bitboard_for_piece(&piece) } } #[cfg(test)] impl Board { pub(crate) fn test_set_en_passant(&mut self, en_passant: EnPassant) { self.en_passant = Some(en_passant); } } impl Default for Board { fn default() -> Self { Self { player_to_move: Color::White, flags: Flags::default(), pieces: PieceSet::default(), en_passant: None, half_move_counter: 0, full_move_number: 1, } } } impl PartialEq for Board { fn eq(&self, other: &Self) -> bool { self.pieces == other.pieces && self.player_to_move == other.player_to_move && self.flags == other.flags && self.en_passant == other.en_passant } } #[cfg(test)] mod tests { use super::*; use crate::test_board; use chessfriend_core::piece; #[test] fn piece_on_square() { let pos = test_board![ Black Bishop on F7, ]; let piece = pos.piece_on_square(Square::F7); assert_eq!(piece, Some(piece!(Black Bishop on F7))); } #[test] fn piece_in_starting_position() { let board = test_board!(starting); assert_eq!( board.piece_on_square(Square::H1), Some(piece!(White Rook on H1)) ); assert_eq!( board.piece_on_square(Square::A8), Some(piece!(Black Rook on A8)) ); } #[test] fn king_not_on_starting_square_cannot_castle() { let board = test_board!(White King on E4); assert!(!board.player_has_right_to_castle(Color::White, Castle::KingSide)); assert!(!board.player_has_right_to_castle(Color::White, Castle::QueenSide)); } #[test] fn king_on_starting_square_can_castle() { let board = test_board!( White King on E1, White Rook on A1, White Rook on H1 ); assert!(board.player_has_right_to_castle(Color::White, Castle::KingSide)); assert!(board.player_has_right_to_castle(Color::White, Castle::QueenSide)); } #[test] fn rook_for_castle() { let board = test_board![ White King on E1, White Rook on H1, White Rook on A1, ]; assert_eq!( board.rook_for_castle(Color::White, Castle::KingSide), Some(piece!(White Rook on H1)) ); assert_eq!( board.rook_for_castle(Color::White, Castle::QueenSide), Some(piece!(White Rook on A1)) ); } }