From 750214d5635df56f1d4cb1dd6a8788565f3793c9 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 22 Aug 2025 22:43:15 +0200 Subject: [PATCH] Fix atomic SEE and endgames (#907) * Fix SEE issues with double king explosions * Fix wrong atomic KQK and KXK endgame evaluation Credits to @johndoknjas for the initial fix. --- src/endgame.cpp | 19 ++++++++++--------- src/position.cpp | 31 ++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/endgame.cpp b/src/endgame.cpp index 06f18d3..6da7d67 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -1074,9 +1074,9 @@ Value Endgame::operator()(const Position& pos) const { assert(pos.endgame_eval() == EG_EVAL_ATOMIC); - // Stalemate detection with lone king + // Stalemate/checkmate detection with lone king if (pos.side_to_move() == weakSide && !MoveList(pos).size()) - return VALUE_DRAW; + return std::min(pos.stalemate_value(), VALUE_MATE_IN_MAX_PLY - 1); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); @@ -1127,22 +1127,23 @@ Value Endgame::operator()(const Position& pos) const { assert(pos.endgame_eval() == EG_EVAL_ATOMIC); - // Stalemate detection with lone king + // Stalemate/checkmate detection with lone king if (pos.side_to_move() == weakSide && !MoveList(pos).size()) - return VALUE_DRAW; + return std::min(pos.stalemate_value(), VALUE_MATE_IN_MAX_PLY - 1); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); int dist = distance(winnerKSq, loserKSq); // Draw in case of adjacent kings - // In the case of dist == 2, the square adjacent to both kings is ensured - // not be occupied by the queen, since eval is not called when in check. - if (dist <= (strongSide == pos.side_to_move() ? 1 : 2)) + if (dist == 1) + return VALUE_DRAW; + // If the weaker side is to move and there is a way to connect kings, it is a draw + if ( pos.side_to_move() == weakSide + && (attacks_bb(winnerKSq) & attacks_bb(loserKSq) & ~pos.pieces())) return VALUE_DRAW; - Value result = pos.non_pawn_material(strongSide) - + push_to_edge(loserKSq, pos) + Value result = Value(push_to_edge(loserKSq, pos)) + push_away(winnerKSq, loserKSq); if (dist >= (strongSide == pos.side_to_move() ? 3 : 4)) diff --git a/src/position.cpp b/src/position.cpp index 5476122..609441d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2415,16 +2415,41 @@ Value Position::blast_see(Move m) const { } // Sum up blast piece values + bool extinctsUs = false; + bool extinctsThem = false; while (blast) { Piece bpc = piece_on(pop_lsb(blast)); if (extinction_piece_types() & type_of(bpc)) - return color_of(bpc) == us ? extinction_value() - : capture(m) ? -extinction_value() - : VALUE_ZERO; + { + if (color_of(bpc) == us) + extinctsUs = true; + else + extinctsThem = true; + } result += color_of(bpc) == us ? -CapturePieceValue[MG][bpc] : CapturePieceValue[MG][bpc]; } + // Evaluate extinctions + if (!capture(m)) + { + // For quiet moves, the opponent can decide whether to capture or not + // so they can pick the better of the two + if (extinctsThem && extinctsUs) + return VALUE_ZERO; + if (extinctsThem) + return std::min(-extinction_value(), VALUE_ZERO); + if (extinctsUs) + return std::min(extinction_value(), VALUE_ZERO); + } + else + { + if (extinctsUs) + return extinction_value(); + if (extinctsThem) + return -extinction_value(); + } + return capture(m) || must_capture() ? result - 1 : std::min(result, VALUE_ZERO); } -- 1.7.0.4