chessfriend/board/src/board.rs

185 lines
5.2 KiB
Rust
Raw Normal View History

// Eryn Wells <eryn@erynwells.me>
2025-05-08 17:37:51 -07:00
use crate::{
castle,
display::DiagramFormatter,
piece_sets::{PlacePieceError, PlacePieceStrategy},
PieceSet,
};
use chessfriend_bitboard::BitBoard;
use chessfriend_core::{Color, Piece, Shape, Square, Wing};
2025-05-08 17:37:51 -07:00
#[derive(Clone, Debug, Default, Eq, PartialEq)]
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,
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,
}
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 {
pieces: PieceSet::new([WHITE_PIECES, BLACK_PIECES]),
..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)
}
2025-05-08 17:37:51 -07:00
pub fn remove_piece(&mut self, square: Square) -> Option<Piece> {
self.pieces.remove(square)
}
}
impl Board {
/// 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)
}
}
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]
}
}
impl Board {
2025-05-08 17:37:51 -07:00
pub fn display(&self) -> DiagramFormatter<'_> {
DiagramFormatter::new(self)
}
2025-05-08 17:37:51 -07:00
}
2025-05-08 17:37:51 -07:00
/*
impl Board {
/// 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)
}
/// 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]
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) {
return false;
}
let castling_parameters = castle.parameters(player);
2025-05-08 17:37:51 -07:00
let all_pieces = self.pieces.all_pieces();
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;
// }
true
}
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)
}
}
2025-05-08 17:37:51 -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![
Black Bishop on F7,
];
2025-05-08 17:37:51 -07:00
assert_eq!(board.get_piece(Square::F7), Some(piece!(Black Bishop)));
}
}