feat: some extra uci info about the best line, etc

This commit is contained in:
dogeystamp 2024-11-02 19:33:29 -04:00
parent b7b3c6c5b8
commit 96b4816f84
4 changed files with 71 additions and 25 deletions

View File

@ -1,7 +1,6 @@
/*
This file is part of chess_inator.
chess_inator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
chess_inator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
@ -13,9 +12,10 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
//! Main UCI engine binary.
use chess_inator::eval::EvalInt;
use chess_inator::fen::FromFen;
use chess_inator::movegen::{FromUCIAlgebraic, Move, ToUCIAlgebraic};
use chess_inator::search::best_move;
use chess_inator::search::{best_line, SearchEval};
use chess_inator::Board;
use std::io;
@ -88,7 +88,24 @@ fn cmd_position(mut tokens: std::str::SplitWhitespace<'_>) -> Board {
/// Play the game.
fn cmd_go(mut _tokens: std::str::SplitWhitespace<'_>, board: &mut Board) {
let chosen = best_move(board);
let (line, eval) = best_line(board);
let chosen = line.last().copied();
println!(
"info pv{}",
line.iter()
.rev()
.map(|mv| mv.to_uci_algebraic())
.fold(String::new(), |a, b| a + " " + &b)
);
match eval {
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
SearchEval::Centipawns(eval) => {
println!(
"info score cp {}",
eval,
)
}
}
match chosen {
Some(mv) => println!("bestmove {}", mv.to_uci_algebraic()),
None => println!("bestmove 0000"),

View File

@ -23,6 +23,8 @@ pub type EvalInt = i16;
pub trait Eval {
/// Evaluate a position and assign it a score.
///
/// Negative for Black advantage and positive for White.
fn eval(&self) -> EvalInt;
}
@ -46,6 +48,7 @@ pub(crate) mod eval_score {
/// Score from a given perspective (e.g. midgame, endgame).
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Debug)]
pub struct EvalScore {
/// Signed score.
pub score: EvalInt,
}
@ -234,8 +237,21 @@ pub const PST_MIDGAME: Pst = Pst([
], 100),
]);
/// Calculate evaluation without incremental updates.
pub(crate) fn refresh_eval(board: &Board) -> EvalInt {
let mut eval: EvalInt = 0;
for sq in Board::squares() {
if let Some(pc) = board.get_piece(sq) {
eval += PST_MIDGAME[pc.pc][pc.col][sq] * EvalInt::from(pc.col.sign());
}
}
eval
}
impl Eval for Board {
fn eval(&self) -> EvalInt {
let score_incremental = self.eval.midgame.score;
debug_assert_eq!(refresh_eval(self), score_incremental);
self.eval.midgame.score
}
}

View File

@ -585,6 +585,11 @@ impl Board {
false
}
/// Get the current player to move.
pub fn get_turn(&self) -> Color {
self.turn
}
/// Maximum amount of moves in the counter to parse before giving up
const MAX_MOVES: usize = 9_999;
}

View File

@ -14,7 +14,7 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
//! Game-tree search.
use crate::eval::{Eval, EvalInt};
use crate::movegen::{Move, MoveGen, ToUCIAlgebraic};
use crate::movegen::{Move, MoveGen};
use crate::Board;
use std::cmp::max;
@ -35,8 +35,8 @@ mod test_eval_int {
}
/// Eval in the context of search.
#[derive(PartialEq, Eq, Clone, Copy)]
enum SearchEval {
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum SearchEval {
/// Mate in |n| - 1 half moves, negative for own mate.
Checkmate(i8),
/// Centipawn score.
@ -66,7 +66,7 @@ impl From<SearchEval> for EvalInt {
SearchEval::Checkmate(n) => {
debug_assert_ne!(n, 0);
if n < 0 {
EVAL_WORST + EvalInt::from(n)
EVAL_WORST - EvalInt::from(n)
} else {
EVAL_BEST - EvalInt::from(n)
}
@ -102,13 +102,13 @@ impl PartialOrd for SearchEval {
///
/// # Returns
///
/// The best move, and its corresponding absolute eval for the current player.
/// The best line (in reverse move order), and its corresponding absolute eval for the current player.
fn minmax(
board: &mut Board,
depth: usize,
alpha: Option<EvalInt>,
beta: Option<EvalInt>,
) -> (Option<Move>, SearchEval) {
) -> (Vec<Move>, SearchEval) {
// default to worst, then gradually improve
let mut alpha = alpha.unwrap_or(EVAL_WORST);
// our best is their worst
@ -116,33 +116,35 @@ fn minmax(
if depth == 0 {
let eval = board.eval();
match board.turn {
crate::Color::White => return (None, SearchEval::Centipawns(eval)),
crate::Color::Black => return (None, SearchEval::Centipawns(-eval)),
}
return (
Vec::new(),
SearchEval::Centipawns(eval * EvalInt::from(board.turn.sign())),
);
}
let mvs: Vec<_> = board.gen_moves().into_iter().collect();
let mut abs_best = SearchEval::Centipawns(EVAL_WORST);
let mut best_move: Option<Move> = None;
let mut best_continuation: Vec<Move> = Vec::new();
if mvs.is_empty() {
if board.is_check(board.turn) {
return (None, SearchEval::Checkmate(-1));
return (Vec::new(), SearchEval::Checkmate(-1));
} else {
// stalemate
return (None, SearchEval::Centipawns(0));
return (Vec::new(), SearchEval::Centipawns(0));
}
}
for mv in mvs {
let anti_mv = mv.make(board);
let (_best_continuation, score) = minmax(board, depth - 1, Some(-beta), Some(-alpha));
let (continuation, score) = minmax(board, depth - 1, Some(-beta), Some(-alpha));
let abs_score = score.increment();
if abs_score >= abs_best {
if abs_score > abs_best {
abs_best = abs_score;
best_move = Some(mv);
best_continuation = continuation;
}
alpha = max(alpha, abs_best.into());
anti_mv.unmake(board);
@ -150,22 +152,28 @@ fn minmax(
// alpha-beta prune.
//
// Beta represents the best eval that the other player can get in sibling branches
// (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.
// Therefore, we stop evaluating this branch at all.
break;
}
}
(best_move, abs_best)
if let Some(mv) = best_move {
best_continuation.push(mv);
}
(best_continuation, abs_best)
}
/// Find the best line (in reverse order) and its evaluation.
pub fn best_line(board: &mut Board) -> (Vec<Move>, SearchEval) {
let (line, eval) = minmax(board, 5, None, None);
(line, eval)
}
/// Find the best move.
pub fn best_move(board: &mut Board) -> Option<Move> {
let (mv, eval) = minmax(board, 5, None, None);
match eval {
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
SearchEval::Centipawns(eval) => println!("info score cp {eval}"),
}
mv
let (line, _eval) = best_line(board);
line.last().copied()
}