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-23 09:53:29 -07:00
|
|
|
pub type HalfMoveClock = u32;
|
|
|
|
pub type FullMoveClock = u32;
|
|
|
|
|
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-23 09:53:29 -07:00
|
|
|
pub half_move_clock: HalfMoveClock,
|
|
|
|
pub full_move_number: FullMoveClock,
|
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)
|
|
|
|
}
|
|
|
|
|
2025-05-23 18:39:18 -07:00
|
|
|
pub fn find_pieces(&self, piece: Piece) -> BitBoard {
|
|
|
|
self.pieces.find_pieces(piece)
|
|
|
|
}
|
|
|
|
|
2025-05-08 17:37:51 -07:00
|
|
|
/// 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-23 18:39:18 -07:00
|
|
|
|
|
|
|
pub fn enemies(&self, color: Color) -> BitBoard {
|
|
|
|
self.pieces.opposing_occupancy(color)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a [`BitBoard`] of all pawns of a given color.
|
|
|
|
pub fn pawns(&self, color: Color) -> BitBoard {
|
|
|
|
self.pieces.find_pieces(Piece::pawn(color))
|
|
|
|
}
|
2025-05-25 11:05:10 -07:00
|
|
|
|
|
|
|
pub fn knights(&self, color: Color) -> BitBoard {
|
|
|
|
self.find_pieces(Piece::knight(color))
|
|
|
|
}
|
[bitboard, board, core, moves] Implement SliderMoveGenerator
This generator produces moves for slider pieces: bishops, rooks, and queens. All
of these pieces behave identically, though with different sets of rays that
emanate from the origin square. Claude helped me significantly with the
implementation and unit testing. All the unit tests that took advantage of Claude
for implementation are marked as such with an _ai_claude suffix to the test name.
One unique aspect of this move generator that Claude suggested to me was to use
loop { } instead of a recursive call to next() when the internal iterators expire.
I may try to port this to the other move generators in the future.
To support this move generator, implement a Slider enum in core that represents
one of the three slider pieces.
Add Board::bishops(), Board::rooks() and Board::queens() to return BitBoards of
those pieces. These are analogous to the pawns() and knights() methods that return
their corresponding pieces.
Also in the board create, replace the separate sight method implementations with
a macro. These are all the same, but with a different sight method called under
the hood.
Finally, derive Clone and Debug for the bit_scanner types.
2025-05-26 17:41:43 -07:00
|
|
|
|
|
|
|
pub fn bishops(&self, color: Color) -> BitBoard {
|
|
|
|
self.find_pieces(Piece::bishop(color))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rooks(&self, color: Color) -> BitBoard {
|
|
|
|
self.find_pieces(Piece::rook(color))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn queens(&self, color: Color) -> BitBoard {
|
|
|
|
self.find_pieces(Piece::queen(color))
|
|
|
|
}
|
2025-05-16 07:47:28 -07:00
|
|
|
}
|
|
|
|
|
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-23 18:37:13 -07:00
|
|
|
impl Board {
|
|
|
|
#[must_use]
|
|
|
|
pub fn unwrap_color(&self, color: Option<Color>) -> Color {
|
|
|
|
color.unwrap_or(self.active_color)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|