Fix ambiguous 960 castling
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 7 Feb 2021 16:37:06 +0000 (17:37 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 7 Feb 2021 20:36:18 +0000 (21:36 +0100)
In case there is more than one "king" piece type that could
represent the castling piece, resolve this ambiguity in the FEN,
e.g., EAH means that an e-file king has a- and h-file rooks
with which it can perform castling.

For non-960 games disambiguation is achieved via configuration.

Closes #239.

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

index f1becbb..f88222a 100644 (file)
@@ -401,7 +401,7 @@ namespace {
     // Castling with non-king piece
     if (!pos.count<KING>(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING))
     {
-        Square from = make_square(FILE_E, pos.castling_rank(Us));
+        Square from = pos.castling_king_square(Us);
         for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
             if (!pos.castling_impeded(cr) && pos.can_castle(cr))
                 moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(cr));
index ddb29c1..90590ad 100644 (file)
@@ -248,6 +248,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("castlingKingsideFile", v->castlingKingsideFile);
     parse_attribute("castlingQueensideFile", v->castlingQueensideFile);
     parse_attribute("castlingRank", v->castlingRank);
+    parse_attribute("castlingKingFile", v->castlingKingFile);
+    parse_attribute("castlingKingPiece", v->castlingKingPiece, v->pieceToChar);
     parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar);
     parse_attribute("kingType", v->kingType, v->pieceToChar);
     parse_attribute("checking", v->checking);
index 5aab5c2..c5176d3 100644 (file)
@@ -315,6 +315,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
 
   // 3-4. Skip parsing castling and en passant flags if not present
   st->epSquare = SQ_NONE;
+  st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE;
   if (!isdigit(ss.peek()) && !sfen)
   {
       // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
@@ -342,26 +343,37 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
           else
               continue;
 
+          // Determine castling "king" position
+          if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE)
+          {
+              Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c));
+              // Ambiguity resolution for 960 variants with more than one "king"
+              // e.g., EAH means that an e-file king can castle with a- and h-file rooks
+              st->castlingKingSquare[c] =  isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece()) ? rsq
+                                         : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings)
+                                         : make_square(castling_king_file(), castling_rank(c));
+          }
+
           // Set gates (and skip castling rights)
           if (gating())
           {
               st->gatesBB[c] |= rsq;
               if (token == 'K' || token == 'Q')
-                  st->gatesBB[c] |= count<KING>(c) ? square<KING>(c) : make_square(FILE_E, castling_rank(c));
+                  st->gatesBB[c] |= st->castlingKingSquare[c];
               // Do not set castling rights for gates unless there are no pieces in hand,
               // which means that the file is referring to a chess960 castling right.
               else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand())
                   continue;
           }
 
-          if (castling_enabled())
+          if (castling_enabled() && piece_on(rsq) == rook)
               set_castling_right(c, rsq);
       }
 
       // Set castling rights for 960 gating variants
       if (gating() && castling_enabled())
           for (Color c : {WHITE, BLACK})
-              if ((gates(c) & pieces(KING)) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand()))
+              if ((gates(c) & pieces(castling_king_piece())) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand()))
               {
                   Bitboard castling_rooks = gates(c) & pieces(castling_rook_piece());
                   while (castling_rooks)
@@ -474,7 +486,8 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
 
 void Position::set_castling_right(Color c, Square rfrom) {
 
-  Square kfrom = count<KING>(c) ? square<KING>(c) : make_square(FILE_E, castling_rank(c));
+  assert(st->castlingKingSquare[c] != SQ_NONE);
+  Square kfrom = st->castlingKingSquare[c];
   CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
 
   st->castlingRights |= cr;
@@ -657,6 +670,10 @@ const string Position::fen(bool sfen, bool showPromoted, int countStarted, std::
 
   ss << (sideToMove == WHITE ? " w " : " b ");
 
+  // Disambiguation for chess960 "king" square
+  if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece()) & rank_bb(castling_rank(WHITE))) > 1)
+      ss << char('A' + castling_king_square(WHITE));
+
   if (can_castle(WHITE_OO))
       ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K');
 
@@ -668,6 +685,10 @@ const string Position::fen(bool sfen, bool showPromoted, int countStarted, std::
           if (gates(WHITE) & file_bb(f))
               ss << char('A' + f);
 
+  // Disambiguation for chess960 "king" square
+  if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece()) & rank_bb(castling_rank(BLACK))) > 1)
+      ss << char('a' + castling_king_square(BLACK));
+
   if (can_castle(BLACK_OO))
       ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
 
@@ -1426,7 +1447,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       // Set castling rights for dropped king or rook
       if (castling_dropped_piece() && rank_of(to) == castling_rank(us))
       {
-          if (type_of(pc) == KING && file_of(to) == FILE_E)
+          if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file())
           {
               Bitboard castling_rooks =  pieces(us, castling_rook_piece())
                                        & rank_bb(castling_rank(us))
@@ -1437,7 +1458,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
           else if (type_of(pc) == castling_rook_piece())
           {
               if (   (file_of(to) == FILE_A || file_of(to) == max_file())
-                  && piece_on(make_square(FILE_E, castling_rank(us))) == make_piece(us, KING))
+                  && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece()))
                   set_castling_right(us, to);
           }
       }
