2024-04-25 13:28:24 -07:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
use crate::{
|
|
|
|
castle,
|
|
|
|
display::DiagramFormatter,
|
|
|
|
piece_sets::{PlacePieceError, PlacePieceStrategy},
|
|
|
|
PieceSet,
|
|
|
|
};
|
2024-04-25 13:28:24 -07:00
|
|
|
use chessfriend_bitboard::BitBoard;
|
2025-05-19 16:50:30 -07:00
|
|
|
use chessfriend_core::{Color, Piece, Shape, Square, Wing};
|
2024-04-25 13:28:24 -07:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
2024-04-25 13:28:24 -07:00
|
|
|
pub struct Board {
|
2025-05-08 17:37:51 -07:00
|
|
|
pub active_color: Color,
|
2025-05-03 16:02:56 -07:00
|
|
|
pub pieces: PieceSet,
|
2025-05-02 15:41:45 -07:00
|
|
|
pub castling_rights: castle::Rights,
|
2025-05-03 16:02:56 -07:00
|
|
|
pub en_passant_target: Option<Square>,
|
2025-05-08 17:37:51 -07:00
|
|
|
pub half_move_clock: u32,
|
|
|
|
pub full_move_number: u32,
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2024-07-13 08:15:14 -07:00
|
|
|
pieces: PieceSet::new([WHITE_PIECES, BLACK_PIECES]),
|
2024-04-25 13:28:24 -07:00
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
2025-05-08 17:37:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Board {
|
|
|
|
#[must_use]
|
|
|
|
pub fn get_piece(&self, square: Square) -> Option<Piece> {
|
|
|
|
self.pieces.get(square)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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> {
|
|
|
|
self.pieces.place(piece, square, strategy)
|
|
|
|
}
|
2024-04-25 13:28:24 -07:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
pub fn remove_piece(&mut self, square: Square) -> Option<Piece> {
|
|
|
|
self.pieces.remove(square)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Board {
|
2025-05-19 16:50:30 -07:00
|
|
|
/// A [`BitBoard`] of squares occupied by pieces of all colors.
|
2025-05-16 07:47:28 -07:00
|
|
|
pub fn occupancy(&self) -> BitBoard {
|
|
|
|
self.pieces.occpuancy()
|
|
|
|
}
|
|
|
|
|
2025-05-19 16:50:30 -07:00
|
|
|
/// A [`BitBoard`] of squares that are vacant.
|
2025-05-16 07:47:28 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-19 16:50:30 -07:00
|
|
|
impl Board {
|
|
|
|
#[must_use]
|
|
|
|
pub fn castling_parameters(&self, wing: Wing) -> &'static castle::Parameters {
|
|
|
|
&castle::Parameters::BY_COLOR[self.active_color as usize][wing as usize]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-16 07:47:28 -07:00
|
|
|
impl Board {
|
2025-05-08 17:37:51 -07:00
|
|
|
pub fn display(&self) -> DiagramFormatter<'_> {
|
|
|
|
DiagramFormatter::new(self)
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
2025-05-08 17:37:51 -07:00
|
|
|
}
|
2024-04-25 13:28:24 -07:00
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
/*
|
|
|
|
impl Board {
|
2024-04-25 13:28:24 -07:00
|
|
|
/// The rook to use for a castling move.
|
|
|
|
#[must_use]
|
|
|
|
pub fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
|
|
|
|
let square = castle.parameters(player).rook_origin_square();
|
|
|
|
self.piece_on_square(square)
|
|
|
|
}
|
|
|
|
|
2025-05-02 15:41:45 -07:00
|
|
|
/// Returns `true` if the player is able to castle on the given side of the board.
|
|
|
|
///
|
|
|
|
/// The following requirements must be met:
|
|
|
|
///
|
|
|
|
/// 1. The player must still have the right to castle on that side of the
|
|
|
|
/// board. The king and rook involved in the castle must not have moved.
|
|
|
|
/// 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
|
2025-05-03 16:02:56 -07:00
|
|
|
#[must_use]
|
2025-05-02 15:41:45 -07:00
|
|
|
pub fn player_can_castle(&self, player: Color, castle: Castle) -> bool {
|
2025-05-08 17:37:51 -07:00
|
|
|
if !self.castling_rights.is_set(player, castle) {
|
2025-05-02 15:41:45 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let castling_parameters = castle.parameters(player);
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
let all_pieces = self.pieces.all_pieces();
|
2025-05-02 15:41:45 -07:00
|
|
|
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
// TODO: Reimplement king_danger here or in Position.
|
|
|
|
// let danger_squares = self.king_danger(player);
|
|
|
|
// if !(danger_squares & castling_parameters.check_squares()).is_empty() {
|
|
|
|
// return false;
|
|
|
|
// }
|
2025-05-02 15:41:45 -07:00
|
|
|
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2024-07-13 11:51:52 -07:00
|
|
|
pub fn iter_all_pieces(&self) -> impl Iterator<Item = PlacedPiece> + '_ {
|
|
|
|
self.pieces.iter()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn iter_pieces_of_color(&self, color: Color) -> impl Iterator<Item = PlacedPiece> + '_ {
|
|
|
|
self.pieces
|
|
|
|
.iter()
|
|
|
|
.filter(move |piece| piece.color() == color)
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
}
|
2025-05-08 17:37:51 -07:00
|
|
|
*/
|
2024-04-25 13:28:24 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use crate::test_board;
|
|
|
|
use chessfriend_core::piece;
|
|
|
|
|
|
|
|
#[test]
|
2025-05-08 17:37:51 -07:00
|
|
|
fn get_piece_on_square() {
|
|
|
|
let board = test_board![
|
2024-04-25 13:28:24 -07:00
|
|
|
Black Bishop on F7,
|
|
|
|
];
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
assert_eq!(board.get_piece(Square::F7), Some(piece!(Black Bishop)));
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
}
|