implemented transposition table
This commit is contained in:
parent
ea9419d7e0
commit
42816a6939
13 changed files with 464 additions and 91 deletions
|
|
@ -3,6 +3,7 @@ use crate::eval::basic::evaluate_board;
|
|||
use crate::movegen::generate_pseudo_legal_moves;
|
||||
use crate::movegen::legal_check::*;
|
||||
use crate::r#move::{Move, MoveList};
|
||||
use crate::tt::{TranspositionTable, NodeType, TTEntry}; // Import TT types
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
// A score high enough to be > any material eval, but low enough to not overflow when adding ply
|
||||
|
|
@ -16,6 +17,29 @@ fn evaluate_board_relative(board: &Board) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
// Helper to adjust mate scores for the TT.
|
||||
// TT stores "pure" scores, independent of ply.
|
||||
// Search uses "mated in X" relative to current ply.
|
||||
fn score_to_tt(score: i32, ply: u8) -> i32 {
|
||||
if score > MATE_SCORE - 1000 {
|
||||
score + (ply as i32)
|
||||
} else if score < -MATE_SCORE + 1000 {
|
||||
score - (ply as i32)
|
||||
} else {
|
||||
score
|
||||
}
|
||||
}
|
||||
|
||||
fn score_from_tt(score: i32, ply: u8) -> i32 {
|
||||
if score > MATE_SCORE - 1000 {
|
||||
score - (ply as i32)
|
||||
} else if score < -MATE_SCORE + 1000 {
|
||||
score + (ply as i32)
|
||||
} else {
|
||||
score
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alpha_beta(
|
||||
board: &mut Board,
|
||||
depth: u8,
|
||||
|
|
@ -25,29 +49,81 @@ pub fn alpha_beta(
|
|||
start_time: Instant,
|
||||
time_limit: Duration,
|
||||
nodes: &mut u64,
|
||||
tt: &mut TranspositionTable, // Added TT parameter
|
||||
) -> (Option<Move>, i32) {
|
||||
// Check for time usage every 4096 nodes to reduce system call overhead
|
||||
// Check for time usage
|
||||
if *nodes % 4096 == 0 {
|
||||
if start_time.elapsed() > time_limit {
|
||||
// Return immediately. The return value here effectively signals an abort,
|
||||
// but the engine must discard this result.
|
||||
return (None, 0);
|
||||
return (None, 0);
|
||||
}
|
||||
}
|
||||
*nodes += 1;
|
||||
|
||||
// -----------------------
|
||||
// 1. TT PROBE
|
||||
// -----------------------
|
||||
// We assume board.hash holds the current Zobrist key (u64)
|
||||
let tt_key = board.hash;
|
||||
let mut tt_move: Option<Move> = None;
|
||||
|
||||
if let Some(entry) = tt.probe(tt_key) {
|
||||
// We remember the move from TT to sort it first later
|
||||
if entry.bm.0 != 0 { // Check if move is valid (not 0)
|
||||
tt_move = Some(entry.bm);
|
||||
}
|
||||
|
||||
// Can we use the score for a cutoff?
|
||||
if entry.depth >= depth {
|
||||
let tt_score = score_from_tt(entry.score, ply);
|
||||
|
||||
match entry.node_type {
|
||||
NodeType::Exact => return (Some(entry.bm), tt_score),
|
||||
NodeType::Alpha => {
|
||||
if tt_score <= alpha {
|
||||
return (Some(entry.bm), tt_score);
|
||||
}
|
||||
}
|
||||
NodeType::Beta => {
|
||||
if tt_score >= beta {
|
||||
return (Some(entry.bm), tt_score);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if depth == 0 {
|
||||
return (None, evaluate_board_relative(board));
|
||||
}
|
||||
|
||||
let mut list = MoveList::new();
|
||||
generate_pseudo_legal_moves(board, &mut list);
|
||||
let mut best_move: Option<Move> = None;
|
||||
let mut best_score: i32 = -i32::MAX; // This is our local "worst case"
|
||||
let mut legal_moves_found = false;
|
||||
|
||||
for mv in list.iter() {
|
||||
let undo_mv = board.make_move(*mv);
|
||||
// -----------------------
|
||||
// MOVE ORDERING (TT Move First)
|
||||
// -----------------------
|
||||
// If we have a move from TT, we want to search it first!
|
||||
if let Some(tm) = tt_move {
|
||||
// Find the move in the list and swap it to the front (index 0)
|
||||
for i in 0..list.len() {
|
||||
if list[i] == tm {
|
||||
list.swap(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut best_move: Option<Move> = None;
|
||||
let mut best_score: i32 = -i32::MAX;
|
||||
let mut legal_moves_found = false;
|
||||
let alpha_orig = alpha; // Save original alpha to determine NodeType later
|
||||
|
||||
for i in 0..list.len() {
|
||||
let mv = list[i];
|
||||
let undo_mv = board.make_move(mv);
|
||||
|
||||
// Optimization: Check legality locally if possible, but for now rely on King check
|
||||
let is_illegal = is_other_king_attacked(board);
|
||||
if is_illegal {
|
||||
board.undo_move(undo_mv);
|
||||
|
|
@ -55,35 +131,28 @@ pub fn alpha_beta(
|
|||
}
|
||||
legal_moves_found = true;
|
||||
|
||||
// Recursive call with negated and swapped alpha/beta
|
||||
// Pass time parameters and node counter down
|
||||
let (_, score) = alpha_beta(board, depth - 1, ply + 1, -beta, -alpha, start_time, time_limit, nodes);
|
||||
|
||||
// If we aborted deeper in the tree (returned 0 due to timeout),
|
||||
// we should technically propagate that up, but checking elapsed()
|
||||
// at the loop start (via recursion) handles it eventually.
|
||||
// For a strict abort, we check here too:
|
||||
let (_, score) = alpha_beta(board, depth - 1, ply + 1, -beta, -alpha, start_time, time_limit, nodes, tt);
|
||||
|
||||
if *nodes % 4096 == 0 && start_time.elapsed() > time_limit {
|
||||
board.undo_move(undo_mv);
|
||||
return (None, 0);
|
||||
board.undo_move(undo_mv);
|
||||
return (None, 0);
|
||||
}
|
||||
|
||||
let current_score = -score;
|
||||
|
||||
if current_score > best_score {
|
||||
best_score = current_score;
|
||||
best_move = Some(*mv);
|
||||
best_move = Some(mv);
|
||||
}
|
||||
|
||||
board.undo_move(undo_mv);
|
||||
|
||||
// Alpha-Beta Pruning logic
|
||||
if best_score > alpha {
|
||||
alpha = best_score;
|
||||
}
|
||||
|
||||
if alpha >= beta {
|
||||
break; // Beta cutoff (Pruning)
|
||||
break; // Beta cutoff
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,10 +160,25 @@ pub fn alpha_beta(
|
|||
if is_current_king_attacked(board) {
|
||||
return (None, -MATE_SCORE + (ply as i32));
|
||||
} else {
|
||||
// Stalemate
|
||||
return (None, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// 2. TT STORE
|
||||
// -----------------------
|
||||
let node_type = if best_score <= alpha_orig {
|
||||
NodeType::Alpha // We didn't improve alpha (Fail Low) -> Upper Bound
|
||||
} else if best_score >= beta {
|
||||
NodeType::Beta // We caused a cutoff (Fail High) -> Lower Bound
|
||||
} else {
|
||||
NodeType::Exact // We found a score between alpha and beta
|
||||
};
|
||||
|
||||
let save_move = best_move.unwrap_or(Move(0)); // Use dummy 0 if no best move
|
||||
let save_score = score_to_tt(best_score, ply);
|
||||
|
||||
tt.store(tt_key, save_score, depth, node_type, save_move);
|
||||
|
||||
(best_move, best_score)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue