From 2b359b2bb9211b25f409472808b9ddaec6c3f21d Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Fri, 13 Dec 2024 15:41:35 -0500 Subject: [PATCH 1/9] chore: make psqt and types public Needed for tuning (cherry picked from commit 9a138e280bed2580781310e8378c82bc53c611f0) --- engine/src/psqt.rs | 30 +++++++++++++++--------------- engine/src/score.rs | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/engine/src/psqt.rs b/engine/src/psqt.rs index a9df231..9d072ab 100644 --- a/engine/src/psqt.rs +++ b/engine/src/psqt.rs @@ -19,15 +19,15 @@ use crate::score::{Score, ScoreType}; /// Mid-game piece values /// Ordered to match the indexing of [`Piece`] /// King, Queen, Rook, Bishop, Knight, Pawn -pub(crate) const MG_VALUE: [ScoreType; 6] = [0, 1025, 477, 365, 337, 82]; +pub const MG_VALUE: [ScoreType; 6] = [0, 1025, 477, 365, 337, 82]; /// End-game piece values /// Ordered to match the indexing of [`Piece`] /// King, Queen, Rook, Bishop, Knight, Pawn -const EG_VALUE: [ScoreType; 6] = [0, 936, 512, 297, 281, 94]; +pub const EG_VALUE: [ScoreType; 6] = [0, 936, 512, 297, 281, 94]; #[rustfmt::skip] -const MG_PAWN_TABLE: [ScoreType; 64] = [ +pub const MG_PAWN_TABLE: [ScoreType; 64] = [ 0, 0, 0, 0, 0, 0, 0, 0, 98, 134, 61, 95, 68, 126, 34, -11, -6, 7, 26, 31, 65, 56, 25, -20, @@ -39,7 +39,7 @@ const MG_PAWN_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const EG_PAWN_TABLE: [ScoreType; 64] = [ +pub const EG_PAWN_TABLE: [ScoreType; 64] = [ 0, 0, 0, 0, 0, 0, 0, 0, 178, 173, 158, 134, 147, 132, 165, 187, 94, 100, 85, 67, 56, 53, 82, 84, @@ -51,7 +51,7 @@ const EG_PAWN_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const MG_KNIGHT_TABLE: [ScoreType; 64] = [ +pub const MG_KNIGHT_TABLE: [ScoreType; 64] = [ -167, -89, -34, -49, 61, -97, -15, -107, -73, -41, 72, 36, 23, 62, 7, -17, -47, 60, 37, 65, 84, 129, 73, 44, @@ -63,7 +63,7 @@ const MG_KNIGHT_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const EG_KNIGHT_TABLE: [ScoreType; 64] = [ +pub const EG_KNIGHT_TABLE: [ScoreType; 64] = [ -58, -38, -13, -28, -31, -27, -63, -99, -25, -8, -25, -2, -9, -25, -24, -52, -24, -20, 10, 9, -1, -9, -19, -41, @@ -75,7 +75,7 @@ const EG_KNIGHT_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const MG_BISHOP_TABLE: [ScoreType; 64] = [ +pub const MG_BISHOP_TABLE: [ScoreType; 64] = [ -29, 4, -82, -37, -25, -42, 7, -8, -26, 16, -18, -13, 30, 59, 18, -47, -16, 37, 43, 40, 35, 50, 37, -2, @@ -87,7 +87,7 @@ const MG_BISHOP_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const EG_BISHOP_TABLE: [ScoreType; 64] = [ +pub const EG_BISHOP_TABLE: [ScoreType; 64] = [ -14, -21, -11, -8, -7, -9, -17, -24, -8, -4, 7, -12, -3, -13, -4, -14, 2, -8, 0, -1, -2, 6, 0, 4, @@ -99,7 +99,7 @@ const EG_BISHOP_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const MG_ROOK_TABLE: [ScoreType; 64] = [ +pub const MG_ROOK_TABLE: [ScoreType; 64] = [ 32, 42, 32, 51, 63, 9, 31, 43, 27, 32, 58, 62, 80, 67, 26, 44, -5, 19, 26, 36, 17, 45, 61, 16, @@ -111,7 +111,7 @@ const MG_ROOK_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const EG_ROOK_TABLE: [ScoreType; 64] = [ +pub const EG_ROOK_TABLE: [ScoreType; 64] = [ 13, 10, 18, 15, 12, 12, 8, 5, 11, 13, 13, 11, -3, 3, 8, 3, 7, 7, 7, 5, 4, -3, -5, -3, @@ -123,7 +123,7 @@ const EG_ROOK_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const MG_QUEEN_TABLE: [ScoreType; 64] = [ +pub const MG_QUEEN_TABLE: [ScoreType; 64] = [ -28, 0, 29, 12, 59, 44, 43, 45, -24, -39, -5, 1, -16, 57, 28, 54, -13, -17, 7, 8, 29, 56, 47, 57, @@ -135,7 +135,7 @@ const MG_QUEEN_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const EG_QUEEN_TABLE: [ScoreType; 64] = [ +pub const EG_QUEEN_TABLE: [ScoreType; 64] = [ -9, 22, 22, 27, 27, 19, 10, 20, -17, 20, 32, 41, 58, 25, 30, 0, -20, 6, 9, 49, 47, 35, 19, 9, @@ -147,7 +147,7 @@ const EG_QUEEN_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const MG_KING_TABLE: [ScoreType; 64] = [ +pub const MG_KING_TABLE: [ScoreType; 64] = [ -65, 23, 16, -15, -56, -34, 2, 13, 29, -1, -20, -7, -8, -4, -38, -29, -9, 24, 2, -16, -20, 6, 22, -22, @@ -159,7 +159,7 @@ const MG_KING_TABLE: [ScoreType; 64] = [ ]; #[rustfmt::skip] -const EG_KING_TABLE: [ScoreType; 64] = [ +pub const EG_KING_TABLE: [ScoreType; 64] = [ -74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14, 17, 17, 38, 23, 11, 10, 17, 23, 15, 20, 45, 44, 13, @@ -195,7 +195,7 @@ const EG_PESTO_TABLE: [&[ScoreType; 64]; 6] = [ /// Game phase increment for each piece /// Ordered to match the indexing of [`Piece`] /// King, Queen, Rook, Bishop, Knight, Pawn -const GAMEPHASE_INC: [ScoreType; 6] = [0, 4, 2, 1, 1, 0]; +pub const GAMEPHASE_INC: [ScoreType; 6] = [0, 4, 2, 1, 1, 0]; /// Piece-Square Tables (PST) for evaluation pub(crate) struct Psqt { diff --git a/engine/src/score.rs b/engine/src/score.rs index 3bf24dc..5aa5783 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -21,7 +21,7 @@ use uci_parser::UciScore; use crate::defs::MAX_DEPTH; -pub(crate) type ScoreType = i16; +pub type ScoreType = i16; pub(crate) type MoveOrderScoreType = i32; /// Represents a score in centipawns. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] From 5d5e88ef2df89a65c044c4f141dd0ff2dc064979 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Mon, 16 Dec 2024 22:42:41 -0500 Subject: [PATCH 2/9] feat: add test for evaluation stability bench: 1583604 --- engine/src/evaluation.rs | 286 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 5 deletions(-) diff --git a/engine/src/evaluation.rs b/engine/src/evaluation.rs index 998b1f1..d1b9d81 100644 --- a/engine/src/evaluation.rs +++ b/engine/src/evaluation.rs @@ -105,13 +105,10 @@ impl Evaluation { #[cfg(test)] mod tests { use chess::{ - moves::{self, Move}, - pieces::{Piece, ALL_PIECES, PIECE_SHORT_NAMES}, - side::Side, - square::Square, + board::Board, moves::{self, Move}, pieces::{Piece, ALL_PIECES, PIECE_SHORT_NAMES}, side::Side, square::Square }; - use crate::{evaluation::Evaluation, score::MoveOrderScoreType}; + use crate::{evaluation::Evaluation, score::{MoveOrderScoreType, ScoreType}}; #[test] fn mvv_lva_scaling() { @@ -177,4 +174,283 @@ mod tests { Evaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) ); } + + #[test] + fn score_stability() { + // These values were determined empirically by running this test and manually copy/pasting the results + // If any changes are made to the evaluation function, these values will need to be updated or the test will need to be augmented with the new evaluation values. + + // standard EPD suite FEN positions + let positions = [ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", + "4k3/8/8/8/8/8/8/4K2R w K - 0 1", + "4k3/8/8/8/8/8/8/R3K3 w Q - 0 1", + "4k2r/8/8/8/8/8/8/4K3 w k - 0 1", + "r3k3/8/8/8/8/8/8/4K3 w q - 0 1", + "4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1", + "r3k2r/8/8/8/8/8/8/4K3 w kq - 0 1", + "8/8/8/8/8/8/6k1/4K2R w K - 0 1", + "8/8/8/8/8/8/1k6/R3K3 w Q - 0 1", + "4k2r/6K1/8/8/8/8/8/8 w k - 0 1", + "r3k3/1K6/8/8/8/8/8/8 w q - 0 1", + "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", + "r3k2r/8/8/8/8/8/8/1R2K2R w Kkq - 0 1", + "r3k2r/8/8/8/8/8/8/2R1K2R w Kkq - 0 1", + "r3k2r/8/8/8/8/8/8/R3K1R1 w Qkq - 0 1", + "1r2k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1", + "2r1k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1", + "r3k1r1/8/8/8/8/8/8/R3K2R w KQq - 0 1", + "4k3/8/8/8/8/8/8/4K2R b K - 0 1", + "4k3/8/8/8/8/8/8/R3K3 b Q - 0 1", + "4k2r/8/8/8/8/8/8/4K3 b k - 0 1", + "r3k3/8/8/8/8/8/8/4K3 b q - 0 1", + "4k3/8/8/8/8/8/8/R3K2R b KQ - 0 1", + "r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1", + "8/8/8/8/8/8/6k1/4K2R b K - 0 1", + "8/8/8/8/8/8/1k6/R3K3 b Q - 0 1", + "4k2r/6K1/8/8/8/8/8/8 b k - 0 1", + "r3k3/1K6/8/8/8/8/8/8 b q - 0 1", + "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1", + "r3k2r/8/8/8/8/8/8/1R2K2R b Kkq - 0 1", + "r3k2r/8/8/8/8/8/8/2R1K2R b Kkq - 0 1", + "r3k2r/8/8/8/8/8/8/R3K1R1 b Qkq - 0 1", + "1r2k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1", + "2r1k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1", + "r3k1r1/8/8/8/8/8/8/R3K2R b KQq - 0 1", + "8/1n4N1/2k5/8/8/5K2/1N4n1/8 w - - 0 1", + "8/1k6/8/5N2/8/4n3/8/2K5 w - - 0 1", + "8/8/4k3/3Nn3/3nN3/4K3/8/8 w - - 0 1", + "K7/8/2n5/1n6/8/8/8/k6N w - - 0 1", + "k7/8/2N5/1N6/8/8/8/K6n w - - 0 1", + "8/1n4N1/2k5/8/8/5K2/1N4n1/8 b - - 0 1", + "8/1k6/8/5N2/8/4n3/8/2K5 b - - 0 1", + "8/8/3K4/3Nn3/3nN3/4k3/8/8 b - - 0 1", + "K7/8/2n5/1n6/8/8/8/k6N b - - 0 1", + "k7/8/2N5/1N6/8/8/8/K6n b - - 0 1", + "B6b/8/8/8/2K5/4k3/8/b6B w - - 0 1", + "8/8/1B6/7b/7k/8/2B1b3/7K w - - 0 1", + "k7/B7/1B6/1B6/8/8/8/K6b w - - 0 1", + "K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1", + "B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1", + "8/8/1B6/7b/7k/8/2B1b3/7K b - - 0 1", + "k7/B7/1B6/1B6/8/8/8/K6b b - - 0 1", + "K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1", + "7k/RR6/8/8/8/8/rr6/7K w - - 0 1", + "R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1", + "7k/RR6/8/8/8/8/rr6/7K b - - 0 1", + "R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1", + "6kq/8/8/8/8/8/8/7K w - - 0 1", + "6KQ/8/8/8/8/8/8/7k b - - 0 1", + "K7/8/8/3Q4/4q3/8/8/7k w - - 0 1", + "6qk/8/8/8/8/8/8/7K b - - 0 1", + "6KQ/8/8/8/8/8/8/7k b - - 0 1", + "K7/8/8/3Q4/4q3/8/8/7k b - - 0 1", + "8/8/8/8/8/K7/P7/k7 w - - 0 1", + "8/8/8/8/8/7K/7P/7k w - - 0 1", + "K7/p7/k7/8/8/8/8/8 w - - 0 1", + "7K/7p/7k/8/8/8/8/8 w - - 0 1", + "8/2k1p3/3pP3/3P2K1/8/8/8/8 w - - 0 1", + "8/8/8/8/8/K7/P7/k7 b - - 0 1", + "8/8/8/8/8/7K/7P/7k b - - 0 1", + "K7/p7/k7/8/8/8/8/8 b - - 0 1", + "7K/7p/7k/8/8/8/8/8 b - - 0 1", + "8/2k1p3/3pP3/3P2K1/8/8/8/8 b - - 0 1", + "8/8/8/8/8/4k3/4P3/4K3 w - - 0 1", + "4k3/4p3/4K3/8/8/8/8/8 b - - 0 1", + "8/8/7k/7p/7P/7K/8/8 w - - 0 1", + "8/8/k7/p7/P7/K7/8/8 w - - 0 1", + "8/8/3k4/3p4/3P4/3K4/8/8 w - - 0 1", + "8/3k4/3p4/8/3P4/3K4/8/8 w - - 0 1", + "8/8/3k4/3p4/8/3P4/3K4/8 w - - 0 1", + "k7/8/3p4/8/3P4/8/8/7K w - - 0 1", + "8/8/7k/7p/7P/7K/8/8 b - - 0 1", + "8/8/k7/p7/P7/K7/8/8 b - - 0 1", + "8/8/3k4/3p4/3P4/3K4/8/8 b - - 0 1", + "8/3k4/3p4/8/3P4/3K4/8/8 b - - 0 1", + "8/8/3k4/3p4/8/3P4/3K4/8 b - - 0 1", + "k7/8/3p4/8/3P4/8/8/7K b - - 0 1", + "7k/3p4/8/8/3P4/8/8/K7 w - - 0 1", + "7k/8/8/3p4/8/8/3P4/K7 w - - 0 1", + "k7/8/8/7p/6P1/8/8/K7 w - - 0 1", + "k7/8/7p/8/8/6P1/8/K7 w - - 0 1", + "k7/8/8/6p1/7P/8/8/K7 w - - 0 1", + "k7/8/6p1/8/8/7P/8/K7 w - - 0 1", + "k7/8/8/3p4/4p3/8/8/7K w - - 0 1", + "k7/8/3p4/8/8/4P3/8/7K w - - 0 1", + "7k/3p4/8/8/3P4/8/8/K7 b - - 0 1", + "7k/8/8/3p4/8/8/3P4/K7 b - - 0 1", + "k7/8/8/7p/6P1/8/8/K7 b - - 0 1", + "k7/8/7p/8/8/6P1/8/K7 b - - 0 1", + "k7/8/8/6p1/7P/8/8/K7 b - - 0 1", + "k7/8/6p1/8/8/7P/8/K7 b - - 0 1", + "k7/8/8/3p4/4p3/8/8/7K b - - 0 1", + "k7/8/3p4/8/8/4P3/8/7K b - - 0 1", + "7k/8/8/p7/1P6/8/8/7K w - - 0 1", + "7k/8/p7/8/8/1P6/8/7K w - - 0 1", + "7k/8/8/1p6/P7/8/8/7K w - - 0 1", + "7k/8/1p6/8/8/P7/8/7K w - - 0 1", + "k7/7p/8/8/8/8/6P1/K7 w - - 0 1", + "k7/6p1/8/8/8/8/7P/K7 w - - 0 1", + "3k4/3pp3/8/8/8/8/3PP3/3K4 w - - 0 1", + "7k/8/8/p7/1P6/8/8/7K b - - 0 1", + "7k/8/p7/8/8/1P6/8/7K b - - 0 1", + "7k/8/8/1p6/P7/8/8/7K b - - 0 1", + "7k/8/1p6/8/8/P7/8/7K b - - 0 1", + "k7/7p/8/8/8/8/6P1/K7 b - - 0 1", + "k7/6p1/8/8/8/8/7P/K7 b - - 0 1", + "3k4/3pp3/8/8/8/8/3PP3/3K4 b - - 0 1", + "8/Pk6/8/8/8/8/6Kp/8 w - - 0 1", + "n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1", + "8/PPPk4/8/8/8/8/4Kppp/8 w - - 0 1", + "n1n5/PPPk4/8/8/8/8/4Kppp/5N1N w - - 0 1", + "8/Pk6/8/8/8/8/6Kp/8 b - - 0 1", + "n1n5/1Pk5/8/8/8/8/5Kp1/5N1N b - - 0 1", + "8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1", + "n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1", + "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1", + "rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3", + ]; + + let scores: [ScoreType; 128] = [ + 0, + 56, + 488, + 499, + -488, + -499, + 980, + -980, + 445, + 458, + -445, + -458, + 0, + 9, + 14, + 12, + -9, + -14, + -12, + -488, + -499, + 488, + 499, + -980, + 980, + -445, + -458, + 445, + 458, + 0, + -9, + -14, + -12, + 9, + 14, + 12, + 2, + 1, + 0, + -342, + 406, + -2, + -1, + 3, + 342, + -406, + 0, + -29, + 634, + -628, + 25, + 29, + -634, + 628, + 0, + -1, + 0, + 1, + -925, + -990, + -77, + 929, + -990, + 77, + 162, + 95, + -162, + -95, + 69, + -162, + -95, + 162, + 95, + -69, + 59, + 59, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + -2, + -27, + 7, + 4, + 7, + -4, + -7, + -169, + 9, + 27, + -7, + -4, + -7, + 4, + 7, + 169, + -9, + -4, + 3, + 4, + -3, + 9, + -9, + 0, + 4, + -3, + -4, + 3, + -9, + 9, + 0, + -3, + 15, + 26, + 42, + 3, + -15, + -26, + -42, + 37, + 53, + ]; + + let eval = Evaluation::new(); + + for (i, fen) in positions.iter().enumerate() { + let board = Board::from_fen(fen).unwrap(); + let score = eval.evaluate_position(&board); + // println!("{},", score.0); + assert_eq!(score.0, scores[i]); + } + + } } From fe1f4e255df613fb21c2d7bb6ea28586c2a1d46f Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 18 Dec 2024 22:15:01 -0500 Subject: [PATCH 3/9] chore: add more docs to `Piece` --- chess/src/pieces.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/chess/src/pieces.rs b/chess/src/pieces.rs index 2f6bdef..289a10a 100644 --- a/chess/src/pieces.rs +++ b/chess/src/pieces.rs @@ -4,7 +4,7 @@ * Created Date: Monday, November 25th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Tue Nov 26 2024 + * Last Modified: Tue Dec 17 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -48,6 +48,11 @@ pub const ALL_PIECES: [Piece; 6] = [ Piece::Pawn, ]; +/// Represents a chess piece. +/// +/// **Note**: The ordinal value of the piece is used throughout the +/// code to index into arrays and tables. Changing the value of a piece +/// would likely be catastrophic and result in a number of bugs and possibly crashes. #[repr(u8)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Piece { From 9783d36e60b02e4164c2fa08e847902a961742be Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 18 Dec 2024 22:15:20 -0500 Subject: [PATCH 4/9] chore: add `-` support for `Rank` --- chess/src/rank.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/chess/src/rank.rs b/chess/src/rank.rs index 323b390..8616d29 100644 --- a/chess/src/rank.rs +++ b/chess/src/rank.rs @@ -4,7 +4,7 @@ * Created Date: Monday, November 25th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Tue Nov 26 2024 + * Last Modified: Wed Dec 18 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -12,6 +12,8 @@ * */ +use std::ops::Sub; + use crate::side::Side; use anyhow::Result; @@ -95,6 +97,14 @@ impl TryFrom for Rank { } } +impl Sub for Rank { + type Output = i8; + + fn sub(self, other: Self) -> i8 { + self as i8 - other as i8 + } +} + #[cfg(test)] mod tests { use super::*; From cb37df514df7fd0255b18fa94fb1991d3504de78 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 18 Dec 2024 22:15:36 -0500 Subject: [PATCH 5/9] chore: add more helper functions to `Square` --- chess/src/square.rs | 90 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/chess/src/square.rs b/chess/src/square.rs index 9abf71c..d5a30fa 100644 --- a/chess/src/square.rs +++ b/chess/src/square.rs @@ -4,7 +4,7 @@ * Created Date: Friday, August 16th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Tue Nov 26 2024 + * Last Modified: Wed Dec 18 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -109,6 +109,76 @@ impl Square { Color::White } } + + /// Flips the current square and returns a new instance at the flipped location. + pub fn flip(&self) -> Self { + let sq = self.to_square_index(); + let flipped_sq = flip(sq); + Self::from_square_index(flipped_sq) + } +} + +/// Flips the square vertically. +/// +/// # Arguments +/// +/// - `sq` - The square to flip. +/// +/// # Returns +/// +/// The flipped square +/// +/// # Examples +/// +/// ``` +/// use chess::square::Square; +/// use chess::square::flip; +/// use chess::file::File; +/// use chess::rank::Rank; +/// +/// let sq = Square::new(File::A, Rank::R1); +/// let flipped_sq = flip(sq.to_square_index()); +/// assert_eq!(flipped_sq, 56); +/// let new_sq = Square::from_square_index(flipped_sq); +/// assert_eq!(new_sq.file, File::A); +/// assert_eq!(new_sq.rank, Rank::R8); +pub const fn flip(sq: u8) -> u8 { + sq ^ 56 +} + +/// Flips the square if the given boolean is `true`. +/// +/// This will flip the square vertically if `flip` is `true`. +/// +/// # Arguments +/// +/// - `flip` - A boolean indicating if the square should be flipped. +/// - `sq` - The square to flip. +/// +/// # Returns +/// +/// The flipped square or sq if `flip` is `false`. +/// +/// # Examples +/// +/// ``` +/// use chess::square::Square; +/// use chess::square::flip_if; +/// use chess::file::File; +/// use chess::rank::Rank; +/// +/// let sq = Square::new(File::A, Rank::R1); +/// let flipped_sq = flip_if(true, sq.to_square_index()); +/// assert_eq!(flipped_sq, 56); +/// let new_sq = Square::from_square_index(flipped_sq); +/// assert_eq!(new_sq.file, File::A); +/// assert_eq!(new_sq.rank, Rank::R8); +pub fn flip_if(should_flip: bool, sq: u8) -> u8 { + if should_flip { + flip(sq) + } else { + sq + } } impl TryFrom<&str> for Square { @@ -184,10 +254,10 @@ pub const fn is_square_on_rank(square: u8, rank: u8) -> bool { #[cfg(test)] mod tests { use crate::{ - definitions::Squares, + definitions::{NumberOf, Squares}, file::File, rank::Rank, - square::{is_square_on_rank, Square}, + square::{is_square_on_rank, to_square, Square}, }; #[test] @@ -215,4 +285,18 @@ mod tests { let new_square = square.offset(-1, -1); assert!(new_square.is_none()); } + + #[test] + fn flip() { + for rank in 0..4_u8 { + for file in 0..NumberOf::FILES as u8 { + let sq = to_square(file, rank); + let square = Square::from_square_index(sq); + let flipped = square.flip(); + assert_eq!(flipped.flip(), square); + assert_eq!(flipped.file, square.file); + assert_eq!(flipped.rank.as_number(), (Rank::R8 - square.rank) as u8); + } + } + } } From e772b521a23891a0fd93e32142a213f7964a39fa Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 18 Dec 2024 22:17:48 -0500 Subject: [PATCH 6/9] feat: re-implement eval using traits and new types Added new types and traits to make eval more generic so we can re-use some aspects of it in our tuner. bench: 1583604 --- engine/src/evaluation.rs | 268 +++++++++++++----------------------- engine/src/hce_values.rs | 118 ++++++++++++++++ engine/src/history_table.rs | 8 +- engine/src/lib.rs | 6 + engine/src/phased_score.rs | 85 ++++++++++++ engine/src/psqt.rs | 38 +++-- engine/src/score.rs | 6 +- engine/src/search.rs | 38 ++--- engine/src/traits.rs | 12 ++ 9 files changed, 371 insertions(+), 208 deletions(-) create mode 100644 engine/src/hce_values.rs create mode 100644 engine/src/phased_score.rs create mode 100644 engine/src/traits.rs diff --git a/engine/src/evaluation.rs b/engine/src/evaluation.rs index d1b9d81..f5924bb 100644 --- a/engine/src/evaluation.rs +++ b/engine/src/evaluation.rs @@ -4,7 +4,7 @@ * Created Date: Thursday, November 21st 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Tue Dec 10 2024 + * Last Modified: Wed Dec 18 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -12,38 +12,29 @@ * */ -use chess::{board::Board, moves::Move, pieces::Piece, side::Side}; +use chess::{bitboard_helpers, board::Board, moves::Move, pieces::Piece, side::Side}; use crate::{ + hce_values::ByteKnightValues, history_table, - psqt::Psqt, - score::{MoveOrderScoreType, Score}, + phased_score::{PhaseType, PhasedScore}, + psqt::GAMEPHASE_INC, + score::{LargeScoreType, Score, ScoreType}, + traits::{Eval, EvalValues}, ttable::TranspositionTableEntry, }; /// Provides static evaluation of a given chess position. -pub struct Evaluation { - psqt: Psqt, +pub struct Evaluation +where + Values: EvalValues + Default, +{ + values: Values, } -impl Default for Evaluation { - fn default() -> Self { - Self::new() - } -} - -impl Evaluation { - pub fn new() -> Self { - Evaluation { psqt: Psqt::new() } - } - - /// Evaluates the given position. - /// - /// # Arguments - /// - /// - `board`: The [`Board`] to evaluate. - pub(crate) fn evaluate_position(&self, board: &Board) -> Score { - self.psqt.evaluate(board) +impl Evaluation { + pub fn new(values: Values) -> Self { + Evaluation { values } } /// Scores a move for ordering. This will return the _negative_ score of @@ -63,9 +54,9 @@ impl Evaluation { mv: &Move, tt_entry: &Option, history_table: &history_table::HistoryTable, - ) -> MoveOrderScoreType { + ) -> LargeScoreType { if tt_entry.is_some_and(|tt| *mv == tt.board_move) { - return MoveOrderScoreType::MIN; + return LargeScoreType::MIN; } let mut score = 0; @@ -82,14 +73,14 @@ impl Evaluation { -score } - pub(crate) fn mvv_lva(captured: Piece, capturing: Piece) -> MoveOrderScoreType { + pub(crate) fn mvv_lva(captured: Piece, capturing: Piece) -> LargeScoreType { let can_capture = captured != Piece::King && captured != Piece::None; - ((can_capture as MoveOrderScoreType) - * (25 * Evaluation::piece_value(captured) - Evaluation::piece_value(capturing))) + ((can_capture as LargeScoreType) + * (25 * Self::piece_value(captured) - Self::piece_value(capturing))) << 16 } - pub(crate) fn piece_value(piece: Piece) -> MoveOrderScoreType { + pub(crate) fn piece_value(piece: Piece) -> LargeScoreType { match piece { Piece::King => 0, Piece::Queen => 5, @@ -102,26 +93,78 @@ impl Evaluation { } } +impl + Default> Eval for Evaluation { + /// Evaluates the given position. + /// + /// # Arguments + /// + /// - `board`: The [`Board`] to evaluate. + fn eval(&self, board: &Board) -> Score { + let side_to_move = board.side_to_move(); + let mut mg: [i32; 2] = [0; 2]; + let mut eg: [i32; 2] = [0; 2]; + let mut game_phase = 0_i32; + + let mut occupancy = board.all_pieces(); + // loop through occupied squares + while occupancy.as_number() > 0 { + let sq = bitboard_helpers::next_bit(&mut occupancy); + let maybe_piece = board.piece_on_square(sq as u8); + if let Some((piece, side)) = maybe_piece { + let phased_score: PhasedScore = self.values.psqt(sq as u8, piece, side); + mg[side as usize] += phased_score.mg() as i32; + eg[side as usize] += phased_score.eg() as i32; + + game_phase += GAMEPHASE_INC[piece as usize] as i32; + } + } + let stm_idx = side_to_move as usize; + let opposite = Side::opposite(side_to_move) as usize; + let mg_score = mg[stm_idx] - mg[opposite]; + let eg_score = eg[stm_idx] - eg[opposite]; + let score = PhasedScore::new(mg_score as ScoreType, eg_score as ScoreType); + // taper the score based on the game phase + let val = score.taper(game_phase.min(24) as PhaseType, 24); + Score::new(val) + } +} + +pub type ByteKnightEvaluation = Evaluation; + +impl Default for ByteKnightEvaluation { + fn default() -> Self { + Self::new(ByteKnightValues::default()) + } +} + #[cfg(test)] mod tests { use chess::{ - board::Board, moves::{self, Move}, pieces::{Piece, ALL_PIECES, PIECE_SHORT_NAMES}, side::Side, square::Square + board::Board, + moves::{self, Move}, + pieces::{Piece, ALL_PIECES, PIECE_SHORT_NAMES}, + side::Side, + square::Square, }; - use crate::{evaluation::Evaluation, score::{MoveOrderScoreType, ScoreType}}; + use crate::{ + evaluation::ByteKnightEvaluation, + score::{LargeScoreType, ScoreType}, + traits::Eval, + }; #[test] fn mvv_lva_scaling() { for captured in ALL_PIECES { for capturing in ALL_PIECES { - let score = Evaluation::mvv_lva(captured, capturing); + let score = ByteKnightEvaluation::mvv_lva(captured, capturing); println!( "{} x {} -> {}", PIECE_SHORT_NAMES[capturing as usize], PIECE_SHORT_NAMES[captured as usize], score ); - assert!((score as i64) < (MoveOrderScoreType::MIN as i64).abs()); + assert!((score as i64) < (LargeScoreType::MIN as i64).abs()); } } } @@ -142,8 +185,8 @@ mod tests { let history_table = Default::default(); // note that these scores are for ordering, so they are negated assert_eq!( - -Evaluation::score_move_for_ordering(side, &mv, &None, &history_table), - Evaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) + -ByteKnightEvaluation::score_move_for_ordering(side, &mv, &None, &history_table), + ByteKnightEvaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) ); mv = Move::new( @@ -156,8 +199,8 @@ mod tests { ); assert_eq!( - -Evaluation::score_move_for_ordering(side, &mv, &None, &history_table), - Evaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) + -ByteKnightEvaluation::score_move_for_ordering(side, &mv, &None, &history_table), + ByteKnightEvaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) ); mv = Move::new( @@ -170,8 +213,8 @@ mod tests { ); assert_eq!( - -Evaluation::score_move_for_ordering(side, &mv, &None, &history_table), - Evaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) + -ByteKnightEvaluation::score_move_for_ordering(side, &mv, &None, &history_table), + ByteKnightEvaluation::mvv_lva(mv.captured_piece().unwrap(), mv.piece()) ); } @@ -311,146 +354,25 @@ mod tests { "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1", "rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3", ]; - + let scores: [ScoreType; 128] = [ - 0, - 56, - 488, - 499, - -488, - -499, - 980, - -980, - 445, - 458, - -445, - -458, - 0, - 9, - 14, - 12, - -9, - -14, - -12, - -488, - -499, - 488, - 499, - -980, - 980, - -445, - -458, - 445, - 458, - 0, - -9, - -14, - -12, - 9, - 14, - 12, - 2, - 1, - 0, - -342, - 406, - -2, - -1, - 3, - 342, - -406, - 0, - -29, - 634, - -628, - 25, - 29, - -634, - 628, - 0, - -1, - 0, - 1, - -925, - -990, - -77, - 929, - -990, - 77, - 162, - 95, - -162, - -95, - 69, - -162, - -95, - 162, - 95, - -69, - 59, - 59, - 0, - 0, - 0, - 0, - 0, - 2, - 0, - 0, - 0, - 0, - 0, - -2, - -27, - 7, - 4, - 7, - -4, - -7, - -169, - 9, - 27, - -7, - -4, - -7, - 4, - 7, - 169, - -9, - -4, - 3, - 4, - -3, - 9, - -9, - 0, - 4, - -3, - -4, - 3, - -9, - 9, - 0, - -3, - 15, - 26, - 42, - 3, - -15, - -26, - -42, - 37, - 53, + 0, 56, 488, 499, -488, -499, 980, -980, 445, 458, -445, -458, 0, 9, 14, 12, -9, -14, + -12, -488, -499, 488, 499, -980, 980, -445, -458, 445, 458, 0, -9, -14, -12, 9, 14, 12, + 2, 1, 0, -342, 406, -2, -1, 3, 342, -406, 0, -29, 634, -628, 25, 29, -634, 628, 0, -1, + 0, 1, -925, -990, -77, 929, -990, 77, 162, 95, -162, -95, 69, -162, -95, 162, 95, -69, + 59, 59, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, -2, -27, 7, 4, 7, -4, -7, -169, 9, 27, -7, -4, + -7, 4, 7, 169, -9, -4, 3, 4, -3, 9, -9, 0, 4, -3, -4, 3, -9, 9, 0, -3, 15, 26, 42, 3, + -15, -26, -42, 37, 53, ]; - let eval = Evaluation::new(); + let eval = ByteKnightEvaluation::default(); for (i, fen) in positions.iter().enumerate() { + println!("Position {}: {}", i, fen); let board = Board::from_fen(fen).unwrap(); - let score = eval.evaluate_position(&board); - // println!("{},", score.0); + let score = eval.eval(&board); + println!("{},", score.0); assert_eq!(score.0, scores[i]); } - } } diff --git a/engine/src/hce_values.rs b/engine/src/hce_values.rs new file mode 100644 index 0000000..5d68241 --- /dev/null +++ b/engine/src/hce_values.rs @@ -0,0 +1,118 @@ +use chess::{ + definitions::NumberOf, + pieces::Piece, + side::Side, + square::{self}, +}; + +use crate::{ + phased_score::{PhasedScore, S}, + traits::EvalValues, +}; + +#[rustfmt::skip] +pub const PSQTS : [[PhasedScore; NumberOf::SQUARES]; NumberOf::PIECE_TYPES] = [ + // King + [ + S( -65, -74), S( 23, -35), S( 16, -18), S( -15, -18), S( -56, -11), S( -34, 15), S( 2, 4), S( 13, -17), + S( 29, -12), S( -1, 17), S( -20, 14), S( -7, 17), S( -8, 17), S( -4, 38), S( -38, 23), S( -29, 11), + S( -9, 10), S( 24, 17), S( 2, 23), S( -16, 15), S( -20, 20), S( 6, 45), S( 22, 44), S( -22, 13), + S( -17, -8), S( -20, 22), S( -12, 24), S( -27, 27), S( -30, 26), S( -25, 33), S( -14, 26), S( -36, 3), + S( -49, -18), S( -1, -4), S( -27, 21), S( -39, 24), S( -46, 27), S( -44, 23), S( -33, 9), S( -51, -11), + S( -14, -19), S( -14, -3), S( -22, 11), S( -46, 21), S( -44, 23), S( -30, 16), S( -15, 7), S( -27, -9), + S( 1, -27), S( 7, -11), S( -8, 4), S( -64, 13), S( -43, 14), S( -16, 4), S( 9, -5), S( 8, -17), + S( -15, -53), S( 36, -34), S( 12, -21), S( -54, -11), S( 8, -28), S( -28, -14), S( 24, -24), S( 14, -43), + ], + // Queen + [ + S( 997, 927), S(1025, 958), S(1054, 958), S(1037, 963), S(1084, 963), S(1069, 955), S(1068, 946), S(1070, 956), + S(1001, 919), S( 986, 956), S(1020, 968), S(1026, 977), S(1009, 994), S(1082, 961), S(1053, 966), S(1079, 936), + S(1012, 916), S(1008, 942), S(1032, 945), S(1033, 985), S(1054, 983), S(1081, 971), S(1072, 955), S(1082, 945), + S( 998, 939), S( 998, 958), S(1009, 960), S(1009, 981), S(1024, 993), S(1042, 976), S(1023, 993), S(1026, 972), + S(1016, 918), S( 999, 964), S(1016, 955), S(1015, 983), S(1023, 967), S(1021, 970), S(1028, 975), S(1022, 959), + S(1011, 920), S(1027, 909), S(1014, 951), S(1023, 942), S(1020, 945), S(1027, 953), S(1039, 946), S(1030, 941), + S( 990, 914), S(1017, 913), S(1036, 906), S(1027, 920), S(1033, 920), S(1040, 913), S(1022, 900), S(1026, 904), + S(1024, 903), S(1007, 908), S(1016, 914), S(1035, 893), S(1010, 931), S(1000, 904), S( 994, 916), S( 975, 895), + ], + // Rook + [ + S( 509, 525), S( 519, 522), S( 509, 530), S( 528, 527), S( 540, 524), S( 486, 524), S( 508, 520), S( 520, 517), + S( 504, 523), S( 509, 525), S( 535, 525), S( 539, 523), S( 557, 509), S( 544, 515), S( 503, 520), S( 521, 515), + S( 472, 519), S( 496, 519), S( 503, 519), S( 513, 517), S( 494, 516), S( 522, 509), S( 538, 507), S( 493, 509), + S( 453, 516), S( 466, 515), S( 484, 525), S( 503, 513), S( 501, 514), S( 512, 513), S( 469, 511), S( 457, 514), + S( 441, 515), S( 451, 517), S( 465, 520), S( 476, 516), S( 486, 507), S( 470, 506), S( 483, 504), S( 454, 501), + S( 432, 508), S( 452, 512), S( 461, 507), S( 460, 511), S( 480, 505), S( 477, 500), S( 472, 504), S( 444, 496), + S( 433, 506), S( 461, 506), S( 457, 512), S( 468, 514), S( 476, 503), S( 488, 503), S( 471, 501), S( 406, 509), + S( 458, 503), S( 464, 514), S( 478, 515), S( 494, 511), S( 493, 507), S( 484, 499), S( 440, 516), S( 451, 492), + ], + // Bishop + [ + S( 336, 283), S( 369, 276), S( 283, 286), S( 328, 289), S( 340, 290), S( 323, 288), S( 372, 280), S( 357, 273), + S( 339, 289), S( 381, 293), S( 347, 304), S( 352, 285), S( 395, 294), S( 424, 284), S( 383, 293), S( 318, 283), + S( 349, 299), S( 402, 289), S( 408, 297), S( 405, 296), S( 400, 295), S( 415, 303), S( 402, 297), S( 363, 301), + S( 361, 294), S( 370, 306), S( 384, 309), S( 415, 306), S( 402, 311), S( 402, 307), S( 372, 300), S( 363, 299), + S( 359, 291), S( 378, 300), S( 378, 310), S( 391, 316), S( 399, 304), S( 377, 307), S( 375, 294), S( 369, 288), + S( 365, 285), S( 380, 294), S( 380, 305), S( 380, 307), S( 379, 310), S( 392, 300), S( 383, 290), S( 375, 282), + S( 369, 283), S( 380, 279), S( 381, 290), S( 365, 296), S( 372, 301), S( 386, 288), S( 398, 282), S( 366, 270), + S( 332, 274), S( 362, 288), S( 351, 274), S( 344, 292), S( 352, 288), S( 353, 281), S( 326, 292), S( 344, 280), + ], + // Knight + [ + S( 170, 223), S( 248, 243), S( 303, 268), S( 288, 253), S( 398, 250), S( 240, 254), S( 322, 218), S( 230, 182), + S( 264, 256), S( 296, 273), S( 409, 256), S( 373, 279), S( 360, 272), S( 399, 256), S( 344, 257), S( 320, 229), + S( 290, 257), S( 397, 261), S( 374, 291), S( 402, 290), S( 421, 280), S( 466, 272), S( 410, 262), S( 381, 240), + S( 328, 264), S( 354, 284), S( 356, 303), S( 390, 303), S( 374, 303), S( 406, 292), S( 355, 289), S( 359, 263), + S( 324, 263), S( 341, 275), S( 353, 297), S( 350, 306), S( 365, 297), S( 356, 298), S( 358, 285), S( 329, 263), + S( 314, 258), S( 328, 278), S( 349, 280), S( 347, 296), S( 356, 291), S( 354, 278), S( 362, 261), S( 321, 259), + S( 308, 239), S( 284, 261), S( 325, 271), S( 334, 276), S( 336, 279), S( 355, 261), S( 323, 258), S( 318, 237), + S( 232, 252), S( 316, 230), S( 279, 258), S( 304, 266), S( 320, 259), S( 309, 263), S( 318, 231), S( 314, 217), + ], + // Pawn + [ + S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), + S( 180, 272), S( 216, 267), S( 143, 252), S( 177, 228), S( 150, 241), S( 208, 226), S( 116, 259), S( 71, 281), + S( 76, 188), S( 89, 194), S( 108, 179), S( 113, 161), S( 147, 150), S( 138, 147), S( 107, 176), S( 62, 178), + S( 68, 126), S( 95, 118), S( 88, 107), S( 103, 99), S( 105, 92), S( 94, 98), S( 99, 111), S( 59, 111), + S( 55, 107), S( 80, 103), S( 77, 91), S( 94, 87), S( 99, 87), S( 88, 86), S( 92, 97), S( 57, 93), + S( 56, 98), S( 78, 101), S( 78, 88), S( 72, 95), S( 85, 94), S( 85, 89), S( 115, 93), S( 70, 86), + S( 47, 107), S( 81, 102), S( 62, 102), S( 59, 104), S( 67, 107), S( 106, 94), S( 120, 96), S( 60, 87), + S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), S( 82, 94), + ], +]; + +#[derive(Debug, Clone, Copy, Default)] +pub struct ByteKnightValues {} + +impl EvalValues for ByteKnightValues { + type ReturnScore = PhasedScore; + + fn psqt(&self, square: u8, piece: Piece, side: Side) -> Self::ReturnScore { + PSQTS[piece as usize][square::flip_if(side == Side::White, square) as usize] + } +} + +#[cfg(test)] +mod tests { + use chess::board::Board; + + use super::*; + use crate::{evaluation::Evaluation, psqt::Psqt, traits::Eval}; + + #[test] + fn verify_values_match_pesto() { + let values = ByteKnightValues::default(); + let eval = Evaluation::new(values); + + let psqt = Psqt::new(); + + let board = + Board::from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1") + .unwrap(); + + let score = psqt.evaluate(&board); + println!("{}", score); + let new_eval_score = eval.eval(&board); + println!("{}", new_eval_score); + assert_eq!(score, new_eval_score); + } +} diff --git a/engine/src/history_table.rs b/engine/src/history_table.rs index 2e3bb04..3451677 100644 --- a/engine/src/history_table.rs +++ b/engine/src/history_table.rs @@ -4,10 +4,10 @@ use chess::{ side::Side, }; -use crate::score::{MoveOrderScoreType, Score}; +use crate::score::{LargeScoreType, Score}; pub struct HistoryTable { - table: [[[MoveOrderScoreType; NumberOf::SQUARES]; NumberOf::PIECE_TYPES]; NumberOf::SIDES], + table: [[[LargeScoreType; NumberOf::SQUARES]; NumberOf::PIECE_TYPES]; NumberOf::SIDES], } impl HistoryTable { @@ -17,7 +17,7 @@ impl HistoryTable { Self { table } } - pub(crate) fn get(&self, side: Side, piece: Piece, square: u8) -> MoveOrderScoreType { + pub(crate) fn get(&self, side: Side, piece: Piece, square: u8) -> LargeScoreType { assert!(side != Side::Both, "Side cannot be Both"); self.table[side as usize][piece as usize][square as usize] } @@ -27,7 +27,7 @@ impl HistoryTable { side: Side, piece: Piece, square: u8, - bonus: MoveOrderScoreType, + bonus: LargeScoreType, ) { assert!(side != Side::Both, "Side cannot be Both"); let current_value = self.table[side as usize][piece as usize][square as usize]; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 3d1d268..b824333 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,12 +1,18 @@ +#![feature(trait_alias)] +#![feature(type_alias_impl_trait)] + pub mod aspiration_window; pub mod defs; pub mod engine; pub mod evaluation; +pub mod hce_values; pub mod history_table; pub mod input_handler; +pub mod phased_score; pub mod psqt; pub mod score; pub mod search; pub mod search_thread; +pub mod traits; pub mod ttable; pub mod tuneable; diff --git a/engine/src/phased_score.rs b/engine/src/phased_score.rs new file mode 100644 index 0000000..a198536 --- /dev/null +++ b/engine/src/phased_score.rs @@ -0,0 +1,85 @@ +use std::fmt::Display; + +use crate::score::{LargeScoreType, ScoreType}; + +/// Represents a phased score in centipawns meaning that the score holds 2 values. One for midgame and one for endgame. +/// +/// The mg score is stored in the upper 16 bits and the eg score in the lower 16 bits. +/// MSB mmmmmmmm mmmmmmmm eeeeeeee eeeeeeee LSB +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +#[must_use] +pub struct PhasedScore { + value: LargeScoreType, +} + +pub type PhaseType = i32; +const BITS: usize = ScoreType::BITS as usize; + +impl PhasedScore { + pub const fn new(mg: ScoreType, eg: ScoreType) -> Self { + // TODO(PT): Check if scores are valid + Self { + value: (((mg as LargeScoreType) << BITS) + eg as LargeScoreType), + } + } + + pub fn mg(&self) -> ScoreType { + // shift 16 bits right + ((self.value + (1 << (BITS - 1))) >> BITS) as ScoreType + } + + pub fn eg(&self) -> ScoreType { + // only use the first 16 bits + (self.value & 0xFFFF) as ScoreType + } + + pub fn taper(&self, phase: PhaseType, max_phase: PhaseType) -> ScoreType { + let mg_phase = phase.min(max_phase); + let eg_phase = max_phase - mg_phase; + ((self.mg() as PhaseType * mg_phase + self.eg() as PhaseType * eg_phase) / max_phase) + as ScoreType + } +} + +const fn phase_score(mg: ScoreType, eg: ScoreType) -> PhasedScore { + PhasedScore::new(mg, eg) +} + +#[allow(non_snake_case)] +pub const fn S(mg: ScoreType, eg: ScoreType) -> PhasedScore { + phase_score(mg, eg) +} + +impl Display for PhasedScore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "mg: {}, eg: {}", self.mg(), self.eg()) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn phased_score() { + use super::PhasedScore; + + let ps = PhasedScore::new(100, 50); + assert_eq!(ps.mg(), 100); + assert_eq!(ps.eg(), 50); + + let phase = 50; + assert_eq!(ps.taper(phase, 100), 75); + + let ps: PhasedScore = PhasedScore::new(40, 80); + assert_eq!(ps.mg(), 40); + assert_eq!(ps.eg(), 80); + + let phase = 12; + assert_eq!(ps.taper(phase, 24), 60); + + let phase = 24; + let ps = PhasedScore::new(56, -26); + assert_eq!(ps.mg(), 56); + assert_eq!(ps.eg(), -26); + assert_eq!(ps.taper(phase, 24), 56); + } +} diff --git a/engine/src/psqt.rs b/engine/src/psqt.rs index 9d072ab..088de0d 100644 --- a/engine/src/psqt.rs +++ b/engine/src/psqt.rs @@ -4,7 +4,7 @@ * Created Date: Thursday, November 21st 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Mon Dec 09 2024 + * Last Modified: Wed Dec 18 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -12,7 +12,7 @@ * */ -use chess::{bitboard_helpers, board::Board, pieces::PIECE_NAMES, side::Side}; +use chess::{bitboard_helpers, board::Board, pieces::PIECE_NAMES, side::Side, square}; use crate::score::{Score, ScoreType}; @@ -172,6 +172,7 @@ pub const EG_KING_TABLE: [ScoreType; 64] = [ /// Opening/mid-game piece-square tables /// Ordered to match the indexing of [`Piece`] +#[allow(dead_code)] const MG_PESTO_TABLE: [&[ScoreType; 64]; 6] = [ &MG_KING_TABLE, &MG_QUEEN_TABLE, @@ -183,6 +184,7 @@ const MG_PESTO_TABLE: [&[ScoreType; 64]; 6] = [ /// Endgame piece-square tables /// Ordered to match the indexing of [`Piece`] +#[allow(dead_code)] const EG_PESTO_TABLE: [&[ScoreType; 64]; 6] = [ &EG_KING_TABLE, &EG_QUEEN_TABLE, @@ -203,10 +205,9 @@ pub(crate) struct Psqt { eg_table: [[ScoreType; 64]; 12], } -const FLIP: fn(usize) -> usize = |sq| sq ^ 56; - impl Psqt { /// Creates a new [`Psqt`] instance and initializes the piece-square tables. + #[allow(dead_code)] pub(crate) fn new() -> Self { let mut psqt = Psqt { mg_table: [[0; 64]; 12], @@ -228,6 +229,7 @@ impl Psqt { /// # Returns /// /// The score of the position. + #[allow(dead_code)] pub(crate) fn evaluate(&self, board: &Board) -> Score { let side_to_move = board.side_to_move(); let mut mg: [i32; 2] = [0; 2]; @@ -251,8 +253,10 @@ impl Psqt { let mg_score = mg[side_to_move as usize] - mg[Side::opposite(side_to_move) as usize]; let eg_score = eg[side_to_move as usize] - eg[Side::opposite(side_to_move) as usize]; + println!("psqt got mg {} eg {}", mg_score, eg_score); let mg_phase = game_phase.min(24); let eg_phase = 24 - mg_phase; + println!("psqt phase {} {}", mg_phase, eg_phase); let score = (mg_score * mg_phase + eg_score * eg_phase) / 24; Score::new(score as i16) } @@ -265,8 +269,10 @@ impl Psqt { fn initialize_tables(&mut self) { for (p, pc) in (0..6).zip((0..12).step_by(2)) { for sq in 0..64 { - self.mg_table[pc][sq] = MG_VALUE[p] + MG_PESTO_TABLE[p][FLIP(sq)]; - self.eg_table[pc][sq] = EG_VALUE[p] + EG_PESTO_TABLE[p][FLIP(sq)]; + self.mg_table[pc][sq] = + MG_VALUE[p] + MG_PESTO_TABLE[p][square::flip(sq as u8) as usize]; + self.eg_table[pc][sq] = + EG_VALUE[p] + EG_PESTO_TABLE[p][square::flip(sq as u8) as usize]; self.mg_table[pc + 1][sq] = MG_VALUE[p] + MG_PESTO_TABLE[p][sq]; self.eg_table[pc + 1][sq] = EG_VALUE[p] + EG_PESTO_TABLE[p][sq]; } @@ -277,25 +283,31 @@ impl Psqt { /// Output is formatted as a 8x8 board with (mg, eg) values for each square, per piece #[allow(dead_code)] fn print_tables(&self) { + println!("#[rustfmt::skip]"); + println!( + "pub const PSQTS : [[PhasedScore; NumberOf::SQUARES]; NumberOf::PIECE_TYPES] = [" + ); for (p, pc) in (0..6).zip((0..12).step_by(2)) { - println!("Piece: {}", PIECE_NAMES[p]); - for row in 0..8 { + println!(" // {}", PIECE_NAMES[p]); + println!(" ["); + for row in (0..=7).rev() { for col in 0..8 { let sq = row * 8 + col; if col == 0 { - print!("| "); + print!(" "); } - print!( - "({:4}, {:4}), ", + "S({:4}, {:4}), ", self.mg_table[pc][sq], self.eg_table[pc][sq] ); if col == 7 { - println!(" |"); + println!(); } } } + println!(" ],"); } + println!("];"); } } @@ -311,6 +323,8 @@ mod tests { let psqt = Psqt::new(); let score = psqt.evaluate(&board); assert_eq!(score, super::Score::new(0)); + + psqt.print_tables(); } #[test] diff --git a/engine/src/score.rs b/engine/src/score.rs index 5aa5783..18aebab 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -4,7 +4,7 @@ * Created Date: Thursday, November 14th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Thu Dec 12 2024 + * Last Modified: Mon Dec 16 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -22,7 +22,7 @@ use uci_parser::UciScore; use crate::defs::MAX_DEPTH; pub type ScoreType = i16; -pub(crate) type MoveOrderScoreType = i32; +pub(crate) type LargeScoreType = i32; /// Represents a score in centipawns. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct Score(pub ScoreType); @@ -36,7 +36,7 @@ impl Score { // Max/min score for history heuristic // Must be lower then the minimum score for captures in MVV_LVA - pub const MAX_HISTORY: MoveOrderScoreType = 16_384; + pub const MAX_HISTORY: LargeScoreType = 16_384; pub fn new(score: ScoreType) -> Score { Score(score) diff --git a/engine/src/search.rs b/engine/src/search.rs index ee243d3..79b9168 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -4,7 +4,7 @@ * Created Date: Thursday, November 21st 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Wed Dec 11 2024 + * Last Modified: Wed Dec 18 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -28,9 +28,10 @@ use uci_parser::{UciInfo, UciResponse, UciSearchOptions}; use crate::{ aspiration_window::AspirationWindow, defs::MAX_DEPTH, - evaluation::Evaluation, + evaluation::ByteKnightEvaluation, history_table::HistoryTable, - score::{MoveOrderScoreType, Score, ScoreType}, + score::{LargeScoreType, Score, ScoreType}, + traits::Eval, ttable::{self, TranspositionTableEntry}, }; use ttable::TranspositionTable; @@ -143,7 +144,7 @@ pub struct Search<'search_lifetime> { move_gen: MoveGenerator, nodes: u64, parameters: SearchParameters, - eval: Evaluation, + eval: ByteKnightEvaluation, stop_flag: Option>, } @@ -159,7 +160,7 @@ impl<'a> Search<'a> { move_gen: MoveGenerator::new(), nodes: 0, parameters: parameters.clone(), - eval: Evaluation::new(), + eval: ByteKnightEvaluation::default(), stop_flag: None, } } @@ -359,7 +360,7 @@ impl<'a> Search<'a> { // sort moves by MVV/LVA let sorted_moves = move_list.iter().sorted_by_cached_key(|mv| { - Evaluation::score_move_for_ordering( + ByteKnightEvaluation::score_move_for_ordering( board.side_to_move(), mv, &tt_entry, @@ -417,7 +418,7 @@ impl<'a> Search<'a> { board.side_to_move(), mv.piece(), mv.to(), - bonus as MoveOrderScoreType, + bonus as LargeScoreType, ); // apply a penalty to all quiets searched so far @@ -426,7 +427,7 @@ impl<'a> Search<'a> { board.side_to_move(), mv.piece(), mv.to(), - -bonus as MoveOrderScoreType, + -bonus as LargeScoreType, ); } } @@ -476,7 +477,7 @@ impl<'a> Search<'a> { /// The score of the position. /// fn quiescence(&mut self, board: &mut Board, alpha: Score, beta: Score) -> Score { - let standing_eval = self.eval.evaluate_position(board); + let standing_eval = self.eval.eval(board); if standing_eval >= beta { return beta; } @@ -497,7 +498,12 @@ impl<'a> Search<'a> { } let sorted_moves = captures.into_iter().sorted_by_cached_key(|mv| { - Evaluation::score_move_for_ordering(board.side_to_move(), mv, &None, self.history_table) + ByteKnightEvaluation::score_move_for_ordering( + board.side_to_move(), + mv, + &None, + self.history_table, + ) }); let mut best = standing_eval; @@ -539,13 +545,13 @@ mod tests { use chess::{board::Board, pieces::ALL_PIECES}; use crate::{ - evaluation::Evaluation, + evaluation::ByteKnightEvaluation, score::Score, search::{Search, SearchParameters}, ttable::TranspositionTable, }; - use super::MoveOrderScoreType; + use super::LargeScoreType; #[test] fn white_mate_in_1() { @@ -684,11 +690,11 @@ mod tests { ..Default::default() }; - let mut min_mvv_lva = MoveOrderScoreType::MAX; - let mut max_mvv_lva = MoveOrderScoreType::MIN; + let mut min_mvv_lva = LargeScoreType::MAX; + let mut max_mvv_lva = LargeScoreType::MIN; for capturing in ALL_PIECES { for captured in ALL_PIECES.iter().filter(|p| !p.is_king() && !p.is_none()) { - let mvv_lva = Evaluation::mvv_lva(*captured, capturing); + let mvv_lva = ByteKnightEvaluation::mvv_lva(*captured, capturing); if mvv_lva < min_mvv_lva { min_mvv_lva = mvv_lva; } @@ -709,7 +715,7 @@ mod tests { assert!(res.best_move.is_some()); let side = board.side_to_move(); - let mut max_history = MoveOrderScoreType::MIN; + let mut max_history = LargeScoreType::MIN; for piece in ALL_PIECES { for square in 0..64 { let score = history_table.get(side, piece, square); diff --git a/engine/src/traits.rs b/engine/src/traits.rs new file mode 100644 index 0000000..f3abd95 --- /dev/null +++ b/engine/src/traits.rs @@ -0,0 +1,12 @@ +use chess::{pieces::Piece, side::Side}; + +use crate::score::Score; + +pub trait Eval { + fn eval(&self, board: &Board) -> Score; +} + +pub trait EvalValues { + type ReturnScore; + fn psqt(&self, square: u8, piece: Piece, side: Side) -> Self::ReturnScore; +} From 10a4d0c12432ba455166a2c011c4915bed952263 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 19 Dec 2024 07:48:53 -0500 Subject: [PATCH 7/9] chore: auto-format bench: 1583604 --- engine/src/history_table.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/engine/src/history_table.rs b/engine/src/history_table.rs index 3451677..431c57a 100644 --- a/engine/src/history_table.rs +++ b/engine/src/history_table.rs @@ -22,13 +22,7 @@ impl HistoryTable { self.table[side as usize][piece as usize][square as usize] } - pub(crate) fn update( - &mut self, - side: Side, - piece: Piece, - square: u8, - bonus: LargeScoreType, - ) { + pub(crate) fn update(&mut self, side: Side, piece: Piece, square: u8, bonus: LargeScoreType) { assert!(side != Side::Both, "Side cannot be Both"); let current_value = self.table[side as usize][piece as usize][square as usize]; let clamped_bonus = bonus.clamp(-Score::MAX_HISTORY, Score::MAX_HISTORY); From 27d7eab9c1ccddab1baad7c04fea4179ab44a3f5 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 19 Dec 2024 09:30:04 -0500 Subject: [PATCH 8/9] fix: use nightly in CI bench: 1583604 --- .github/workflows/build_and_test.yml | 2 ++ .github/workflows/clippy.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0ee6a91..8df987f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -15,6 +15,8 @@ jobs: runs-on: ubuntu-22.04 steps: + - name: Rust nightly + run: rustup default nightly - uses: actions/checkout@v4 with: lfs: true diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index a146215..86d6c7b 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -18,6 +18,8 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + - name: Rust nightly + run: rustup default nightly - name: Install just uses: extractions/setup-just@v2 - name: clippy From 6d87db3bc7f589511fe4ba75149857ea244ab692 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 19 Dec 2024 09:38:09 -0500 Subject: [PATCH 9/9] fix: add clippy to the CI for nightly bench: 1583604 --- .github/workflows/clippy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 86d6c7b..56add77 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -19,7 +19,9 @@ jobs: with: lfs: true - name: Rust nightly - run: rustup default nightly + run: | + rustup default nightly + rustup component add clippy - name: Install just uses: extractions/setup-just@v2 - name: clippy