2024-01-21 09:23:39 -08:00
|
|
|
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
|
|
|
|
use crate::{
|
2024-01-24 17:08:27 -08:00
|
|
|
position::flags::Flags, r#move::Castle, sight::SightExt, MakeMoveError, Move, Position,
|
2024-01-21 09:23:39 -08:00
|
|
|
};
|
2024-01-24 09:18:12 -08:00
|
|
|
use chessfriend_bitboard::BitBoard;
|
2024-01-24 17:08:27 -08:00
|
|
|
use chessfriend_core::{Color, Direction, Piece, PlacedPiece, Shape, Square};
|
2024-01-21 09:23:39 -08:00
|
|
|
|
|
|
|
/// 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>,
|
2024-01-21 15:10:04 -08:00
|
|
|
increment_ply: bool,
|
2024-01-21 09:23:39 -08:00
|
|
|
},
|
|
|
|
Castle {
|
|
|
|
castle: Castle,
|
|
|
|
king: PlacedPiece,
|
|
|
|
rook: PlacedPiece,
|
|
|
|
flags: Flags,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MoveToMake for NoMove {}
|
|
|
|
impl MoveToMake for ValidatedMove {}
|
|
|
|
|
2024-01-22 08:18:19 -08:00
|
|
|
impl<'p> Builder<'p, NoMove> {
|
2024-01-21 09:23:39 -08:00
|
|
|
pub fn new(position: &'p Position) -> Builder<'p, NoMove> {
|
|
|
|
Builder {
|
|
|
|
position,
|
|
|
|
move_to_make: NoMove,
|
|
|
|
}
|
|
|
|
}
|
2024-01-22 08:18:19 -08:00
|
|
|
}
|
2024-01-21 09:23:39 -08:00
|
|
|
|
2024-01-22 08:18:19 -08:00
|
|
|
impl<'p, M> Builder<'p, M>
|
|
|
|
where
|
|
|
|
M: MoveToMake,
|
|
|
|
{
|
2024-01-21 09:23:39 -08:00
|
|
|
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)?;
|
|
|
|
|
|
|
|
let to_square = mv.to_square();
|
2024-01-24 17:10:10 -08:00
|
|
|
|
2024-01-28 00:25:53 -08:00
|
|
|
let sight = self.position.sight_of_piece(&piece);
|
2024-01-24 17:10:10 -08:00
|
|
|
if !sight.is_set(to_square) {
|
|
|
|
return Err(MakeMoveError::IllegalSquare(to_square));
|
|
|
|
}
|
|
|
|
|
2024-01-21 09:23:39 -08:00
|
|
|
let player = self.position.player_to_move();
|
|
|
|
|
2024-01-21 11:21:37 -08:00
|
|
|
let captured_piece: Option<PlacedPiece> = if mv.is_en_passant() {
|
|
|
|
// En passant captures the pawn directly ahead (in the player's direction) of the en passant square.
|
|
|
|
let capture_square = match player {
|
|
|
|
Color::White => to_square.neighbor(Direction::South),
|
|
|
|
Color::Black => to_square.neighbor(Direction::North),
|
|
|
|
}
|
|
|
|
.ok_or(MakeMoveError::NoCapturedPiece)?;
|
|
|
|
|
|
|
|
Some(
|
|
|
|
self.position
|
|
|
|
.piece_on_square(capture_square)
|
|
|
|
.ok_or(MakeMoveError::NoCapturedPiece)?,
|
|
|
|
)
|
|
|
|
} else if mv.is_capture() {
|
2024-01-21 09:23:39 -08:00
|
|
|
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,
|
2024-01-21 15:10:04 -08:00
|
|
|
increment_ply: !(mv.is_capture() || piece.is_pawn()),
|
2024-01-21 09:23:39 -08:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'p> Builder<'p, ValidatedMove> {
|
|
|
|
pub fn build(&self) -> Position {
|
|
|
|
let player = self.position.player_to_move();
|
|
|
|
|
2024-01-21 15:10:04 -08:00
|
|
|
let updated_move_number =
|
|
|
|
self.position.move_number() + if player == Color::Black { 1 } else { 0 };
|
|
|
|
|
2024-01-21 09:23:39 -08:00
|
|
|
match self.move_to_make {
|
|
|
|
ValidatedMove::RegularMove {
|
|
|
|
from_square,
|
|
|
|
to_square,
|
|
|
|
moving_piece,
|
|
|
|
captured_piece,
|
|
|
|
promotion,
|
|
|
|
flags,
|
|
|
|
en_passant_square,
|
2024-01-21 15:10:04 -08:00
|
|
|
increment_ply,
|
2024-01-21 09:23:39 -08:00
|
|
|
} => {
|
|
|
|
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
|
2024-01-21 10:39:57 -08:00
|
|
|
.place_piece(&PlacedPiece::new(Piece::new(player, promotion), to_square));
|
2024-01-21 09:23:39 -08:00
|
|
|
} else {
|
|
|
|
pieces.move_piece(moving_piece.piece(), from_square, to_square);
|
|
|
|
}
|
|
|
|
|
2024-01-21 15:10:04 -08:00
|
|
|
let ply = if increment_ply {
|
|
|
|
self.position.ply_counter() + 1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
2024-01-21 09:23:39 -08:00
|
|
|
Position::new(
|
|
|
|
self.position.player_to_move().other(),
|
|
|
|
flags,
|
|
|
|
pieces,
|
|
|
|
en_passant_square,
|
2024-01-21 15:10:04 -08:00
|
|
|
ply,
|
|
|
|
updated_move_number,
|
2024-01-21 09:23:39 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
ValidatedMove::Castle {
|
|
|
|
castle,
|
|
|
|
king,
|
|
|
|
rook,
|
|
|
|
flags,
|
|
|
|
} => {
|
|
|
|
let mut pieces = self.position.piece_bitboards().clone();
|
|
|
|
|
2024-01-21 10:38:50 -08:00
|
|
|
let target_squares = castle.target_squares(player);
|
|
|
|
|
2024-01-21 09:23:39 -08:00
|
|
|
let king_from: BitBoard = king.square().into();
|
2024-01-21 10:38:50 -08:00
|
|
|
let king_to: BitBoard = target_squares.king.into();
|
2024-01-21 09:23:39 -08:00
|
|
|
*pieces.bitboard_for_piece_mut(king.piece()) ^= king_from | king_to;
|
|
|
|
|
|
|
|
let rook_from: BitBoard = rook.square().into();
|
2024-01-21 10:38:50 -08:00
|
|
|
let rook_to: BitBoard = target_squares.rook.into();
|
2024-01-21 09:23:39 -08:00
|
|
|
*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);
|
|
|
|
|
2024-01-21 15:10:04 -08:00
|
|
|
Position::new(
|
|
|
|
player.other(),
|
|
|
|
flags,
|
|
|
|
pieces,
|
|
|
|
None,
|
|
|
|
self.position.ply_counter() + 1,
|
|
|
|
updated_move_number,
|
|
|
|
)
|
2024-01-21 09:23:39 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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::*;
|
2024-01-24 17:08:27 -08:00
|
|
|
use crate::{position, r#move::Castle, MoveBuilder, PositionBuilder};
|
|
|
|
use chessfriend_core::piece;
|
2024-01-21 09:23:39 -08:00
|
|
|
|
|
|
|
#[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(())
|
|
|
|
}
|
2024-01-21 11:21:37 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn en_passant_capture() -> Result<(), MakeMoveError> {
|
|
|
|
let pos = PositionBuilder::new()
|
|
|
|
.place_piece(piece!(White Pawn on B5))
|
|
|
|
.place_piece(piece!(Black Pawn on A7))
|
|
|
|
.to_move(Color::Black)
|
|
|
|
.build();
|
|
|
|
println!("{pos}");
|
|
|
|
|
|
|
|
let black_pawn_move = MoveBuilder::new(piece!(Black Pawn), Square::A7, Square::A5).build();
|
|
|
|
assert!(black_pawn_move.is_double_push());
|
|
|
|
assert!(!black_pawn_move.is_en_passant());
|
|
|
|
|
|
|
|
let en_passant_position = Builder::<NoMove>::new(&pos).make(&black_pawn_move)?.build();
|
|
|
|
println!("{en_passant_position}");
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
en_passant_position.piece_on_square(Square::A5),
|
|
|
|
Some(piece!(Black Pawn on A5))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
en_passant_position.piece_on_square(Square::B5),
|
|
|
|
Some(piece!(White Pawn on B5))
|
|
|
|
);
|
|
|
|
|
|
|
|
let white_pawn_capture = MoveBuilder::new(piece!(White Pawn), Square::B5, Square::A6)
|
|
|
|
.capturing_en_passant(piece!(Black Pawn on A5))
|
|
|
|
.build();
|
|
|
|
let en_passant_capture = Builder::<NoMove>::new(&en_passant_position)
|
|
|
|
.make(&white_pawn_capture)?
|
|
|
|
.build();
|
|
|
|
println!("{en_passant_capture}");
|
|
|
|
|
|
|
|
assert_eq!(en_passant_capture.piece_on_square(Square::A5), None);
|
|
|
|
assert_eq!(en_passant_capture.piece_on_square(Square::B5), None);
|
|
|
|
assert_eq!(
|
|
|
|
en_passant_capture.piece_on_square(Square::A6),
|
|
|
|
Some(piece!(White Pawn on A6))
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-01-21 09:23:39 -08:00
|
|
|
}
|