use crate::chess::movegen::utils::between;
use crate::chess::{self};
const SEE_VALUES: [i32; chess::Role::NUM] = [126, 781, 825, 1276, 2538, 0];
pub fn see(pos: &chess::Position, mv: chess::Move, threshold: i32) -> bool {
let from = mv.from();
let to = mv.to();
let mut value = see_best_case(pos, mv) - threshold;
if value < 0 {
return false; }
let mut next_victim = if mv.promotion().is_some() {
mv.promotion().unwrap()
} else {
pos.role_at(from)
.expect("Invalid move: from square does not have a piece")
};
value -= SEE_VALUES[next_victim as usize];
if value >= 0 {
return true;
}
let orth_sliders = pos.by_role[chess::Role::Rook] | pos.by_role[chess::Role::Queen];
let diag_sliders = pos.by_role[chess::Role::Bishop] | pos.by_role[chess::Role::Queen];
let mut occ = pos.occupancy ^ from | to;
if mv.move_type(next_victim, pos.ep_square) == chess::chessmove::MoveType::EnPassant {
let captured_pawn_sq = to.down(pos.side).unwrap();
occ ^= captured_pawn_sq;
}
let mut side = pos.side.opponent();
let stm_pinned = pos.pinned[side];
let nstm_pinned = pos.pinned[side.opponent()];
let stm_king = chess::Square::from(pos.king_of(side));
let nstm_king = chess::Square::from(pos.king_of(side.opponent()));
let stm_king_between = between(to, stm_king);
let nstm_king_between = between(to, nstm_king);
let allowed = !(stm_pinned | nstm_pinned)
| (stm_pinned & stm_king_between)
| (nstm_pinned & nstm_king_between);
let mut attackers = all_attackers(to, pos) & allowed;
loop {
let stm_attackers = attackers & pos.by_color[side];
if stm_attackers.none() {
break; }
for victim in chess::Role::ALL {
next_victim = victim;
if (stm_attackers & pos.by_role[victim]).any() {
break; }
}
occ ^= (stm_attackers & pos.by_role[next_victim]).lsb().unwrap();
if next_victim == chess::Role::Bishop
|| next_victim == chess::Role::Queen
|| next_victim == chess::Role::Pawn
{
attackers |= chess::movegen::utils::get_bishop_moves(to, occ) & diag_sliders;
}
if next_victim == chess::Role::Rook || next_victim == chess::Role::Queen {
attackers |= chess::movegen::utils::get_rook_moves(to, occ) & orth_sliders;
}
attackers &= occ;
side = side.opponent();
value = -value - 1 - SEE_VALUES[next_victim as usize];
if value > 0 {
if next_victim == chess::Role::King && (attackers & pos.by_color[side]).any() {
side = side.opponent();
}
break;
}
}
pos.side != side
}
fn see_best_case(pos: &chess::Position, mv: chess::Move) -> i32 {
let to = mv.to();
let from = mv.from();
match mv.move_type(pos.role_at(from).unwrap(), pos.ep_square) {
chess::MoveType::Promotion => {
SEE_VALUES[mv.promotion().unwrap() as usize]
+ SEE_VALUES[pos.role_at(from).unwrap() as usize]
- SEE_VALUES[chess::Role::Pawn as usize]
}
chess::MoveType::EnPassant => {
SEE_VALUES[chess::Role::Pawn as usize]
}
_ => SEE_VALUES[pos.role_at(to).unwrap() as usize],
}
}
fn all_attackers(to: chess::Square, pos: &chess::Position) -> chess::bitboard::Bitboard {
let mut attackers = chess::bitboard::Bitboard::EMPTY;
attackers |= chess::movegen::utils::get_pawn_attacks(to, chess::Color::Black)
& pos.by_color_role(chess::Color::White, chess::Role::Pawn);
attackers |= chess::movegen::utils::get_pawn_attacks(to, chess::Color::White)
& pos.by_color_role(chess::Color::Black, chess::Role::Pawn);
attackers |= chess::movegen::utils::get_knight_moves(to) & pos.by_role[chess::Role::Knight];
attackers |= chess::movegen::utils::get_king_moves(to) & pos.by_role[chess::Role::King];
attackers |= chess::movegen::utils::get_rook_moves(to, pos.occupancy)
& (pos.by_role[chess::Role::Rook] | pos.by_role[chess::Role::Queen]);
attackers |= chess::movegen::utils::get_bishop_moves(to, pos.occupancy)
& (pos.by_role[chess::Role::Bishop] | pos.by_role[chess::Role::Queen]);
attackers
}
#[cfg(test)]
mod test {
use super::*;
use crate::chess::position::fen::Fen;
use crate::init;
#[test]
fn test_pxp() {
init();
let Fen(pos) = Fen::parse("3k4/8/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w - d6 0 1").unwrap();
assert!(see(&pos, "e5d6".parse().unwrap(), 0));
assert!(!see(&pos, "e5d6".parse().unwrap(), 200));
}
#[test]
fn test_pxp_retake() {
init();
let Fen(pos) = Fen::parse("3k4/2q5/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w - d6 0 1").unwrap();
assert!(see(&pos, "e5d6".parse().unwrap(), 0));
assert!(!see(&pos, "e5d6".parse().unwrap(), 1));
}
#[test]
fn test_pxp_no_retake() {
init();
let Fen(pos) = Fen::parse("3k4/2q5/8/2PpP3/8/8/PP1P1PPP/RNBQKBNR w - d6 0 1").unwrap();
assert!(see(&pos, "e5d6".parse().unwrap(), 50));
assert!(!see(&pos, "e5d6".parse().unwrap(), 200));
}
#[test]
fn test_diag_revealed() {
init();
let Fen(pos) = Fen::parse("1q1k4/4p3/3b4/4P3/8/3R2B1/PP1P1PPP/1N1QKBNR w - - 0 1").unwrap();
assert!(see(&pos, "e5d6".parse().unwrap(), 0));
}
#[test]
fn test_pinned() {
init();
let Fen(pos) =
Fen::parse("r2qkbnr/p2b1ppp/2n5/8/Q7/8/PPPPPPPP/RN2KBNR w KQkq - 0 1").unwrap();
assert!(!see(&pos, "a4c6".parse().unwrap(), 0));
}
#[test]
fn en_passant() {
init();
let Fen(pos) =
Fen::parse("2b1kbnr/5ppp/4p3/q1pP1Q1r/6P1/1NP5/PP2PP1P/R1B1KBNR w - c6 0 1").unwrap();
assert!(see(&pos, "d5c6".parse().unwrap(), 15));
assert!(!see(&pos, "d5c6".parse().unwrap(), 200));
}
}