use core::fmt;
use crate::{BitMove, Bitboard, CastlingRights, Color, EvalPosition, FenString, File, MoveFlag, MoveMasks, Piece, Square, ZobristKey, PIECE_TYPE_COUNT, SQUARE_COUNT};
#[derive(Clone)]
pub struct Position {
#[cfg(feature = "bb_array")]
pub(crate) pps: [Option<Piece>; SQUARE_COUNT],
pub(crate) bitboards: [Bitboard; PIECE_TYPE_COUNT],
pub(crate) white_occupancy: Bitboard,
pub(crate) black_occupancy: Bitboard,
pub(crate) all_occupancy: Bitboard,
pub side: Color,
pub en_passant_option: Option<Square>,
pub castling_rights: CastlingRights,
pub zobrist_key: ZobristKey,
pub(crate) ply: u16,
#[cfg(feature = "tapered_eval")]
pub(crate) game_phase_score: i16,
}
impl Default for Position {
fn default() -> Position {
Position {
#[cfg(feature = "bb_array")]
pps: [None; SQUARE_COUNT],
bitboards: [Bitboard::EMPTY; PIECE_TYPE_COUNT],
white_occupancy: Bitboard::EMPTY,
black_occupancy: Bitboard::EMPTY,
all_occupancy: Bitboard::EMPTY,
side: Color::White,
en_passant_option: None,
castling_rights: CastlingRights::NONE,
ply: 0,
zobrist_key: ZobristKey(0),
#[cfg(feature = "tapered_eval")]
game_phase_score: 0,
}
}
}
impl Position {
#[inline(always)]
pub(crate) fn merge_occupancies(&mut self) {
self.all_occupancy = self.white_occupancy | self.black_occupancy;
}
#[inline(always)]
pub(crate) fn populate_occupancies(&mut self) {
self.white_occupancy = self.bitboards[Piece::WP]
| self.bitboards[Piece::WN]
| self.bitboards[Piece::WB]
| self.bitboards[Piece::WR]
| self.bitboards[Piece::WQ]
| self.bitboards[Piece::WK];
self.black_occupancy = self.bitboards[Piece::BP]
| self.bitboards[Piece::BN]
| self.bitboards[Piece::BB]
| self.bitboards[Piece::BR]
| self.bitboards[Piece::BQ]
| self.bitboards[Piece::BK];
self.merge_occupancies();
}
pub fn starting_position() -> Position {
let mut position = Position {
#[cfg(feature = "bb_array")]
pps: [
Some(Piece::BR), Some(Piece::BN), Some(Piece::BB), Some(Piece::BQ), Some(Piece::BK), Some(Piece::BB), Some(Piece::BN), Some(Piece::BR),
Some(Piece::BP), Some(Piece::BP), Some(Piece::BP), Some(Piece::BP), Some(Piece::BP), Some(Piece::BP), Some(Piece::BP), Some(Piece::BP),
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
Some(Piece::WP), Some(Piece::WP), Some(Piece::WP), Some(Piece::WP), Some(Piece::WP), Some(Piece::WP), Some(Piece::WP), Some(Piece::WP),
Some(Piece::WR), Some(Piece::WN), Some(Piece::WB), Some(Piece::WQ), Some(Piece::WK), Some(Piece::WB), Some(Piece::WN), Some(Piece::WR),
],
bitboards: [
Bitboard::WP,
Bitboard::WN,
Bitboard::WB,
Bitboard::WR,
Bitboard::WQ,
Bitboard::WK,
Bitboard::BP,
Bitboard::BN,
Bitboard::BB,
Bitboard::BR,
Bitboard::BQ,
Bitboard::BK,
],
white_occupancy: Bitboard::WHITE_STARTING_PIECES,
black_occupancy: Bitboard::BLACK_STARTING_PIECES,
all_occupancy: Bitboard::ALL_STARTING_PIECES,
side: Color::White,
en_passant_option: None,
castling_rights: CastlingRights::DEFAULT,
ply: 0,
zobrist_key: ZobristKey(0),
#[cfg(feature = "tapered_eval")]
game_phase_score: 0,
};
position.zobrist_key = ZobristKey::generate(&position);
#[cfg(feature = "tapered_eval")]
{ position.game_phase_score = EvalPosition::get_game_phase_score(&position); }
position
}
#[inline(always)]
pub fn set_piece(&mut self, piece: Piece, sq: Square) {
self.bitboards[piece].set_sq(sq);
#[cfg(feature = "bb_array")]
{ self.pps[sq] = Some(piece); }
self.zobrist_key.mod_piece(piece, sq);
}
#[inline(always)]
pub fn remove_piece(&mut self, piece: Piece, sq: Square) {
self.bitboards[piece].pop_sq(sq);
#[cfg(feature = "bb_array")]
{ self.pps[sq] = None; }
self.zobrist_key.mod_piece(piece, sq);
}
#[inline(always)]
pub(crate) fn zobrist_mods(&mut self) {
self.zobrist_key.mod_side(self.side);
self.zobrist_key.mod_castling(self.castling_rights);
self.zobrist_key.mod_en_passant(self.en_passant_option);
}
#[inline]
pub fn make_move(&mut self, bit_move: BitMove) {
#[cfg(feature = "bb")]
let (source, target, piece, capture_option, flag_option) = bit_move.decode();
#[cfg(feature = "bb_array")]
let (source, target, flag_option) = bit_move.decode();
#[cfg(feature = "bb_array")]
let piece = self.get_piece(source);
#[cfg(feature = "bb_array")]
let capture_option = self.get_piece_option(target);
debug_assert_eq!(capture_option, self.get_piece_option(target));
debug_assert_eq!(piece.color(), self.side);
debug_assert!(capture_option.is_none_or(|capture| capture.color() == self.side.opposite()));
debug_assert!(self.bitboards[piece].is_set_sq(source));
debug_assert!(capture_option.is_none_or(|capture| self.bitboards[capture].is_set_sq(target)));
self.zobrist_mods();
if let Some(capture) = capture_option {
self.remove_piece(capture, target);
#[cfg(feature = "tapered_eval")]
{ self.game_phase_score -= EvalPosition::get_game_phase_piece_score(capture); }
}
self.remove_piece(piece, source);
self.set_piece(piece, target);
self.en_passant_option = None;
match flag_option {
None => (),
Some(MoveFlag::WDoublePawn) => self.en_passant_option = Some(target.below()),
Some(MoveFlag::BDoublePawn) => self.en_passant_option = Some(target.above()),
Some(MoveFlag::WEnPassant) => {
self.remove_piece(Piece::BP, target.below());
#[cfg(feature = "tapered_eval")]
{ self.game_phase_score -= EvalPosition::get_game_phase_piece_score(Piece::BP); }
},
Some(MoveFlag::BEnPassant) => {
self.remove_piece(Piece::WP, target.above());
#[cfg(feature = "tapered_eval")]
{ self.game_phase_score -= EvalPosition::get_game_phase_piece_score(Piece::WP); }
},
Some(MoveFlag::WKCastle) => {
self.remove_piece(Piece::WR, Square::H1);
self.set_piece(Piece::WR, Square::F1);
}
Some(MoveFlag::WQCastle) => {
self.remove_piece(Piece::WR, Square::A1);
self.set_piece(Piece::WR, Square::D1);
}
Some(MoveFlag::BKCastle) => {
self.remove_piece(Piece::BR, Square::H8);
self.set_piece(Piece::BR, Square::F8);
}
Some(MoveFlag::BQCastle) => {
self.remove_piece(Piece::BR, Square::A8);
self.set_piece(Piece::BR, Square::D8);
}
Some(MoveFlag::PromoQ) => {
self.remove_piece(piece, target);
self.set_piece(
match self.side {
Color::White => Piece::WQ,
Color::Black => Piece::BQ,
},
target,
);
#[cfg(feature = "tapered_eval")]
{
self.game_phase_score -= EvalPosition::get_game_phase_piece_score(piece);
self.game_phase_score += EvalPosition::get_game_phase_piece_score(Piece::WQ);
}
}
Some(MoveFlag::PromoR) => {
self.remove_piece(piece, target);
self.set_piece(
match self.side {
Color::White => Piece::WR,
Color::Black => Piece::BR,
},
target,
);
#[cfg(feature = "tapered_eval")]
{
self.game_phase_score -= EvalPosition::get_game_phase_piece_score(piece);
self.game_phase_score += EvalPosition::get_game_phase_piece_score(Piece::WR);
}
}
Some(MoveFlag::PromoN) => {
self.remove_piece(piece, target);
self.set_piece(
match self.side {
Color::White => Piece::WN,
Color::Black => Piece::BN,
},
target,
);
#[cfg(feature = "tapered_eval")]
{
self.game_phase_score -= EvalPosition::get_game_phase_piece_score(piece);
self.game_phase_score += EvalPosition::get_game_phase_piece_score(Piece::WR);
}
}
Some(MoveFlag::PromoB) => {
self.remove_piece(piece, target);
self.set_piece(
match self.side {
Color::White => Piece::WB,
Color::Black => Piece::BB,
},
target,
);
#[cfg(feature = "tapered_eval")]
{
self.game_phase_score -= EvalPosition::get_game_phase_piece_score(piece);
self.game_phase_score += EvalPosition::get_game_phase_piece_score(Piece::WR);
}
}
};
self.castling_rights.update(source, target);
self.populate_occupancies();
self.side.switch();
self.zobrist_mods();
debug_assert_eq!(self.zobrist_key, ZobristKey::generate(self), "{self}");
}
#[inline]
#[cfg(feature = "revert_undo")]
pub(crate) fn undo_move(&mut self, bit_move: BitMove, old_castling_rights: CastlingRights) {
let (source, target, piece, capture_option, flag_option) = bit_move.decode();
self.side.switch();
debug_assert_eq!(piece.color(), self.side);
debug_assert!(capture_option.is_none_or(|capture| capture.color() == self.side.opposite()));
self.set_piece(piece, source);
self.remove_piece(piece, target);
if let Some(capture) = capture_option {
self.set_piece(capture, target);
}
self.en_passant_option = None;
match flag_option {
None | Some(MoveFlag::WDoublePawn) | Some(MoveFlag::BDoublePawn) => (),
Some(MoveFlag::WEnPassant) => {
self.en_passant_option = Some(target);
self.set_piece(Piece::BP, target.below())
}
Some(MoveFlag::BEnPassant) => {
self.en_passant_option = Some(target);
self.set_piece(Piece::WP, target.above())
}
Some(MoveFlag::WKCastle) => {
self.set_piece(Piece::WR, Square::H1);
self.remove_piece(Piece::WR, Square::F1);
}
Some(MoveFlag::WQCastle) => {
self.set_piece(Piece::WR, Square::A1);
self.remove_piece(Piece::WR, Square::D1);
}
Some(MoveFlag::BKCastle) => {
self.set_piece(Piece::BR, Square::H8);
self.remove_piece(Piece::BR, Square::F8);
}
Some(MoveFlag::BQCastle) => {
self.set_piece(Piece::BR, Square::A8);
self.remove_piece(Piece::BR, Square::D8);
}
Some(MoveFlag::PromoQ) => {
self.remove_piece(
match self.side {
Color::White => Piece::WQ,
Color::Black => Piece::BQ,
},
target,
);
}
Some(MoveFlag::PromoR) => {
self.remove_piece(
match self.side {
Color::White => Piece::WR,
Color::Black => Piece::BR,
},
target,
);
}
Some(MoveFlag::PromoN) => {
self.remove_piece(
match self.side {
Color::White => Piece::WN,
Color::Black => Piece::BN,
},
target,
);
}
Some(MoveFlag::PromoB) => {
self.remove_piece(
match self.side {
Color::White => Piece::WB,
Color::Black => Piece::BB,
},
target,
);
}
};
self.castling_rights = old_castling_rights;
self.populate_occupancies();
}
#[inline(always)]
pub(crate) fn apply_pseudo_legal_move(&mut self, bit_move: BitMove) -> bool {
self.make_move(bit_move);
if !self.in_check(self.side.opposite()) {
self.ply += 1;
true
} else {
false
}
}
#[inline(always)]
pub fn is_square_attacked(&self, defending_side: Color, square: Square) -> bool {
let &[enemy_pawn, enemy_knight, enemy_bishop, enemy_rook, enemy_queen, enemy_king] = match defending_side {
Color::White => &Piece::BLACK_PIECES,
Color::Black => &Piece::WHITE_PIECES,
};
(MoveMasks::get_pawn_capture_mask(defending_side, square) & self.bitboards[enemy_pawn]).is_not_empty() ||
(MoveMasks::get_knight_mask(square) & self.bitboards[enemy_knight]).is_not_empty() ||
(MoveMasks::get_bishop_mask(square, self.all_occupancy) & self.bitboards[enemy_bishop]).is_not_empty() ||
(MoveMasks::get_rook_mask(square, self.all_occupancy) & self.bitboards[enemy_rook]).is_not_empty() ||
(MoveMasks::get_queen_mask(square, self.all_occupancy) & self.bitboards[enemy_queen]).is_not_empty() ||
(MoveMasks::get_king_mask(square) & self.bitboards[enemy_king]).is_not_empty()
}
pub fn in_check(&self, defending_side: Color) -> bool {
match defending_side {
Color::White => self.is_square_attacked(defending_side, Square::from(self.bitboards[Piece::WK])),
Color::Black => self.is_square_attacked(defending_side, Square::from(self.bitboards[Piece::BK])),
}
}
#[inline(always)]
#[cfg(feature = "bb")]
pub fn get_piece(&self, square: Square) -> Piece {
for piece in Piece::ALL_PIECES {
if self.bitboards[piece].is_set_sq(square) {
return piece;
}
}
panic!("Couldn't find some piece on {}", square);
}
#[inline(always)]
#[cfg(feature = "bb")]
pub fn get_piece_option(&self, square: Square) -> Option<Piece> {
for piece in Piece::ALL_PIECES {
if self.bitboards[piece].is_set_sq(square) {
return Some(piece);
}
}
None
}
#[inline(always)]
#[cfg(feature = "bb_array")]
pub fn get_piece(&self, square: Square) -> Piece {
self.pps[square].unwrap()
}
#[inline(always)]
#[cfg(feature = "bb_array")]
pub fn get_piece_option(&self, square: Square) -> Option<Piece> {
self.pps[square]
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = String::from("\n");
for sq in Square::ALL_SQUARES {
if sq.file() == File::FA {
s += &format!(" {} ", sq.rank());
}
match self.get_piece_option(sq) {
None => s += ". ",
Some(piece) => s += &format!("{piece} "),
}
if sq.file() == File::FH {
s += "\n";
}
}
s += "\n a b c d e f g h\n";
s += &format!("
FEN: {}
Side: {}
En-passant: {:?}
Castling: {}
Zobrist Key: {:#x}\n",
FenString::from(self),
self.side,
self.en_passant_option,
self.castling_rights,
self.zobrist_key.0,
);
f.pad(&s)
}
}