Support multiple castling rook types (#603)
authorFabian Fichter <ianfab@users.noreply.github.com>
Sat, 25 Mar 2023 13:35:08 +0000 (14:35 +0100)
committerGitHub <noreply@github.com>
Sat, 25 Mar 2023 13:35:08 +0000 (14:35 +0100)
Closes #52.

src/apiutil.h
src/parser.cpp
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
src/variants.ini
tests/perft.sh

index 3cdbf4d..6f36579 100644 (file)
@@ -495,11 +495,12 @@ public:
         return s;
     }
     /// Returns all square positions for a given piece
-    std::vector<CharSquare> get_squares_for_piece(char piece) const {
+    std::vector<CharSquare> get_squares_for_pieces(Color color, PieceSet ps, const std::string& pieceChars) const {
         std::vector<CharSquare> squares;
+        size_t pcIdx;
         for (int r = 0; r < nbRanks; ++r)
             for (int c = 0; c < nbFiles; ++c)
-                if (get_piece(r, c) == piece)
+                if ((pcIdx = pieceChars.find(get_piece(r, c))) != std::string::npos && (ps & type_of(Piece(pcIdx))) && color_of(Piece(pcIdx)) == color)
                     squares.emplace_back(CharSquare(r, c));
         return squares;
     }
@@ -688,7 +689,6 @@ inline Validation check_castling_rank(const std::array<std::string, 2>& castling
     for (Color c : {WHITE, BLACK})
     {
         const Rank castlingRank = relative_rank(c, v->castlingRank, v->maxRank);
-        char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)];
         for (char castlingFlag : castlingInfoSplitted[c])
         {
             if (tolower(castlingFlag) == 'k' || tolower(castlingFlag) == 'q')
@@ -700,8 +700,11 @@ inline Validation check_castling_rank(const std::array<std::string, 2>& castling
                 }
                 bool kingside = tolower(castlingFlag) == 'k';
                 bool castlingRook = false;
+                size_t pcIdx;
                 for (int f = kingside ? board.get_nb_files() - 1 : 0; f != kingPositions[c].fileIdx; kingside ? f-- : f++)
-                    if (board.get_piece(castlingRank, f) == rookChar)
+                    if (   (pcIdx = v->pieceToChar.find(board.get_piece(castlingRank, f))) != std::string::npos
+                        && (v->castlingRookPieces[c] & type_of(Piece(pcIdx)))
+                        && color_of(Piece(pcIdx)) == c)
                     {
                         castlingRook = true;
                         break;
@@ -740,10 +743,12 @@ inline Validation check_standard_castling(std::array<std::string, 2>& castlingIn
         {
             CharSquare rookStartingSquare = castling == QUEEN_SIDE ? rookPositionsStart[c][0] : rookPositionsStart[c][1];
             char targetChar = castling == QUEEN_SIDE ? 'q' : 'k';
-            char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)];
+            size_t pcIdx;
             if (castlingInfoSplitted[c].find(targetChar) != std::string::npos)
             {
-                if (board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx) != rookChar)
+                if (   (pcIdx = v->pieceToChar.find(board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx))) == std::string::npos
+                    || !(v->castlingRookPieces[c] & type_of(Piece(pcIdx)))
+                    || color_of(Piece(pcIdx)) != c)
                 {
                     std::cerr << "The " << color_to_string(c) << " ROOK on the "<<  castling_rights_to_string(castling) << " has moved. "
                               << castling_rights_to_string(castling) << " castling is no longer valid for " << color_to_string(c) << "." << std::endl;
@@ -994,8 +999,8 @@ inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool
         if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0)
         {
             std::array<CharSquare, 2> kingPositions;
-            kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece]));
-            kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece]));
+            kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece[WHITE]]));
+            kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece[BLACK]]));
 
             CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files());
             fill_char_board(startBoard, v->startFen, validSpecialCharactersFirstField, v);
@@ -1008,11 +1013,11 @@ inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool
             if (!v->chess960 && !v->castlingDroppedPiece && !chess960)
             {
                 std::array<CharSquare, 2> kingPositionsStart;
-                kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece)]);
-                kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece)]);
+                kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece[WHITE])]);
+                kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece[BLACK])]);
                 std::array<std::vector<CharSquare>, 2> rookPositionsStart;
