[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
|
||||
}
|
||||
|
||||
fn clear_square(&mut self, sq: Square) {
|
||||
pub fn clear_square(&mut self, sq: Square) {
|
||||
let sq_bb: BitBoard = sq.into();
|
||||
*self &= !sq_bb
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
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 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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue