108 lines
3.2 KiB
Rust
108 lines
3.2 KiB
Rust
use crate::{
|
|
board::game::Game,
|
|
evaluation::{MAX_SCORE, MIN_SCORE},
|
|
interface::uci::Response,
|
|
movegen::r#move::Move,
|
|
};
|
|
|
|
use super::{
|
|
negamax, time::TimeInfo, ASPIRATION_WINDOW_DEPTH_THRESHOLD, ASPIRATION_WINDOW_EXPANSION,
|
|
ASPIRATION_WINDOW_INITIAL,
|
|
};
|
|
|
|
pub fn iterative_deepening(
|
|
game: &mut Game,
|
|
max_depth: u8,
|
|
time: &TimeInfo,
|
|
) -> anyhow::Result<Option<Move>> {
|
|
let mut best_move = None;
|
|
let mut best_score = MIN_SCORE;
|
|
|
|
'iterative_deepening: for depth in 1..=max_depth {
|
|
if time.exceed_soft_limit() {
|
|
println!("Soft limit exceeded in negamax");
|
|
return Ok(best_move);
|
|
}
|
|
|
|
let mut nodes = 0;
|
|
let mut score;
|
|
|
|
if depth < ASPIRATION_WINDOW_DEPTH_THRESHOLD {
|
|
score = negamax::negamax(game, MIN_SCORE, MAX_SCORE, depth, 0, time, &mut nodes, true);
|
|
} else {
|
|
let mut window_size = ASPIRATION_WINDOW_INITIAL;
|
|
let mut alpha = (best_score - window_size).max(MIN_SCORE);
|
|
let mut beta = (best_score + window_size).min(MAX_SCORE);
|
|
|
|
'aspiration_windows: loop {
|
|
score = negamax::negamax(game, alpha, beta, depth, 0, time, &mut nodes, true);
|
|
|
|
let score = match score {
|
|
Ok(score) => score,
|
|
Err(ref e) => {
|
|
println!("Error: {}", Response::Info(e.to_string()));
|
|
break 'iterative_deepening;
|
|
}
|
|
};
|
|
|
|
if score >= beta {
|
|
beta = MAX_SCORE.min(beta.saturating_add(window_size));
|
|
window_size = window_size.saturating_mul(ASPIRATION_WINDOW_EXPANSION);
|
|
} else if score <= alpha {
|
|
alpha = MIN_SCORE.max(alpha.saturating_sub(window_size));
|
|
window_size = window_size.saturating_mul(ASPIRATION_WINDOW_EXPANSION);
|
|
} else {
|
|
best_score = score;
|
|
break 'aspiration_windows;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Err(e) = score {
|
|
println!("Error: {}", Response::Info(e.to_string()));
|
|
break;
|
|
}
|
|
|
|
best_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
|
|
|
println!(
|
|
"info depth {} ms {} nodes {} nps {} eval {} bestmove {}",
|
|
depth,
|
|
time.instant.elapsed().as_millis(),
|
|
nodes,
|
|
time.nps(nodes),
|
|
score?,
|
|
best_move.expect("No best move found")
|
|
);
|
|
}
|
|
Ok(best_move)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{
|
|
board::fen::from_fen,
|
|
movegen::attack_generator::init_attacks,
|
|
search::{iterative_deepening, time::TimeInfo, INC, MAX_DEPTH, TIME},
|
|
};
|
|
|
|
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
|
|
|
#[test]
|
|
fn test_iterative_deepening() -> anyhow::Result<()> {
|
|
init_attacks();
|
|
let mut game = from_fen(FEN).unwrap();
|
|
let instant = std::time::Instant::now();
|
|
|
|
iterative_deepening::iterative_deepening(
|
|
&mut game,
|
|
MAX_DEPTH,
|
|
&TimeInfo::new(instant, TIME, INC),
|
|
)?;
|
|
|
|
dbg!(instant.elapsed());
|
|
|
|
Ok(())
|
|
}
|
|
}
|