-                rookPositionsStart[WHITE] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingRookPiece)]);
-                rookPositionsStart[BLACK] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingRookPiece)]);
+                rookPositionsStart[WHITE] = startBoard.get_squares_for_pieces(WHITE, v->castlingRookPieces[WHITE], v->pieceToChar);
+                rookPositionsStart[BLACK] = startBoard.get_squares_for_pieces(BLACK, v->castlingRookPieces[BLACK], v->pieceToChar);
 
                 if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart, v) == NOK)
                     return FEN_INVALID_CASTLING_INFO;
index 6ca9385..a4142cd 100644 (file)
@@ -289,6 +289,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     }
     parse_attribute<false>("whiteFlag", v->flagRegion[WHITE]);
     parse_attribute<false>("blackFlag", v->flagRegion[BLACK]);
+    parse_attribute<false>("castlingRookPiece", v->castlingRookPieces[WHITE], v->pieceToChar);
+    parse_attribute<false>("castlingRookPiece", v->castlingRookPieces[BLACK], v->pieceToChar);
 
     // Parse aliases
     parse_attribute("pawnTypes", v->promotionPawnType[WHITE], v->pieceToChar);
@@ -372,8 +374,14 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     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("castlingKingPiece", v->castlingKingPiece[WHITE], v->pieceToChar);
+    parse_attribute("castlingKingPiece", v->castlingKingPiece[BLACK], v->pieceToChar);
+    parse_attribute("castlingKingPieceWhite", v->castlingKingPiece[WHITE], v->pieceToChar);
+    parse_attribute("castlingKingPieceBlack", v->castlingKingPiece[BLACK], v->pieceToChar);
+    parse_attribute("castlingRookPieces", v->castlingRookPieces[WHITE], v->pieceToChar);
+    parse_attribute("castlingRookPieces", v->castlingRookPieces[BLACK], v->pieceToChar);
+    parse_attribute("castlingRookPiecesWhite", v->castlingRookPieces[WHITE], v->pieceToChar);
+    parse_attribute("castlingRookPiecesBlack", v->castlingRookPieces[BLACK], v->pieceToChar);
     parse_attribute("checking", v->checking);
     parse_attribute("dropChecks", v->dropChecks);
     parse_attribute("mustCapture", v->mustCapture);
index a934c6b..7daec3a 100644 (file)
@@ -345,15 +345,14 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
       {
           Square rsq;
           Color c = islower(token) ? BLACK : WHITE;
-          Piece rook = make_piece(c, castling_rook_piece());
 
           token = char(toupper(token));
 
           if (token == 'K')
-              for (rsq = make_square(FILE_MAX, castling_rank(c)); piece_on(rsq) != rook; --rsq) {}
+              for (rsq = make_square(FILE_MAX, castling_rank(c)); !(castling_rook_pieces(c) & type_of(piece_on(rsq))) || color_of(piece_on(rsq)) != c; --rsq) {}
 
           else if (token == 'Q')
-              for (rsq = make_square(FILE_A, castling_rank(c)); piece_on(rsq) != rook; ++rsq) {}
+              for (rsq = make_square(FILE_A, castling_rank(c)); !(castling_rook_pieces(c) & type_of(piece_on(rsq))) || color_of(piece_on(rsq)) != c; ++rsq) {}
 
           else if (token >= 'A' && token <= 'A' + max_file())
               rsq = make_square(File(token - 'A'), castling_rank(c));
@@ -364,10 +363,10 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
           // Determine castling "king" position
           if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE)
           {
-              Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c));
+              Bitboard castlingKings = pieces(c, castling_king_piece(c)) & 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
+              st->castlingKingSquare[c] =  isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece(c)) ? rsq
                                          : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings)
                                          : make_square(castling_king_file(), castling_rank(c));
           }
@@ -384,18 +383,22 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
                   continue;
           }
 
-          if (castling_enabled() && piece_on(rsq) == rook)
+          if (castling_enabled() && (castling_rook_pieces(c) & type_of(piece_on(rsq))) && color_of(piece_on(rsq)) == c)
               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(castling_king_piece())) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand()))
+              if ((gates(c) & pieces(castling_king_piece(c))) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand()))
               {
-                  Bitboard castling_rooks = gates(c) & pieces(castling_rook_piece());
+                  Bitboard castling_rooks = gates(c) & pieces(c);
                   while (castling_rooks)
-                      set_castling_right(c, pop_lsb(castling_rooks));
+                  {
+                      Square s = pop_lsb(castling_rooks);
+                      if (castling_rook_pieces(c) & type_of(piece_on(s)))
+                          set_castling_right(c, s);
+                  }
               }
 
       // counting limit
@@ -741,7 +744,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
   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)
