// Eryn Wells use super::{flags::Flags, Pieces}; use crate::{ move_generator::Moves, piece::{Color, Piece, PiecePlacementError, PlacedPiece, Shape}, sight::Sight, BitBoard, Square, }; use std::cell::OnceCell; /// A lateral side of the board relative to the player. Kingside is the side the /// player's king starts on. Queenside is the side of the board the player's /// queen starts on. pub(crate) enum BoardSide { King, Queen, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct Position { color_to_move: Color, flags: Flags, /// Composite bitboards for all the pieces of a particular color. pieces_per_color: [BitBoard; 2], /// Bitboards representing positions of particular piece types per color. pieces_per_type: [[BitBoard; 6]; 2], en_passant_square: Option, sight: [OnceCell; 2], } impl Position { pub fn empty() -> Position { Position { color_to_move: Color::White, flags: Default::default(), pieces_per_color: [BitBoard::empty(); 2], pieces_per_type: [ [ BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), ], [ BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), BitBoard::empty(), ], ], en_passant_square: None, sight: [OnceCell::new(), OnceCell::new()], } } /// Return a starting position. pub fn starting() -> Position { let white_pieces = [ BitBoard::new(0x00FF000000000000), BitBoard::new(0x4200000000000000), BitBoard::new(0x2400000000000000), BitBoard::new(0x8100000000000000), BitBoard::new(0x1000000000000000), BitBoard::new(0x0800000000000000), ]; let black_pieces = [ BitBoard::new(0xFF00), BitBoard::new(0x0042), BitBoard::new(0x0024), BitBoard::new(0x0081), BitBoard::new(0x0010), BitBoard::new(0x0008), ]; Position { color_to_move: Color::White, flags: Default::default(), pieces_per_color: [ white_pieces.iter().fold(BitBoard::empty(), |a, b| a | *b), black_pieces.iter().fold(BitBoard::empty(), |a, b| a | *b), ], pieces_per_type: [white_pieces, black_pieces], en_passant_square: None, sight: [OnceCell::new(), OnceCell::new()], } } /// 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. /// /// The following requirements must also be met: /// /// 1. The spaces between the king and rook must be clear /// 2. The king must not be in check /// 3. In the course of castling on that side, the king must not pass /// through a square that an enemy piece can see pub(crate) fn player_has_right_to_castle(&self, color: Color, side: BoardSide) -> bool { self.flags.player_has_right_to_castle(color, side) } pub fn place_piece(&mut self, piece: Piece, square: Square) -> Result<(), PiecePlacementError> { let type_bb = self.bitboard_for_piece_mut(piece); if type_bb.is_set(square) { return Err(PiecePlacementError::ExistsOnSquare); } type_bb.set_square(square); self.bitboard_for_color_mut(piece.color()) .set_square(square); Ok(()) } pub fn moves(&self) -> Moves { Moves::new(self, self.color_to_move) } /// Return a BitBoard representing the set of squares containing a piece. #[inline] pub(crate) fn occupied_squares(&self) -> BitBoard { self.pieces_per_color[Color::White as usize] | self.pieces_per_color[Color::Black as usize] } #[inline] pub(crate) fn friendly_pieces(&self) -> BitBoard { self.bitboard_for_color(self.color_to_move) } #[inline] pub(crate) fn opposing_pieces(&self) -> BitBoard { self.bitboard_for_color(self.color_to_move.other()) } /// Return a BitBoard representing the set of squares containing a piece. /// This set is the inverse of `occupied_squares`. #[inline] pub(crate) fn empty_squares(&self) -> BitBoard { !self.occupied_squares() } pub(crate) fn bitboard_for_piece(&self, piece: Piece) -> BitBoard { self.pieces_per_type[piece.color() as usize][piece.shape() as usize] } fn bitboard_for_piece_mut(&mut self, piece: Piece) -> &mut BitBoard { &mut self.pieces_per_type[piece.color() as usize][piece.shape() as usize] } pub(crate) fn bitboard_for_color(&self, color: Color) -> BitBoard { self.pieces_per_color[color as usize] } fn bitboard_for_color_mut(&mut self, color: Color) -> &mut BitBoard { &mut self.pieces_per_color[color as usize] } 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); let bb = self.bitboard_for_piece(piece); if bb.is_set(sq) { return Some(PlacedPiece::new(piece, sq)); } } } None } pub fn pieces(&self, color: Color) -> Pieces { Pieces::new(&self, color) } pub fn en_passant_square(&self) -> Option { self.en_passant_square } pub(crate) fn sight_of_player(&self, color: Color) -> BitBoard { *self.sight[color as usize].get_or_init(|| { self.pieces(color).fold(BitBoard::empty(), |acc, pp| { acc | pp.sight_in_position(&self) }) }) } pub(crate) fn is_king_in_check(&self) -> bool { let sight_of_opposing_player = self.sight_of_player(self.color_to_move.other()); sight_of_opposing_player.is_set(self.king_square()) } fn king_square(&self) -> Square { self.bitboard_for_piece(Piece::king(self.color_to_move)) .occupied_squares() .next() .unwrap() } } #[cfg(test)] impl Position { pub(crate) fn test_set_en_passant_square(&mut self, square: Square) { self.en_passant_square = Some(square); } } impl Default for Position { fn default() -> Self { Self::empty() } } impl FromIterator for Position { fn from_iter>(iter: T) -> Self { let mut position = Position::empty(); for placed_piece in iter { _ = position.place_piece(placed_piece.piece(), placed_piece.square()); } position } } #[cfg(test)] mod tests { use super::*; use crate::piece::Shape; #[test] fn place_piece() { let mut position = Position::empty(); let piece = Piece::new(Color::White, Shape::Queen); let square = Square::E4; position .place_piece(piece, square) .expect("Unable to place white queen on e4"); assert_eq!( position.bitboard_for_color(piece.color()).clone(), BitBoard::new(1 << 28) ); assert_eq!( position.bitboard_for_piece(piece).clone(), BitBoard::new(1 << 28) ); position .place_piece(piece, square) .expect_err("Placed white queen on e4 a second time?!"); } #[test] fn king_is_in_check() { let pos = position![ White King on E1, Black Rook on E8, ]; assert!(pos.is_king_in_check()); } #[test] fn king_is_not_in_check() { let pos = position![ White King on F1, Black Rook on E8, ]; assert!(!pos.is_king_in_check()); } }