diff --git a/src/search/iterative_deepening.rs b/src/search/iterative_deepening.rs index 630cba9..401e071 100644 --- a/src/search/iterative_deepening.rs +++ b/src/search/iterative_deepening.rs @@ -1,5 +1,3 @@ -use std::time::Instant; - use crate::{ board::game::Game, evaluation::{MAX_SCORE, MIN_SCORE}, @@ -7,8 +5,9 @@ use crate::{ }; use super::{ - negamax, time::TimeInfo, transposition_table::TranspositionTable, HARD_LIMIT_DIVISION, - SOFT_EVAL_THRESHOLD, SOFT_LIMIT_DIVISION, + negamax, + time::{time_limit_reached, TimeInfo}, + transposition_table::TranspositionTable, }; pub fn iterative_deepening( @@ -48,18 +47,6 @@ pub fn iterative_deepening( 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)] mod tests { use crate::{ diff --git a/src/search/mod.rs b/src/search/mod.rs index f4dc3c0..6462ac2 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -10,10 +10,10 @@ pub mod transposition_table; pub const MAX_DEPTH: u8 = 7; pub const REMAINING_TIME_DEFAULT: u128 = 100000; // in ms -pub const HARD_LIMIT_DIVISION: u128 = 10; -pub const SOFT_LIMIT_DIVISION: u128 = 20; +pub const HARD_LIMIT_DIVISION: u128 = 10; // % of the remaining time +pub const SOFT_LIMIT_DIVISION: u128 = HARD_LIMIT_DIVISION / 2; 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 best_move: Option, diff --git a/src/search/move_ordering.rs b/src/search/move_ordering.rs index 5ca7d68..6212eb0 100644 --- a/src/search/move_ordering.rs +++ b/src/search/move_ordering.rs @@ -2,10 +2,23 @@ use crate::{ 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, mailbox: &Mailbox, tt_move: Option) -> Vec { + 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)) { (Some(aggressor), Some(victim)) => material_score(victim) - material_score(aggressor), - _ => 0, + _ => -1000, } } diff --git a/src/search/negamax.rs b/src/search/negamax.rs index cbe7d40..a3f5d54 100644 --- a/src/search/negamax.rs +++ b/src/search/negamax.rs @@ -6,10 +6,9 @@ use crate::{ }; use super::{ - iterative_deepening::hard_limit, - move_ordering::mvv_lva, + move_ordering, quiescence::quiescence, - time::TimeInfo, + time::{hard_limit, TimeInfo}, transposition_table::{Bound, TTEntry, TranspositionTable}, SearchResult, }; @@ -32,33 +31,18 @@ pub fn negamax( 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 (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 pseudo_legal_moves = game.board.pseudo_moves_all(); - pseudo_legal_moves.sort_unstable_by_key(|mv| mvv_lva(&game.mailbox, *mv)); + let mut bound = Bound::Alpha; + let all_moves = game.board.pseudo_moves_all(); - if let Some(tt_move) = tt_move { - if let Some(tt_move_index) = pseudo_legal_moves.iter().position(|&mv| mv == tt_move) { - pseudo_legal_moves.remove(tt_move_index); - pseudo_legal_moves.insert(0, tt_move); - } - } + let tt_move = tt.lookup(game.hash).and_then(|entry| entry.mv); + let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move); - for mv in pseudo_legal_moves { + for mv in moves { game.make_move(&mv); if game.board.king_under_check(color) { @@ -67,62 +51,35 @@ pub fn negamax( } legal_moves += 1; - let move_score = - -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, tt)?.best_score; + let score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, tt)?.best_score; game.unmake_move(); - if move_score > best_score { - best_score = move_score; + if score > best_score { + best_score = score; best_move = Some(mv); } - if move_score >= beta { - tt.insert(TTEntry::new( - game.hash, - depth, - best_score, - best_move, - Bound::Lower, - )); - return Ok(SearchResult::new(best_move, beta)); + if score >= beta { + bound = Bound::Beta; + best_score = beta; + best_move = Some(mv); + break; } - alpha = alpha.max(move_score); + if score > alpha { + bound = Bound::Exact; + alpha = score; + } } if legal_moves == 0 { 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)); } - tt.insert(TTEntry::new(game.hash, depth, 0, best_move, Bound::Exact)); return Ok(SearchResult::new(None, 0)); } - if best_score < alpha { - 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, - )); - } - + tt.insert(TTEntry::new(game.hash, depth, best_score, best_move, bound)); Ok(SearchResult::new(best_move, best_score)) } diff --git a/src/search/quiescence.rs b/src/search/quiescence.rs index 7ba52dd..872ac5f 100644 --- a/src/search/quiescence.rs +++ b/src/search/quiescence.rs @@ -2,43 +2,47 @@ use anyhow::{bail, Result}; 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 { if hard_limit(&time_info.time, time_info.remaining_time_in_ms) { bail!("Time is up! In Quiescence"); } - let color = game.current_player(); let stand_pat = evaluate_position(&game.board); if stand_pat >= beta { return Ok(beta); } - if alpha < stand_pat { + if stand_pat > alpha { alpha = stand_pat; } - let mut captures: Vec<_> = game.board.pseudo_moves_all_captures(); - captures.sort_unstable_by_key(|mv| mvv_lva(&game.mailbox, *mv)); + let color = game.current_player(); + 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); if game.board.king_under_check(color) { game.unmake_move(); continue; } - let move_score = -quiescence(game, -beta, -alpha, time_info)?; + + let score = -quiescence(game, -beta, -alpha, time_info)?; game.unmake_move(); - if move_score >= beta { + if score >= beta { return Ok(beta); } - if alpha > move_score { - alpha = move_score + if score > alpha { + alpha = score } } diff --git a/src/search/time.rs b/src/search/time.rs index e8f5246..0bfde8d 100644 --- a/src/search/time.rs +++ b/src/search/time.rs @@ -1,5 +1,7 @@ use std::time::Instant; +use super::{HARD_LIMIT_DIVISION, SOFT_EVAL_THRESHOLD, SOFT_LIMIT_DIVISION}; + pub struct TimeInfo { pub time: Instant, 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 +} diff --git a/src/search/transposition_table.rs b/src/search/transposition_table.rs index 8aa0256..3cc79f5 100644 --- a/src/search/transposition_table.rs +++ b/src/search/transposition_table.rs @@ -65,8 +65,8 @@ impl TTEntry { #[derive(Clone)] pub enum Bound { Exact, - Lower, - Upper, + Alpha, + Beta, } #[cfg(test)]