feat: tapered eval for endgame
This commit is contained in:
parent
da6b3f20f9
commit
7d7a2531ad
@ -99,10 +99,7 @@ fn cmd_go(mut _tokens: std::str::SplitWhitespace<'_>, board: &mut Board) {
|
|||||||
match eval {
|
match eval {
|
||||||
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
|
SearchEval::Checkmate(n) => println!("info score mate {}", n / 2),
|
||||||
SearchEval::Centipawns(eval) => {
|
SearchEval::Centipawns(eval) => {
|
||||||
println!(
|
println!("info score cp {}", eval,)
|
||||||
"info score cp {}",
|
|
||||||
eval,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match chosen {
|
match chosen {
|
||||||
|
157
src/eval.rs
157
src/eval.rs
@ -14,6 +14,7 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
|||||||
//! Position evaluation.
|
//! Position evaluation.
|
||||||
|
|
||||||
use crate::{Board, Color, Piece, Square, N_COLORS, N_PIECES, N_SQUARES};
|
use crate::{Board, Color, Piece, Square, N_COLORS, N_PIECES, N_SQUARES};
|
||||||
|
use core::cmp::max;
|
||||||
use core::ops::Index;
|
use core::ops::Index;
|
||||||
|
|
||||||
/// Signed centipawn type.
|
/// Signed centipawn type.
|
||||||
@ -31,7 +32,7 @@ pub trait Eval {
|
|||||||
pub(crate) mod eval_score {
|
pub(crate) mod eval_score {
|
||||||
//! Opaque "score" counters to be used in the board.
|
//! Opaque "score" counters to be used in the board.
|
||||||
|
|
||||||
use super::{EvalInt, Pst};
|
use super::{EvalInt, PST_ENDGAME, PST_MIDGAME};
|
||||||
use crate::{ColPiece, Square};
|
use crate::{ColPiece, Square};
|
||||||
|
|
||||||
/// Internal score-keeping for a board.
|
/// Internal score-keeping for a board.
|
||||||
@ -43,6 +44,43 @@ pub(crate) mod eval_score {
|
|||||||
pub midgame: EvalScore,
|
pub midgame: EvalScore,
|
||||||
/// End-game perspective evaluation of this board.
|
/// End-game perspective evaluation of this board.
|
||||||
pub endgame: EvalScore,
|
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).
|
/// Score from a given perspective (e.g. midgame, endgame).
|
||||||
@ -51,18 +89,6 @@ pub(crate) mod eval_score {
|
|||||||
/// Signed score.
|
/// Signed score.
|
||||||
pub score: EvalInt,
|
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.
|
/// 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),
|
], 100),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// Calculate evaluation without incremental updates.
|
#[rustfmt::skip]
|
||||||
pub(crate) fn refresh_eval(board: &Board) -> EvalInt {
|
pub const PST_ENDGAME: Pst = Pst([
|
||||||
let mut eval: EvalInt = 0;
|
// rook
|
||||||
for sq in Board::squares() {
|
make_pst([
|
||||||
if let Some(pc) = board.get_piece(sq) {
|
0, 0, 0, 0, 0, 0, 0, 0, // 8
|
||||||
eval += PST_MIDGAME[pc.pc][pc.col][sq] * EvalInt::from(pc.col.sign());
|
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
|
||||||
eval
|
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 {
|
impl Eval for Board {
|
||||||
fn eval(&self) -> EvalInt {
|
fn eval(&self) -> EvalInt {
|
||||||
let score_incremental = self.eval.midgame.score;
|
// we'll define endgame as the moment when there are 7 non pawn/king pieces left on the
|
||||||
debug_assert_eq!(refresh_eval(self), score_incremental);
|
// board in total.
|
||||||
self.eval.midgame.score
|
//
|
||||||
|
// `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 board2 = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/8/4K3 w kq - 0 1").unwrap();
|
||||||
let eval2 = board2.eval();
|
let eval2 = board2.eval();
|
||||||
|
|
||||||
assert!(eval1 > 0, "got eval {eval1}");
|
assert!(eval1 > 0, "got eval {eval1} ({:?})", board1.eval);
|
||||||
assert!(eval2 < 0, "got eval {eval2}");
|
assert!(eval2 < 0, "got eval {eval2} ({:?})", board2.eval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ pub mod search;
|
|||||||
|
|
||||||
use crate::fen::{FromFen, ToFen, START_POSITION};
|
use crate::fen::{FromFen, ToFen, START_POSITION};
|
||||||
use eval::eval_score::EvalScores;
|
use eval::eval_score::EvalScores;
|
||||||
use eval::PST_MIDGAME;
|
|
||||||
|
|
||||||
const BOARD_WIDTH: usize = 8;
|
const BOARD_WIDTH: usize = 8;
|
||||||
const BOARD_HEIGHT: usize = 8;
|
const BOARD_HEIGHT: usize = 8;
|
||||||
@ -469,7 +468,7 @@ impl Board {
|
|||||||
let pl = &mut self[pc.col];
|
let pl = &mut self[pc.col];
|
||||||
pl[pc.into()].on_sq(sq);
|
pl[pc.into()].on_sq(sq);
|
||||||
*self.mail.sq_mut(sq) = Some(pc);
|
*self.mail.sq_mut(sq) = Some(pc);
|
||||||
self.eval.midgame.add_piece(pc, sq, &PST_MIDGAME);
|
self.eval.add_piece(pc, sq);
|
||||||
dest_pc
|
dest_pc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,7 +486,7 @@ impl Board {
|
|||||||
let pl = &mut self[pc.col];
|
let pl = &mut self[pc.col];
|
||||||
pl[pc.into()].off_sq(sq);
|
pl[pc.into()].off_sq(sq);
|
||||||
*self.mail.sq_mut(sq) = None;
|
*self.mail.sq_mut(sq) = None;
|
||||||
self.eval.midgame.del_piece(pc, sq, &PST_MIDGAME);
|
self.eval.del_piece(pc, sq);
|
||||||
Some(pc)
|
Some(pc)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
Loading…
Reference in New Issue
Block a user