Remove tt cutoffs, fix capture move sorting, refactor
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::game::Game,
|
board::game::Game,
|
||||||
evaluation::{MAX_SCORE, MIN_SCORE},
|
evaluation::{MAX_SCORE, MIN_SCORE},
|
||||||
@@ -7,8 +5,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
negamax, time::TimeInfo, transposition_table::TranspositionTable, HARD_LIMIT_DIVISION,
|
negamax,
|
||||||
SOFT_EVAL_THRESHOLD, SOFT_LIMIT_DIVISION,
|
time::{time_limit_reached, TimeInfo},
|
||||||
|
transposition_table::TranspositionTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn iterative_deepening(
|
pub fn iterative_deepening(
|
||||||
@@ -48,18 +47,6 @@ pub fn iterative_deepening(
|
|||||||
Ok(best_move)
|
Ok(best_move)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time_limit_reached(time: &Instant, remaining_time: u128, eval: i32) -> bool {
|
|
||||||
hard_limit(time, remaining_time) || soft_limit(time, remaining_time, eval)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hard_limit(time_now: &Instant, remaining_time: u128) -> bool {
|
|
||||||
time_now.elapsed().as_millis() >= remaining_time / HARD_LIMIT_DIVISION
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn soft_limit(time: &Instant, remaining_time: u128, eval: i32) -> bool {
|
|
||||||
time.elapsed().as_millis() >= remaining_time / SOFT_LIMIT_DIVISION && eval > SOFT_EVAL_THRESHOLD
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ pub mod transposition_table;
|
|||||||
|
|
||||||
pub const MAX_DEPTH: u8 = 7;
|
pub const MAX_DEPTH: u8 = 7;
|
||||||
pub const REMAINING_TIME_DEFAULT: u128 = 100000; // in ms
|
pub const REMAINING_TIME_DEFAULT: u128 = 100000; // in ms
|
||||||
pub const HARD_LIMIT_DIVISION: u128 = 10;
|
pub const HARD_LIMIT_DIVISION: u128 = 10; // % of the remaining time
|
||||||
pub const SOFT_LIMIT_DIVISION: u128 = 20;
|
pub const SOFT_LIMIT_DIVISION: u128 = HARD_LIMIT_DIVISION / 2;
|
||||||
pub const SOFT_EVAL_THRESHOLD: i32 = 500;
|
pub const SOFT_EVAL_THRESHOLD: i32 = 500;
|
||||||
pub const MAX_TT_SIZE: u64 = 2000000;
|
pub const MAX_TT_SIZE: u64 = 1000000;
|
||||||
|
|
||||||
pub struct SearchResult {
|
pub struct SearchResult {
|
||||||
pub best_move: Option<Move>,
|
pub best_move: Option<Move>,
|
||||||
|
|||||||
@@ -2,10 +2,23 @@ use crate::{
|
|||||||
board::mailbox::Mailbox, evaluation::evaluation::material_score, movegen::r#move::Move,
|
board::mailbox::Mailbox, evaluation::evaluation::material_score, movegen::r#move::Move,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const fn mvv_lva(mailbox: &Mailbox, mv: Move) -> i32 {
|
pub fn sort_moves(mut moves: Vec<Move>, mailbox: &Mailbox, tt_move: Option<Move>) -> Vec<Move> {
|
||||||
|
if let Some(tt_move) = tt_move {
|
||||||
|
if let Some(tt_move_index) = moves.iter().position(|&mv| mv == tt_move) {
|
||||||
|
moves.remove(tt_move_index);
|
||||||
|
moves.insert(0, tt_move);
|
||||||
|
return moves;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moves.sort_unstable_by_key(|mv| std::cmp::Reverse(mvv_lva(mailbox, *mv)));
|
||||||
|
moves
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn mvv_lva(mailbox: &Mailbox, mv: Move) -> i32 {
|
||||||
match (mailbox.piece_at(mv.src), mailbox.piece_at(mv.dst)) {
|
match (mailbox.piece_at(mv.src), mailbox.piece_at(mv.dst)) {
|
||||||
(Some(aggressor), Some(victim)) => material_score(victim) - material_score(aggressor),
|
(Some(aggressor), Some(victim)) => material_score(victim) - material_score(aggressor),
|
||||||
_ => 0,
|
_ => -1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
iterative_deepening::hard_limit,
|
move_ordering,
|
||||||
move_ordering::mvv_lva,
|
|
||||||
quiescence::quiescence,
|
quiescence::quiescence,
|
||||||
time::TimeInfo,
|
time::{hard_limit, TimeInfo},
|
||||||
transposition_table::{Bound, TTEntry, TranspositionTable},
|
transposition_table::{Bound, TTEntry, TranspositionTable},
|
||||||
SearchResult,
|
SearchResult,
|
||||||
};
|
};
|
||||||
@@ -32,33 +31,18 @@ pub fn negamax(
|
|||||||
return Ok(SearchResult::new(None, q_score));
|
return Ok(SearchResult::new(None, q_score));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tt_move = None;
|
|
||||||
|
|
||||||
if let Some(entry) = tt.lookup(game.hash) {
|
|
||||||
if entry.depth >= depth
|
|
||||||
&& (matches!(entry.bound, Bound::Exact)
|
|
||||||
|| (matches!(entry.bound, Bound::Lower) && entry.score >= beta)
|
|
||||||
|| (matches!(entry.bound, Bound::Upper) && entry.score <= alpha))
|
|
||||||
{
|
|
||||||
return Ok(SearchResult::new(entry.mv, entry.score));
|
|
||||||
}
|
|
||||||
tt_move = entry.mv;
|
|
||||||
}
|
|
||||||
|
|
||||||
let color = game.current_player();
|
let color = game.current_player();
|
||||||
let (mut best_move, mut best_score, mate_score) = (None, MIN_SCORE, -MATE_SCORE + plies as i32);
|
let mut best_move = None;
|
||||||
|
let mut best_score = MIN_SCORE;
|
||||||
|
let mate_score = -MATE_SCORE + plies as i32;
|
||||||
let mut legal_moves = 0;
|
let mut legal_moves = 0;
|
||||||
let mut pseudo_legal_moves = game.board.pseudo_moves_all();
|
let mut bound = Bound::Alpha;
|
||||||
pseudo_legal_moves.sort_unstable_by_key(|mv| mvv_lva(&game.mailbox, *mv));
|
let all_moves = game.board.pseudo_moves_all();
|
||||||
|
|
||||||
if let Some(tt_move) = tt_move {
|
let tt_move = tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||||
if let Some(tt_move_index) = pseudo_legal_moves.iter().position(|&mv| mv == tt_move) {
|
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move);
|
||||||
pseudo_legal_moves.remove(tt_move_index);
|
|
||||||
pseudo_legal_moves.insert(0, tt_move);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for mv in pseudo_legal_moves {
|
for mv in moves {
|
||||||
game.make_move(&mv);
|
game.make_move(&mv);
|
||||||
|
|
||||||
if game.board.king_under_check(color) {
|
if game.board.king_under_check(color) {
|
||||||
@@ -67,62 +51,35 @@ pub fn negamax(
|
|||||||
}
|
}
|
||||||
|
|
||||||
legal_moves += 1;
|
legal_moves += 1;
|
||||||
let move_score =
|
let score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, tt)?.best_score;
|
||||||
-negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, tt)?.best_score;
|
|
||||||
game.unmake_move();
|
game.unmake_move();
|
||||||
|
|
||||||
if move_score > best_score {
|
if score > best_score {
|
||||||
best_score = move_score;
|
best_score = score;
|
||||||
best_move = Some(mv);
|
best_move = Some(mv);
|
||||||
}
|
}
|
||||||
|
|
||||||
if move_score >= beta {
|
if score >= beta {
|
||||||
tt.insert(TTEntry::new(
|
bound = Bound::Beta;
|
||||||
game.hash,
|
best_score = beta;
|
||||||
depth,
|
best_move = Some(mv);
|
||||||
best_score,
|
break;
|
||||||
best_move,
|
|
||||||
Bound::Lower,
|
|
||||||
));
|
|
||||||
return Ok(SearchResult::new(best_move, beta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alpha = alpha.max(move_score);
|
if score > alpha {
|
||||||
|
bound = Bound::Exact;
|
||||||
|
alpha = score;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if legal_moves == 0 {
|
if legal_moves == 0 {
|
||||||
if game.board.king_under_check(color) {
|
if game.board.king_under_check(color) {
|
||||||
tt.insert(TTEntry::new(
|
|
||||||
game.hash,
|
|
||||||
depth,
|
|
||||||
mate_score,
|
|
||||||
best_move,
|
|
||||||
Bound::Exact,
|
|
||||||
));
|
|
||||||
return Ok(SearchResult::new(None, mate_score));
|
return Ok(SearchResult::new(None, mate_score));
|
||||||
}
|
}
|
||||||
tt.insert(TTEntry::new(game.hash, depth, 0, best_move, Bound::Exact));
|
|
||||||
return Ok(SearchResult::new(None, 0));
|
return Ok(SearchResult::new(None, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if best_score < alpha {
|
tt.insert(TTEntry::new(game.hash, depth, best_score, best_move, bound));
|
||||||
tt.insert(TTEntry::new(
|
|
||||||
game.hash,
|
|
||||||
depth,
|
|
||||||
best_score,
|
|
||||||
best_move,
|
|
||||||
Bound::Upper,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
tt.insert(TTEntry::new(
|
|
||||||
game.hash,
|
|
||||||
depth,
|
|
||||||
best_score,
|
|
||||||
best_move,
|
|
||||||
Bound::Exact,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SearchResult::new(best_move, best_score))
|
Ok(SearchResult::new(best_move, best_score))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,43 +2,47 @@ use anyhow::{bail, Result};
|
|||||||
|
|
||||||
use crate::{board::game::Game, evaluation::evaluation::evaluate_position};
|
use crate::{board::game::Game, evaluation::evaluation::evaluate_position};
|
||||||
|
|
||||||
use super::{iterative_deepening::hard_limit, move_ordering::mvv_lva, time::TimeInfo};
|
use super::{
|
||||||
|
move_ordering,
|
||||||
|
time::{hard_limit, TimeInfo},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32, time_info: &TimeInfo) -> Result<i32> {
|
pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32, time_info: &TimeInfo) -> Result<i32> {
|
||||||
if hard_limit(&time_info.time, time_info.remaining_time_in_ms) {
|
if hard_limit(&time_info.time, time_info.remaining_time_in_ms) {
|
||||||
bail!("Time is up! In Quiescence");
|
bail!("Time is up! In Quiescence");
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = game.current_player();
|
|
||||||
let stand_pat = evaluate_position(&game.board);
|
let stand_pat = evaluate_position(&game.board);
|
||||||
|
|
||||||
if stand_pat >= beta {
|
if stand_pat >= beta {
|
||||||
return Ok(beta);
|
return Ok(beta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha < stand_pat {
|
if stand_pat > alpha {
|
||||||
alpha = stand_pat;
|
alpha = stand_pat;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut captures: Vec<_> = game.board.pseudo_moves_all_captures();
|
let color = game.current_player();
|
||||||
captures.sort_unstable_by_key(|mv| mvv_lva(&game.mailbox, *mv));
|
let all_moves = game.board.pseudo_moves_all_captures();
|
||||||
|
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, None);
|
||||||
|
|
||||||
for mv in captures {
|
for mv in moves {
|
||||||
game.make_move(&mv);
|
game.make_move(&mv);
|
||||||
|
|
||||||
if game.board.king_under_check(color) {
|
if game.board.king_under_check(color) {
|
||||||
game.unmake_move();
|
game.unmake_move();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let move_score = -quiescence(game, -beta, -alpha, time_info)?;
|
|
||||||
|
let score = -quiescence(game, -beta, -alpha, time_info)?;
|
||||||
game.unmake_move();
|
game.unmake_move();
|
||||||
|
|
||||||
if move_score >= beta {
|
if score >= beta {
|
||||||
return Ok(beta);
|
return Ok(beta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if alpha > move_score {
|
if score > alpha {
|
||||||
alpha = move_score
|
alpha = score
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use super::{HARD_LIMIT_DIVISION, SOFT_EVAL_THRESHOLD, SOFT_LIMIT_DIVISION};
|
||||||
|
|
||||||
pub struct TimeInfo {
|
pub struct TimeInfo {
|
||||||
pub time: Instant,
|
pub time: Instant,
|
||||||
pub remaining_time_in_ms: u128,
|
pub remaining_time_in_ms: u128,
|
||||||
@@ -13,3 +15,15 @@ impl TimeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn time_limit_reached(time: &Instant, remaining_time: u128, eval: i32) -> bool {
|
||||||
|
hard_limit(time, remaining_time) || soft_limit(time, remaining_time, eval)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hard_limit(time_now: &Instant, remaining_time: u128) -> bool {
|
||||||
|
time_now.elapsed().as_millis() >= remaining_time / HARD_LIMIT_DIVISION
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn soft_limit(time: &Instant, remaining_time: u128, eval: i32) -> bool {
|
||||||
|
time.elapsed().as_millis() >= remaining_time / SOFT_LIMIT_DIVISION && eval > SOFT_EVAL_THRESHOLD
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ impl TTEntry {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Bound {
|
pub enum Bound {
|
||||||
Exact,
|
Exact,
|
||||||
Lower,
|
Alpha,
|
||||||
Upper,
|
Beta,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user