Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions chess/src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -916,4 +916,20 @@ mod tests {
assert_eq!(piece_kind_bb, *black_pawns_bb | *white_pawns_bb);
assert_eq!(piece_kind_bb.number_of_occupied_squares(), 16);
}

#[test]
fn piece_bitboards_for_side() {
let board = Board::default_board();
let white_pieces_bb = board.pieces(Side::White);
let black_pieces_bb = board.pieces(Side::Black);

let expected_white_bb = 0x000000000000FFFF;
let expected_black_bb = 0xFFFF000000000000;

assert_eq!(white_pieces_bb, Bitboard::new(expected_white_bb));
assert_eq!(black_pieces_bb, Bitboard::new(expected_black_bb));

let all = board.all_pieces();
assert_eq!(all, white_pieces_bb | black_pieces_bb);
}
}
30 changes: 6 additions & 24 deletions chess/src/move_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
moves::{Move, MoveDescriptor, MoveType, PromotionDescriptor},
non_slider_piece::NonSliderPiece,
piece_category::PieceCategory,
pieces::{Piece, SQUARE_NAME},
pieces::{ALL_PIECES, Piece, SQUARE_NAME},
rank::Rank,
side::Side,
sliding_piece_attacks::SlidingPieceAttacks,
Expand Down Expand Up @@ -553,25 +553,16 @@ impl MoveGenerator {
/// # Returns
///
/// A bitboard representing all squares currently being attacked by the given side.
pub(crate) fn get_attacked_squares(
pub fn get_attacked_squares(
&self,
board: &Board,
side: Side,
occupancy: &Bitboard,
) -> Bitboard {
let mut attacks = Bitboard::default();

// get the squares attacked by each piece
for piece in [
Piece::Bishop,
Piece::Rook,
Piece::Queen,
Piece::King,
Piece::Knight,
Piece::Pawn,
]
.iter()
{
// Get the squares attacked by each piece
for piece in ALL_PIECES.iter() {
let mut piece_bb = *board.piece_bitboard(*piece, side);
if piece_bb.as_number() == 0 {
continue;
Expand Down Expand Up @@ -618,14 +609,6 @@ impl MoveGenerator {
self.get_non_slider_attacks(Side::opposite(attacking_side), non_slider, square)
}
}
// TODO(PT): Remove this old code
// if piece.is_slider() {
// self.get_slider_attacks(piece, square, occupancy)
// } else if piece == Piece::Pawn {
// self.pawn_attacks[Side::opposite(attacking_side) as usize][square as usize]
// } else {
// self.get_non_slider_attacks(piece, square)
// }
}

/// Generates pseudo-legal moves for the current board state.
Expand Down Expand Up @@ -894,9 +877,8 @@ impl MoveGenerator {
// en passant
let bb_en_passant = match board.en_passant_square() {
Some(en_passant_square) => {
// we only want to add the en passant square if it is within range of the pawn
// this means that the en passant square is within 1 rank of the pawn and the en passant square
// is in the pawn's attack table
// We only want to add the en passant square if it is within range of the pawn.
// This means that the en passant square is within 1 rank of the pawn and the en passant square is in the pawn's attack table
let en_passant_bb = Bitboard::from_square(en_passant_square);
let result = en_passant_bb & !(attack_bb);
let is_in_range = result == 0;
Expand Down
116 changes: 91 additions & 25 deletions engine/src/history_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use chess::{
use crate::score::{LargeScoreType, Score};

pub struct HistoryTable {
table: [[[LargeScoreType; NumberOf::SQUARES]; NumberOf::PIECE_TYPES]; NumberOf::SIDES],
/// The history table is a 5D array indexed by [is_from_attacked][is_to_attacked][side][piece_type][square (to)]
table: [[[[[LargeScoreType; NumberOf::SQUARES]; NumberOf::PIECE_TYPES]; NumberOf::SIDES];
NumberOf::SIDES]; NumberOf::SIDES],
}

/// Safe calculation of the bonus applied to quiet moves that are inserted into the history table.
Expand All @@ -28,31 +30,53 @@ pub(crate) fn calculate_bonus_for_depth(depth: i16) -> i16 {

impl HistoryTable {
pub(crate) fn new() -> Self {
let table =
[[[Default::default(); NumberOf::SQUARES]; NumberOf::PIECE_TYPES]; NumberOf::SIDES];
let table = [[[[[Default::default(); NumberOf::SQUARES]; NumberOf::PIECE_TYPES];
NumberOf::SIDES]; NumberOf::SIDES]; NumberOf::SIDES];
Self { table }
}

pub(crate) fn get(&self, side: Side, piece: Piece, square: u8) -> LargeScoreType {
self.table[side as usize][piece as usize][square as usize]
pub(crate) fn get(
&self,
side: Side,
piece: Piece,
square: u8,
is_from_attacked: bool,
is_to_attacked: bool,
) -> LargeScoreType {
self.table[is_from_attacked as usize][is_to_attacked as usize][side as usize]
[piece as usize][square as usize]
}

pub(crate) fn update(&mut self, side: Side, piece: Piece, square: u8, bonus: LargeScoreType) {
let current_value = self.table[side as usize][piece as usize][square as usize];
fn get_mut(
&mut self,
side: Side,
piece: Piece,
square: u8,
is_from_attacked: bool,
is_to_attacked: bool,
) -> &mut LargeScoreType {
&mut self.table[is_from_attacked as usize][is_to_attacked as usize][side as usize]
[piece as usize][square as usize]
}

pub(crate) fn update(
&mut self,
side: Side,
piece: Piece,
square: u8,
is_from_attacked: bool,
is_to_attacked: bool,
bonus: LargeScoreType,
) {
let current_value = self.get(side, piece, square, is_from_attacked, is_to_attacked);
let clamped_bonus = bonus.clamp(-Score::MAX_HISTORY, Score::MAX_HISTORY);
let new_value = current_value + clamped_bonus
- current_value * clamped_bonus.abs() / Score::MAX_HISTORY;
self.table[side as usize][piece as usize][square as usize] = new_value;
*self.get_mut(side, piece, square, is_from_attacked, is_to_attacked) = new_value;
}

pub(crate) fn clear(&mut self) {
for side in 0..NumberOf::SIDES {
for piece_type in 0..NumberOf::PIECE_TYPES {
for square in 0..NumberOf::SQUARES {
self.table[side][piece_type][square] = Default::default();
}
}
}
*self = Self::new();
}

pub(crate) fn print_for_side(&self, side: Side) {
Expand All @@ -63,7 +87,11 @@ impl HistoryTable {
print!("|");
for file in 0..NumberOf::FILES {
let square = file + rank * NumberOf::FILES;
print!("{:5} ", self.table[side as usize][piece_type][square]);
let grid = self.table[side as usize][piece_type][square];
print!(
"{:5} | {:5}\n------\n[{:5} | {:5} ]",
grid[0][0], grid[0][1], grid[1][0], grid[1][1]
);
Comment on lines +90 to +94
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't actually tested this to see what it looks like - might have broken things.

}
println!("|");
}
Expand All @@ -88,13 +116,23 @@ mod tests {
fn initialize_history_table() {
let history_table = HistoryTable::new();
// loop through all sides, piece types, and squares
for side in 0..2 {
for side in 0..2u8 {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the actual enum type here Side

for piece_type in 0..6 {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, use Piece directly

for square in 0..64 {
assert_eq!(
history_table.table[side][piece_type][square],
Default::default()
);
for is_from_attacked in 0..2 {
for is_to_attacked in 0..2 {
assert_eq!(
history_table.get(
Side::try_from(side).unwrap(),
Piece::try_from(piece_type).unwrap(),
square as u8,
is_from_attacked != 0,
is_to_attacked != 0,
),
Default::default()
);
}
}
}
}
}
Expand All @@ -107,10 +145,18 @@ mod tests {
let piece = Piece::Pawn;
let square = Squares::A1;
let score = 37;
history_table.update(side, piece, square, score);
assert_eq!(history_table.get(side, piece, square), score);
history_table.update(side, piece, square, score);
assert_eq!(history_table.get(side, piece, square), score + score);
let is_from_attacked = true;
let is_to_attacked = false;
history_table.update(side, piece, square, is_from_attacked, is_to_attacked, score);
assert_eq!(
history_table.get(side, piece, square, is_from_attacked, is_to_attacked),
score
);
history_table.update(side, piece, square, is_from_attacked, is_to_attacked, score);
assert_eq!(
history_table.get(side, piece, square, is_from_attacked, is_to_attacked),
score + score
);
}

#[test]
Expand All @@ -121,4 +167,24 @@ mod tests {
assert!(bonus as i32 <= i16::MAX.into());
}
}

#[test]
fn update_and_then_clear() {
let mut history_table = HistoryTable::new();
let side = Side::White;
let piece = Piece::Knight;
let square = Squares::E4;
let is_from_attacked = false;
let is_to_attacked = true;
let bonus = 50;

history_table.update(side, piece, square, is_from_attacked, is_to_attacked, bonus);
let stored_value = history_table.get(side, piece, square, is_from_attacked, is_to_attacked);
assert_eq!(stored_value, bonus);

history_table.clear();
let cleared_value =
history_table.get(side, piece, square, is_from_attacked, is_to_attacked);
assert_eq!(cleared_value, 0);
}
}
42 changes: 36 additions & 6 deletions engine/src/move_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use std::cmp::Ordering;

use anyhow::{Ok, Result};
use arrayvec::ArrayVec;
use chess::{definitions::MAX_MOVE_LIST_SIZE, moves::Move, pieces::Piece, side::Side};
use chess::{
bitboard::Bitboard, definitions::MAX_MOVE_LIST_SIZE, moves::Move, pieces::Piece, side::Side,
};

use crate::{
evaluation::Evaluation, hce_values::ByteKnightValues, history_table, score::LargeScoreType,
Expand Down Expand Up @@ -60,6 +62,7 @@ impl MoveOrder {
mv: &Move,
tt_move: &Option<Move>,
history_table: &history_table::HistoryTable,
attacked_by_opponent: &Bitboard,
) -> Self {
if tt_move.is_some_and(|tt| *mv == tt) {
return Self::TtMove;
Expand All @@ -71,7 +74,9 @@ impl MoveOrder {
return Self::Capture(victim, attacker);
}

let score = history_table.get(stm, mv.piece(), mv.to());
let is_from_attacked = attacked_by_opponent.is_square_occupied(mv.from());
let is_to_attacked = attacked_by_opponent.is_square_occupied(mv.to());
let score = history_table.get(stm, mv.piece(), mv.to(), is_from_attacked, is_to_attacked);
Self::Quiet(score)
}

Expand All @@ -80,12 +85,19 @@ impl MoveOrder {
moves: &[Move],
tt_move: &Option<Move>,
history_table: &history_table::HistoryTable,
attacked_by_opponent: &Bitboard,
move_order: &mut ArrayVec<MoveOrder, MAX_MOVE_LIST_SIZE>,
) -> Result<()> {
move_order.clear();

for mv in moves.iter() {
move_order.try_push(Self::classify(stm, mv, tt_move, history_table))?;
move_order.try_push(Self::classify(
stm,
mv,
tt_move,
history_table,
attacked_by_opponent,
))?;
}

Ok(())
Expand All @@ -94,10 +106,13 @@ impl MoveOrder {

#[cfg(test)]
mod tests {
use chess::{board::Board, move_generation::MoveGenerator, move_list::MoveList, moves::Move};
use chess::{
board::Board, move_generation::MoveGenerator, move_list::MoveList, moves::Move, side::Side,
};
use itertools::Itertools;

use crate::{
history_table::HistoryTable,
move_order::MoveOrder,
score::Score,
ttable::{EntryFlag, TranspositionTable, TranspositionTableEntry},
Expand All @@ -106,14 +121,20 @@ mod tests {
#[test]
fn verify_move_ordering() {
let mut tt = TranspositionTable::from_capacity(10);
let mut history_table = crate::history_table::HistoryTable::new();
let mut history_table = HistoryTable::new();

let move_gen = MoveGenerator::new();
let mut move_list = MoveList::new();
let board =
Board::from_fen("rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB1R w KQkq - 0 1").unwrap();
move_gen.generate_legal_moves(&board, &mut move_list);

let attacked_by_opponent = move_gen.get_attacked_squares(
&board,
Side::opposite(board.side_to_move()),
&board.all_pieces(),
);

assert!(move_list.len() >= 6);
let depth = 3i32;
let first_mv = move_list.at(4).unwrap();
Expand All @@ -130,15 +151,24 @@ mod tests {
board.side_to_move(),
second_mv.piece(),
second_mv.to(),
attacked_by_opponent.is_square_occupied(second_mv.from()),
attacked_by_opponent.is_square_occupied(second_mv.to()),
300 * depth - 250,
);
let tt_entry = tt.get_entry(board.zobrist_hash()).unwrap();
let tt_move = tt_entry.board_move;

// sort the moves
let moves = move_list
.iter()
.sorted_by_key(|mv| {
MoveOrder::classify(board.side_to_move(), mv, &Some(tt_move), &history_table)
MoveOrder::classify(
board.side_to_move(),
mv,
&Some(tt_move),
&history_table,
&attacked_by_opponent,
)
})
.collect::<Vec<&Move>>();

Expand Down
Loading