2024-04-03 21:15:17 +02:00
|
|
|
use crate::board::{Bitboard, CastleRights, ChessBoard, Color, Piece, Square, ValidationError};
|
2024-04-01 22:19:55 +02:00
|
|
|
|
|
|
|
/// Build a [ChessBoard] one piece at a time.
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub struct ChessBoardBuilder {
|
|
|
|
/// The list of [Piece] on the board. Indexed by [Square::index].
|
|
|
|
pieces: [Option<(Piece, Color)>; 64],
|
|
|
|
// Same fields as [ChessBoard].
|
|
|
|
castle_rights: [CastleRights; Color::NUM_VARIANTS],
|
|
|
|
en_passant: Option<Square>,
|
2024-04-02 00:14:11 +02:00
|
|
|
half_move_clock: u32,
|
2024-04-01 22:19:55 +02:00
|
|
|
side: Color,
|
2024-04-01 23:46:44 +02:00
|
|
|
// 1-based, a turn is *two* half-moves (i.e: both players have played).
|
|
|
|
turn_count: u32,
|
2024-04-01 22:19:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ChessBoardBuilder {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
pieces: [None; 64],
|
|
|
|
castle_rights: [CastleRights::NoSide; 2],
|
|
|
|
en_passant: Default::default(),
|
|
|
|
half_move_clock: Default::default(),
|
|
|
|
side: Color::White,
|
2024-04-01 23:46:44 +02:00
|
|
|
turn_count: 1,
|
2024-04-01 22:19:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_castle_rights(&mut self, rights: CastleRights, color: Color) -> &mut Self {
|
|
|
|
self.castle_rights[color.index()] = rights;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_en_passant(&mut self, square: Square) -> &mut Self {
|
|
|
|
self.en_passant = Some(square);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn without_en_passant(&mut self) -> &mut Self {
|
|
|
|
self.en_passant = None;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-04-02 00:14:11 +02:00
|
|
|
pub fn with_half_move_clock(&mut self, clock: u32) -> &mut Self {
|
2024-04-01 22:19:55 +02:00
|
|
|
self.half_move_clock = clock;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2024-04-01 23:46:44 +02:00
|
|
|
pub fn with_turn_count(&mut self, count: u32) -> &mut Self {
|
|
|
|
self.turn_count = count;
|
2024-04-01 22:19:55 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_current_player(&mut self, color: Color) -> &mut Self {
|
|
|
|
self.side = color;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ChessBoardBuilder {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Index a [ChessBoardBuilder] with a [Square] to access its pieces.
|
|
|
|
impl std::ops::Index<Square> for ChessBoardBuilder {
|
|
|
|
type Output = Option<(Piece, Color)>;
|
|
|
|
|
|
|
|
fn index(&self, square: Square) -> &Self::Output {
|
|
|
|
&self.pieces[square.index()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Index a [ChessBoardBuilder] with a [Square] to access its pieces.
|
|
|
|
impl std::ops::IndexMut<Square> for ChessBoardBuilder {
|
|
|
|
fn index_mut(&mut self, square: Square) -> &mut Self::Output {
|
|
|
|
&mut self.pieces[square.index()]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<ChessBoardBuilder> for ChessBoard {
|
2024-04-03 21:15:17 +02:00
|
|
|
type Error = ValidationError;
|
2024-04-01 22:19:55 +02:00
|
|
|
|
|
|
|
fn try_from(builder: ChessBoardBuilder) -> Result<Self, Self::Error> {
|
|
|
|
let mut piece_occupancy: [Bitboard; Piece::NUM_VARIANTS] = Default::default();
|
|
|
|
let mut color_occupancy: [Bitboard; Color::NUM_VARIANTS] = Default::default();
|
|
|
|
let mut combined_occupancy: Bitboard = Default::default();
|
|
|
|
let ChessBoardBuilder {
|
|
|
|
pieces,
|
|
|
|
castle_rights,
|
|
|
|
en_passant,
|
|
|
|
half_move_clock,
|
|
|
|
side,
|
2024-04-01 23:46:44 +02:00
|
|
|
turn_count,
|
2024-04-01 22:19:55 +02:00
|
|
|
} = builder;
|
|
|
|
|
|
|
|
for square in Square::iter() {
|
|
|
|
let Some((piece, color)) = pieces[square.index()] else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
piece_occupancy[piece.index()] |= square;
|
|
|
|
color_occupancy[color.index()] |= square;
|
|
|
|
combined_occupancy |= square;
|
|
|
|
}
|
|
|
|
|
2024-04-01 23:46:44 +02:00
|
|
|
let total_plies = (turn_count - 1) * 2 + if side == Color::White { 0 } else { 1 };
|
|
|
|
|
2024-04-01 22:19:55 +02:00
|
|
|
let board = ChessBoard {
|
|
|
|
piece_occupancy,
|
|
|
|
color_occupancy,
|
|
|
|
combined_occupancy,
|
|
|
|
castle_rights,
|
|
|
|
en_passant,
|
|
|
|
half_move_clock,
|
|
|
|
total_plies,
|
|
|
|
side,
|
|
|
|
};
|
|
|
|
|
|
|
|
board.validate()?;
|
|
|
|
Ok(board)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
fn from_board(board: &ChessBoard) -> ChessBoardBuilder {
|
|
|
|
let mut builder = ChessBoardBuilder::new();
|
|
|
|
|
|
|
|
for piece in Piece::iter() {
|
|
|
|
for color in Color::iter() {
|
|
|
|
for square in board.occupancy(piece, color) {
|
|
|
|
builder[square] = Some((piece, color));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for color in Color::iter() {
|
|
|
|
builder.with_castle_rights(board.castle_rights(color), color);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(square) = board.en_passant() {
|
|
|
|
builder.with_en_passant(square);
|
|
|
|
} else {
|
|
|
|
builder.without_en_passant();
|
|
|
|
}
|
|
|
|
|
|
|
|
builder
|
|
|
|
.with_half_move_clock(board.half_move_clock())
|
2024-04-01 23:46:44 +02:00
|
|
|
.with_turn_count(board.total_plies() / 2 + 1)
|
2024-04-01 22:19:55 +02:00
|
|
|
.with_current_player(board.current_player());
|
|
|
|
|
|
|
|
builder
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn default_board() {
|
|
|
|
let board = ChessBoard::default();
|
|
|
|
let builder = from_board(&board);
|
|
|
|
assert_eq!(board, builder.try_into().unwrap())
|
|
|
|
}
|
|
|
|
}
|