Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 131 additions & 1 deletion chess/src/attacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ use crate::{
attacks,
bitboard::Bitboard,
bitboard_helpers,
board::Board,
definitions::NumberOf,
file::File,
magics::{BISHOP_MAGICS, ROOK_MAGICS},
pieces::Piece,
rays,
side::Side,
};

Expand Down Expand Up @@ -478,7 +480,7 @@ pub fn knight(square: u8) -> Bitboard {
///
/// # Returns
/// - A [`Bitboard`] representing the possible attacks of piece on the given square with the given occupancy.
pub fn for_piece(piece: Piece, square: u8, occupancy: Bitboard, side: Side) -> Bitboard {
pub fn for_piece_on_square(piece: Piece, square: u8, occupancy: Bitboard, side: Side) -> Bitboard {
match piece {
Piece::Bishop => attacks::bishop(square, occupancy),
Piece::King => attacks::king(square),
Expand All @@ -489,16 +491,75 @@ pub fn for_piece(piece: Piece, square: u8, occupancy: Bitboard, side: Side) -> B
}
}

/// Get attack bitboard for all pieces of a given type on the board for the given side.
///
/// # Arguments
/// - `piece` - The [`Piece`] to get attacks for.
/// - `board` - The current [`Board`].
/// - `side` - The side to get attacks for.
///
/// # Returns
/// - A [`Bitboard`] representing the possible attacks of all pieces of the given type for the given side.
pub fn for_piece(piece: Piece, board: &Board, side: Side, occupancy: Bitboard) -> Bitboard {
let mut attacks_bb = Bitboard::default();
let piece_bb = board.piece_bitboard(piece, side);
for square in piece_bb.iter() {
attacks_bb |= for_piece_on_square(piece, square, occupancy, side);
}

attacks_bb
}

/// Get all pieces that are blocking the king from being in check.
///
/// # Arguments
/// - `board` - The current [`Board`].
/// - `side` - The side to check for blockers.
///
/// # Returns
/// - A [`Bitboard`] representing all the pieces that are blocking the king from being in check.
pub fn blockers_for_king(board: &Board, side: Side) -> Bitboard {
let king_square = board.king_square(side);
let mut blockers = Bitboard::default();

let rook_attacks = attacks::rook(king_square, Bitboard::default());
let bishop_attacks = attacks::bishop(king_square, Bitboard::default());

let them = side.opposite();

let rooks = board.piece_kind_bitboard(Piece::Rook);
let bishops = board.piece_kind_bitboard(Piece::Bishop);
let queens = board.piece_kind_bitboard(Piece::Queen);

// Snipers attack the king square when other other snipers are removed
let snipers = (rook_attacks & (rooks | queens))
| (bishop_attacks & (bishops | queens)) & board.pieces(them);

let occ = board.all_pieces() ^ snipers;

for sq in snipers.iter() {
let bb = rays::between(king_square, sq) & occ;
if bb.number_of_occupied_squares() == 1 {
blockers |= bb;
}
}

blockers
}

#[cfg(test)]
mod tests {
use crate::{
attacks::{self, BISHOP_ATTACKS, ROOK_ATTACKS},
bitboard::Bitboard,
board::Board,
definitions::{NumberOf, Squares},
file::File,
magics::{BISHOP_MAGICS, ROOK_MAGICS},
move_generation::MoveGenerator,
pieces::Piece,
rank::Rank,
side::Side,
};

const EXPECTED_ORTHOGONAL_ATTACKS: [u64; NumberOf::SQUARES] = [
Expand Down Expand Up @@ -1060,6 +1121,33 @@ mod tests {
}
}

#[test]
fn test_attacks_for_piece_on_square() {
let board = Board::default();

for sq in 0..NumberOf::SQUARES as u8 {
for piece in Piece::iter() {
let occ = board.all_pieces();
let side = Side::White;
let attacks = attacks::for_piece_on_square(piece, sq, occ, side);
let expected_attacks = match piece {
Piece::Bishop => attacks::bishop(sq, occ),
Piece::King => attacks::king(sq),
Piece::Knight => attacks::knight(sq),
Piece::Pawn => attacks::pawn(sq, side),
Piece::Queen => attacks::queen(sq, occ),
Piece::Rook => attacks::rook(sq, occ),
};

assert_eq!(
attacks, expected_attacks,
"Mismatch for piece {:?} on square {}",
piece, sq
);
}
}
}

#[test]
fn check_rook_attacks() {
for square in 0..NumberOf::SQUARES {
Expand All @@ -1080,6 +1168,31 @@ mod tests {
}
}

#[test]
fn test_attacks_for_piece() {
let board = Board::default();

for piece in Piece::iter() {
for side in Side::iter() {
let attacks = attacks::for_piece(piece, &board, side, board.all_pieces());
let mut expected_attacks = Bitboard::default();
let piece_bb = board.piece_bitboard(piece, side);
let occ = board.all_pieces();
for square in piece_bb.iter() {
expected_attacks |= attacks::for_piece_on_square(piece, square, occ, side);
}

println!("{}", attacks);

assert_eq!(
attacks, expected_attacks,
"Mismatch for piece {:?} for side {:?}",
piece, side
);
}
}
}

#[test]
fn check_bishop_attacks() {
for square in 0..1 {
Expand Down Expand Up @@ -1117,4 +1230,21 @@ mod tests {
println!("attacks without edges: \n{attacks_without_edges}");
assert_eq!(attacks_without_edges, queen_bb);
}

#[test]
fn test_blockers_for_king() {
const FEN: &str = "r6/2q2p1k/2P1b1pp/bB2P1n1/R2B2PN/p4P1P/P1Q4K/1R6 b - - 2 38";
let mut board = Board::from_fen(FEN).unwrap();
board.make_uci_move("f7f5").unwrap();
println!("{}\n{}", board.to_fen(), board);
let blockers = attacks::blockers_for_king(&board, Side::White);
let expected_blockers = Bitboard::from_square(Squares::E5);
assert_eq!(blockers, expected_blockers);

const FEN_2: &str = "8/p2r1pK1/6p1/1kp1P1P1/2p5/2P5/8/4R3 b - - 0 43";
let board_2 = Board::from_fen(FEN_2).unwrap();
let b2_blockers = attacks::blockers_for_king(&board_2, Side::White);
let expected_b2_blockers = Bitboard::from_square(Squares::F7);
assert_eq!(b2_blockers, expected_b2_blockers);
}
}
74 changes: 73 additions & 1 deletion chess/src/bitboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
},
};

use crate::square::Square;
use crate::{bitboard_helpers, square::Square};

/// Bitboard representation of a chess board.
/// LSB (bit 0) is a1, MSB (bit 63) is h8.
Expand Down Expand Up @@ -107,6 +107,38 @@ impl Bitboard {
pub const fn intersects(&self, other: Bitboard) -> bool {
Bitboard::new(self.as_number() & other.as_number()).number_of_occupied_squares() > 0
}

/// Iterate over the occupied squares in the bitboard.
///
/// Returns an iterator over the square indices (0-63) of the occupied squares.
/// Implemented using `bitboard_helpers::next_bit` to efficiently find the next set
/// using the Carry Rippler method.
pub fn iter(&self) -> impl Iterator<Item = u8> {
let mut bitboard = *self;
std::iter::from_fn(move || {
if bitboard.data == 0 {
None
} else {
Some(bitboard_helpers::next_bit(&mut bitboard) as u8)
}
})
}

/// Check if more than one bit is set in the bitboard.
///
/// # Returns
/// - `bool` - True if more than one bit is set, false otherwise.
pub fn more_than_one(&self) -> bool {
self.data & (self.data.saturating_sub(1)) != 0
}

/// Check if the bitboard is empty.
///
/// # Returns
/// - `bool` - True if the bitboard is empty. False otherwise.
pub fn empty(&self) -> bool {
self.data == 0
}
}

impl PartialOrd<u64> for Bitboard {
Expand Down Expand Up @@ -445,4 +477,44 @@ mod tests {
let bb = Bitboard::filled();
assert_eq!(bb.data, u64::MAX);
}

#[test]
fn iter() {
let bb = Bitboard::new(0b10110);
let squares: Vec<u8> = bb.iter().collect();
assert_eq!(squares, vec![1, 2, 4]);

// Now check the default position
let bb = Bitboard::new(0xFFFF00000000FFFF);
println!("{}", bb);
let squares: Vec<u8> = bb.iter().collect();
assert_eq!(
squares,
vec![
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63
]
);
}

#[test]
fn more_than_one() {
let bb = Bitboard::new(0b10110);
assert!(bb.more_than_one());

let bb2 = Bitboard::new(0b000001);
assert!(!bb2.more_than_one());

let bb3 = Bitboard::new(0b000000);
assert!(!bb3.more_than_one());
}

#[test]
fn test_empty() {
let bb = Bitboard::new(0b101010);
let result = bb ^ bb;
println!("{}", result);
assert!(result.number_of_occupied_squares() == 0);
assert!(result.empty());
}
}
2 changes: 2 additions & 0 deletions chess/src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// GNU General Public License v3.0 or later
// https://www.gnu.org/licenses/gpl-3.0-standalone.html

pub mod move_making;

use std::fmt::Display;
use std::iter::zip;

Expand Down
Loading