+  if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece(WHITE)) & rank_bb(castling_rank(WHITE))) > 1)
       ss << char('A' + castling_king_square(WHITE));
 
   if (can_castle(WHITE_OO))
@@ -760,7 +763,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
               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)
+  if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece(BLACK)) & rank_bb(castling_rank(BLACK))) > 1)
       ss << char('a' + castling_king_square(BLACK));
 
   if (can_castle(BLACK_OO))
@@ -1511,7 +1514,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   if (type_of(m) == CASTLING)
   {
       assert(type_of(pc) != NO_PIECE_TYPE);
-      assert(captured == make_piece(us, castling_rook_piece()));
+      assert(castling_rook_pieces(us) & type_of(captured));
 
       Square rfrom, rto;
       do_castling<true>(us, from, to, rfrom, rto);
@@ -1666,19 +1669,23 @@ 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) == castling_king_piece() && file_of(to) == castling_king_file())
+          if (type_of(pc) == castling_king_piece(us) && file_of(to) == castling_king_file())
           {
               st->castlingKingSquare[us] = to;
-              Bitboard castling_rooks =  pieces(us, castling_rook_piece())
+              Bitboard castling_rooks =  pieces(us)
                                        & rank_bb(castling_rank(us))
                                        & (file_bb(FILE_A) | file_bb(max_file()));
               while (castling_rooks)
-                  set_castling_right(us, pop_lsb(castling_rooks));
+              {
+                  Square s = pop_lsb(castling_rooks);
+                  if (castling_rook_pieces(us) & type_of(piece_on(s)))
+                      set_castling_right(us, s);
+              }
           }
-          else if (type_of(pc) == castling_rook_piece())
+          else if (castling_rook_pieces(us) & type_of(pc))
           {
               if (   (file_of(to) == FILE_A || file_of(to) == max_file())
-                  && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece()))
+                  && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece(us)))
               {
                   st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us));
                   set_castling_right(us, to);
