Add history heuristics, reorder killer move to come after captures

This commit is contained in:
stefiosif
2025-02-07 19:51:18 +02:00
parent bdd065efff
commit 614590b18e
6 changed files with 62 additions and 11 deletions

View File

@@ -38,6 +38,7 @@ pub fn from_fen(fen: &str) -> Result<Game, FenError> {
hash, hash,
tt: TranspositionTable::new(), tt: TranspositionTable::new(),
killer: [None; MAX_DEPTH as usize], killer: [None; MAX_DEPTH as usize],
history_heuristic: [[[0; 64]; 64]; 2],
}) })
} }

View File

@@ -30,6 +30,7 @@ pub struct Game {
pub hash: ZobristHash, pub hash: ZobristHash,
pub tt: TranspositionTable, pub tt: TranspositionTable,
pub killer: [Option<Move>; MAX_DEPTH as usize], pub killer: [Option<Move>; MAX_DEPTH as usize],
pub history_heuristic: [[[i16; 64]; 64]; 2],
} }
impl Game { impl Game {
@@ -41,6 +42,7 @@ impl Game {
hash: zobrist_keys().calculate_hash(&Board::startpos()), hash: zobrist_keys().calculate_hash(&Board::startpos()),
tt: TranspositionTable::new(), tt: TranspositionTable::new(),
killer: [None; MAX_DEPTH as usize], killer: [None; MAX_DEPTH as usize],
history_heuristic: [[[0; 64]; 64]; 2],
} }
} }

View File

@@ -12,3 +12,4 @@ pub const INC: u128 = 80;
pub const HARD_LIMIT_DIVISION: u128 = 3; pub const HARD_LIMIT_DIVISION: u128 = 3;
pub const SOFT_LIMIT_DIVISION: u128 = 20; pub const SOFT_LIMIT_DIVISION: u128 = 20;
pub const TT_SIZE_IN_MB: usize = 64; pub const TT_SIZE_IN_MB: usize = 64;
pub const MAX_HISTORY: i16 = 8192;

View File

@@ -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( pub fn score_move(
mailbox: &Mailbox, mailbox: &Mailbox,
mv: Move, mv: Move,
killer_move: Option<Move>, killer_move: Option<Move>,
color: Color,
history_heuristic: &[[[i16; 64]; 64]; 2],
tt_move: Option<Move>, tt_move: Option<Move>,
) -> i16 { ) -> i16 {
if Some(mv) == tt_move { if Some(mv) == tt_move {
return -100; 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 { if Some(mv) == killer_move {
return -90; return 6;
} }
let aggressor = mailbox.piece_at(mv.src()).expect("No aggressor found."); -history_heuristic[color][mv.src()][mv.dst()] + MAX_HISTORY + 6 + 1
if let Some(victim) = mailbox.piece_at(mv.dst()) {
return (aggressor.0.idx() - 8 * victim.0.idx()) as i16;
}
100
} }
#[cfg(test)] #[cfg(test)]
@@ -41,11 +52,29 @@ mod tests {
let castle = Move::new_with_type(Square::E1, Square::C1, MoveType::QueenCastle); let castle = Move::new_with_type(Square::E1, Square::C1, MoveType::QueenCastle);
let mut moves = vec![castle, queen_takes_pawn, pawn_takes_queen]; 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]); 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]); assert_eq!(moves, vec![castle, pawn_takes_queen, queen_takes_pawn]);

View File

@@ -10,6 +10,7 @@ use super::{
quiescence::quiescence, quiescence::quiescence,
time::TimeInfo, time::TimeInfo,
transposition_table::{NodeType, TTEntry}, transposition_table::{NodeType, TTEntry},
MAX_HISTORY,
}; };
pub fn negamax( pub fn negamax(
@@ -78,6 +79,8 @@ pub fn negamax(
&game.mailbox, &game.mailbox,
*mv, *mv,
game.killer[plies as usize], game.killer[plies as usize],
color,
&game.history_heuristic,
entry.and_then(|entry| entry.mv), entry.and_then(|entry| entry.mv),
) )
}); });
@@ -129,8 +132,16 @@ pub fn negamax(
if score >= beta { if score >= beta {
if !mv.is_capture() { if !mv.is_capture() {
game.killer[plies as usize] = Some(mv); 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; break;
} else if !mv.is_capture() {
game.history_heuristic[color][mv.src()][mv.dst()] -= 1;
} }
} }

View File

@@ -38,7 +38,14 @@ pub fn quiescence(
let mut moves = game.board.pseudo_moves_all_captures(); let mut moves = game.board.pseudo_moves_all_captures();
moves.sort_unstable_by_key(|mv| { 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 { for mv in moves {