[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:
parent
7b8ce3b31d
commit
21b5266789
7 changed files with 323 additions and 4 deletions
|
@ -61,7 +61,7 @@ impl BitBoard {
|
||||||
*self |= sq_bb
|
*self |= sq_bb
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_square(&mut self, sq: Square) {
|
pub fn clear_square(&mut self, sq: Square) {
|
||||||
let sq_bb: BitBoard = sq.into();
|
let sq_bb: BitBoard = sq.into();
|
||||||
*self &= !sq_bb
|
*self &= !sq_bb
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,14 @@ use std::fmt;
|
||||||
|
|
||||||
pub(crate) use move_formatter::AlgebraicMoveFormatter;
|
pub(crate) use move_formatter::AlgebraicMoveFormatter;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MakeMoveError {
|
||||||
|
PlayerOutOfTurn,
|
||||||
|
NoPiece,
|
||||||
|
NoCapturedPiece,
|
||||||
|
IllegalCastle,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Castle {
|
pub enum Castle {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
mod move_builder;
|
||||||
mod position_builder;
|
mod position_builder;
|
||||||
|
|
||||||
|
pub use move_builder::Builder as MoveBuilder;
|
||||||
pub use position_builder::Builder as PositionBuilder;
|
pub use position_builder::Builder as PositionBuilder;
|
||||||
|
|
273
board/src/position/builders/move_builder.rs
Normal file
273
board/src/position/builders/move_builder.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ mod piece_sets;
|
||||||
mod pieces;
|
mod pieces;
|
||||||
mod position;
|
mod position;
|
||||||
|
|
||||||
pub use builders::Builder as PositionBuilder;
|
pub use builders::{MoveBuilder, PositionBuilder};
|
||||||
pub use diagram_formatter::DiagramFormatter;
|
pub use diagram_formatter::DiagramFormatter;
|
||||||
pub use pieces::Pieces;
|
pub use pieces::Pieces;
|
||||||
pub use position::Position;
|
pub use position::Position;
|
||||||
|
|
|
@ -95,12 +95,28 @@ impl PieceBitBoards {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.by_color_and_shape.set_square(square, piece.piece());
|
||||||
self.by_color.set_square(square, color);
|
self.by_color.set_square(square, color);
|
||||||
self.bitboard_for_piece_mut(piece.piece())
|
|
||||||
.set_square(square);
|
|
||||||
|
|
||||||
Ok(())
|
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 {
|
impl FromIterator<PlacedPiece> for PieceBitBoards {
|
||||||
|
@ -132,6 +148,11 @@ impl ByColor {
|
||||||
self.0.set_square(square);
|
self.0.set_square(square);
|
||||||
self.1[color as usize].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 {
|
impl ByColorAndShape {
|
||||||
|
@ -146,4 +167,8 @@ impl ByColorAndShape {
|
||||||
fn set_square(&mut self, square: Square, piece: &Piece) {
|
fn set_square(&mut self, square: Square, piece: &Piece) {
|
||||||
self.bitboard_for_piece_mut(piece).set_square(square);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,17 @@ impl Position {
|
||||||
true
|
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 {
|
pub fn moves(&self) -> Moves {
|
||||||
Moves::new(self, self.color_to_move)
|
Moves::new(self, self.color_to_move)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue