Refactor time info, update zobrist to tuple struct, naming and clippy

This commit is contained in:
stefiosif
2025-01-26 20:16:01 +02:00
parent 36aa30ea17
commit 53beda7fe3
11 changed files with 78 additions and 77 deletions

View File

@@ -31,6 +31,7 @@ pub const fn lsb(bitboard: Bitboard) -> usize {
bitboard.trailing_zeros() as usize bitboard.trailing_zeros() as usize
} }
#[allow(dead_code)]
pub const fn bit_count(bitboard: Bitboard) -> usize { pub const fn bit_count(bitboard: Bitboard) -> usize {
bitboard.count_ones() as usize bitboard.count_ones() as usize
} }

View File

@@ -83,7 +83,8 @@ impl Game {
mailbox.set_piece_at(mv.dst, Some(piece_at_src)); mailbox.set_piece_at(mv.dst, Some(piece_at_src));
} }
MoveType::Capture => { MoveType::Capture => {
let piece_at_dst = piece_at_dst.expect("Expected piece at: {mv.dst}"); let piece_at_dst =
piece_at_dst.unwrap_or_else(|| panic!("Expected piece at: {}", mv.dst));
board.remove_opponent_piece(mv.dst, piece_at_dst.0); board.remove_opponent_piece(mv.dst, piece_at_dst.0);
hash.update_capture(mv.src, mv.dst, piece_at_src.0, piece_at_dst.0, color); hash.update_capture(mv.src, mv.dst, piece_at_src.0, piece_at_dst.0, color);
board.move_piece(mv.src, mv.dst, piece_at_src.0); board.move_piece(mv.src, mv.dst, piece_at_src.0);
@@ -116,7 +117,8 @@ impl Game {
mailbox.set_piece_at(mv.dst, Some((promote.into_piece_type(), color))); mailbox.set_piece_at(mv.dst, Some((promote.into_piece_type(), color)));
} }
MoveType::PromotionCapture(promote) => { MoveType::PromotionCapture(promote) => {
let piece_at_dst = piece_at_dst.expect("Expected piece at dst: {mv.dst}"); let piece_at_dst =
piece_at_dst.unwrap_or_else(|| panic!("Expected piece at: {}", mv.dst));
board.remove_own_piece(mv.src, piece_at_src.0); board.remove_own_piece(mv.src, piece_at_src.0);
board.remove_opponent_piece(mv.dst, piece_at_dst.0); board.remove_opponent_piece(mv.dst, piece_at_dst.0);
board.promote_piece(mv.dst, promote); board.promote_piece(mv.dst, promote);

View File

@@ -1,5 +1,6 @@
pub struct Square {} pub struct Square {}
#[allow(dead_code)]
impl Square { impl Square {
pub const A1: usize = 0; pub const A1: usize = 0;
pub const B1: usize = 1; pub const B1: usize = 1;

View File

@@ -17,7 +17,7 @@ pub fn zobrist_keys() -> &'static ZobristKeys {
} }
pub struct ZobristKeys { pub struct ZobristKeys {
piece_square_color: [[[u64; 2]; 6]; 64], square_piece_color: [[[u64; 2]; 6]; 64],
en_passant: [u64; 8], en_passant: [u64; 8],
castling_ability: [[u64; 2]; 4], castling_ability: [[u64; 2]; 4],
side_to_move: u64, side_to_move: u64,
@@ -30,8 +30,8 @@ impl ZobristKeys {
for square in Square::A1..=Square::H8 { for square in Square::A1..=Square::H8 {
for piece_idx in 0..6 { for piece_idx in 0..6 {
keys.piece_square_color[square][piece_idx][0] = state.next_u64(); keys.square_piece_color[square][piece_idx][0] = state.next_u64();
keys.piece_square_color[square][piece_idx][1] = state.next_u64(); keys.square_piece_color[square][piece_idx][1] = state.next_u64();
} }
} }
@@ -56,7 +56,7 @@ impl ZobristKeys {
while bitboard != 0 { while bitboard != 0 {
let square = lsb(bitboard); let square = lsb(bitboard);
hash ^= self.piece_square_color[square][piece_type][Color::White]; hash ^= self.square_piece_color[square][piece_type][Color::White];
bitboard &= bitboard - 1; bitboard &= bitboard - 1;
} }
@@ -64,7 +64,7 @@ impl ZobristKeys {
while bitboard != 0 { while bitboard != 0 {
let square = lsb(bitboard); let square = lsb(bitboard);
hash ^= self.piece_square_color[square][piece_type][Color::Black]; hash ^= self.square_piece_color[square][piece_type][Color::Black];
bitboard &= bitboard - 1; bitboard &= bitboard - 1;
} }
}); });
@@ -87,7 +87,7 @@ impl ZobristKeys {
impl Default for ZobristKeys { impl Default for ZobristKeys {
fn default() -> Self { fn default() -> Self {
Self { Self {
piece_square_color: [[[0; 2]; 6]; 64], square_piece_color: [[[0; 2]; 6]; 64],
en_passant: [0; 8], en_passant: [0; 8],
castling_ability: [[0; 2]; 4], castling_ability: [[0; 2]; 4],
side_to_move: 0, side_to_move: 0,
@@ -96,18 +96,16 @@ impl Default for ZobristKeys {
} }
#[derive(Debug, Clone, PartialEq, Eq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct ZobristHash { pub struct ZobristHash(pub u64);
pub hash: u64,
}
impl ZobristHash { impl ZobristHash {
pub const fn new(hash: u64) -> Self { pub const fn new(hash: u64) -> Self {
Self { hash } Self(hash)
} }
pub fn update_side_to_move_key(&mut self) { pub fn update_side_to_move_key(&mut self) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.side_to_move self.0 ^= keys.side_to_move
} }
pub fn update_castling_ability_keys( pub fn update_castling_ability_keys(
@@ -116,16 +114,16 @@ impl ZobristHash {
new_castling_ability: [Castle; 2], new_castling_ability: [Castle; 2],
) { ) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.castling_ability[old_castling_ability[0]][0]; self.0 ^= keys.castling_ability[old_castling_ability[0]][0];
self.hash ^= keys.castling_ability[old_castling_ability[1]][1]; self.0 ^= keys.castling_ability[old_castling_ability[1]][1];
self.hash ^= keys.castling_ability[new_castling_ability[0]][0]; self.0 ^= keys.castling_ability[new_castling_ability[0]][0];
self.hash ^= keys.castling_ability[new_castling_ability[1]][1] self.0 ^= keys.castling_ability[new_castling_ability[1]][1]
} }
pub fn update_quiet(&mut self, src: usize, dst: usize, piece_at_src: PieceType, color: Color) { pub fn update_quiet(&mut self, src: usize, dst: usize, piece_at_src: PieceType, color: Color) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][piece_at_src][color]; self.0 ^= keys.square_piece_color[src][piece_at_src][color];
self.hash ^= keys.piece_square_color[dst][piece_at_src][color]; self.0 ^= keys.square_piece_color[dst][piece_at_src][color];
} }
pub fn update_capture( pub fn update_capture(
@@ -137,16 +135,16 @@ impl ZobristHash {
color: Color, color: Color,
) { ) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][piece_at_src][color]; self.0 ^= keys.square_piece_color[src][piece_at_src][color];
self.hash ^= keys.piece_square_color[dst][piece_at_src][color]; self.0 ^= keys.square_piece_color[dst][piece_at_src][color];
self.hash ^= keys.piece_square_color[dst][piece_at_dst][color.opponent()] self.0 ^= keys.square_piece_color[dst][piece_at_dst][color.opponent()]
} }
pub fn update_en_passant(&mut self, src: usize, dst: usize, ep_capture: usize, color: Color) { pub fn update_en_passant(&mut self, src: usize, dst: usize, ep_capture: usize, color: Color) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][PieceType::Pawn][color]; self.0 ^= keys.square_piece_color[src][PieceType::Pawn][color];
self.hash ^= keys.piece_square_color[dst][PieceType::Pawn][color]; self.0 ^= keys.square_piece_color[dst][PieceType::Pawn][color];
self.hash ^= keys.piece_square_color[ep_capture][PieceType::Pawn][color.opponent()] self.0 ^= keys.square_piece_color[ep_capture][PieceType::Pawn][color.opponent()]
} }
pub fn update_double_push( pub fn update_double_push(
@@ -157,18 +155,18 @@ impl ZobristHash {
new_en_passant_target: Option<usize>, new_en_passant_target: Option<usize>,
) { ) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][PieceType::Pawn][color]; self.0 ^= keys.square_piece_color[src][PieceType::Pawn][color];
self.hash ^= keys.piece_square_color[dst][PieceType::Pawn][color]; self.0 ^= keys.square_piece_color[dst][PieceType::Pawn][color];
if let Some(new_en_passant) = new_en_passant_target { if let Some(new_en_passant) = new_en_passant_target {
self.hash ^= keys.en_passant[square::to_file(new_en_passant)]; self.0 ^= keys.en_passant[square::to_file(new_en_passant)];
} }
} }
pub fn update_promotion(&mut self, src: usize, dst: usize, promote: &Promote, color: Color) { pub fn update_promotion(&mut self, src: usize, dst: usize, promote: &Promote, color: Color) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][PieceType::Pawn][color]; self.0 ^= keys.square_piece_color[src][PieceType::Pawn][color];
self.hash ^= keys.piece_square_color[dst][promote.into_piece_type()][color]; self.0 ^= keys.square_piece_color[dst][promote.into_piece_type()][color];
} }
pub fn update_promotion_capture( pub fn update_promotion_capture(
@@ -180,9 +178,9 @@ impl ZobristHash {
color: Color, color: Color,
) { ) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][PieceType::Pawn][color]; self.0 ^= keys.square_piece_color[src][PieceType::Pawn][color];
self.hash ^= keys.piece_square_color[dst][piece_at_dst][color.opponent()]; self.0 ^= keys.square_piece_color[dst][piece_at_dst][color.opponent()];
self.hash ^= keys.piece_square_color[dst][promote.into_piece_type()][color]; self.0 ^= keys.square_piece_color[dst][promote.into_piece_type()][color];
} }
pub fn update_castle( pub fn update_castle(
@@ -195,16 +193,16 @@ impl ZobristHash {
color: Color, color: Color,
) { ) {
let keys = zobrist_keys(); let keys = zobrist_keys();
self.hash ^= keys.piece_square_color[src][piece_at_src][color]; self.0 ^= keys.square_piece_color[src][piece_at_src][color];
self.hash ^= keys.piece_square_color[dst][piece_at_src][color]; self.0 ^= keys.square_piece_color[dst][piece_at_src][color];
self.hash ^= keys.piece_square_color[rook_src][PieceType::Rook][color]; self.0 ^= keys.square_piece_color[rook_src][PieceType::Rook][color];
self.hash ^= keys.piece_square_color[rook_dst][PieceType::Rook][color]; self.0 ^= keys.square_piece_color[rook_dst][PieceType::Rook][color];
} }
pub fn drop_en_passant_hash(&mut self, en_passant: Option<usize>) { pub fn drop_en_passant_hash(&mut self, en_passant: Option<usize>) {
let keys = zobrist_keys(); let keys = zobrist_keys();
if let Some(ep) = en_passant { if let Some(ep) = en_passant {
self.hash ^= keys.en_passant[square::to_file(ep)] self.0 ^= keys.en_passant[square::to_file(ep)]
} }
} }
} }

