Compare commits
3 Commits
e44cc0586e
...
b0a662460a
Author | SHA1 | Date | |
---|---|---|---|
b0a662460a | |||
68afcb232c | |||
ab1dfb3aa1 |
@ -69,7 +69,7 @@ impl From<Color> for char {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
enum Piece {
|
pub enum Piece {
|
||||||
Rook,
|
Rook,
|
||||||
Bishop,
|
Bishop,
|
||||||
Knight,
|
Knight,
|
||||||
|
14
src/main.rs
14
src/main.rs
@ -135,18 +135,26 @@ fn cmd_go(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ourtime_ms, theirtime_ms) = if state.board.get_turn() == Color::White {
|
let (mut ourtime_ms, theirtime_ms) = if state.board.get_turn() == Color::White {
|
||||||
(wtime, btime)
|
(wtime, btime)
|
||||||
} else {
|
} else {
|
||||||
(btime, wtime)
|
(btime, wtime)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ourtime_ms == 0 {
|
||||||
|
ourtime_ms = 300_000
|
||||||
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
.tx_engine
|
.tx_engine
|
||||||
.send(MsgToEngine::Go(Box::new(GoMessage {
|
.send(MsgToEngine::Go(Box::new(GoMessage {
|
||||||
board: state.board,
|
board: state.board,
|
||||||
config: state.config,
|
config: state.config,
|
||||||
time_lims: TimeLimits::from_ourtime_theirtime(ourtime_ms, theirtime_ms),
|
time_lims: TimeLimits::from_ourtime_theirtime(
|
||||||
|
ourtime_ms,
|
||||||
|
theirtime_ms,
|
||||||
|
eval_metrics(&state.board),
|
||||||
|
),
|
||||||
})))
|
})))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@ -217,7 +225,7 @@ fn outp_bestmove(bestmove: MsgBestmove) {
|
|||||||
);
|
);
|
||||||
match bestmove.eval {
|
match bestmove.eval {
|
||||||
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
|
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
|
||||||
SearchEval::Centipawns(eval) => {
|
SearchEval::Exact(eval) | SearchEval::Lower(eval) | SearchEval::Upper(eval) => {
|
||||||
println!("info score cp {}", eval,)
|
println!("info score cp {}", eval,)
|
||||||
}
|
}
|
||||||
SearchEval::Stopped => {
|
SearchEval::Stopped => {
|
||||||
|
@ -13,9 +13,9 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
|||||||
|
|
||||||
//! Prelude that you can import entirely to use the library conveniently.
|
//! Prelude that you can import entirely to use the library conveniently.
|
||||||
|
|
||||||
pub use crate::eval::{eval_metrics, EvalMetrics};
|
pub use crate::eval::{eval_metrics, EvalMetrics, EvalInt, Eval};
|
||||||
pub use crate::fen::{FromFen, ToFen};
|
pub use crate::fen::{FromFen, ToFen};
|
||||||
pub use crate::movegen::{FromUCIAlgebraic, Move, MoveGen, ToUCIAlgebraic};
|
pub use crate::movegen::{FromUCIAlgebraic, Move, MoveGen, ToUCIAlgebraic};
|
||||||
pub use crate::search::{best_line, best_move, SearchEval, TranspositionTable, EngineState, SearchConfig, TimeLimits};
|
pub use crate::search::{best_line, best_move, SearchEval, TranspositionTable, EngineState, SearchConfig, TimeLimits};
|
||||||
pub use crate::{Board, Color, BOARD_HEIGHT, BOARD_WIDTH, N_COLORS, N_PIECES, N_SQUARES};
|
pub use crate::{Board, Color, BOARD_HEIGHT, BOARD_WIDTH, N_COLORS, N_PIECES, N_SQUARES, ColPiece, Piece};
|
||||||
pub use crate::coordination::{UCIMode, UCIModeTransition, UCIModeMachine, MsgBestmove, MsgToMain, MsgToEngine, GoMessage};
|
pub use crate::coordination::{UCIMode, UCIModeTransition, UCIModeMachine, MsgBestmove, MsgToMain, MsgToEngine, GoMessage};
|
||||||
|
@ -13,14 +13,11 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
|||||||
|
|
||||||
//! Game-tree search.
|
//! Game-tree search.
|
||||||
|
|
||||||
use crate::coordination::MsgToEngine;
|
|
||||||
use crate::eval::{Eval, EvalInt};
|
|
||||||
use crate::hash::ZobristTable;
|
use crate::hash::ZobristTable;
|
||||||
use crate::movegen::{Move, MoveGen};
|
use crate::prelude::*;
|
||||||
use crate::{Board, Piece};
|
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
// min can't be represented as positive
|
// min can't be represented as positive
|
||||||
const EVAL_WORST: EvalInt = -(EvalInt::MAX);
|
const EVAL_WORST: EvalInt = -(EvalInt::MAX);
|
||||||
@ -43,8 +40,12 @@ mod test_eval_int {
|
|||||||
pub enum SearchEval {
|
pub enum SearchEval {
|
||||||
/// Mate in |n| - 1 half moves, negative for own mate.
|
/// Mate in |n| - 1 half moves, negative for own mate.
|
||||||
Checkmate(i8),
|
Checkmate(i8),
|
||||||
/// Centipawn score.
|
/// Centipawn score (exact).
|
||||||
Centipawns(EvalInt),
|
Exact(EvalInt),
|
||||||
|
/// Centipawn score (lower bound).
|
||||||
|
Lower(EvalInt),
|
||||||
|
/// Centipawn score (upper bound).
|
||||||
|
Upper(EvalInt),
|
||||||
/// Search was hard-stopped.
|
/// Search was hard-stopped.
|
||||||
Stopped,
|
Stopped,
|
||||||
}
|
}
|
||||||
@ -61,7 +62,9 @@ impl SearchEval {
|
|||||||
Self::Checkmate(-(n + 1))
|
Self::Checkmate(-(n + 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchEval::Centipawns(eval) => Self::Centipawns(-eval),
|
SearchEval::Exact(eval) => Self::Exact(-eval),
|
||||||
|
SearchEval::Lower(eval) => Self::Upper(-eval),
|
||||||
|
SearchEval::Upper(eval) => Self::Lower(-eval),
|
||||||
SearchEval::Stopped => SearchEval::Stopped,
|
SearchEval::Stopped => SearchEval::Stopped,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,7 +81,9 @@ impl From<SearchEval> for EvalInt {
|
|||||||
EVAL_BEST - EvalInt::from(n)
|
EVAL_BEST - EvalInt::from(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchEval::Centipawns(eval) => eval,
|
SearchEval::Exact(eval) => eval,
|
||||||
|
SearchEval::Lower(eval) => eval,
|
||||||
|
SearchEval::Upper(eval) => eval,
|
||||||
SearchEval::Stopped => panic!("Attempted to evaluate a halted search"),
|
SearchEval::Stopped => panic!("Attempted to evaluate a halted search"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,14 +128,6 @@ impl Default for SearchConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If a move is a capture, return which piece is capturing what.
|
|
||||||
fn move_get_capture(board: &mut Board, mv: &Move) -> Option<(Piece, Piece)> {
|
|
||||||
// TODO: en passant
|
|
||||||
board
|
|
||||||
.get_piece(mv.dest)
|
|
||||||
.map(|cap_pc| (board.get_piece(mv.src).unwrap().into(), cap_pc.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Least valuable victim, most valuable attacker heuristic for captures.
|
/// Least valuable victim, most valuable attacker heuristic for captures.
|
||||||
fn lvv_mva_eval(src_pc: Piece, cap_pc: Piece) -> EvalInt {
|
fn lvv_mva_eval(src_pc: Piece, cap_pc: Piece) -> EvalInt {
|
||||||
let pc_values = [500, 300, 300, 20000, 900, 100];
|
let pc_values = [500, 300, 300, 20000, 900, 100];
|
||||||
@ -138,14 +135,23 @@ fn lvv_mva_eval(src_pc: Piece, cap_pc: Piece) -> EvalInt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Assign a priority to a move based on how promising it is.
|
/// Assign a priority to a move based on how promising it is.
|
||||||
fn move_priority(board: &mut Board, mv: &Move) -> EvalInt {
|
fn move_priority(board: &mut Board, mv: &Move, state: &mut EngineState) -> EvalInt {
|
||||||
// move eval
|
// move eval
|
||||||
let mut eval: EvalInt = 0;
|
let mut eval: EvalInt = 0;
|
||||||
if let Some((src_pc, cap_pc)) = move_get_capture(board, mv) {
|
let src_pc = board.get_piece(mv.src).unwrap();
|
||||||
|
let anti_mv = mv.make(board);
|
||||||
|
|
||||||
|
if state.config.enable_trans_table {
|
||||||
|
if let Some(entry) = &state.cache[board.zobrist] {
|
||||||
|
eval = entry.eval.into();
|
||||||
|
}
|
||||||
|
} else if let Some(cap_pc) = anti_mv.cap {
|
||||||
// least valuable victim, most valuable attacker
|
// least valuable victim, most valuable attacker
|
||||||
eval += lvv_mva_eval(src_pc, cap_pc)
|
eval += lvv_mva_eval(src_pc.into(), cap_pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anti_mv.unmake(board);
|
||||||
|
|
||||||
eval
|
eval
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +206,7 @@ fn minmax(
|
|||||||
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
let eval = board.eval() * EvalInt::from(board.turn.sign());
|
let eval = board.eval() * EvalInt::from(board.turn.sign());
|
||||||
return (Vec::new(), SearchEval::Centipawns(eval));
|
return (Vec::new(), SearchEval::Exact(eval));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mvs: Vec<_> = board
|
let mut mvs: Vec<_> = board
|
||||||
@ -208,17 +214,17 @@ fn minmax(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mv| (move_priority(board, &mv), mv))
|
.map(|mv| (move_priority(board, &mv, state), mv))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// get transposition table entry
|
// get transposition table entry
|
||||||
if state.config.enable_trans_table {
|
if state.config.enable_trans_table {
|
||||||
if let Some(entry) = &state.cache[board.zobrist] {
|
if let Some(entry) = &state.cache[board.zobrist] {
|
||||||
// the entry has a deeper knowledge than we do, so follow its best move exactly instead of
|
|
||||||
// just prioritizing what it thinks is best
|
|
||||||
if entry.depth >= depth {
|
if entry.depth >= depth {
|
||||||
// we don't save PV line in transposition table, so no information on that
|
if let SearchEval::Exact(_) | SearchEval::Upper(_) = entry.eval {
|
||||||
return (vec![entry.best_move], entry.eval);
|
// no point looking for a better move
|
||||||
|
return (vec![entry.best_move], entry.eval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mvs.push((EVAL_BEST, entry.best_move));
|
mvs.push((EVAL_BEST, entry.best_move));
|
||||||
}
|
}
|
||||||
@ -227,7 +233,7 @@ fn minmax(
|
|||||||
// sort moves by decreasing priority
|
// sort moves by decreasing priority
|
||||||
mvs.sort_unstable_by_key(|mv| -mv.0);
|
mvs.sort_unstable_by_key(|mv| -mv.0);
|
||||||
|
|
||||||
let mut abs_best = SearchEval::Centipawns(EVAL_WORST);
|
let mut abs_best = SearchEval::Exact(EVAL_WORST);
|
||||||
let mut best_move: Option<Move> = None;
|
let mut best_move: Option<Move> = None;
|
||||||
let mut best_continuation: Vec<Move> = Vec::new();
|
let mut best_continuation: Vec<Move> = Vec::new();
|
||||||
|
|
||||||
@ -236,7 +242,7 @@ fn minmax(
|
|||||||
return (Vec::new(), SearchEval::Checkmate(-1));
|
return (Vec::new(), SearchEval::Checkmate(-1));
|
||||||
} else {
|
} else {
|
||||||
// stalemate
|
// stalemate
|
||||||
return (Vec::new(), SearchEval::Centipawns(0));
|
return (Vec::new(), SearchEval::Exact(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +270,9 @@ fn minmax(
|
|||||||
// (different moves in the parent node). Alpha > beta means the eval here is _worse_
|
// (different moves in the parent node). Alpha > beta means the eval here is _worse_
|
||||||
// for the other player, so they will never make the move that leads into this branch.
|
// for the other player, so they will never make the move that leads into this branch.
|
||||||
// Therefore, we stop evaluating this branch at all.
|
// Therefore, we stop evaluating this branch at all.
|
||||||
|
if let SearchEval::Upper(eval) | SearchEval::Exact(eval) = abs_best {
|
||||||
|
abs_best = SearchEval::Lower(eval);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,12 +354,23 @@ pub struct TimeLimits {
|
|||||||
|
|
||||||
impl TimeLimits {
|
impl TimeLimits {
|
||||||
/// Make time limits based on wtime, btime (but color-independent).
|
/// Make time limits based on wtime, btime (but color-independent).
|
||||||
pub fn from_ourtime_theirtime(ourtime_ms: u64, _theirtime_ms: u64) -> TimeLimits {
|
///
|
||||||
|
/// Also takes in eval metrics, for instance to avoid wasting too much time in the opening.
|
||||||
|
pub fn from_ourtime_theirtime(
|
||||||
|
ourtime_ms: u64,
|
||||||
|
_theirtime_ms: u64,
|
||||||
|
eval: EvalMetrics,
|
||||||
|
) -> TimeLimits {
|
||||||
// hard timeout (max)
|
// hard timeout (max)
|
||||||
let mut hard_ms = 100_000;
|
let mut hard_ms = 100_000;
|
||||||
// soft timeout (max)
|
// soft timeout (max)
|
||||||
let mut soft_ms = 1_200;
|
let mut soft_ms = 1_200;
|
||||||
|
|
||||||
|
// if we have more than 5 minutes, and we're out of the opening, we can afford to think longer
|
||||||
|
if ourtime_ms > 300_000 && eval.phase <= 13 {
|
||||||
|
soft_ms = 4_500
|
||||||
|
}
|
||||||
|
|
||||||
let factor = if ourtime_ms > 5_000 { 10 } else { 40 };
|
let factor = if ourtime_ms > 5_000 { 10 } else { 40 };
|
||||||
hard_ms = min(ourtime_ms / factor, hard_ms);
|
hard_ms = min(ourtime_ms / factor, hard_ms);
|
||||||
soft_ms = min(ourtime_ms / 50, soft_ms);
|
soft_ms = min(ourtime_ms / 50, soft_ms);
|
||||||
|
Loading…
Reference in New Issue
Block a user