@@ -3062,7 +3069,7 @@ bool Position::pos_is_ok() const {
           if (!can_castle(cr))
               continue;
 
-          if (   piece_on(castlingRookSquare[cr]) != make_piece(c, castling_rook_piece())
+          if (   !(castling_rook_pieces(c) & type_of(piece_on(castlingRookSquare[cr])))
               || castlingRightsMask[castlingRookSquare[cr]] != cr
               || (count<KING>(c) && (castlingRightsMask[square<KING>(c)] & cr) != cr))
               assert(0 && "pos_is_ok: Castling");
index 2c2c94b..2fe0b7e 100644 (file)
@@ -149,8 +149,8 @@ public:
   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 castling_king_piece(Color c) const;
+  PieceSet castling_rook_pieces(Color c) const;
   PieceType king_type() const;
   PieceType nnue_king() const;
   Square nnue_king_square(Color c) const;
@@ -529,14 +529,14 @@ inline File Position::castling_king_file() const {
   return var->castlingKingFile;
 }
 
-inline PieceType Position::castling_king_piece() const {
+inline PieceType Position::castling_king_piece(Color c) const {
   assert(var != nullptr);
-  return var->castlingKingPiece;
+  return var->castlingKingPiece[c];
 }
 
-inline PieceType Position::castling_rook_piece() const {
+inline PieceSet Position::castling_rook_pieces(Color c) const {
   assert(var != nullptr);
-  return var->castlingRookPiece;
+  return var->castlingRookPieces[c];
 }
 
 inline PieceType Position::king_type() const {
index c29ce1e..ee9f67f 100644 (file)
@@ -316,7 +316,7 @@ namespace {
         v->remove_piece(KNIGHT);
         v->add_piece(ROOKNI, 'r');
         v->add_piece(KNIROO, 'n');
-        v->castlingRookPiece = ROOKNI;
+        v->castlingRookPieces[WHITE] = v->castlingRookPieces[BLACK] = piece_set(ROOKNI);
         v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOKNI | BISHOP | KNIROO;
         v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOKNI | BISHOP | KNIROO;
         return v;
@@ -352,7 +352,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 = KING;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = KING;
         v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP;
         v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP;
         return v;
@@ -377,7 +377,7 @@ namespace {
         v->variantTemplate = "giveaway";
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT;
         v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT;
         v->stalemateValue = VALUE_MATE;
@@ -420,7 +420,7 @@ namespace {
         Variant* v = chess_variant_base()->init();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT;
         v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT;
         v->extinctionValue = -VALUE_MATE;
@@ -442,7 +442,7 @@ namespace {
         Variant* v = chess_variant_base()->init();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1";
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = piece_set(COMMONER);
@@ -467,7 +467,7 @@ namespace {
         v->variantTemplate = "atomic";
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = piece_set(COMMONER);
         v->blastOnCapture = true;
@@ -487,7 +487,7 @@ namespace {
         Variant* v = chess_variant_base()->init();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = piece_set(COMMONER);
         v->duckGating = true;
@@ -603,7 +603,7 @@ namespace {
         Variant* v = bughouse_variant()->init();
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->mustDrop = true;
         v->mustDropType = COMMONER;
         v->extinctionValue = -VALUE_MATE;
@@ -975,6 +975,19 @@ namespace {
         v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP;
         return v;
     }
+    // Perfect chess
+    // https://www.chessvariants.com/diffmove.dir/perfectchess.html
+    Variant* perfect_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->add_piece(CHANCELLOR, 'c');
+        v->add_piece(ARCHBISHOP, 'm');
+        v->add_piece(AMAZON, 'g');
+        v->startFen = "cmqgkbnr/pppppppp/8/8/8/8/PPPPPPPP/CMQGKBNR w KQkq - 0 1";
+        v->promotionPieceTypes[WHITE] = piece_set(AMAZON) | CHANCELLOR | ARCHBISHOP | QUEEN | ROOK | BISHOP | KNIGHT;
+        v->promotionPieceTypes[BLACK] = piece_set(AMAZON) | CHANCELLOR | ARCHBISHOP | QUEEN | ROOK | BISHOP | KNIGHT;
+        v->castlingRookPieces[WHITE] = v->castlingRookPieces[BLACK] |= piece_set(CHANCELLOR);
+        return v;
+    }
     // Spartan chess
     // https://www.chessvariants.com/rules/spartan-chess
     Variant* spartan_variant() {
@@ -1167,7 +1180,7 @@ namespace {
         v->capturesToHand = false;
         v->pieceDrops = false;
         v->promotedPieceType[CUSTOM_PIECES] = COMMONER;
-        v->castlingKingPiece = COMMONER;
+        v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER;
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = piece_set(COMMONER);
         v->extinctionPseudoRoyal = true;
@@ -1791,6 +1804,7 @@ void VariantMap::init() {
     add("gardner", gardner_variant());
     add("almost", almost_variant());
     add("chigorin", chigorin_variant());
+    add("perfect", perfect_variant());
     add("spartan", spartan_variant());
     add("shatar", shatar_variant());
     add("coregal", coregal_variant());
index 3af6138..d4e2745 100644 (file)
@@ -76,8 +76,8 @@ struct Variant {
   File castlingQueensideFile = FILE_C;
   Rank castlingRank = RANK_1;
   File castlingKingFile = FILE_E;
-  PieceType castlingKingPiece = KING;
-  PieceType castlingRookPiece = ROOK;
+  PieceType castlingKingPiece[COLOR_NB] = {KING, KING};
+  PieceSet castlingRookPieces[COLOR_NB] = {piece_set(ROOK), piece_set(ROOK)};
   PieceType kingType = KING;
   bool checking = true;
   bool dropChecks = true;
index 0bd032e..d5bda0d 100644 (file)
 # castlingRank: relative rank of castling [Rank] (default: 1)
 # 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)
+# castlingRookPieces: second piece type that participates in castling [PieceSet] (default: r)
 # checking: allow checks [bool] (default: true)
 # dropChecks: allow checks by piece drops [bool] (default: true)
 # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false)
index e11d01e..ec08d5f 100755 (executable)
@@ -61,6 +61,8 @@ if [[ $1 == "all" || $1 == "variant" ]]; then
   expect perft.exp sittuyin "fen 8/6s1/5P2/3n4/pR2K2S/1P6/1k4p1/8[] w - - 1 50" 4 268869 > /dev/null
   expect perft.exp sittuyin "fen 1k5K/3r2P1/8/8/8/8/8/8[] w - - 0 1" 5 68662 > /dev/null
   expect perft.exp chigorin "fen 8/7P/2k5/8/8/5K2/p7/8 w - - 0 1" 2 120 > /dev/null
+  expect perft.exp perfect startpos 3 15082 > /dev/null
+  expect perft.exp perfect "fen c3k2r/pppppppp/8/8/8/8/PPPPPPPP/C3K2R w KQkq - 0 1" 3 17500 > /dev/null
   expect perft.exp spartan startpos 3 14244 > /dev/null
   # duple check & mate
   expect perft.exp spartan "fen k6k/hh2Q2h/8/8/8/8/8/4K3 w - - 0 1" 3 6130 > /dev/null