View File

@@ -194,13 +194,13 @@ pub fn rook_attacks_on_the_fly(bitboard: Bitboard, blocker: Bitboard) -> Bitboar
attacks attacks
} }
pub fn set_occupancy(index: u64, relevant_bits: usize, mut attack_mask: Bitboard) -> Bitboard { pub fn set_occupancy(idx: u64, relevant_bits: usize, mut attack_mask: Bitboard) -> Bitboard {
let mut occupancy = EMPTY; let mut occupancy = EMPTY;
for bit in 0..relevant_bits { for bit in 0..relevant_bits {
let square = lsb(attack_mask); let square = lsb(attack_mask);
attack_mask &= !square_to_bitboard(square); attack_mask &= !square_to_bitboard(square);
if have_common_bit(index, square_to_bitboard(bit)) { if have_common_bit(idx, square_to_bitboard(bit)) {
occupancy |= square_to_bitboard(square); occupancy |= square_to_bitboard(square);
} }
} }
@@ -269,12 +269,12 @@ pub fn init_bishop_attacks() -> Box<[[Bitboard; 512]; 64]> {
let attack_mask = bishop_masks[square]; let attack_mask = bishop_masks[square];
let occupancy_indices = square_to_bitboard(BISHOP_RELEVANT_BITS[square]); let occupancy_indices = square_to_bitboard(BISHOP_RELEVANT_BITS[square]);
for index in 0..occupancy_indices { for idx in 0..occupancy_indices {
let occupancy = set_occupancy(index, BISHOP_RELEVANT_BITS[square], attack_mask); let occupancy = set_occupancy(idx, BISHOP_RELEVANT_BITS[square], attack_mask);
let magic_index = (occupancy.wrapping_mul(BISHOP_MAGIC[square]) let magic_idx = (occupancy.wrapping_mul(BISHOP_MAGIC[square])
>> (64 - BISHOP_RELEVANT_BITS[square])) as usize; >> (64 - BISHOP_RELEVANT_BITS[square])) as usize;
bishop_atks[square][magic_index] = bishop_atks[square][magic_idx] =
bishop_attacks_on_the_fly(square_to_bitboard(square), occupancy); bishop_attacks_on_the_fly(square_to_bitboard(square), occupancy);
} }
} }
@@ -297,11 +297,11 @@ pub fn init_rook_attacks() -> HashMap<usize, HashMap<usize, Bitboard>> {
for idx in 0..occupancy_indices { for idx in 0..occupancy_indices {
let occupancy = set_occupancy(idx, ROOK_RELEVANT_BITS[square], attack_mask); let occupancy = set_occupancy(idx, ROOK_RELEVANT_BITS[square], attack_mask);
let magic_index = (occupancy.wrapping_mul(ROOK_MAGIC[square]) let magic_idx = (occupancy.wrapping_mul(ROOK_MAGIC[square])
>> (64 - ROOK_RELEVANT_BITS[square])) as usize; >> (64 - ROOK_RELEVANT_BITS[square])) as usize;
square_map.insert( square_map.insert(
magic_index, magic_idx,
rook_attacks_on_the_fly(square_to_bitboard(square), occupancy), rook_attacks_on_the_fly(square_to_bitboard(square), occupancy),
); );
} }

View File

@@ -9,25 +9,26 @@ use crate::{
use super::{ use super::{
negamax, negamax,
time::{hard_limit, TimeInfo}, time::TimeInfo,
}; };
pub fn iterative_deepening( pub fn iterative_deepening(
game: &mut Game, game: &mut Game,
max_depth: u8, max_depth: u8,
time_info: &TimeInfo, time: &TimeInfo,
) -> anyhow::Result<Option<Move>> { ) -> anyhow::Result<Option<Move>> {
let mut best_move = None; let mut best_move = None;
for depth in 1..=max_depth { for depth in 1..=max_depth {
if hard_limit(time_info.instant, time_info.time, time_info.inc) { if time.exceed_hard_limit() {
return Ok(best_move); return Ok(best_move);
} }
let mut nodes = 0; let mut nodes = 0;
let score = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, time_info, &mut nodes); let score = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, time, &mut nodes);
if score.is_err() { if let Err(e) = score {
write_response(&mut io::stdout(), &Response::Info(format!("{e}")))?;
break; break;
} }
@@ -37,9 +38,9 @@ pub fn iterative_deepening(
&mut io::stdout(), &mut io::stdout(),
&log_depth_results( &log_depth_results(
depth, depth,
time_info.instant.elapsed().as_millis() as u64, time.instant.elapsed().as_millis() as u64,
nodes, nodes,
time_info.nps(nodes), time.nps(nodes),
score?, score?,
best_move, best_move,
), ),

View File

@@ -6,10 +6,9 @@ pub fn score_move(mailbox: &Mailbox, mv: Move, tt_move: Option<Move>) -> i32 {
} }
let aggressor = mailbox.piece_at(mv.src).expect("No aggressor found."); let aggressor = mailbox.piece_at(mv.src).expect("No aggressor found.");
match mailbox.piece_at(mv.dst) { mailbox.piece_at(mv.dst).map_or(100, |victim| {
Some(victim) => aggressor.0.idx() as i32 - (victim.0.idx() * 8) as i32, aggressor.0.idx() as i32 - (victim.0.idx() * 8) as i32
None => 100, })
}
} }
#[cfg(test)] #[cfg(test)]
@@ -35,7 +34,6 @@ mod tests {
assert_eq!(moves, vec![pawn_takes_queen, queen_takes_pawn, castle]); assert_eq!(moves, vec![pawn_takes_queen, queen_takes_pawn, castle]);
let castle = Move::with_type(Square::E1, Square::C1, MoveType::Castle);
moves.sort_unstable_by_key(|mv| score_move(&game.mailbox, *mv, Some(castle))); moves.sort_unstable_by_key(|mv| score_move(&game.mailbox, *mv, Some(castle)));
assert_eq!(moves, vec![castle, pawn_takes_queen, queen_takes_pawn]); assert_eq!(moves, vec![castle, pawn_takes_queen, queen_takes_pawn]);

View File

@@ -8,7 +8,7 @@ use crate::{
use super::{ use super::{
move_ordering::score_move, move_ordering::score_move,
quiescence::quiescence, quiescence::quiescence,
time::{hard_limit, TimeInfo}, time::TimeInfo,
transposition_table::TTEntry, transposition_table::TTEntry,
}; };
@@ -18,11 +18,11 @@ pub fn negamax(
beta: i32, beta: i32,
depth: u8, depth: u8,
plies: u8, plies: u8,
time_info: &TimeInfo, time: &TimeInfo,
nodes: &mut u64, nodes: &mut u64,
) -> Result<i32> { ) -> Result<i32> {
if hard_limit(time_info.instant, time_info.time, time_info.inc) { if time.exceed_hard_limit() {
bail!("Time is up! In Negamax"); bail!("Hard limit exceeded in negamax");
} }
if plies != 0 && game.in_repetition() { if plies != 0 && game.in_repetition() {
@@ -31,7 +31,7 @@ pub fn negamax(
if depth == 0 { if depth == 0 {
let q_score = let q_score =
quiescence(game, alpha, beta, time_info, nodes).map_err(|e| anyhow!("{e}"))?; quiescence(game, alpha, beta, time, nodes).map_err(|e| anyhow!("{e}"))?;
return Ok(q_score); return Ok(q_score);
} }
@@ -55,7 +55,7 @@ pub fn negamax(
*nodes += 1; *nodes += 1;
let score = if legal_moves == 1 { let score = if legal_moves == 1 {
-negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, nodes)? -negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes)?
} else { } else {
let mut score = -negamax( let mut score = -negamax(
game, game,
@@ -63,11 +63,11 @@ pub fn negamax(
-alpha, -alpha,
depth - 1, depth - 1,
plies + 1, plies + 1,
time_info, time,
nodes, nodes,
)?; )?;
if score > alpha && score < beta { if score > alpha && score < beta {
score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, nodes)?; score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time, nodes)?;
} }
score score
}; };

View File

@@ -4,18 +4,18 @@ use crate::{board::game::Game, evaluation::pesto::pesto};
use super::{ use super::{
move_ordering::score_move, move_ordering::score_move,
time::{hard_limit, TimeInfo}, time::TimeInfo,
}; };
pub fn quiescence( pub fn quiescence(
game: &mut Game, game: &mut Game,
mut alpha: i32, mut alpha: i32,
beta: i32, beta: i32,
time_info: &TimeInfo, time: &TimeInfo,
total_nodes_searched: &mut u64, total_nodes_searched: &mut u64,
) -> Result<i32> { ) -> Result<i32> {
if hard_limit(time_info.instant, time_info.time, time_info.inc) { if time.exceed_hard_limit() {
bail!("Time is up! In Quiescence"); bail!("Hard limit exceeded in quiescence");
} }
let stand_pat = pesto().eval(game); let stand_pat = pesto().eval(game);
@@ -44,7 +44,7 @@ pub fn quiescence(
} }
*total_nodes_searched += 1; *total_nodes_searched += 1;
let score = -quiescence(game, -beta, -alpha, time_info, &mut 0)?; let score = -quiescence(game, -beta, -alpha, time, &mut 0)?;
game.unmake_move(); game.unmake_move();
if score > best_score { if score > best_score {

View File

@@ -16,8 +16,8 @@ impl TimeInfo {
pub fn nps(&self, nodes: u64) -> u64 { pub fn nps(&self, nodes: u64) -> u64 {
((nodes * 1_000_000) as u128).div_ceil(self.instant.elapsed().as_micros()) as u64 ((nodes * 1_000_000) as u128).div_ceil(self.instant.elapsed().as_micros()) as u64
} }
}
pub fn hard_limit(time_now: Instant, time: u128, inc: u128) -> bool { pub fn exceed_hard_limit(&self) -> bool {
time_now.elapsed().as_millis() >= time / HARD_LIMIT_DIVISION + inc / 2 self.instant.elapsed().as_millis() >= self.time / HARD_LIMIT_DIVISION + self.inc / 2
}
} }

View File

@@ -17,15 +17,15 @@ impl TranspositionTable {
pub fn lookup(&self, zobrist_hash: ZobristHash) -> Option<&TTEntry> { pub fn lookup(&self, zobrist_hash: ZobristHash) -> Option<&TTEntry> {
let entry = self let entry = self
.positions .positions
.get((zobrist_hash.hash % self.size) as usize) .get((zobrist_hash.0 % self.size) as usize)
.and_then(|entry| entry.as_ref()); .and_then(|entry| entry.as_ref());
entry entry
} }
pub fn insert(&mut self, tt_entry: TTEntry) { pub fn insert(&mut self, tt_entry: TTEntry) {
let index = (tt_entry.hash.hash % self.size) as usize; let idx = (tt_entry.hash.0 % self.size) as usize;
self.positions[index] = Some(tt_entry); self.positions[idx] = Some(tt_entry);
} }
} }