index 8719ef7..065ae67 100644 (file)
@@ -52,6 +52,7 @@ struct StateInfo {
   int    countingLimit;
   CheckCount checksRemaining[COLOR_NB];
   Square epSquare;
+  Square castlingKingSquare[COLOR_NB];
   Bitboard gatesBB[COLOR_NB];
 
   // Not copied when making a move (will be recomputed anyhow)
@@ -136,6 +137,8 @@ public:
   File castling_kingside_file() const;
   File castling_queenside_file() const;
   Rank castling_rank(Color c) const;
+  File castling_king_file() const;
+  PieceType castling_king_piece() const;
   PieceType castling_rook_piece() const;
   PieceType king_type() const;
   bool checking_permitted() const;
@@ -202,12 +205,14 @@ public:
   Piece piece_on(Square s) const;
   Piece unpromoted_piece_on(Square s) const;
   Square ep_square() const;
+  Square castling_king_square(Color c) const;
   Bitboard gates(Color c) const;
   bool empty(Square s) const;
   int count(Color c, PieceType pt) const;
   template<PieceType Pt> int count(Color c) const;
   template<PieceType Pt> int count() const;
   template<PieceType Pt> Square square(Color c) const;
+  Square square(Color c, PieceType pt) const;
   bool is_on_semiopen_file(Color c, Square s) const;
 
   // Castling
@@ -475,6 +480,16 @@ inline Rank Position::castling_rank(Color c) const {
   return relative_rank(c, var->castlingRank, max_rank());
 }
 
+inline File Position::castling_king_file() const {
+  assert(var != nullptr);
+  return var->castlingKingFile;
+}
+
+inline PieceType Position::castling_king_piece() const {
+  assert(var != nullptr);
+  return var->castlingKingPiece;
+}
+
 inline PieceType Position::castling_rook_piece() const {
   assert(var != nullptr);
   return var->castlingRookPiece;
@@ -891,10 +906,19 @@ template<PieceType Pt> inline Square Position::square(Color c) const {
   return lsb(pieces(c, Pt));
 }
 
+inline Square Position::square(Color c, PieceType pt) const {
+  assert(count(c, pt) == 1);
+  return lsb(pieces(c, pt));
+}
+
 inline Square Position::ep_square() const {
   return st->epSquare;
 }
 
+inline Square Position::castling_king_square(Color c) const {
+  return st->castlingKingSquare[c];
+}
+
 inline Bitboard Position::gates(Color c) const {
   assert(var != nullptr);
   return st->gatesBB[c];
index 1909e5b..7d500f0 100644 (file)
@@ -209,6 +209,7 @@ namespace {
         v->remove_piece(KNIGHT);
         v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1";
         v->kingType = KNIGHT;
+        v->castlingKingPiece = KNIGHT;
         v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP};
         return v;
     }
@@ -227,6 +228,7 @@ namespace {
         v->variantTemplate = "giveaway";
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
+        v->castlingKingPiece = COMMONER;
         v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
         v->stalemateValue = VALUE_MATE;
         v->extinctionValue = VALUE_MATE;
@@ -255,6 +257,7 @@ namespace {
         Variant* v = fairy_variant_base();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
+        v->castlingKingPiece = COMMONER;
         v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT, PAWN};
@@ -272,6 +275,7 @@ namespace {
         Variant* v = fairy_variant_base();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
+        v->castlingKingPiece = COMMONER;
         v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1";
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = {COMMONER};
@@ -296,6 +300,7 @@ namespace {
         v->variantTemplate = "atomic";
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
+        v->castlingKingPiece = COMMONER;
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = {COMMONER};
         v->blastOnCapture = true;
@@ -352,6 +357,7 @@ namespace {
         Variant* v = bughouse_variant();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
+        v->castlingKingPiece = COMMONER;
         v->mustDrop = true;
         v->mustDropType = COMMONER;
         v->extinctionValue = -VALUE_MATE;
index 539f627..13a8707 100644 (file)
@@ -65,6 +65,8 @@ struct Variant {
   File castlingKingsideFile = FILE_G;
   File castlingQueensideFile = FILE_C;
   Rank castlingRank = RANK_1;
+  File castlingKingFile = FILE_E;
+  PieceType castlingKingPiece = KING;
   PieceType castlingRookPiece = ROOK;
   PieceType kingType = KING;
   bool checking = true;
index 5087ec3..dff56c8 100644 (file)
 # castlingKingsideFile: destination file of king after kingside castling [File] (default: g)
 # castlingQueensideFile: destination file of king after queenside castling [File] (default: c)
 # castlingRank: relative rank of castling [Rank] (default: 1)
-# castlingRookPiece: piece type that participates in castling [PieceType] (default: r)
+# castlingKingFile: starting file of the castlingKingPiece if there can be more than one of that type [File] (default: e)
+# castlingKingPiece: first piece type that participates in castling [PieceType] (default: k)
+# castlingRookPiece: second piece type that participates in castling [PieceType] (default: r)
 # kingType: piece type defining moves of king/royal piece (default: k)
 # checking: allow checks [bool] (default: true)
 # dropChecks: allow checks by piece drops [bool] (default: true)