feat: tapered eval for endgame

This commit is contained in:
dogeystamp 2024-11-02 22:16:21 -04:00
parent da6b3f20f9
commit 7d7a2531ad
3 changed files with 132 additions and 35 deletions

View File

@ -99,10 +99,7 @@ fn cmd_go(mut _tokens: std::str::SplitWhitespace<'_>, board: &mut Board) {
match eval {
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
SearchEval::Centipawns(eval) => {
println!(
"info score cp {}",
eval,
)
println!("info score cp {}", eval,)
}
}
match chosen {

View File

@ -14,6 +14,7 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
//! Position evaluation.
use crate::{Board, Color, Piece, Square, N_COLORS, N_PIECES, N_SQUARES};
use core::cmp::max;
use core::ops::Index;
/// Signed centipawn type.
@ -31,7 +32,7 @@ pub trait Eval {
pub(crate) mod eval_score {
//! Opaque "score" counters to be used in the board.
use super::{EvalInt, Pst};
use super::{EvalInt, PST_ENDGAME, PST_MIDGAME};
use crate::{ColPiece, Square};
/// Internal score-keeping for a board.
@ -43,6 +44,43 @@ pub(crate) mod eval_score {
pub midgame: EvalScore,
/// End-game perspective evaluation of this board.
pub endgame: EvalScore,
/// Non-pawn/king piece count, used to determine when the endgame has begun.
pub min_maj_pieces: u8,
}
impl EvalScores {
/// Add/remove the value of a piece based on the PST.
///
/// Use +1 as sign to add, -1 to delete.
fn change_piece(&mut self, pc: ColPiece, sq: Square, sign: i8) {
assert!(sign == 1 || sign == -1);
let tables = [
(&mut self.midgame, PST_MIDGAME),
(&mut self.endgame, PST_ENDGAME),
];
for (phase, pst) in tables {
phase.score += pst[pc.pc][pc.col][sq] * EvalInt::from(pc.col.sign() * sign);
}
use crate::Piece::*;
if matches!(pc.pc, Rook | Queen | Knight | Bishop) {
match sign {
-1 => self.min_maj_pieces -= 1,
1 => self.min_maj_pieces += 1,
_ => panic!(),
}
}
}
/// Remove the value of a piece on a square.
pub fn del_piece(&mut self, pc: ColPiece, sq: Square) {
self.change_piece(pc, sq, -1);
}
/// Add the value of a piece on a square.
pub fn add_piece(&mut self, pc: ColPiece, sq: Square) {
self.change_piece(pc, sq, 1);
}
}
/// Score from a given perspective (e.g. midgame, endgame).
@ -51,18 +89,6 @@ pub(crate) mod eval_score {
/// Signed score.
pub score: EvalInt,
}
impl EvalScore {
/// Remove the value of a piece on a square.
pub fn del_piece(&mut self, pc: ColPiece, sq: Square, pst: &Pst) {
self.score -= pst[pc.pc][pc.col][sq] * EvalInt::from(pc.col.sign());
}
/// Add the value of a piece on a square.
pub fn add_piece(&mut self, pc: ColPiece, sq: Square, pst: &Pst) {
self.score += pst[pc.pc][pc.col][sq] * EvalInt::from(pc.col.sign());
}
}
}
/// The main piece-square-table (PST) type that assigns scores to pieces on given squares.
@ -237,22 +263,97 @@ 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
}
#[rustfmt::skip]
pub const PST_ENDGAME: Pst = Pst([
// rook
make_pst([
0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, // 7
1, 2, 3, 1, 2, 1, 2, 1, // 6
1, 2, 1, 2, 1, 1, 1, 1, // 5
1, 1, 2, 1, 1, 1, 2, 1, // 4
2, 1, 1, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, // 1
// a b c d e f g h
], 500),
// bishop
make_pst([
0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, // 1
// a b c d e f g h
], 300),
// knight
make_pst([
-5, -5, -5, -5, -5, -5, -5, -5, // 8
-5, 0, 0, 0, 0, 0, 0, -5, // 7
-5, 0, 0, 0, 0, 0, 0, -5, // 6
-5, 0, 0, 0, 0, 0, 0, -5, // 5
-5, 0, 0, 0, 0, 0, 0, -5, // 4
-5, 0, 0, 0, 0, 0, 0, -5, // 3
-5, 0, 0, 0, 0, 0, 0, -5, // 2
-5, -5, -5, -5, -5, -5, -5, -5, // 1
// a b c d e f g h
], 300),
// king
make_pst([
-50, -50, -50, -50, -50, -50, -50, -50, // 8
-50, -10, -10, -10, -10, -10, -10, -50, // 7
-50, -10, 0, 0, 0, 0, -10, -50, // 6
-50, -10, 0, 4, 4, 0, -10, -50, // 5
-50, -10, 0, 4, 4, 0, -10, -50, // 4
-50, -10, 0, 0, 0, 0, -10, -50, // 3
-50, -10, -10, -10, -10, -10, -10, -50, // 2
-50, -50, -50, -50, -50, -50, -50, -50, // 1
// a b c d e f g h
], 20_000),
// queen
make_pst([
0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, // 1
// a b c d e f g h
], 900),
// pawn
make_pst([
10, 10, 10, 10, 10, 10, 10, 10, // 8
39, 39, 39, 39, 39, 39, 39, 39, // 7
38, 38, 38, 38, 38, 38, 38, 38, // 6
37, 37, 37, 38, 38, 37, 37, 37, // 5
36, 36, 36, 36, 36, 36, 36, 36, // 4
32, 32, 32, 34, 34, 30, 32, 30, // 3
0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, // 1
// a b c d e f g h
], 100),
]);
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
// we'll define endgame as the moment when there are 7 non pawn/king pieces left on the
// board in total.
//
// `phase` will range from 7 (game start) to 0 (endgame).
let phase = i32::from(self.eval.min_maj_pieces.saturating_sub(7));
let eval = i32::from(self.eval.midgame.score) * phase / 7
+ i32::from(self.eval.endgame.score) * max(7 - phase, 0) / 7;
eval.try_into().unwrap()
}
}
@ -269,7 +370,7 @@ mod tests {
let board2 = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/8/4K3 w kq - 0 1").unwrap();
let eval2 = board2.eval();
assert!(eval1 > 0, "got eval {eval1}");
assert!(eval2 < 0, "got eval {eval2}");
assert!(eval1 > 0, "got eval {eval1} ({:?})", board1.eval);
assert!(eval2 < 0, "got eval {eval2} ({:?})", board2.eval);
}
}

View File

@ -24,7 +24,6 @@ pub mod search;
use crate::fen::{FromFen, ToFen, START_POSITION};
use eval::eval_score::EvalScores;
use eval::PST_MIDGAME;
const BOARD_WIDTH: usize = 8;
const BOARD_HEIGHT: usize = 8;
@ -469,7 +468,7 @@ impl Board {
let pl = &mut self[pc.col];
pl[pc.into()].on_sq(sq);
*self.mail.sq_mut(sq) = Some(pc);
self.eval.midgame.add_piece(pc, sq, &PST_MIDGAME);
self.eval.add_piece(pc, sq);
dest_pc
}
@ -487,7 +486,7 @@ impl Board {
let pl = &mut self[pc.col];
pl[pc.into()].off_sq(sq);
*self.mail.sq_mut(sq) = None;
self.eval.midgame.del_piece(pc, sq, &PST_MIDGAME);
self.eval.del_piece(pc, sq);
Some(pc)
} else {
None