263 lines
7.5 KiB
Rust
263 lines
7.5 KiB
Rust
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
use super::captures::CapturesList;
|
|
use crate::move_record::MoveRecord;
|
|
use chessfriend_bitboard::BitBoard;
|
|
use chessfriend_board::{
|
|
display::DiagramFormatter, fen::ToFenStr, Board, PlacePieceError, PlacePieceStrategy,
|
|
};
|
|
use chessfriend_core::{Color, Piece, Square};
|
|
use std::{cell::OnceCell, fmt};
|
|
|
|
#[must_use]
|
|
#[derive(Clone, Debug, Default, Eq)]
|
|
pub struct Position {
|
|
pub board: Board,
|
|
pub(crate) moves: Vec<MoveRecord>,
|
|
pub(crate) captures: CapturesList,
|
|
}
|
|
|
|
impl Position {
|
|
pub fn empty() -> Self {
|
|
Position::default()
|
|
}
|
|
|
|
/// Return a starting position.
|
|
pub fn starting() -> Self {
|
|
Self {
|
|
board: Board::starting(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
pub fn new(board: Board) -> Self {
|
|
Self {
|
|
board,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Position {
|
|
/// Place a piece on the board.
|
|
///
|
|
/// ## Errors
|
|
///
|
|
/// See [`chessfriend_board::Board::place_piece`].
|
|
pub fn place_piece(
|
|
&mut self,
|
|
piece: Piece,
|
|
square: Square,
|
|
strategy: PlacePieceStrategy,
|
|
) -> Result<(), PlacePieceError> {
|
|
self.board.place_piece(piece, square, strategy)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn get_piece(&self, square: Square) -> Option<Piece> {
|
|
self.board.get_piece(square)
|
|
}
|
|
|
|
pub fn remove_piece(&mut self, square: Square) -> Option<Piece> {
|
|
self.board.remove_piece(square)
|
|
}
|
|
}
|
|
|
|
impl Position {
|
|
pub fn sight(&self, square: Square) -> BitBoard {
|
|
self.board.sight(square)
|
|
}
|
|
|
|
pub fn movement(&self, square: Square) -> BitBoard {
|
|
self.board.movement(square)
|
|
}
|
|
}
|
|
|
|
impl Position {
|
|
pub fn active_sight(&self) -> BitBoard {
|
|
self.board.active_sight()
|
|
}
|
|
|
|
/// A [`BitBoard`] of all squares the given color can see.
|
|
pub fn friendly_sight(&self, color: Color) -> BitBoard {
|
|
self.board.friendly_sight(color)
|
|
}
|
|
|
|
/// A [`BitBoard`] of all squares visible by colors that oppose the given color.
|
|
pub fn active_color_opposing_sight(&self) -> BitBoard {
|
|
self.board.active_color_opposing_sight()
|
|
}
|
|
}
|
|
|
|
/*
|
|
impl Position {
|
|
pub fn moves(&self) -> &Moves {
|
|
self.moves.get_or_init(|| {
|
|
let player_to_move = self.player_to_move();
|
|
let checking_pieces = self.checking_pieces();
|
|
match checking_pieces.count() {
|
|
// Normal, unrestricted move generation
|
|
0 => Moves::new(
|
|
&self.board,
|
|
player_to_move,
|
|
BitBoard::full(),
|
|
BitBoard::full(),
|
|
),
|
|
1 => {
|
|
// Calculate push and capture masks for checking piece. Moves are restricted to
|
|
// those that intersect those masks.
|
|
let capture_mask = checking_pieces.capture_mask();
|
|
let push_mask = checking_pieces.push_mask(self.king_bitboard(player_to_move));
|
|
Moves::new(&self.board, player_to_move, capture_mask, push_mask)
|
|
}
|
|
// With more than one checking piece, the only legal moves are king moves.
|
|
_ => Moves::new(
|
|
&self.board,
|
|
player_to_move,
|
|
BitBoard::empty(),
|
|
BitBoard::empty(),
|
|
),
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn moves_for_piece(&self, piece: &PlacedPiece) -> Option<&MoveSet> {
|
|
self.moves().moves_for_piece(piece)
|
|
}
|
|
|
|
pub(crate) fn checking_pieces(&self) -> CheckingPieces {
|
|
let opponent = self.player_to_move().other();
|
|
let king_square = self.king_square(self.player_to_move());
|
|
|
|
let checking_pawns = {
|
|
// The current player's pawn attack moves *from* this square are the
|
|
// same as the pawn moves for the opposing player attacking this square.
|
|
let pawn_moves_to_king_square =
|
|
BitBoard::pawn_attacks(king_square, self.player_to_move());
|
|
let opposing_pawn = Piece::pawn(opponent);
|
|
let opposing_pawns = self.board.bitboard_for_piece(opposing_pawn);
|
|
|
|
pawn_moves_to_king_square & opposing_pawns
|
|
};
|
|
|
|
macro_rules! checking_piece {
|
|
($moves_bb_fn:path, $piece_fn:ident) => {{
|
|
let moves_from_opposing_square = $moves_bb_fn(king_square);
|
|
let piece = Piece::$piece_fn(opponent);
|
|
let opposing_pieces = self.board.bitboard_for_piece(piece);
|
|
|
|
moves_from_opposing_square & opposing_pieces
|
|
}};
|
|
}
|
|
|
|
let checking_knights = checking_piece!(BitBoard::knight_moves, knight);
|
|
let checking_bishops = checking_piece!(BitBoard::bishop_moves, bishop);
|
|
let checking_rooks = checking_piece!(BitBoard::rook_moves, rook);
|
|
let checking_queens = checking_piece!(BitBoard::queen_moves, queen);
|
|
|
|
CheckingPieces::new(
|
|
checking_pawns,
|
|
checking_knights,
|
|
checking_bishops,
|
|
checking_rooks,
|
|
checking_queens,
|
|
)
|
|
}
|
|
}
|
|
*/
|
|
|
|
impl Position {
|
|
pub fn display(&self) -> DiagramFormatter {
|
|
self.board.display()
|
|
}
|
|
}
|
|
|
|
impl ToFenStr for Position {
|
|
type Error = <Board as ToFenStr>::Error;
|
|
|
|
fn to_fen_str(&self) -> Result<String, Self::Error> {
|
|
self.board.to_fen_str()
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Position {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.board == other.board
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Position {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.board.display())?;
|
|
|
|
if !self.captures.is_empty() {
|
|
write!(f, "\n\n{}", self.captures)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{test_position, Position};
|
|
use chessfriend_core::piece;
|
|
|
|
#[test]
|
|
fn piece_on_square() {
|
|
let pos = test_position![
|
|
Black Bishop on F7,
|
|
];
|
|
|
|
let piece = pos.board.get_piece(Square::F7);
|
|
assert_eq!(piece, Some(piece!(Black Bishop)));
|
|
}
|
|
|
|
#[test]
|
|
fn piece_in_starting_position() {
|
|
let pos = test_position!(starting);
|
|
|
|
assert_eq!(pos.board.get_piece(Square::H1), Some(piece!(White Rook)));
|
|
assert_eq!(pos.board.get_piece(Square::A8), Some(piece!(Black Rook)));
|
|
}
|
|
|
|
// #[test]
|
|
// fn king_is_in_check() {
|
|
// let pos = position![
|
|
// White King on E1,
|
|
// Black Rook on E8,
|
|
// ];
|
|
// assert!(pos.is_king_in_check());
|
|
// }
|
|
|
|
// #[test]
|
|
// fn king_is_not_in_check() {
|
|
// let pos = position![
|
|
// White King on F1,
|
|
// Black Rook on E8,
|
|
// ];
|
|
// assert!(!pos.is_king_in_check());
|
|
// }
|
|
|
|
// #[test]
|
|
// fn king_not_on_starting_square_cannot_castle() {
|
|
// let pos = test_position!(White King on E4);
|
|
// let rights = pos.board.castling_rights;
|
|
// assert!(!rights.color_has_right(Color::White, Castle::KingSide));
|
|
// assert!(!rights.color_has_right(Color::White, Castle::QueenSide));
|
|
// }
|
|
|
|
// #[test]
|
|
// fn danger_squares() {
|
|
// let pos = test_position!(Black, [
|
|
// White King on E1,
|
|
// Black King on E7,
|
|
// White Rook on E4,
|
|
// ]);
|
|
|
|
// let danger_squares = pos.king_danger(Color::Black);
|
|
// let expected = bitboard![D1 F1 D2 E2 F2 E3 A4 B4 C4 D4 F4 G4 H4 E5 E6 E7 E8];
|
|
// assert_eq_bitboards!(danger_squares, expected);
|
|
// }
|
|
}
|