Support Functions
The board implementation contains a number of small support functions that are used throughout the engine. Most of them are simple accessors or utility routines that provide frequently requested information, such as the side to move, piece locations, occupancy bitboards, square properties, and rank-related calculations.
Because these functions are called extremely often by move generation, evaluation, legality checking, and search, nearly all of them are marked with #[inline(always)]. The purpose is to eliminate the overhead of a function call for operations that typically consist of only a few instructions. While modern compilers are usually capable of making sensible inlining decisions on their own, explicitly marking these tiny helper functions communicates their intent and encourages the compiler to embed their code directly at the call site. Since many of these functions are executed millions or even billions of times during a search, even a very small reduction in overhead can contribute to overall engine performance.
reset()
Resets the board to an empty state. All bitboards, game-state information, move history, and piece lists are cleared, preparing the board for a new position.
pub fn reset(&mut self) {
self.bb_pieces = EverySide::new(EveryPiece::new(Bitboard::empty()));
self.bb_side = EverySide::new(Bitboard::empty());
self.game_state.clear();
self.history.clear();
self.piece_list = EverySquare::new(Piece::None)
}
us()
Returns the side whose turn it is to move.
#[inline(always)]
pub fn us(&self) -> Side {
self.game_state.active_color
}
opponent()
Returns the side that is not currently moving.
#[inline(always)]
pub fn opponent(&self) -> Side {
self.game_state.active_color.other()
}
occupancy()
Returns a bitboard containing all occupied squares on the board, regardless of color.
#[inline(always)]
pub fn occupancy(&self) -> Bitboard {
self.bb_side[Side::White] | self.bb_side[Side::Black]
}
my_pieces()
Returns a bitboard containing all pieces belonging to the side to move.
#[inline(always)]
pub fn my_pieces(&self) -> Bitboard {
self.bb_side[self.us()]
}
opponent_pieces()
Returns a bitboard containing all pieces belonging to the side that is not to move.
#[inline(always)]
pub fn opponent_pieces(&self) -> Bitboard {
self.bb_side[self.opponent()]
}
white_pieces()
Returns a bitboard containing all white pieces.
#[inline(always)]
pub fn white_pieces(&self) -> Bitboard {
self.bb_side[Side::White]
}
black_pieces()
Returns a bitboard containing all black pieces.
#[inline(always)]
pub fn black_pieces(&self) -> Bitboard {
self.bb_side[Side::Black]
}
empty()
Returns a bitboard containing all empty squares on the board.
#[inline(always)]
pub fn empty(&self) -> Bitboard {
!self.occupancy()
}
get_pieces()
Returns the bitboard for a specific piece type belonging to a given side.
#[inline(always)]
pub fn get_pieces(&self, side: Side, piece: Piece) -> Bitboard {
self.bb_pieces[side][piece]
}
get_bitboards()
Returns a reference to all piece bitboards for the specified side.
#[inline(always)]
pub fn get_bitboards(&self, side: Side) -> &EveryPiece<Bitboard> {
&self.bb_pieces[side]
}
king_square()
Returns the square occupied by the king of the specified side. Since a legal position contains exactly one king per side, the square can be obtained directly from the king bitboard.
#[inline(always)]
pub fn king_square(&self, side: Side) -> Square {
let square_nr = self.bb_pieces[side][Piece::King].trailing_zeros();
Square::from(square_nr)
}
has_bishop_pair()
Determines whether a side possesses the bishop pair. The function verifies that at least one bishop resides on a light square and at least one bishop resides on a dark square.
pub fn has_bishop_pair(&self, side: Side) -> bool {
let mut bishops = self.get_pieces(side, Piece::Bishop);
let mut white_square = 0;
let mut black_square = 0;
if bishops.count_ones() >= 2 {
while bishops > Bitboard::empty() {
let square = bits::next(&mut bishops);
if Board::is_white_square(square) {
white_square += 1;
} else {
black_square += 1;
}
}
}
white_square >= 1 && black_square >= 1
}
file_and_rank()
Converts a square index into separate file and rank coordinates.
#[inline(always)]
pub fn file_and_rank(square: Square) -> Location {
let file = (square % 8) as u8;
let rank = (square / 8) as u8;
Location { file, rank }
}
square_on_rank()
Checks whether a given square belongs to a specified rank.
#[inline(always)]
pub fn square_on_rank(square: Square, rank: u8) -> bool {
let start = rank * 8;
let end = start + 7;
let square_nr = square as u8;
(start..=end).contains(&square_nr)
}
fourth_rank()
Returns the fourth rank from the perspective of the given side. For White this is rank four; for Black it is rank five.
#[inline(always)]
pub const fn fourth_rank(side: Side) -> Rank {
match side {
Side::White => Rank::R4,
Side::Black => Rank::R5,
}
}
promotion_rank()
Returns the promotion rank for the specified side.
#[inline(always)]
pub const fn promotion_rank(side: Side) -> Rank {
match side {
Side::White => Rank::R8,
Side::Black => Rank::R1,
}
}
is_white_square()
Determines whether a square is a light-colored square on the chessboard.
#[inline(always)]
pub fn is_white_square(square: Square) -> bool {
let rank = square / 8;
let even_square = (square as usize) & 1 == 0;
let even_rank = (rank & 1) == 0;
(even_rank && !even_square) || (!even_rank && even_square)
}
pawn_direction()
Returns the forward movement direction for a pawn of the given side. White pawns advance toward increasing square numbers, while Black pawns advance toward decreasing square numbers.
#[inline(always)]
pub const fn pawn_direction(side: Side) -> i8 {
match side {
Side::White => 8,
Side::Black => -8,
}
}
is_white_to_move()
Convenience function that returns true when White is the side to move and false otherwise.
#[inline(always)]
pub fn is_white_to_move(&self) -> bool {
self.us() == Side::White
}
The support functions presented in this section are small, but they play an important role throughout the engine, especially with regard to code readability. They provide quick access to frequently needed information and encapsulate common board-related operations behind a clean and consistent interface. As the engine grows, these helper functions will be called from virtually every subsystem.
With these utilities in place, we can now move on to one of the board’s most important responsibilities: setting up a position from a Forsyth-Edwards Notation (FEN) string. Every chess engine must be able to initialize arbitrary positions accurately and efficiently, making FEN parsing one of the foundational board functions we need to implement.