use chess::{get_king_moves, BitBoard, Board, Color, Piece, EMPTY, NUM_COLORS, NUM_SQUARES};
use super::utils::{get_piece_attacks, subsets_of_size};
pub(crate) struct Analysis {
pub(crate) board: Board,
pub(crate) reachable: [BitBoard; NUM_SQUARES],
pub(crate) steady: BitBoard,
pub(crate) clear: BitBoard,
pub(crate) attacked: [BitBoard; NUM_COLORS],
pub(crate) capturable: BitBoard,
pub(crate) movable: BitBoard,
pub(crate) reachable_by_non_bishop: [BitBoard; NUM_COLORS],
pub(crate) sacrificable: [BitBoard; NUM_COLORS],
pub(crate) uncapturable_while_steady: Option<BitBoard>,
pub(crate) capturable_only_while_steady: Option<BitBoard>,
pub(crate) first_steady_capturable: Option<BitBoard>,
pub(crate) promotable: bool,
}
impl Analysis {
pub(crate) fn new(board: Board) -> Self {
let mut analysis = Self {
board,
reachable: [EMPTY; NUM_SQUARES],
steady: *board.combined(),
clear: !*board.combined(),
attacked: [EMPTY; NUM_COLORS],
sacrificable: [EMPTY; NUM_COLORS],
capturable: EMPTY,
movable: EMPTY,
reachable_by_non_bishop: [EMPTY; NUM_COLORS],
uncapturable_while_steady: None,
capturable_only_while_steady: None,
first_steady_capturable: None,
promotable: false,
};
for s in *board.combined() {
analysis.reachable[s.to_index()] |= BitBoard::from_square(s);
}
analysis.update_attacked_squares();
analysis
}
pub(crate) fn black_could_get_mated(&self) -> bool {
let white_king = self.board.color_combined(Color::White) & self.board.pieces(Piece::King);
let white_king_reachable = self.reachable[white_king.to_square().to_index()];
let black_king = self.board.color_combined(Color::Black) & self.board.pieces(Piece::King);
let black_king_region = self.reachable[black_king.to_square().to_index()];
let (visitors, visited_region) = self.visitors(black_king_region);
if visitors == EMPTY {
return false;
}
for mating_square in visited_region {
let escaping_squares =
get_king_moves(mating_square) & black_king_region & !visited_region;
let (blockers, blocking_region) = self.blockers(escaping_squares);
let n = blockers.popcnt().min(blocking_region.popcnt());
for covered_by_black in subsets_of_size(blocking_region, n) {
let remaining = escaping_squares & !covered_by_black;
if remaining == EMPTY {
return true;
}
let candidate_king_squares =
remaining.into_iter().fold(EMPTY, |acc, r| acc | get_king_moves(r))
& !(BitBoard::from_square(mating_square) | get_king_moves(mating_square));
if (candidate_king_squares & white_king_reachable)
.into_iter()
.any(|s| get_king_moves(s) & remaining == remaining)
{
return true;
}
}
}
false
}
fn visitors(&self, region: BitBoard) -> (BitBoard, BitBoard) {
let mut visitors = EMPTY;
let mut visited_region = EMPTY;
for s in *self.board.color_combined(Color::White) {
let piece = self.board.piece_on(s).unwrap();
if piece == Piece::King {
continue;
}
if self.reachable[s.to_index()] == BitBoard::from_square(s) {
continue;
}
for t in self.reachable[s.to_index()] {
let attacked_by_piece = get_piece_attacks(piece, Color::White, t, !self.clear);
if region & attacked_by_piece != EMPTY {
visitors |= BitBoard::from_square(s);
visited_region |= region & attacked_by_piece;
}
}
}
(visitors, visited_region)
}
fn blockers(&self, region: BitBoard) -> (BitBoard, BitBoard) {
let mut blockers = EMPTY;
let mut blocking_region = EMPTY;
for s in *self.board.color_combined(Color::Black) & !self.board.pieces(Piece::King) {
let reached_by_blocker = self.reachable[s.to_index()];
if region & reached_by_blocker != EMPTY {
blockers |= BitBoard::from_square(s);
blocking_region |= region & reached_by_blocker;
}
}
(blockers, blocking_region)
}
}