aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2023-09-03 19:29:54 +0300
committereug-vs <eugene@eug-vs.xyz>2023-09-03 19:29:54 +0300
commite2934b95cbad65bc89cbc285c3ece2124bf44a55 (patch)
tree99252dd59ee15cace85730f68db0cd85ea628330
parent9927af55dd12bc5eca7ff17e9dbc2665c42fcfb7 (diff)
downloadchessnost-e2934b95cbad65bc89cbc285c3ece2124bf44a55.tar.gz
feat: improved scoring for (semi)dead positions
-rw-r--r--src/board/mod.rs21
-rw-r--r--src/board/piece.rs11
-rw-r--r--src/grossmeister/evaluation.rs44
-rw-r--r--src/grossmeister/search.rs20
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);
+ }
}