use std::cmp::max;
use chess::{Board, Color, MoveGen, Piece, EMPTY};
use rustc_hash::FxHashSet;
use crate::{
deduction::is_statically_unwinnable,
search::{
check_chain_search, has_lonely_pawns, insufficient_white_material, nb_blocked_pawns,
only_pawns_and_bishops, play_forced_moves, Search,
},
Winnability,
};
pub(crate) fn is_unwinnable(board: &Board) -> bool {
let mut board = *board;
let mut search = Search::new();
board = play_forced_moves(&board, &mut search);
if search.interrupted {
return true;
}
let mut legal_moves = MoveGen::new_legal(&board).peekable();
if legal_moves.peek().is_none() {
return !(board.side_to_move() == Color::Black && *board.checkers() != EMPTY);
}
let only_pawns_and_bishops = only_pawns_and_bishops(&board);
let nb_pawns = board.pieces(Piece::Pawn).popcnt();
let initial_depth = if only_pawns_and_bishops { 7 } else { 4 };
let mut moved = MovedPieces::default();
if is_dynamically_unwinnable(&board, initial_depth, false, &mut moved) {
return true;
}
let low_entropy_candidate =
!moved.qrn && nb_blocked_pawns(&board) >= max(1i8, (nb_pawns as i8 / 2) - 2) as u32;
if only_pawns_and_bishops
&& low_entropy_candidate
&& moved.king != [true, true]
&& legal_moves.count() <= 8
&& is_dynamically_unwinnable(&board, 15, false, &mut moved)
{
return true;
}
let blocked_candidate = low_entropy_candidate && !has_lonely_pawns(&board);
if blocked_candidate && is_statically_unwinnable(&board) {
return true;
}
if *board.checkers() != EMPTY
&& blocked_candidate
&& nb_pawns >= 6
&& only_pawns_and_bishops
&& matches!(
check_chain_search(&board, &mut FxHashSet::default(), 0),
Some(Winnability::Unwinnable)
)
{
return true;
}
if blocked_candidate
&& only_pawns_and_bishops
&& nb_pawns >= 10
&& *board.pieces(Piece::Bishop) == EMPTY
&& !moved.two_pawn_moves_in_a_line
{
return exhaustively_unwinnable(&board);
}
false
}
#[derive(Default, Debug)]
struct MovedPieces {
king: [bool; 2],
bishop: bool,
qrn: bool,
two_pawn_moves_in_a_line: bool,
}
fn is_dynamically_unwinnable(
board: &Board,
depth: u8,
pawn_move_seen: bool,
moved: &mut MovedPieces,
) -> bool {
if insufficient_white_material(board) {
return true;
}
let mut legal_moves = MoveGen::new_legal(board).peekable();
if legal_moves.peek().is_none() {
return !(board.side_to_move() == Color::Black && *board.checkers() != EMPTY);
}
if depth == 0 {
return false;
}
for m in legal_moves {
let moved_piece = board.piece_on(m.get_source()).unwrap();
match moved_piece {
Piece::King => moved.king[board.side_to_move().to_index()] = true,
Piece::Pawn => moved.two_pawn_moves_in_a_line |= pawn_move_seen,
Piece::Bishop => moved.bishop = true,
_ => moved.qrn = true,
};
let new_board = board.make_move_new(m);
let pawn_move_seen = pawn_move_seen || moved_piece == Piece::Pawn;
if !is_dynamically_unwinnable(&new_board, depth - 1, pawn_move_seen, moved) {
return false;
}
}
true
}
pub(crate) fn exhaustively_unwinnable(board: &Board) -> bool {
let mut seen = FxHashSet::default();
seen.insert(*board);
let mut table = vec![*board];
let mut next_table = Vec::new();
while !table.is_empty() {
for board in &table {
if insufficient_white_material(board) {
continue;
}
let mut legal_moves = MoveGen::new_legal(board).peekable();
if legal_moves.peek().is_none() {
if board.side_to_move() == Color::Black && *board.checkers() != EMPTY {
return false;
}
continue;
}
for m in legal_moves {
let new_board = board.make_move_new(m);
if seen.insert(new_board) {
if seen.len() > 3000 {
return false;
}
next_table.push(new_board);
}
}
}
table.clear();
std::mem::swap(&mut table, &mut next_table);
}
true
}