Fix atomic SEE and endgames (#907)
authorFabian Fichter <ianfab@users.noreply.github.com>
Fri, 22 Aug 2025 20:43:15 +0000 (22:43 +0200)
committerGitHub <noreply@github.com>
Fri, 22 Aug 2025 20:43:15 +0000 (22:43 +0200)
* 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
src/position.cpp

index 06f18d3..6da7d67 100644 (file)
@@ -1074,9 +1074,9 @@ Value Endgame<KXK, EG_EVAL_ATOMIC>::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<LEGAL>(pos).size())
-      return VALUE_DRAW;
+      return std::min(pos.stalemate_value(), VALUE_MATE_IN_MAX_PLY - 1);
 
   Square winnerKSq = pos.square<COMMONER>(strongSide);
   Square loserKSq = pos.square<COMMONER>(weakSide);
@@ -1127,22 +1127,23 @@ Value Endgame<KQK, EG_EVAL_ATOMIC>::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<LEGAL>(pos).size())
-      return VALUE_DRAW;
+      return std::min(pos.stalemate_value(), VALUE_MATE_IN_MAX_PLY - 1);
 
   Square winnerKSq = pos.square<COMMONER>(strongSide);
   Square loserKSq = pos.square<COMMONER>(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<KING>(winnerKSq) & attacks_bb<KING>(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))
index 5476122..609441d 100644 (file)
@@ -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);
 }