From 614590b18e15462bd6eadb461761e917132cb14e Mon Sep 17 00:00:00 2001 From: stefiosif Date: Fri, 7 Feb 2025 19:51:18 +0200 Subject: [PATCH] Add history heuristics, reorder killer move to come after captures --- src/board/fen.rs | 1 + src/board/game.rs | 2 ++ src/search/mod.rs | 1 + src/search/move_ordering.rs | 49 +++++++++++++++++++++++++++++-------- src/search/negamax.rs | 11 +++++++++ src/search/quiescence.rs | 9 ++++++- 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/board/fen.rs b/src/board/fen.rs index 1074c9d..8cc54b7 100644 --- a/src/board/fen.rs +++ b/src/board/fen.rs @@ -38,6 +38,7 @@ pub fn from_fen(fen: &str) -> Result { hash, tt: TranspositionTable::new(), killer: [None; MAX_DEPTH as usize], + history_heuristic: [[[0; 64]; 64]; 2], }) } diff --git a/src/board/game.rs b/src/board/game.rs index 992bb44..e0afa04 100644 --- a/src/board/game.rs +++ b/src/board/game.rs @@ -30,6 +30,7 @@ pub struct Game { pub hash: ZobristHash, pub tt: TranspositionTable, pub killer: [Option; MAX_DEPTH as usize], + pub history_heuristic: [[[i16; 64]; 64]; 2], } impl Game { @@ -41,6 +42,7 @@ impl Game { hash: zobrist_keys().calculate_hash(&Board::startpos()), tt: TranspositionTable::new(), killer: [None; MAX_DEPTH as usize], + history_heuristic: [[[0; 64]; 64]; 2], } } diff --git a/src/search/mod.rs b/src/search/mod.rs index 00343b8..81a96a4 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -12,3 +12,4 @@ pub const INC: u128 = 80; pub const HARD_LIMIT_DIVISION: u128 = 3; pub const SOFT_LIMIT_DIVISION: u128 = 20; pub const TT_SIZE_IN_MB: usize = 64; +pub const MAX_HISTORY: i16 = 8192; diff --git a/src/search/move_ordering.rs b/src/search/move_ordering.rs index 6903b67..2361f71 100644 --- a/src/search/move_ordering.rs +++ b/src/search/move_ordering.rs @@ -1,25 +1,36 @@ -use crate::{board::mailbox::Mailbox, movegen::r#move::Move}; +use crate::{ + board::{board::Color, mailbox::Mailbox}, + movegen::r#move::Move, +}; +use super::MAX_HISTORY; + +// ttmove: -100, mvvlva: [-32, 5], killer:6, quiet[7, ..] pub fn score_move( mailbox: &Mailbox, mv: Move, killer_move: Option, + color: Color, + history_heuristic: &[[[i16; 64]; 64]; 2], tt_move: Option, ) -> i16 { if Some(mv) == tt_move { return -100; } + if mv.is_capture() { + let aggressor = mailbox.piece_at(mv.src()).unwrap(); + if let Some(victim) = mailbox.piece_at(mv.dst()) { + return aggressor.0.idx() as i16 - 8 * victim.0.idx() as i16; + } + return 0; //en passant + } + if Some(mv) == killer_move { - return -90; + return 6; } - let aggressor = mailbox.piece_at(mv.src()).expect("No aggressor found."); - if let Some(victim) = mailbox.piece_at(mv.dst()) { - return (aggressor.0.idx() - 8 * victim.0.idx()) as i16; - } - - 100 + -history_heuristic[color][mv.src()][mv.dst()] + MAX_HISTORY + 6 + 1 } #[cfg(test)] @@ -41,11 +52,29 @@ mod tests { let castle = Move::new_with_type(Square::E1, Square::C1, MoveType::QueenCastle); let mut moves = vec![castle, queen_takes_pawn, pawn_takes_queen]; - moves.sort_unstable_by_key(|mv| score_move(&game.mailbox, *mv, None, None)); + moves.sort_unstable_by_key(|mv| { + score_move( + &game.mailbox, + *mv, + None, + game.current_player(), + &[[[0; 64]; 64]; 2], + None, + ) + }); assert_eq!(moves, vec![pawn_takes_queen, queen_takes_pawn, castle]); - moves.sort_unstable_by_key(|mv| score_move(&game.mailbox, *mv, None, Some(castle))); + moves.sort_unstable_by_key(|mv| { + score_move( + &game.mailbox, + *mv, + None, + game.current_player(), + &[[[0; 64]; 64]; 2], + Some(castle), + ) + }); assert_eq!(moves, vec![castle, pawn_takes_queen, queen_takes_pawn]); diff --git a/src/search/negamax.rs b/src/search/negamax.rs index affc8ec..931acef 100644 --- a/src/search/negamax.rs +++ b/src/search/negamax.rs @@ -10,6 +10,7 @@ use super::{ quiescence::quiescence, time::TimeInfo, transposition_table::{NodeType, TTEntry}, + MAX_HISTORY, }; pub fn negamax( @@ -78,6 +79,8 @@ pub fn negamax( &game.mailbox, *mv, game.killer[plies as usize], + color, + &game.history_heuristic, entry.and_then(|entry| entry.mv), ) }); @@ -129,8 +132,16 @@ pub fn negamax( if score >= beta { if !mv.is_capture() { game.killer[plies as usize] = Some(mv); + + let current_score = game.history_heuristic[color][mv.src()][mv.dst()]; + let bonus = (depth * depth) as i16; + let clamped_bonus = bonus.clamp(-MAX_HISTORY, MAX_HISTORY); + game.history_heuristic[color][mv.src()][mv.dst()] += + clamped_bonus - current_score * clamped_bonus.abs() / MAX_HISTORY; } break; + } else if !mv.is_capture() { + game.history_heuristic[color][mv.src()][mv.dst()] -= 1; } } diff --git a/src/search/quiescence.rs b/src/search/quiescence.rs index 6cafb90..224bded 100644 --- a/src/search/quiescence.rs +++ b/src/search/quiescence.rs @@ -38,7 +38,14 @@ pub fn quiescence( let mut moves = game.board.pseudo_moves_all_captures(); moves.sort_unstable_by_key(|mv| { - score_move(&game.mailbox, *mv, None, entry.and_then(|entry| entry.mv)) + score_move( + &game.mailbox, + *mv, + None, + color, + &[[[0; 64]; 64]; 2], + entry.and_then(|entry| entry.mv), + ) }); for mv in moves {