From e2934b95cbad65bc89cbc285c3ece2124bf44a55 Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 3 Sep 2023 19:29:54 +0300 Subject: feat: improved scoring for (semi)dead positions --- src/board/mod.rs | 21 ++++++++++++++++++++ src/board/piece.rs | 11 +++++++++++ src/grossmeister/evaluation.rs | 44 ++++++++++++------------------------------ src/grossmeister/search.rs | 20 +++++++++++++++---- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/board/mod.rs b/src/board/mod.rs index 1e4fd93..01a5b03 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -392,6 +392,27 @@ impl Board { pub fn threefold_repetition(&self) -> bool { self.positions.iter().filter(|&&p| p == self.hash).count() >= 3 } + + pub fn is_theoretically_winnable(&self, color: Color) -> bool { + if [ + Piece::Pawn, + Piece::Rook, + Piece::Queen, + ].iter().any(|&piece| { + self.piece_sets[piece.colored(color) as usize].pop_count() > 0 + }) { + return true; + }; + + let minor_pieces = [ + Piece::Knight, + Piece::Bishop, + ].iter().fold(0, |acc, &piece| { + acc + self.piece_sets[piece.colored(color) as usize].pop_count() + }); + + minor_pieces > 1 + } } diff --git a/src/board/piece.rs b/src/board/piece.rs index 1889e94..3de27ca 100644 --- a/src/board/piece.rs +++ b/src/board/piece.rs @@ -1,3 +1,5 @@ +use super::color::Color; + #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] #[repr(usize)] pub enum Piece { @@ -21,6 +23,15 @@ impl Piece { let index = *self as usize; Self::from(index % 6) } + + /// Note: assumes self is without color! + pub fn colored(&self, color: Color) -> Self { + match color { + Color::White => *self, + Color::Black => Self::from(*self as usize + 6) + } + } + // Return the price of the peice pub fn static_eval(&self) -> f32 { match self.without_color() { diff --git a/src/grossmeister/evaluation.rs b/src/grossmeister/evaluation.rs index b78c49a..6fc4149 100644 --- a/src/grossmeister/evaluation.rs +++ b/src/grossmeister/evaluation.rs @@ -1,3 +1,5 @@ +use std::f32::INFINITY; + use crate::{board::{piece::Piece, color::Color, CastlingSide}, bitboard::{Bitboard, BitboardFns}, square::Square}; use super::Grossmeister; @@ -117,34 +119,6 @@ impl Grossmeister { } } - pub fn is_dead_position(&self) -> bool { - let non_minor_exists = [ - Piece::Pawn, - Piece::PawnBlack, - Piece::Rook, - Piece::RookBlack, - Piece::Queen, - Piece::QueenBlack - ].iter().any(|&piece| { - self.board.piece_sets[piece as usize].pop_count() > 0 - }); - - if non_minor_exists { - return false; - } - - let minor_pieces = [ - Piece::Knight, - Piece::KnightBlack, - Piece::Bishop, - Piece::BishopBlack - ].iter().fold(0, |acc, &piece| { - acc + self.board.piece_sets[piece as usize].pop_count() - }); - - minor_pieces <= 1 - } - /// Return number of pawns above the king (+left/right) pub fn pawn_shield(&self, color: Color) -> f32 { let behind_pawns = match color { @@ -236,12 +210,16 @@ impl Grossmeister { /// Evaluate a position relative to the current player pub fn evaluate(&self) -> f32 { - if self.is_dead_position() { - return 0.0 - } let color = self.board.color(); let opponent_color = color.flip(); + let winnable = self.board.is_theoretically_winnable(color); + let loseable = self.board.is_theoretically_winnable(opponent_color); + + if !winnable && !loseable { + return 0.0 + } + let material_advantage = self.material(color) - self.material(opponent_color); let mobility_advantage = self.mobility(color) - self.mobility(opponent_color); let pawn_shield_advantage = self.pawn_shield(color) - self.pawn_shield(opponent_color); @@ -265,7 +243,9 @@ impl Grossmeister { let phase = self.phase(); let tapered_eval = (middlegame_eval * phase as f32 + endgame_eval * (240 - phase) as f32) / 240.; - material_advantage + tapered_eval + (material_advantage + tapered_eval) + .min(if winnable { INFINITY } else { 0.0 }) // Can not score > 0 if not winnable + .max(if loseable { -INFINITY } else { 0.0 }) // Can not score < 0 if not loseable } /// Count pseudo-legal moves without actually generating them diff --git a/src/grossmeister/search.rs b/src/grossmeister/search.rs index 2d20e3a..cad2b99 100644 --- a/src/grossmeister/search.rs +++ b/src/grossmeister/search.rs @@ -71,16 +71,16 @@ impl Grossmeister { } } - if depth_left == 0 { - return (self.quiscence(alpha, beta, root_distance), principal_variation); - } - // Mate distance pruning let mating_score = Grossmeister::MDP(&mut alpha, &mut beta, root_distance); if mating_score != 0.0 { return (mating_score, principal_variation) } + if depth_left == 0 { + return (self.quiscence(alpha, beta, root_distance), principal_variation); + } + let mut should_pv_search = true; let mut legal_move_found = false; @@ -509,4 +509,16 @@ mod tests { dbg!(score, pv); assert_eq!(SCORE_MATE - score, 3.0); // Mate in 3 plies } + + #[test] + fn unwinnable() { + let fen = String::from("8/8/2P1k3/1K1n4/8/8/8/8 b - - 0 55"); + let board = Board::from_FEN(fen); + + let mut gm = Grossmeister::new(board); + gm.debug = true; + let (score, pv) = gm.iterative_deepening(5); + dbg!(score, pv); + assert!(score <= 0.0); + } } -- cgit v1.2.3