use crate::constants::{FILE_A, FILE_H, NOT_A_FILE, NOT_AB_FILE, NOT_GH_FILE, NOT_H_FILE};
use crate::model::bitboard::BitBoard;
use crate::model::gameboard::GameBoard;
use crate::model::rays::{DIR_OFFSETS, RAYS};
fn is_square_attacked_pawn(board: &GameBoard, square: u8) -> bool {
if square >= 64 {
return false;
}
let opponent_white = !board.playing;
let desired_for_opponent = !opponent_white;
let opponent_pawns = board.pawns & board.combined_coloured(desired_for_opponent);
let attacks = if opponent_white {
let left_attacks = (opponent_pawns & BitBoard::new(!FILE_A)) << 7;
let right_attacks = (opponent_pawns & BitBoard::new(!FILE_H)) << 9;
left_attacks | right_attacks
} else {
let left_attacks = (opponent_pawns & BitBoard::new(!FILE_A)) >> 9;
let right_attacks = (opponent_pawns & BitBoard::new(!FILE_H)) >> 7;
left_attacks | right_attacks
};
attacks.get_bit_unchecked(square)
}
fn is_square_attacked_knight(board: &GameBoard, square: u8) -> bool {
let opponent_white = !board.playing;
let desired = !opponent_white;
let opponent_knights = board.knights & board.combined_coloured(desired);
let knights = opponent_knights.raw();
let l1 = (knights >> 1) & NOT_H_FILE;
let l2 = (knights >> 2) & NOT_GH_FILE;
let r1 = (knights << 1) & NOT_A_FILE;
let r2 = (knights << 2) & NOT_AB_FILE;
let h1 = l1 | r1;
let h2 = l2 | r2;
let attacks = (h1 << 16) | (h1 >> 16) | (h2 << 8) | (h2 >> 8);
(attacks & (1u64 << square)) != 0
}
fn is_square_attacked_king(board: &GameBoard, square: u8) -> bool {
let opponent_white = !board.playing;
let desired = !opponent_white;
let opponent_kings = board.kings & board.combined_coloured(desired);
let kings = opponent_kings.raw();
let east = (kings << 1) & NOT_A_FILE;
let west = (kings >> 1) & NOT_H_FILE;
let attacks = east | west;
let king_set = kings | attacks;
let north = king_set << 8;
let south = king_set >> 8;
let all_attacks = attacks | north | south;
(all_attacks & (1u64 << square)) != 0
}
fn is_square_attacked_sliding(
board: &GameBoard,
square: u8,
dirs: &[i8],
piece_bb: BitBoard,
opponent_white: bool,
) -> bool {
let occ: u64 = board.combined().into();
let colour_mask: u64 = board.colour.into();
let piece_mask: u64 = piece_bb.into();
for &dir in dirs {
let mut idx: usize = 0;
while idx < DIR_OFFSETS.len() {
if DIR_OFFSETS[idx] == dir {
break;
}
idx += 1;
}
if idx >= DIR_OFFSETS.len() {
continue; }
let ray_mask = RAYS[square as usize][idx];
let blockers = occ & ray_mask;
if blockers == 0 {
continue;
}
let blocker_sq: u8 = if DIR_OFFSETS[idx] > 0 {
blockers.trailing_zeros() as u8
} else {
(63 - blockers.leading_zeros()) as u8
};
let bit = 1u64 << blocker_sq;
let square_is_opponent = ((colour_mask & bit) != 0) == opponent_white;
if square_is_opponent && (piece_mask & bit) != 0 {
return true;
}
}
false
}
fn is_square_attacked_rook(board: &GameBoard, square: u8) -> bool {
let opponent_white = !board.playing;
let desired = !opponent_white;
let opponent_rooks = board.rooks & board.combined_coloured(desired);
let opponent_queens = board.queens & board.combined_coloured(desired);
let piece_bb = opponent_rooks | opponent_queens;
let dirs: [i8; 4] = [1, -1, 8, -8];
is_square_attacked_sliding(board, square, &dirs, piece_bb, opponent_white)
}
fn is_square_attacked_bishop(board: &GameBoard, square: u8) -> bool {
let opponent_white = !board.playing;
let desired = !opponent_white;
let opponent_bishops = board.bishops & board.combined_coloured(desired);
let opponent_queens = board.queens & board.combined_coloured(desired);
let piece_bb = opponent_bishops | opponent_queens;
let dirs: [i8; 4] = [9, -9, 7, -7];
is_square_attacked_sliding(board, square, &dirs, piece_bb, opponent_white)
}
pub fn is_square_attacked(board: &GameBoard, square: u8) -> bool {
is_square_attacked_pawn(board, square)
|| is_square_attacked_knight(board, square)
|| is_square_attacked_king(board, square)
|| is_square_attacked_rook(board, square)
|| is_square_attacked_bishop(board, square)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::*;
use crate::model::gamedata::GameData;
fn get_board(fen: &str) -> GameBoard {
GameData::from_fen(fen).unwrap().board
}
#[test]
fn test_pawn_attacks() {
let board = get_board("8/8/8/8/4p3/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, D3));
assert!(is_square_attacked(&board, F3));
assert!(!is_square_attacked(&board, E4));
assert!(!is_square_attacked(&board, E3));
let board = get_board("8/8/8/4P3/8/8/8/8 b - - 0 1");
assert!(is_square_attacked(&board, D6));
assert!(is_square_attacked(&board, F6));
assert!(!is_square_attacked(&board, E5));
assert!(!is_square_attacked(&board, E6));
}
#[test]
fn test_pawn_edge_attacks() {
let board = get_board("8/8/8/p7/8/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, B4));
assert!(!is_square_attacked(&board, A4));
assert!(!is_square_attacked(&board, A5));
let board = get_board("8/8/8/8/7P/8/8/8 b - - 0 1");
assert!(is_square_attacked(&board, G5));
assert!(!is_square_attacked(&board, H5));
assert!(!is_square_attacked(&board, H4));
}
#[test]
fn test_pawn_backward_not_attack() {
let board = get_board("8/8/8/8/4p3/8/8/8 w - - 0 1");
assert!(!is_square_attacked(&board, E5));
assert!(!is_square_attacked(&board, D5));
assert!(!is_square_attacked(&board, F5));
let board = get_board("8/8/8/4P3/8/8/8/8 b - - 0 1");
assert!(!is_square_attacked(&board, D4));
assert!(!is_square_attacked(&board, F4));
}
#[test]
fn test_pawn_attacks_occupied_target() {
let board = get_board("8/8/8/8/4p3/3n4/8/8 w - - 0 1");
assert!(is_square_attacked(&board, D3));
let board = get_board("8/8/5N2/4P3/8/8/8/8 b - - 0 1");
assert!(is_square_attacked(&board, F6));
}
#[test]
fn test_knight_attacks() {
let board = get_board("8/8/8/8/3n4/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, C2));
assert!(is_square_attacked(&board, E2));
assert!(is_square_attacked(&board, B3));
assert!(is_square_attacked(&board, F3));
assert!(is_square_attacked(&board, B5));
assert!(is_square_attacked(&board, F5));
assert!(is_square_attacked(&board, C6));
assert!(is_square_attacked(&board, E6));
assert!(!is_square_attacked(&board, D4));
}
#[test]
fn test_knight_edge_attacks() {
let board = get_board("8/8/8/8/8/8/8/n7 w - - 0 1");
assert!(is_square_attacked(&board, B3));
assert!(is_square_attacked(&board, C2));
assert!(!is_square_attacked(&board, A1));
assert!(!is_square_attacked(&board, A2));
}
#[test]
fn test_knight_blocking_irrelevant() {
let board = get_board("8/8/3P1P2/8/3n4/3P1P2/8/8 w - - 0 1");
assert!(is_square_attacked(&board, C2));
assert!(is_square_attacked(&board, E2));
assert!(is_square_attacked(&board, B5));
assert!(is_square_attacked(&board, F5));
}
#[test]
fn test_knight_only_opponent_counted() {
let board = get_board("8/8/8/3N4/3n4/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, C2));
assert!(!is_square_attacked(&board, C3));
}
#[test]
fn test_bishop_attacks() {
let board = get_board("8/8/8/8/3b4/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, A1));
assert!(is_square_attacked(&board, B2));
assert!(is_square_attacked(&board, C3));
assert!(is_square_attacked(&board, E5));
assert!(is_square_attacked(&board, F6));
assert!(is_square_attacked(&board, G7));
assert!(is_square_attacked(&board, H8));
assert!(!is_square_attacked(&board, D4));
assert!(!is_square_attacked(&board, D5));
}
#[test]
fn test_bishop_blocked() {
let board = get_board("8/8/8/2P1P3/3b4/2P1P3/8/8 w - - 0 1");
assert!(!is_square_attacked(&board, A1));
assert!(!is_square_attacked(&board, H8));
assert!(is_square_attacked(&board, C3));
assert!(is_square_attacked(&board, E5));
}
#[test]
fn test_bishop_corner() {
let board = get_board("8/8/8/8/8/8/8/b7 w - - 0 1");
assert!(is_square_attacked(&board, B2));
assert!(is_square_attacked(&board, C3));
assert!(is_square_attacked(&board, H8));
assert!(!is_square_attacked(&board, A1));
}
#[test]
fn test_rook_attacks() {
let board = get_board("8/8/8/8/3r4/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, D1));
assert!(is_square_attacked(&board, D8));
assert!(is_square_attacked(&board, A4));
assert!(is_square_attacked(&board, H4));
assert!(!is_square_attacked(&board, E5));
}
#[test]
fn test_rook_blocked() {
let board = get_board("8/8/3P4/3r4/3P4/8/8/8 w - - 0 1");
assert!(!is_square_attacked(&board, D1));
assert!(!is_square_attacked(&board, D8));
assert!(is_square_attacked(&board, A5));
assert!(is_square_attacked(&board, H5));
}
#[test]
fn test_rook_friendly_block() {
let board = get_board("8/8/8/8/2prp3/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, D1));
assert!(is_square_attacked(&board, D8));
assert!(is_square_attacked(&board, C4)); assert!(is_square_attacked(&board, E4)); assert!(!is_square_attacked(&board, A4));
assert!(!is_square_attacked(&board, H4));
}
#[test]
fn test_queen_attacks() {
let board = get_board("8/8/8/8/3q4/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, D1));
assert!(is_square_attacked(&board, D8));
assert!(is_square_attacked(&board, A4));
assert!(is_square_attacked(&board, H4));
assert!(is_square_attacked(&board, A1));
assert!(is_square_attacked(&board, H8));
assert!(!is_square_attacked(&board, E2));
}
#[test]
fn test_queen_blocked() {
let board = get_board("8/8/8/3p1p2/2pqp3/5p2/8/8 w - - 0 1");
assert!(is_square_attacked(&board, C3)); assert!(!is_square_attacked(&board, C2)); assert!(!is_square_attacked(&board, H4)); }
#[test]
fn test_queen_mixed_attacks() {
let board = get_board("8/8/8/8/3q4/3P4/8/8 w - - 0 1");
assert!(is_square_attacked(&board, C3));
assert!(is_square_attacked(&board, D3));
assert!(!is_square_attacked(&board, D2));
assert!(is_square_attacked(&board, E3));
}
#[test]
fn test_king_attacks() {
let board = get_board("8/8/8/8/3k4/8/8/8 w - - 0 1");
assert!(is_square_attacked(&board, C3));
assert!(is_square_attacked(&board, D3));
assert!(is_square_attacked(&board, E3));
assert!(is_square_attacked(&board, C4));
assert!(is_square_attacked(&board, E4));
assert!(is_square_attacked(&board, C5));
assert!(is_square_attacked(&board, D5));
assert!(is_square_attacked(&board, E5));
assert!(!is_square_attacked(&board, D4));
assert!(!is_square_attacked(&board, A1));
}
#[test]
fn test_no_attacks() {
let board = get_board("8/8/8/8/8/8/8/8 w - - 0 1");
for i in 0..64 {
assert!(!is_square_attacked(&board, i));
}
}
#[test]
fn test_initial_position_no_attacks_in_center() {
let board = get_board("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
assert!(!is_square_attacked(&board, E4));
assert!(!is_square_attacked(&board, D4));
assert!(!is_square_attacked(&board, E5));
assert!(!is_square_attacked(&board, D5));
}
#[test]
fn test_reciprocal_king_attack() {
let board = get_board("4k3/8/8/8/8/8/8/4K3 b - - 0 1");
assert!(is_square_attacked(&board, E2));
assert!(is_square_attacked(&board, D2));
assert!(is_square_attacked(&board, F2));
}
#[test]
fn test_king_edge_attacks() {
let board = get_board("8/8/8/8/8/8/8/k7 w - - 0 1");
assert!(is_square_attacked(&board, A2));
assert!(is_square_attacked(&board, B1));
assert!(is_square_attacked(&board, B2));
assert!(!is_square_attacked(&board, A1));
assert!(!is_square_attacked(&board, C2));
}
}