[board] Implement position::builders::MoveBuilder

This builder takes a Position and a Move, validates the move, and makes the move
in that position. Its build() method returns a new Position with the move made.
This commit is contained in:
Eryn Wells 2024-01-21 09:23:39 -08:00
parent 7b8ce3b31d
commit 21b5266789
7 changed files with 323 additions and 4 deletions

View file

@ -61,7 +61,7 @@ impl BitBoard {
*self |= sq_bb
}
fn clear_square(&mut self, sq: Square) {
pub fn clear_square(&mut self, sq: Square) {
let sq_bb: BitBoard = sq.into();
*self &= !sq_bb
}

View file

@ -10,6 +10,14 @@ use std::fmt;
pub(crate) use move_formatter::AlgebraicMoveFormatter;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MakeMoveError {
PlayerOutOfTurn,
NoPiece,
NoCapturedPiece,
IllegalCastle,
}
#[repr(u16)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Castle {

View file

@ -1,5 +1,7 @@
// Eryn Wells <eryn@erynwells.me>
mod move_builder;
mod position_builder;
pub use move_builder::Builder as MoveBuilder;
pub use position_builder::Builder as PositionBuilder;

View file

@ -0,0 +1,273 @@
// Eryn Wells <eryn@erynwells.me>
use crate::{
piece::{PlacedPiece, Shape},
position::flags::Flags,
r#move::Castle,
square::Direction,
BitBoard, Color, MakeMoveError, Move, Piece, Position, Square,
};
/// A position builder that builds a new position by making a move.
#[derive(Clone)]
pub struct Builder<'p, M: MoveToMake> {
position: &'p Position,
move_to_make: M,
}
pub trait MoveToMake {}
pub struct NoMove;
pub enum ValidatedMove {
RegularMove {
from_square: Square,
to_square: Square,
moving_piece: PlacedPiece,
captured_piece: Option<PlacedPiece>,
promotion: Option<Shape>,
flags: Flags,
en_passant_square: Option<Square>,
},
Castle {
castle: Castle,
king: PlacedPiece,
rook: PlacedPiece,
flags: Flags,
},
}
impl MoveToMake for NoMove {}
impl MoveToMake for ValidatedMove {}
impl<'p, M> Builder<'p, M>
where
M: MoveToMake,
{
pub fn new(position: &'p Position) -> Builder<'p, NoMove> {
Builder {
position,
move_to_make: NoMove,
}
}
pub fn make(self, mv: &Move) -> Result<Builder<'p, ValidatedMove>, MakeMoveError> {
let from_square = mv.from_square();
let piece = self
.position
.piece_on_square(from_square)
.ok_or(MakeMoveError::NoPiece)?;
println!("{}", &piece);
let to_square = mv.to_square();
let player = self.position.player_to_move();
let captured_piece: Option<PlacedPiece> = if mv.is_capture() {
Some(
self.position
.piece_on_square(to_square)
.ok_or(MakeMoveError::NoCapturedPiece)?,
)
} else {
None
};
// TODO: Check whether the move is legal.
let piece_is_king = piece.is_king();
let mut flags = self.position.flags().clone();
if piece_is_king {
flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide);
flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide);
} else if piece.is_kingside_rook() {
flags.clear_player_has_right_to_castle_flag(player, Castle::KingSide);
} else if piece.is_queenside_rook() {
flags.clear_player_has_right_to_castle_flag(player, Castle::QueenSide);
}
if let Some(castle) = mv.castle() {
println!("piece is king: {}", piece_is_king);
if !piece_is_king || !self.position.player_can_castle(player, castle) {
return Err(MakeMoveError::IllegalCastle);
}
let rook = self
.position
.rook_for_castle(player, castle)
.ok_or(MakeMoveError::NoPiece)?;
Ok(Builder {
position: self.position,
move_to_make: ValidatedMove::Castle {
castle,
king: piece,
rook,
flags,
},
})
} else {
let en_passant_square: Option<Square> = if mv.is_double_push() {
match piece.color() {
Color::White => to_square.neighbor(Direction::South),
Color::Black => to_square.neighbor(Direction::North),
}
} else {
None
};
Ok(Builder {
position: self.position,
move_to_make: ValidatedMove::RegularMove {
from_square,
to_square,
moving_piece: piece,
captured_piece,
promotion: mv.promotion(),
flags,
en_passant_square,
},
})
}
}
}
impl<'p> Builder<'p, ValidatedMove> {
pub fn build(&self) -> Position {
let player = self.position.player_to_move();
match self.move_to_make {
ValidatedMove::RegularMove {
from_square,
to_square,
moving_piece,
captured_piece,
promotion,
flags,
en_passant_square,
} => {
let mut pieces = self.position.piece_bitboards().clone();
if let Some(captured_piece) = captured_piece {
pieces.remove_piece(&captured_piece);
}
if let Some(promotion) = promotion {
pieces.remove_piece(&moving_piece);
let _ = pieces
.place_piece(PlacedPiece::new(Piece::new(player, promotion), to_square));
} else {
pieces.move_piece(moving_piece.piece(), from_square, to_square);
}
Position::new(
self.position.player_to_move().other(),
flags,
pieces,
en_passant_square,
)
}
ValidatedMove::Castle {
castle,
king,
rook,
flags,
} => {
let mut pieces = self.position.piece_bitboards().clone();
let king_from: BitBoard = king.square().into();
let king_to: BitBoard = Square::king_castle_target(player, castle).into();
*pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to;
let rook_from: BitBoard = rook.square().into();
let rook_to: BitBoard = Square::rook_castle_target(player, castle).into();
*pieces.bitboard_for_piece_mut(rook.piece()) ^= rook_from | rook_to;
*pieces.bitboard_for_color_mut(player) &=
!(king_from | rook_from) | (king_to | rook_to);
Position::new(player.other(), flags, pieces, None)
}
}
}
}
impl<'p> From<&'p Position> for Builder<'p, NoMove> {
fn from(position: &'p Position) -> Self {
Self {
position,
move_to_make: NoMove,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{r#move::Castle, MoveBuilder};
#[test]
fn move_white_pawn_one_square() -> Result<(), MakeMoveError> {
let pos = position![White Pawn on E2];
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E3).build();
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
println!("{}", &new_position);
assert_eq!(
new_position.piece_on_square(Square::E3),
Some(piece!(White Pawn on E3))
);
Ok(())
}
#[test]
fn move_white_pawn_two_squares() -> Result<(), MakeMoveError> {
let pos = position![White Pawn on E2];
let mv = MoveBuilder::new(piece!(White Pawn), Square::E2, Square::E4).build();
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
println!("{}", &new_position);
assert_eq!(
new_position.piece_on_square(Square::E4),
Some(piece!(White Pawn on E4))
);
assert_eq!(new_position.en_passant_square(), Some(Square::E3));
Ok(())
}
#[test]
fn white_kingside_castle() -> Result<(), MakeMoveError> {
let pos = position![
White King on E1,
White Rook on H1,
White Pawn on E2,
White Pawn on F2,
White Pawn on G2,
White Pawn on H2
];
println!("{}", &pos);
let mv = MoveBuilder::new(piece!(White King), Square::E1, Square::G1)
.castle(Castle::KingSide)
.build();
let new_position = Builder::<NoMove>::new(&pos).make(&mv)?.build();
println!("{}", &new_position);
assert_eq!(
new_position.piece_on_square(Square::G1),
Some(piece!(White King on G1))
);
assert_eq!(
new_position.piece_on_square(Square::F1),
Some(piece!(White Rook on F1))
);
Ok(())
}
}

View file

@ -7,7 +7,7 @@ mod piece_sets;
mod pieces;
mod position;
pub use builders::Builder as PositionBuilder;
pub use builders::{MoveBuilder, PositionBuilder};
pub use diagram_formatter::DiagramFormatter;
pub use pieces::Pieces;
pub use position::Position;

View file

@ -95,12 +95,28 @@ impl PieceBitBoards {
}
}
self.by_color_and_shape.set_square(square, piece.piece());
self.by_color.set_square(square, color);
self.bitboard_for_piece_mut(piece.piece())
.set_square(square);
Ok(())
}
pub(super) fn remove_piece(&mut self, piece: &PlacedPiece) {
let color = piece.color();
let square = piece.square();
self.by_color_and_shape.clear_square(square, piece.piece());
self.by_color.clear_square(square, color);
}
pub(super) fn move_piece(&mut self, piece: &Piece, from_square: Square, to_square: Square) {
let color = piece.color();
self.by_color_and_shape.clear_square(from_square, piece);
self.by_color.clear_square(from_square, color);
self.by_color_and_shape.set_square(to_square, piece);
self.by_color.set_square(to_square, color);
}
}
impl FromIterator<PlacedPiece> for PieceBitBoards {
@ -132,6 +148,11 @@ impl ByColor {
self.0.set_square(square);
self.1[color as usize].set_square(square)
}
fn clear_square(&mut self, square: Square, color: Color) {
self.0.clear_square(square);
self.1[color as usize].clear_square(square);
}
}
impl ByColorAndShape {
@ -146,4 +167,8 @@ impl ByColorAndShape {
fn set_square(&mut self, square: Square, piece: &Piece) {
self.bitboard_for_piece_mut(piece).set_square(square);
}
fn clear_square(&mut self, square: Square, piece: &Piece) {
self.bitboard_for_piece_mut(piece).clear_square(square);
}
}

View file

@ -109,6 +109,17 @@ impl Position {
true
}
pub(crate) fn rook_for_castle(&self, player: Color, castle: Castle) -> Option<PlacedPiece> {
let square = match (player, castle) {
(Color::White, Castle::KingSide) => Square::H1,
(Color::White, Castle::QueenSide) => Square::A1,
(Color::Black, Castle::KingSide) => Square::H8,
(Color::Black, Castle::QueenSide) => Square::A8,
};
self.piece_on_square(square)
}
pub fn moves(&self) -> Moves {
Moves::new(self, self.color_to_move)
}