use crate::attacks::ADVISOR_ATTACKS;
use crate::attacks::KING_ATTACKS;
use crate::attacks::PAWN_ATTACKS;
use crate::attacks::bishop_attacks;
use crate::attacks::knight_attacks;
use crate::bitboard::BitBoard;
use crate::color::Color;
use crate::magic::cannon_attacks;
use crate::magic::rook_attacks;
use crate::mv::Move;
use crate::piece::PieceType;
use crate::position::Position;
use crate::square::Square;
pub const SEE_KING: i32 = 10_000;
pub const SEE_ROOK: i32 = 500;
pub const SEE_CANNON: i32 = 300;
pub const SEE_KNIGHT: i32 = 280;
pub const SEE_ADVISOR: i32 = 120;
pub const SEE_BISHOP: i32 = 120;
pub const SEE_PAWN: i32 = 100;
#[inline]
pub const fn see_value(kind: PieceType) -> i32 {
match kind {
PieceType::King => SEE_KING,
PieceType::Rook => SEE_ROOK,
PieceType::Cannon => SEE_CANNON,
PieceType::Knight => SEE_KNIGHT,
PieceType::Advisor => SEE_ADVISOR,
PieceType::Bishop => SEE_BISHOP,
PieceType::Pawn => SEE_PAWN,
}
}
pub fn attackers_to(pos: &Position, dst: Square, occ: BitBoard) -> BitBoard {
let mut attackers = BitBoard::EMPTY;
for color in Color::ALL {
attackers |= KING_ATTACKS[dst.raw() as usize] & pos.pieces(color, PieceType::King);
attackers |= ADVISOR_ATTACKS[dst.raw() as usize] & pos.pieces(color, PieceType::Advisor);
attackers |= bishop_attacks(dst, occ) & pos.pieces(color, PieceType::Bishop);
let mut knights = pos.pieces(color, PieceType::Knight);
while knights.any() {
let k = knights.pop_lsb();
if knight_attacks(k, occ).has(dst) {
attackers |= BitBoard::from_square(k);
}
}
attackers |= rook_attacks(dst, occ) & pos.pieces(color, PieceType::Rook);
let (_, cannon_caps) = cannon_attacks(dst, occ);
attackers |= cannon_caps & pos.pieces(color, PieceType::Cannon);
let mut pawns = pos.pieces(color, PieceType::Pawn);
while pawns.any() {
let p = pawns.pop_lsb();
if PAWN_ATTACKS[color.index()][p.raw() as usize].has(dst) {
attackers |= BitBoard::from_square(p);
}
}
}
attackers
}
#[inline]
fn least_valuable(pos: &Position, attackers: BitBoard, color: Color) -> Option<(Square, PieceType)> {
for kind in [
PieceType::Pawn,
PieceType::Cannon,
PieceType::Knight,
PieceType::Bishop,
PieceType::Advisor,
PieceType::Rook,
PieceType::King,
] {
let bb = pos.pieces(color, kind) & attackers;
if bb.any() {
return Some((bb.lsb_square(), kind));
}
}
None
}
pub fn see(pos: &Position, mv: Move) -> i32 {
let src = mv.src();
let dst = mv.dst();
let attacker = match pos.piece_at(src) {
Some(p) => p,
None => return 0,
};
let victim_value = pos.piece_at(dst).map(|p| see_value(p.kind())).unwrap_or(0);
let mut gain = [0i32; 32];
let mut d = 0usize;
gain[0] = victim_value;
let mut occ = pos.occupancy() ^ BitBoard::from_square(src);
let mut attackers = attackers_to(pos, dst, occ) & !BitBoard::from_square(src);
let mut side = attacker.color().flip();
let mut on_square_value = see_value(attacker.kind());
loop {
let side_attackers = attackers & pos.color_occupancy(side);
let Some((att_sq, att_kind)) = least_valuable(pos, side_attackers, side) else {
break;
};
d += 1;
gain[d] = on_square_value - gain[d - 1];
if gain[d].max(-gain[d - 1]) < 0 {
break;
}
occ ^= BitBoard::from_square(att_sq);
attackers &= !BitBoard::from_square(att_sq);
side = side.flip();
on_square_value = see_value(att_kind);
}
while d > 0 {
d -= 1;
gain[d] = -(gain[d + 1].max(-gain[d]));
}
gain[0]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fen::STARTING_FEN;
use crate::mv::Move as M;
#[test]
fn free_capture_returns_victim_value() {
let _ = STARTING_FEN; let fen = "9/9/9/9/9/9/3P5/9/9/4K4 w";
let pos = Position::from_fen(fen).unwrap();
let mv = M::from_iccs("d3-d4").unwrap();
assert_eq!(see(&pos, mv), 0);
}
#[test]
fn rook_takes_undefended_piece_is_positive() {
let fen = "9/9/9/9/9/n8/9/9/9/R8 w";
let pos = Position::from_fen(fen).unwrap();
let mv = M::from_iccs("a0-a4").unwrap();
assert_eq!(see(&pos, mv), SEE_KNIGHT);
}
#[test]
fn defended_capture_breaks_even() {
let fen = "r8/9/9/9/9/n8/9/9/9/R8 w";
let pos = Position::from_fen(fen).unwrap();
let mv = M::from_iccs("a0-a4").unwrap();
let s = see(&pos, mv);
assert!(s < 0, "expected losing trade but SEE = {s}");
assert_eq!(s, SEE_KNIGHT - SEE_ROOK);
}
#[test]
fn pawn_for_rook_is_a_winning_trade() {
let fen = "9/9/9/9/9/p8/9/9/9/R8 w";
let pos = Position::from_fen(fen).unwrap();
let mv = M::from_iccs("a0-a4").unwrap();
assert_eq!(see(&pos, mv), SEE_PAWN);
}
}