Add CastlingRights as dedicated config type
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 10 Sep 2023 12:10:44 +0000 (14:10 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 10 Sep 2023 12:10:44 +0000 (14:10 +0200)
No functional change.

src/parser.cpp
src/position.cpp
src/variant.cpp
src/variant.h
src/variants.ini

index df0ce18..078c338 100644 (file)
@@ -124,6 +124,27 @@ namespace {
         return !ss.fail();
     }
 
+    template <> bool set(const std::string& value, CastlingRights& target) {
+        char c;
+        CastlingRights castlingRight;
+        std::stringstream ss(value);
+        target = NO_CASTLING;
+        bool valid = true;
+        while (ss >> c && c != '-')
+        {
+            castlingRight =  c == 'K' ? WHITE_OO
+                           : c == 'Q' ? WHITE_OOO
+                           : c == 'k' ? BLACK_OO
+                           : c == 'q' ? BLACK_OOO
+                           : NO_CASTLING;
+            if (castlingRight)
+                target = CastlingRights(target | castlingRight);
+            else
+                valid = false;
+        }
+        return valid;
+    }
+
     template <typename T> void set(PieceType pt, T& target) {
         target.insert(pt);
     }
@@ -158,6 +179,7 @@ template <bool Current, class T> bool VariantParser<DoCheck>::parse_attribute(co
                                   : std::is_same<T, ChasingRule>() ? "ChasingRule"
                                   : std::is_same<T, EnclosingRule>() ? "EnclosingRule"
                                   : std::is_same<T, Bitboard>() ? "Bitboard"
+                                  : std::is_same<T, CastlingRights>() ? "CastlingRights"
                                   : typeid(T).name();
             std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl;
         }
index 8f7185c..a55d50b 100644 (file)
@@ -2738,32 +2738,28 @@ bool Position::is_immediate_game_end(Value& result, int ply) const {
   }
 
   // Castle chess
-  if (var->castlingWinConditions) {
-      if (type_of(st->move) == CASTLING)
+  if (var->castlingWins)
+  {
+      if (st->pliesFromNull > 0 && type_of(st->move) == CASTLING)
       {
-          //check for victory first, because castling also removes castling rights.
-          CastlingRights justCastled = static_cast<CastlingRights>((sideToMove == BLACK ? WHITE_OOO : BLACK_OOO)
-                                       >> ((from_sq(st->move) < to_sq(st->move)) ? 1 : 0));
-          if (var->castlingWinConditions & justCastled)
+          // check for victory first, because castling also removes castling rights.
+          CastlingRights justCastled = ~sideToMove & ((from_sq(st->move) < to_sq(st->move)) ? KING_SIDE : QUEEN_SIDE);
+          if (var->castlingWins & justCastled)
           {
               result = mated_in(ply);
               return true;
           }
       }
-      if ((var->castlingWinConditions & BLACK_CASTLING) && (!(var->castlingWinConditions & BLACK_CASTLING & st->castlingRights)))
-      {
-          //black permanently losing castling rights. either through moving a castling piece,
-          //or having their rook captured. Either way, black lost.
-          result = sideToMove == WHITE ? mate_in(ply) : mated_in(ply);
-          return true;
-      }
-      if ((var->castlingWinConditions & WHITE_CASTLING) && (!(var->castlingWinConditions & WHITE_CASTLING & st->castlingRights)))
-      {
-          //white permanently losing castling rights. either through moving a castling piece,
-          //or having their rook captured. Either way, white lost.
-          result = sideToMove == BLACK ? mate_in(ply) : mated_in(ply);
-          return true;
-      }
+      // We check the opponent side first, because a rook capturing a rook could remove both sides castling rights,
+      // which should likely be seen as losing, analogous to extinction rules.
+      for (Color c : { ~sideToMove, sideToMove })
+          if ((c & var->castlingWins) && !(c & var->castlingWins & st->castlingRights))
+          {
+              // player permanently losing castling rights. either through moving a castling piece,
+              // or having their rook captured.
+              result = c == sideToMove ? mated_in(ply) : mate_in(ply);
+              return true;
+          }
   }
 
   // nCheck
index 6162a4b..e25d7ba 100644 (file)
@@ -2041,15 +2041,6 @@ Variant* Variant::conclude() {
         connect_directions.push_back(SOUTH_EAST);
     }
 
-    for (char c : castlingWins) {
-        switch (c) {
-            case 'K': castlingWinConditions = static_cast<CastlingRights>(castlingWinConditions | WHITE_OO); break;
-            case 'Q': castlingWinConditions = static_cast<CastlingRights>(castlingWinConditions | WHITE_OOO); break;
-            case 'k': castlingWinConditions = static_cast<CastlingRights>(castlingWinConditions | BLACK_OO); break;
-            case 'q': castlingWinConditions = static_cast<CastlingRights>(castlingWinConditions | BLACK_OOO); break;
-        }
-    }
-
     return this;
 }
 
index 449407e..dd6036a 100644 (file)
@@ -157,7 +157,7 @@ struct Variant {
   bool connectDiagonal = true;
   MaterialCounting materialCounting = NO_MATERIAL_COUNTING;
   CountingRule countingRule = NO_COUNTING;
-  std::string castlingWins = "";
+  CastlingRights castlingWins = NO_CASTLING;
 
   // Derived properties
   bool fastAttacks = true;
@@ -173,7 +173,6 @@ struct Variant {
   bool endgameEval = false;
   bool shogiStylePromotions = false;
   std::vector<Direction> connect_directions;
-  CastlingRights castlingWinConditions = NO_CASTLING;
 
   void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') {
       // Avoid ambiguous definition by removing existing piece with same letter
index 8e7881a..7d0b36f 100644 (file)
 # [int]: any natural number [0, 1, ...]
 # [PieceType]: a piece type [letters defined for pieces, e.g., p]
 # [PieceSet]: multiple piece types [letters defined for pieces, e.g., nbrq]
+# [CastlingRights]: set of castling rights [letters for castling rights as in FEN, e.g., KQkq]
 # [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank)
 # [Value]: game result for the side to move [win, loss, draw]
 # [MaterialCounting]: material counting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none]
 # connectDiagonal: connectN looks at Diagonal rows [bool] (default: true)
 # materialCounting: enable material counting rules [MaterialCounting] (default: none)
 # countingRule: enable counting rules [CountingRule] (default: none)
-# castlingWins: Specified castling moves are win conditions. Losing these rights is losing. (ie. KQkq) [string] (default: "")
+# castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -)
 
 ################################################
 ### Example for minishogi configuration that would be equivalent to the built-in variant: