2024-04-25 13:28:24 -07:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
2024-07-13 08:15:14 -07:00
|
|
|
use crate::{
|
2025-05-02 15:41:45 -07:00
|
|
|
castle, display::DiagramFormatter, piece_sets::PlacePieceError, EnPassant, MoveCounter,
|
2025-05-02 15:18:37 -07:00
|
|
|
PieceSet,
|
2024-07-13 08:15:14 -07:00
|
|
|
};
|
2024-04-25 13:28:24 -07:00
|
|
|
use chessfriend_bitboard::BitBoard;
|
|
|
|
use chessfriend_core::{Color, Piece, PlacedPiece, Shape, Square};
|
2024-07-13 11:51:52 -07:00
|
|
|
use std::iter::Iterator;
|
2024-04-25 13:28:24 -07:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq)]
|
|
|
|
pub struct Board {
|
2024-07-13 08:15:14 -07:00
|
|
|
pieces: PieceSet,
|
2024-04-25 13:28:24 -07:00
|
|
|
en_passant: Option<EnPassant>,
|
2025-05-02 15:18:37 -07:00
|
|
|
pub move_counter: MoveCounter,
|
2025-05-02 15:41:45 -07:00
|
|
|
pub castling_rights: castle::Rights,
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
pub fn player_to_move(&self) -> Color {
|
2025-05-02 15:18:37 -07:00
|
|
|
self.move_counter.active_color
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
2024-07-13 12:00:02 -07:00
|
|
|
/// Returns `true` if the player has the right to castle on the given side
|
|
|
|
/// of the board.
|
2024-04-25 13:28:24 -07:00
|
|
|
///
|
2024-07-13 12:00:02 -07:00
|
|
|
/// A player retains the right to castle on a particular side of the board
|
|
|
|
/// as long as they have not moved their king, or the rook on that side of
|
|
|
|
/// the board.
|
2024-04-25 13:28:24 -07:00
|
|
|
#[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<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
|
|
|
|
pub fn player_can_castle(&self, player: Color, castle: Castle) -> bool {
|
|
|
|
if !self
|
|
|
|
.castling_rights
|
|
|
|
.player_has_right_to_castle(player, castle.into())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let castling_parameters = castle.parameters(player);
|
|
|
|
|
|
|
|
let all_pieces = self.all_pieces_bitboard();
|
|
|
|
if !(all_pieces & castling_parameters.clear_squares()).is_empty() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let danger_squares = self.king_danger(player);
|
|
|
|
if !(danger_squares & castling_parameters.check_squares()).is_empty() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2024-07-13 12:00:02 -07:00
|
|
|
/// A [`BitBoard`] representing the set of squares containing a piece. This
|
|
|
|
/// set is the inverse of [`Board::empty_squares`].
|
2024-04-25 13:28:24 -07:00
|
|
|
#[must_use]
|
2024-07-13 12:03:19 -07:00
|
|
|
pub fn occupied_squares(&self) -> BitBoard {
|
2024-04-25 13:28:24 -07:00
|
|
|
self.pieces.all_pieces()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
2024-07-13 12:03:19 -07:00
|
|
|
pub fn friendly_pieces_bitboard(&self) -> BitBoard {
|
2024-04-25 13:28:24 -07:00
|
|
|
self.pieces.all_pieces_of_color(self.player_to_move)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
2024-07-13 12:03:19 -07:00
|
|
|
pub fn opposing_pieces_bitboard(&self) -> BitBoard {
|
2024-04-25 13:28:24 -07:00
|
|
|
self.pieces.all_pieces_of_color(self.player_to_move.other())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
2024-07-13 12:03:19 -07:00
|
|
|
pub fn all_pieces(&self) -> (BitBoard, BitBoard) {
|
|
|
|
(
|
|
|
|
self.friendly_pieces_bitboard(),
|
|
|
|
self.opposing_pieces_bitboard(),
|
|
|
|
)
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
2024-07-13 12:00:02 -07:00
|
|
|
/// A [BitBoard] representing the set of squares containing a piece. This
|
|
|
|
/// set is the inverse of [`Board::occupied_squares`].
|
2024-04-25 13:28:24 -07:00
|
|
|
#[must_use]
|
|
|
|
pub fn empty_squares(&self) -> BitBoard {
|
|
|
|
!self.occupied_squares()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
2024-07-13 11:57:57 -07:00
|
|
|
pub fn piece_on_square(&self, square: Square) -> Option<PlacedPiece> {
|
|
|
|
self.pieces
|
|
|
|
.get(square)
|
|
|
|
.map(|piece| PlacedPiece::new(piece, square))
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
2024-07-13 11:51:52 -07:00
|
|
|
pub fn iter_all_pieces(&self) -> impl Iterator<Item = PlacedPiece> + '_ {
|
|
|
|
self.pieces.iter()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
pub fn has_en_passant_square(&self) -> bool {
|
|
|
|
self.en_passant.is_some()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
pub fn en_passant(&self) -> Option<EnPassant> {
|
|
|
|
self.en_passant
|
|
|
|
}
|
|
|
|
|
2024-07-13 12:03:19 -07:00
|
|
|
fn king_bitboard(&self, player: Color) -> BitBoard {
|
|
|
|
self.pieces.bitboard_for_piece(Piece::king(player))
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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]
|
2024-07-13 12:03:19 -07:00
|
|
|
pub fn bitboard_for_color(&self, color: Color) -> BitBoard {
|
2024-04-25 13:28:24 -07:00
|
|
|
self.pieces.bitboard_for_color(color)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[must_use]
|
2024-07-13 12:03:19 -07:00
|
|
|
pub fn bitboard_for_piece(&self, piece: Piece) -> BitBoard {
|
|
|
|
self.pieces.bitboard_for_piece(piece)
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Board {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2025-05-02 15:41:45 -07:00
|
|
|
castling_rights: castle::Rights::default(),
|
2024-07-13 08:15:14 -07:00
|
|
|
pieces: PieceSet::default(),
|
2024-04-25 13:28:24 -07:00
|
|
|
en_passant: None,
|
2025-05-02 15:18:37 -07:00
|
|
|
move_counter: MoveCounter::default(),
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Board {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.pieces == other.pieces
|
2025-05-02 15:41:45 -07:00
|
|
|
&& self.castling_rights == other.castling_rights
|
2024-04-25 13:28:24 -07:00
|
|
|
&& self.en_passant == other.en_passant
|
2025-05-02 15:18:37 -07:00
|
|
|
&& self.move_counter == other.move_counter
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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);
|
2025-05-02 15:41:45 -07:00
|
|
|
assert!(!board
|
|
|
|
.castling_rights
|
|
|
|
.player_has_right_to_castle(Color::White, Castle::KingSide));
|
|
|
|
assert!(!board
|
|
|
|
.castling_rights
|
|
|
|
.player_has_right_to_castle(Color::White, Castle::QueenSide));
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn king_on_starting_square_can_castle() {
|
|
|
|
let board = test_board!(
|
|
|
|
White King on E1,
|
|
|
|
White Rook on A1,
|
|
|
|
White Rook on H1
|
|
|
|
);
|
|
|
|
|
2025-05-02 15:41:45 -07:00
|
|
|
assert!(board
|
|
|
|
.castling_rights
|
|
|
|
.player_has_right_to_castle(Color::White, Castle::KingSide));
|
|
|
|
assert!(board
|
|
|
|
.castling_rights
|
|
|
|
.player_has_right_to_castle(Color::White, Castle::QueenSide));
|
2024-04-25 13:28:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|