Support custom pawn pieces
authorFabian Fichter <ianfab@users.noreply.github.com>
Fri, 24 Mar 2023 15:54:31 +0000 (16:54 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Fri, 24 Mar 2023 16:33:45 +0000 (17:33 +0100)
This introduces support for
* initial moves, promotions, and en passant for all pieces.
* arbitrarily shaped double/triple step and promotion regions.
* per color configuration of promotion types and zone, etc.

These new configuration options enable support for
* Berolina, Spartan, Wolf
* Torpedo, pawnsideways, pawnback
* Legan, Troitzky

Most changed configuration options are kept as deprected options for now
for backwards compatibility, but it is recommended to migrate variant
configuration files as the deprecated options might be removed later.

Closes #305, closes #4, closes #209, closes #260, closes #51, closes #328, closes #596.

No functional change for other variants.

24 files changed:
src/apiutil.h
src/bitboard.cpp
src/bitboard.h
src/endgame.cpp
src/evaluate.cpp
src/magic.h
src/material.cpp
src/movegen.cpp
src/parser.cpp
src/parser.h
src/pawns.cpp
src/piece.cpp
src/piece.h
src/position.cpp
src/position.h
src/psqt.cpp
src/search.cpp
src/types.h
src/ucioption.cpp
src/variant.cpp
src/variant.h
src/variants.ini
test.py
tests/perft.sh

index f75d830..34e7729 100644 (file)
@@ -386,7 +386,7 @@ inline bool has_insufficient_material(Color c, const Position& pos) {
 
     // Mating pieces
     for (PieceType pt : { ROOK, QUEEN, ARCHBISHOP, CHANCELLOR, SILVER, GOLD, COMMONER, CENTAUR })
-        if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, PAWN) && pos.promotion_piece_types().find(pt) != pos.promotion_piece_types().end()))
+        if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, pos.promotion_pawn_type(c)) && pos.promotion_piece_types(c).find(pt) != pos.promotion_piece_types(c).end()))
             return false;
 
     // Color-bound pieces
@@ -895,7 +895,7 @@ inline std::string get_valid_special_chars(const Variant* v) {
     // Whether or not '-', '+', '~', '[', ']' are valid depends on the variant being played.
     if (v->shogiStylePromotions)
         validSpecialCharactersFirstField += '+';
-    if (!v->promotionPieceTypes.empty())
+    if (!v->promotionPieceTypes[WHITE].empty() || !v->promotionPieceTypes[BLACK].empty())
         validSpecialCharactersFirstField += '~';
     if (!v->freeDrops && (v->pieceDrops || v->seirawanGating))
         validSpecialCharactersFirstField += "[-]";
index 63f2bfd..b4671f1 100644 (file)
@@ -33,18 +33,19 @@ Bitboard SquareBB[SQUARE_NB];
 Bitboard LineBB[SQUARE_NB][SQUARE_NB];
 Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
 Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
-Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+Bitboard PseudoMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
 Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
-Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+Bitboard LeaperMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
 Bitboard BoardSizeBB[FILE_NB][RANK_NB];
 RiderType AttackRiderTypes[PIECE_TYPE_NB];
-RiderType MoveRiderTypes[PIECE_TYPE_NB];
+RiderType MoveRiderTypes[2][PIECE_TYPE_NB];
 
 Magic RookMagicsH[SQUARE_NB];
 Magic RookMagicsV[SQUARE_NB];
 Magic BishopMagics[SQUARE_NB];
 Magic CannonMagicsH[SQUARE_NB];
 Magic CannonMagicsV[SQUARE_NB];
+Magic LameDabbabaMagics[SQUARE_NB];
 Magic HorseMagics[SQUARE_NB];
 Magic ElephantMagics[SQUARE_NB];
 Magic JanggiElephantMagics[SQUARE_NB];
@@ -55,7 +56,7 @@ Magic GrasshopperMagicsV[SQUARE_NB];
 Magic GrasshopperMagicsD[SQUARE_NB];
 
 Magic* magics[] = {BishopMagics, RookMagicsH, RookMagicsV, CannonMagicsH, CannonMagicsV,
-                   HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics,
+                   LameDabbabaMagics, HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics,
                    GrasshopperMagicsH, GrasshopperMagicsV, GrasshopperMagicsD};
 
 namespace {
@@ -68,6 +69,7 @@ namespace {
   Bitboard BishopTable[0x33C00]; // To store bishop attacks
   Bitboard CannonTableH[0x11800];  // To store horizontal cannon attacks
   Bitboard CannonTableV[0x4800];  // To store vertical cannon attacks
+  Bitboard LameDabbabaTable[0x500];  // To store lame dabbaba attacks
   Bitboard HorseTable[0x500];  // To store horse attacks
   Bitboard ElephantTable[0x400];  // To store elephant attacks
   Bitboard JanggiElephantTable[0x1C000];  // To store janggi elephant attacks
@@ -82,6 +84,7 @@ namespace {
   Bitboard BishopTable[0x1480]; // To store bishop attacks
   Bitboard CannonTableH[0xA00];  // To store horizontal cannon attacks
   Bitboard CannonTableV[0xA00];  // To store vertical cannon attacks
+  Bitboard LameDabbabaTable[0x240];  // To store lame dabbaba attacks
   Bitboard HorseTable[0x240];  // To store horse attacks
   Bitboard ElephantTable[0x1A0];  // To store elephant attacks
   Bitboard JanggiElephantTable[0x5C00];  // To store janggi elephant attacks
@@ -96,6 +99,7 @@ namespace {
   const std::map<Direction, int> RookDirectionsV { {NORTH, 0}, {SOUTH, 0}};
   const std::map<Direction, int> RookDirectionsH { {EAST, 0}, {WEST, 0} };
   const std::map<Direction, int> BishopDirections { {NORTH_EAST, 0}, {SOUTH_EAST, 0}, {SOUTH_WEST, 0}, {NORTH_WEST, 0} };
+  const std::map<Direction, int> LameDabbabaDirections { {2 * NORTH, 0}, {2 * EAST, 0}, {2 * SOUTH, 0}, {2 * WEST, 0} };
   const std::map<Direction, int> HorseDirections { {2 * SOUTH + WEST, 0}, {2 * SOUTH + EAST, 0}, {SOUTH + 2 * WEST, 0}, {SOUTH + 2 * EAST, 0},
                                                    {NORTH + 2 * WEST, 0}, {NORTH + 2 * EAST, 0}, {2 * NORTH + WEST, 0}, {2 * NORTH + EAST, 0} };
   const std::map<Direction, int> ElephantDirections { {2 * NORTH_EAST, 0}, {2 * SOUTH_EAST, 0}, {2 * SOUTH_WEST, 0}, {2 * NORTH_WEST, 0} };
@@ -232,36 +236,44 @@ void Bitboards::init_pieces() {
       // Detect rider types
       for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE})
       {
-          auto& riderTypes = modality == MODALITY_CAPTURE ? AttackRiderTypes[pt] : MoveRiderTypes[pt];
-          riderTypes = NO_RIDER;
-          for (auto const& [d, limit] : pi->steps[modality])
+          for (bool initial : {false, true})
           {
-              if (limit && HorseDirections.find(d) != HorseDirections.end())
-                  riderTypes |= RIDER_HORSE;
-              if (limit && ElephantDirections.find(d) != ElephantDirections.end())
-                  riderTypes |= RIDER_ELEPHANT;
-              if (limit && JanggiElephantDirections.find(d) != JanggiElephantDirections.end())
-                  riderTypes |= RIDER_JANGGI_ELEPHANT;
-          }
-          for (auto const& [d, limit] : pi->slider[modality])
-          {
-              if (BishopDirections.find(d) != BishopDirections.end())
-                  riderTypes |= RIDER_BISHOP;
-              if (RookDirectionsH.find(d) != RookDirectionsH.end())
-                  riderTypes |= RIDER_ROOK_H;
-              if (RookDirectionsV.find(d) != RookDirectionsV.end())
-                  riderTypes |= RIDER_ROOK_V;
-              if (HorseDirections.find(d) != HorseDirections.end())
-                  riderTypes |= RIDER_NIGHTRIDER;
-          }
-          for (auto const& [d, limit] : pi->hopper[modality])
-          {
-              if (RookDirectionsH.find(d) != RookDirectionsH.end())
-                  riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_H : RIDER_CANNON_H;
-              if (RookDirectionsV.find(d) != RookDirectionsV.end())
-                  riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_V : RIDER_CANNON_V;
-              if (BishopDirections.find(d) != BishopDirections.end())
-                  riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_D : RIDER_CANNON_DIAG;
+              // We do not support initial captures
+              if (modality == MODALITY_CAPTURE && initial)
+                  continue;
+              auto& riderTypes = modality == MODALITY_CAPTURE ? AttackRiderTypes[pt] : MoveRiderTypes[initial][pt];
+              riderTypes = NO_RIDER;
+              for (auto const& [d, limit] : pi->steps[initial][modality])
+              {
+                  if (limit && LameDabbabaDirections.find(d) != LameDabbabaDirections.end())
+                      riderTypes |= RIDER_LAME_DABBABA;
+                  if (limit && HorseDirections.find(d) != HorseDirections.end())
+                      riderTypes |= RIDER_HORSE;
+                  if (limit && ElephantDirections.find(d) != ElephantDirections.end())
+                      riderTypes |= RIDER_ELEPHANT;
+                  if (limit && JanggiElephantDirections.find(d) != JanggiElephantDirections.end())
+                      riderTypes |= RIDER_JANGGI_ELEPHANT;
+              }
+              for (auto const& [d, limit] : pi->slider[initial][modality])
+              {
+                  if (BishopDirections.find(d) != BishopDirections.end())
+                      riderTypes |= RIDER_BISHOP;
+                  if (RookDirectionsH.find(d) != RookDirectionsH.end())
+                      riderTypes |= RIDER_ROOK_H;
+                  if (RookDirectionsV.find(d) != RookDirectionsV.end())
+                      riderTypes |= RIDER_ROOK_V;
+                  if (HorseDirections.find(d) != HorseDirections.end())
+                      riderTypes |= RIDER_NIGHTRIDER;
+              }
+              for (auto const& [d, limit] : pi->hopper[initial][modality])
+              {
+                  if (RookDirectionsH.find(d) != RookDirectionsH.end())
+                      riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_H : RIDER_CANNON_H;
+                  if (RookDirectionsV.find(d) != RookDirectionsV.end())
+                      riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_V : RIDER_CANNON_V;
+                  if (BishopDirections.find(d) != BishopDirections.end())
+                      riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_D : RIDER_CANNON_DIAG;
+              }
           }
       }
 
@@ -272,18 +284,24 @@ void Bitboards::init_pieces() {
           {
               for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE})
               {
-                  auto& pseudo = modality == MODALITY_CAPTURE ? PseudoAttacks[c][pt][s] : PseudoMoves[c][pt][s];
-                  auto& leaper = modality == MODALITY_CAPTURE ? LeaperAttacks[c][pt][s] : LeaperMoves[c][pt][s];
-                  pseudo = 0;
-                  leaper = 0;
-                  for (auto const& [d, limit] : pi->steps[modality])
+                  for (bool initial : {false, true})
                   {
-                      pseudo |= safe_destination(s, c == WHITE ? d : -d);
-                      if (!limit)
-                          leaper |= safe_destination(s, c == WHITE ? d : -d);
+                      // We do not support initial captures
+                      if (modality == MODALITY_CAPTURE && initial)
+                          continue;
+                      auto& pseudo = modality == MODALITY_CAPTURE ? PseudoAttacks[c][pt][s] : PseudoMoves[initial][c][pt][s];
+                      auto& leaper = modality == MODALITY_CAPTURE ? LeaperAttacks[c][pt][s] : LeaperMoves[initial][c][pt][s];
+                      pseudo = 0;
+                      leaper = 0;
+                      for (auto const& [d, limit] : pi->steps[initial][modality])
+                      {
+                          pseudo |= safe_destination(s, c == WHITE ? d : -d);
+                          if (!limit)
+                              leaper |= safe_destination(s, c == WHITE ? d : -d);
+                      }
+                      pseudo |= sliding_attack<RIDER>(pi->slider[initial][modality], s, 0, c);
+                      pseudo |= sliding_attack<UNLIMITED_RIDER>(pi->hopper[initial][modality], s, 0, c);
                   }
-                  pseudo |= sliding_attack<RIDER>(pi->slider[modality], s, 0, c);
-                  pseudo |= sliding_attack<UNLIMITED_RIDER>(pi->hopper[modality], s, 0, c);
               }
           }
       }
@@ -316,6 +334,7 @@ void Bitboards::init() {
   init_magics<RIDER>(BishopTable, BishopMagics, BishopDirections, BishopMagicInit);
   init_magics<HOPPER>(CannonTableH, CannonMagicsH, RookDirectionsH, CannonMagicHInit);
   init_magics<HOPPER>(CannonTableV, CannonMagicsV, RookDirectionsV, CannonMagicVInit);
+  init_magics<LAME_LEAPER>(LameDabbabaTable, LameDabbabaMagics, LameDabbabaDirections, LameDabbabaMagicInit);
   init_magics<LAME_LEAPER>(HorseTable, HorseMagics, HorseDirections, HorseMagicInit);
   init_magics<LAME_LEAPER>(ElephantTable, ElephantMagics, ElephantDirections, ElephantMagicInit);
   init_magics<LAME_LEAPER>(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections, JanggiElephantMagicInit);
@@ -330,6 +349,7 @@ void Bitboards::init() {
   init_magics<RIDER>(BishopTable, BishopMagics, BishopDirections);
   init_magics<HOPPER>(CannonTableH, CannonMagicsH, RookDirectionsH);
   init_magics<HOPPER>(CannonTableV, CannonMagicsV, RookDirectionsV);
+  init_magics<LAME_LEAPER>(LameDabbabaTable, LameDabbabaMagics, LameDabbabaDirections);
   init_magics<LAME_LEAPER>(HorseTable, HorseMagics, HorseDirections);
   init_magics<LAME_LEAPER>(ElephantTable, ElephantMagics, ElephantDirections);
   init_magics<LAME_LEAPER>(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections);
index 60a57a4..b1b6f3d 100644 (file)
@@ -106,13 +106,13 @@ extern Bitboard SquareBB[SQUARE_NB];
 extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
 extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
 extern Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
-extern Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+extern Bitboard PseudoMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
 extern Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
-extern Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
+extern Bitboard LeaperMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB];
 extern Bitboard SquareBB[SQUARE_NB];
 extern Bitboard BoardSizeBB[FILE_NB][RANK_NB];
 extern RiderType AttackRiderTypes[PIECE_TYPE_NB];
-extern RiderType MoveRiderTypes[PIECE_TYPE_NB];
+extern RiderType MoveRiderTypes[2][PIECE_TYPE_NB];
 
 #ifdef LARGEBOARDS
 int popcount(Bitboard b); // required for 128 bit pext
@@ -149,6 +149,7 @@ extern Magic RookMagicsV[SQUARE_NB];
 extern Magic BishopMagics[SQUARE_NB];
 extern Magic CannonMagicsH[SQUARE_NB];
 extern Magic CannonMagicsV[SQUARE_NB];
+extern Magic LameDabbabaMagics[SQUARE_NB];
 extern Magic HorseMagics[SQUARE_NB];
 extern Magic ElephantMagics[SQUARE_NB];
 extern Magic JanggiElephantMagics[SQUARE_NB];
@@ -407,6 +408,7 @@ inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) {
                   : R == RIDER_ROOK_V ? RookMagicsV[s]
                   : R == RIDER_CANNON_H ? CannonMagicsH[s]
                   : R == RIDER_CANNON_V ? CannonMagicsV[s]
+                  : R == RIDER_LAME_DABBABA ? LameDabbabaMagics[s]
                   : R == RIDER_HORSE ? HorseMagics[s]
                   : R == RIDER_ELEPHANT ? ElephantMagics[s]
                   : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s]
@@ -477,12 +479,13 @@ inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) {
 }
 
 
+template <bool Initial=false>
 inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) {
-  Bitboard b = LeaperMoves[c][pt][s];
-  RiderType r = MoveRiderTypes[pt];
+  Bitboard b = LeaperMoves[Initial][c][pt][s];
+  RiderType r = MoveRiderTypes[Initial][pt];
   while (r)
       b |= rider_attacks_bb(pop_rider(&r), s, occupied);
-  return b & PseudoMoves[c][pt][s];
+  return b & PseudoMoves[Initial][c][pt][s];
 }
 
 
index 80de6b9..186258a 100644 (file)
@@ -183,9 +183,9 @@ Value Endgame<KPK>::operator()(const Position& pos) const {
   Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
 
   // Non-standard promotion, evaluation unclear
-  if (   pos.promotion_rank() != RANK_8
+  if (   pos.promotion_zone(us) != rank_bb(relative_rank(us, RANK_8, pos.max_rank()))
       || RANK_MAX != RANK_8
-      || pos.promotion_piece_types().find(QUEEN) == pos.promotion_piece_types().end())
+      || pos.promotion_piece_types(us).find(QUEEN) == pos.promotion_piece_types(us).end())
   {
       Value result = PawnValueEg + Value(rank_of(strongPawn));
       return strongSide == pos.side_to_move() ? result : -result;
@@ -371,10 +371,14 @@ Value Endgame<KFsPsK>::operator()(const Position& pos) const {
       Bitboard b = pos.pieces(strongSide, PAWN);
       while (b && (!dark || !light))
       {
-          if (file_of(pop_lsb(b)) % 2 != relative_rank(strongSide, pos.promotion_rank(), pos.max_rank()) % 2)
-              light = true;
-          else
-              dark = true;
+          Square s = pos.promotion_square(strongSide, pop_lsb(b));
+          if (s != SQ_NONE)
+          {
+              if (DarkSquares & s)
+                  dark = true;
+              else
+                  light = true;
+          }
       }
       if (!dark || !light)
           return VALUE_DRAW; // we can not checkmate with same colored ferzes
@@ -933,9 +937,9 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
 
   // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
   // it's probably at least a draw even with the pawn.
-  if (   pos.promotion_rank() != RANK_8
+  if (   pos.promotion_zone(us) != rank_bb(relative_rank(us, RANK_8, pos.max_rank()))
       || RANK_MAX != RANK_8
-      || pos.promotion_piece_types().find(QUEEN) == pos.promotion_piece_types().end())
+      || pos.promotion_piece_types(us).find(QUEEN) == pos.promotion_piece_types(us).end())
       return SCALE_FACTOR_NONE;
 
   return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
index b22e0b2..24ca187 100644 (file)
@@ -494,7 +494,7 @@ namespace {
         // Piece promotion bonus
         if (pos.promoted_piece_type(Pt) != NO_PIECE_TYPE)
         {
-            Bitboard zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank());
+            Bitboard zone = pos.promotion_zone(Us);
             if (zone & (b | s))
                 score += make_score(PieceValue[MG][pos.promoted_piece_type(Pt)] - PieceValue[MG][Pt],
                                     PieceValue[EG][pos.promoted_piece_type(Pt)] - PieceValue[EG][Pt]) / (zone & s && b ? 6 : 12);
@@ -734,7 +734,7 @@ namespace {
             if (pos.promoted_piece_type(pt))
             {
                 otherChecks = attacks_bb(Us, pos.promoted_piece_type(pt), ksq, pos.pieces()) & attackedBy[Them][pt]
-                                 & zone_bb(Them, pos.promotion_rank(), pos.max_rank()) & pos.board_bb();
+                                 & pos.promotion_zone(Them) & pos.board_bb();
                 if (otherChecks & safe)
                     kingDanger += SafeCheck[FAIRY_PIECES][more_than_one(otherChecks & safe)];
                 else
@@ -1016,7 +1016,7 @@ namespace {
 
         assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
 
-        int r = std::max(RANK_8 - std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 0), 0);
+        int r = std::max(RANK_8 - std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 0), 0);
 
         Score bonus = PassedRank[r];
 
@@ -1067,7 +1067,7 @@ namespace {
 
     // Scale by maximum promotion piece value
     Value maxMg = VALUE_ZERO, maxEg = VALUE_ZERO;
-    for (PieceType pt : pos.promotion_piece_types())
+    for (PieceType pt : pos.promotion_piece_types(Us))
     {
         maxMg = std::max(maxMg, PieceValue[MG][pt]);
         maxEg = std::max(maxEg, PieceValue[EG][pt]);
@@ -1083,11 +1083,11 @@ namespace {
         while (b)
         {
             Square s = pop_lsb(b);
-            if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || relative_rank(Us, s, pos.max_rank()) == pos.max_rank())
+            if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || pos.promotion_square(Us, s) == SQ_NONE)
                 continue;
 
             Square blockSq = s + Up;
-            int d = 2 * std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 1);
+            int d = 2 * std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 1);
             d += !!(attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us] & blockSq);
             score += make_score(PieceValue[MG][pt], PieceValue[EG][pt]) / (d * d);
         }
@@ -1111,7 +1111,7 @@ namespace {
     bool pawnsOnly = !(pos.pieces(Us) ^ pos.pieces(Us, PAWN));
 
     // Early exit if, for example, both queens or 6 minor pieces have been exchanged
-    if (pos.non_pawn_material() < SpaceThreshold && !pawnsOnly && pos.double_step_enabled())
+    if (pos.non_pawn_material() < SpaceThreshold && !pawnsOnly && pos.double_step_region(Us))
         return SCORE_ZERO;
 
     constexpr Color Them     = ~Us;
@@ -1194,7 +1194,7 @@ namespace {
             {
                 Square s = pop_lsb(current);
                 Bitboard attacks = (  (PseudoAttacks[Us][ptCtf][s] & pos.pieces())
-                                    | (PseudoMoves[Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb();
+                                    | (PseudoMoves[0][Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb();
                 ctfPieces |= attacks & ~blocked;
                 onHold |= attacks & ~doubleBlocked;
                 onHold2 |= attacks & ~inaccessible;
@@ -1345,7 +1345,8 @@ namespace {
 
     // Compute the initiative bonus for the attacking side
     complexity =       9 * pe->passed_count()
-                    + 12 * pos.count<PAWN>()
+                    + 12 * pos.count(WHITE, pos.promotion_pawn_type(WHITE)) * bool(pos.promotion_pawn_type(WHITE))
+                    + 12 * pos.count(BLACK, pos.promotion_pawn_type(BLACK)) * bool(pos.promotion_pawn_type(BLACK))
                     + 15 * pos.count<SOLDIER>()
                     +  9 * outflanking
                     + 21 * pawnsOnBothFlanks
index 7fb2d1b..ff1453d 100644 (file)
@@ -635,6 +635,128 @@ namespace Stockfish {
       B(0xA00002102810020, 0xB1080240015408),
       B(0x810080200806, 0x410440804080046),
   };
+  constexpr Bitboard LameDabbabaMagicInit[SQUARE_NB] = {
+      B(0x3C080482A592000C, 0x540104000020000),
+      B(0x2802C40008000420, 0x4A00000001818009),
+      B(0x1083040280804000, 0x120004C20100880),
+      B(0x6840940880000892, 0x2014A01080800C2),
+      B(0x8401489004000180, 0x2000800000400000),
+      B(0x820161C800000110, 0x8000100000204020),
+      B(0x610011A122000109, 0x1000004020008004),
+      B(0x83282004023000, 0xE000020004848446),
+      B(0x6840940880000892, 0x2014A01080800C2),
+      B(0x4020120800800002, 0x88008000010020),
+      B(0x30025B140A1000, 0x3141801401000040),
+      B(0x41104D1810100050, 0x8141002010910),
+      B(0x4200828A298400, 0x400340001040C000),
+      B(0x8016A4900110040, 0x844812001068020),
+      B(0x2250035820400A2, 0x8012010080900),
+      B(0x820080083A009000, 0x880404091080110),
+      B(0x80401500AF0020, 0x240000082201A04),
+      B(0x668020020C081005, 0x4008001004100021),
+      B(0x240100910000000, 0x82000A0030454000),
+      B(0xA24091400008, 0x200014880004A921),
+      B(0x840110042200410, 0x100080000A400000),
+      B(0x40024024102000, 0x1000000002180404),
+      B(0x92828423000530, 0x118800020110),
+      B(0x1122404A1C90A8, 0x822040280020D00),
+      B(0x41201A40900A000, 0x80C0480040605100),
+      B(0x2504A85005488280, 0x3028112120022800),
+      B(0x210180080626B048, 0x8000401000014000),
+      B(0x1000410401040200, 0x41014000050C0106),
+      B(0x1040650210802200, 0x80C0041000000),
+      B(0x4020C10110900002, 0x2140C2001050009),
+      B(0x191180092200022, 0x6010008400400800),
+      B(0x8010821088080202, 0xCA240011008208),
+      B(0x8C0488120024214, 0x8414880202291),
+      B(0x8C0488120024214, 0x8414880202291),
+      B(0x22080C8A0161401, 0x200C10004C002002),
+      B(0x8430818023034080, 0x210090800000801),
+      B(0x4845087008200, 0x40661480000),
+      B(0x1202804428812050, 0x100022038020000),
+      B(0x400016001201080, 0x24002200402060),
+      B(0x680E041300800800, 0xE00130080004000),
+      B(0x3409080200, 0x282840210000000),
+      B(0x803310108400, 0x85200000080100A0),
+      B(0xE180008A04162104, 0x9088240412404),
+      B(0x20080100920020, 0x2002248010242052),
+      B(0x8A000400C2410, 0x1000024086014300),
+      B(0x1821040024663, 0x100000100010009),
+      B(0x4000822310611, 0x120280406014008),
+      B(0x1004008010818D08, 0x800000141892000),
+      B(0x8010800004024042, 0x44B106008800896),
+      B(0xA0063423444, 0x41002C15811008),
+      B(0x2040012381001282, 0x4804080104A4000),
+      B(0x10840101820880, 0xA800008000020020),
+      B(0x10840101820880, 0xA800008000020020),
+      B(0x60201D8300408190, 0x2010020920200000),
+      B(0x4048100200090090, 0x2008090100000900),
+      B(0x24200000280210, 0xD440050008004000),
+      B(0x1280001000580020, 0x2200040089000A4),
+      B(0x10208018C1020A20, 0x84C0432240610014),
+      B(0x10208018C1020A20, 0x84C0432240610014),
+      B(0x4108000010209089, 0x913000000024840),
+      B(0x410C208008008E02, 0xE8000000000001),
+      B(0x802208004005, 0x94206000022080),
+      B(0xC00290018902002, 0x4204100000000000),
+      B(0x2102801400093816, 0x9810004001000202),
+      B(0x8008304000015800, 0x4A5C000000020000),
+      B(0x1020108380800514, 0x1144210000000080),
+      B(0xC0001000008090, 0x2812060000204000),
+      B(0x1001100200003100, 0x246240060A004004),
+      B(0xA00020A008002030, 0x2440C40000110B00),
+      B(0x80502104000C008, 0x8222200042100010),
+      B(0xC020200088014, 0x422094000000480),
+      B(0x1029002000001030, 0x8105841120000210),
+      B(0x49040D, 0x2310808A14042C0),
+      B(0x200040200080A02C, 0xB890290400080000),
+      B(0x2240180C0800002, 0x4151050280000100),
+      B(0x2240180C0800002, 0x4151050280000100),
+      B(0x8220224180420006, 0x4024501212011000),
+      B(0x1806810A0881000, 0x802002048400080),
+      B(0x400400A080842, 0x9305000401180000),
+      B(0x10008001444110, 0x4420401040041833),
+      B(0x2000002C02010E00, 0x400408D08009804),
+      B(0x69D008200020100, 0x100842240049021),
+      B(0x42C24450020000, 0xD38400880090884),
+      B(0x485800800100001, 0x2484086522018840),
+      B(0x900200020820042, 0x22302421400040C0),
+      B(0x50B0413001818000, 0x452014040800C40),
+      B(0x8004040021008, 0x20088A08000290),
+      B(0x600C000801000004, 0x8015084010200020),
+      B(0x208000C00, 0xE004804021100100),
+      B(0x20001000040204, 0x948110C0B2081),
+      B(0x268502400100021, 0x80A201840802080),
+      B(0x408C000008, 0x8822102408014),
+      B(0x1182080410100000, 0x608002046A0100),
+      B(0x100820A083C00002, 0x3100100410A00),
+      B(0x8401040000400124, 0x2000081288202200),
+      B(0xB014040003000800, 0x11960D1101210),
+      B(0x10040001900C000, 0x85603C1001280),
+      B(0x2000844000000100, 0x2000024C60800800),
+      B(0x120004234800900, 0x210010841040),
+      B(0x8010300040000002, 0x4200008222104100),
+      B(0x1000120402200100, 0x209080CC040108B4),
+      B(0x110049A00000800, 0x80000420022180A8),
+      B(0x80001C00080384, 0x1400101111081001),
+      B(0x8011200008100428, 0x2020000880800922),
+      B(0x10001000000204C8, 0x280C11104240),
+      B(0x50100C82C000500, 0x28000280618DD1),
+      B(0x8800498020000, 0x20500A0200320128),
+      B(0x20010104000860, 0x8021720186008),
+      B(0x4000000000100080, 0x35040084270C04),
+      B(0x4500080000800, 0x280100002482C842),
+      B(0x10400000000000, 0x20080051100130C2),
+      B(0x10400000000000, 0x20080051100130C2),
+      B(0x2000002110202014, 0x121004004004681),
+      B(0x400202001006D40, 0x82240082202424),
+      B(0x4500080000800, 0x280100002482C842),
+      B(0xC6000000D00804, 0x1050020C0081090C),
+      B(0x200080000000042, 0x10800661),
+      B(0x2000001011200200, 0x2A420000802A0222),
+      B(0x802020001202412, 0x2400404148426),
+      B(0x8000440801040002, 0x444002800010052A),
+  };
   constexpr Bitboard HorseMagicInit[SQUARE_NB] = {
       B(0x3C080482A592000C, 0x540104000020000),
       B(0x2802C40008000420, 0x4A00000001818009),
index 2a16c55..e806093 100644 (file)
@@ -69,8 +69,8 @@ namespace {
 
   // Helper used to detect a given material distribution
   bool is_KFsPsK(const Position& pos, Color us) {
-    return    pos.promotion_piece_types().size() == 1
-          &&  pos.promotion_piece_types().find(FERS) != pos.promotion_piece_types().end()
+    return    pos.promotion_piece_types(us).size() == 1
+          &&  pos.promotion_piece_types(us).find(FERS) != pos.promotion_piece_types(us).end()
           && !more_than_one(pos.pieces(~us))
           && (pos.count<FERS>(us) || pos.count<PAWN>(us))
           && !(pos.count<ALL_PIECES>(us) - pos.count<FERS>(us) - pos.count<PAWN>(us) - pos.count<KING>(us));
index c17becb..e713f8e 100644 (file)
@@ -40,7 +40,7 @@ namespace {
             b ^= square_bb(to) ^ kto ^ rto;
         }
         if (T == EN_PASSANT)
-            b ^= to - pawn_push(us);
+            b ^= pos.capture_square(to);
         if (pos.variant()->arrowGating)
             b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from);
         if (pos.variant()->staticGating)
@@ -73,7 +73,7 @@ namespace {
 
     if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
     {
-        for (PieceType pt : pos.promotion_piece_types())
+        for (PieceType pt : pos.promotion_piece_types(c))
             if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt))
                 moveList = make_move_and_gating<PROMOTION>(pos, moveList, pos.side_to_move(), to - D, to, pt);
         PieceType pt = pos.promoted_piece_type(PAWN);
@@ -114,48 +114,62 @@ namespace {
   template<Color Us, GenType Type>
   ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
 
+    if (!pos.pieces(Us, PAWN))
+        return moveList;
+
     constexpr Color     Them     = ~Us;
     constexpr Direction Up       = pawn_push(Us);
-    constexpr Direction Down     = -pawn_push(Us);
     constexpr Direction UpRight  = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
     constexpr Direction UpLeft   = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
 
-    Bitboard TRank8BB = pos.sittuyin_promotion() ? Bitboard(0) : zone_bb(Us, pos.promotion_rank(), pos.max_rank());
-    Bitboard TRank7BB = shift<Down>(TRank8BB);
-    // Define squares a pawn can pass during a double step
-    Bitboard  TRank3BB =  forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_min(), pos.max_rank()))
-                        & ~shift<Up>(forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_max(), pos.max_rank())));
+    const Bitboard promotionZone = pos.promotion_zone(Us);
+    const Bitboard standardPromotionZone = pos.sittuyin_promotion() ? Bitboard(0) : promotionZone;
+    const Bitboard doubleStepRegion = pos.double_step_region(Us);
+    const Bitboard tripleStepRegion = pos.triple_step_region(Us);
 
-    const Bitboard emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces()) & pos.board_bb(Us, PAWN);
-    const Bitboard enemies      = (Type == EVASIONS ? (pos.checkers() & pos.non_sliding_riders() ? pos.pieces(Them) : pos.checkers())
-                                 : Type == CAPTURES ? target : pos.pieces(Them)) & pos.board_bb(Us, PAWN);
+    const Bitboard pawns      = pos.pieces(Us, PAWN);
+    const Bitboard movable    = pos.board_bb(Us, PAWN) & ~pos.pieces();
+    const Bitboard capturable = pos.board_bb(Us, PAWN) &  pos.pieces(Them);
 
-    Bitboard pawnsOn7    = pos.pieces(Us, PAWN) &  TRank7BB;
-    Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & (pos.mandatory_pawn_promotion() ? ~TRank7BB : AllSquares);
+    target = Type == EVASIONS ? target : AllSquares;
 
-    // Single and double pawn pushes, no promotions
-    if (Type != CAPTURES)
-    {
-        Bitboard b1 = shift<Up>(pawnsNotOn7)   & emptySquares;
-        Bitboard b2 = pos.double_step_enabled() ? shift<Up>(b1 & TRank3BB) & emptySquares : Bitboard(0);
+    // Define single and double push, left and right capture, as well as respective promotion moves
+    Bitboard b1 = shift<Up>(pawns) & movable & target;
+    Bitboard b2 = shift<Up>(shift<Up>(pawns & doubleStepRegion) & movable) & movable & target;
+    Bitboard b3 = shift<Up>(shift<Up>(shift<Up>(pawns & tripleStepRegion) & movable) & movable) & movable & target;
+    Bitboard brc = shift<UpRight>(pawns) & capturable & target;
+    Bitboard blc = shift<UpLeft >(pawns) & capturable & target;
 
-        if (Type == EVASIONS) // Consider only blocking squares
-        {
-            b1 &= target;
-            b2 &= target;
-        }
+    Bitboard b1p = b1 & standardPromotionZone;
+    Bitboard b2p = b2 & standardPromotionZone;
+    Bitboard b3p = b3 & standardPromotionZone;
+    Bitboard brcp = brc & standardPromotionZone;
+    Bitboard blcp = blc & standardPromotionZone;
 
-        if (Type == QUIET_CHECKS && pos.count<KING>(Them))
-        {
-            // To make a quiet check, you either make a direct check by pushing a pawn
-            // or push a blocker pawn that is not on the same file as the enemy king.
-            // Discovered check promotion has been already generated amongst the captures.
-            Square ksq = pos.square<KING>(Them);
-            Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
-            b1 &= pawn_attacks_bb(Them, ksq) | shift<   Up>(dcCandidatePawns);
-            b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
-        }
+    // Restrict regions based on rules and move generation type
+    if (pos.mandatory_pawn_promotion())
+    {
+        b1 &= ~standardPromotionZone;
+        b2 &= ~standardPromotionZone;
+        b3 &= ~standardPromotionZone;
+        brc &= ~standardPromotionZone;
+        blc &= ~standardPromotionZone;
+    }
 
+    if (Type == QUIET_CHECKS && pos.count<KING>(Them))
+    {
+        // To make a quiet check, you either make a direct check by pushing a pawn
+        // or push a blocker pawn that is not on the same file as the enemy king.
+        // Discovered check promotion has been already generated amongst the captures.
+        Square ksq = pos.square<KING>(Them);
+        Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
+        b1 &= pawn_attacks_bb(Them, ksq) | shift<   Up>(dcCandidatePawns);
+        b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
+    }
+
+    // Single and double pawn pushes, no promotions
+    if (Type != CAPTURES)
+    {
         while (b1)
         {
             Square to = pop_lsb(b1);
@@ -167,48 +181,43 @@ namespace {
             Square to = pop_lsb(b2);
             moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - Up - Up, to);
         }
+
+        while (b3)
+        {
+            Square to = pop_lsb(b3);
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - Up - Up - Up, to);
+        }
     }
 
     // Promotions and underpromotions
-    if (pawnsOn7)
-    {
-        Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
-        Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
-        Bitboard b3 = shift<Up     >(pawnsOn7) & emptySquares;
+    while (brcp)
+        moveList = make_promotions<Us, Type, UpRight>(pos, moveList, pop_lsb(brcp));
 
-        if (Type == EVASIONS)
-            b3 &= target;
+    while (blcp)
+        moveList = make_promotions<Us, Type, UpLeft >(pos, moveList, pop_lsb(blcp));
 
-        while (b1)
-            moveList = make_promotions<Us, Type, UpRight>(pos, moveList, pop_lsb(b1));
+    while (b1p)
+        moveList = make_promotions<Us, Type, Up     >(pos, moveList, pop_lsb(b1p));
 
-        while (b2)
-            moveList = make_promotions<Us, Type, UpLeft >(pos, moveList, pop_lsb(b2));
+    while (b2p)
+        moveList = make_promotions<Us, Type, Up+Up  >(pos, moveList, pop_lsb(b2p));
 
-        while (b3)
-            moveList = make_promotions<Us, Type, Up     >(pos, moveList, pop_lsb(b3));
-    }
+    while (b3p)
+        moveList = make_promotions<Us, Type, Up+Up+Up>(pos, moveList, pop_lsb(b3p));
 
     // Sittuyin promotions
     if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS))
     {
-        Bitboard pawns = pos.pieces(Us, PAWN);
-        // Pawns need to be on diagonals on opponent's half if there is more than one pawn
-        if (pos.count<PAWN>(Us) > 1)
-            pawns &=  (  PseudoAttacks[Us][BISHOP][make_square(FILE_A, relative_rank(Us, RANK_1, pos.max_rank()))]
-                       | PseudoAttacks[Us][BISHOP][make_square(pos.max_file(), relative_rank(Us, RANK_1, pos.max_rank()))])
-                    & forward_ranks_bb(Us, relative_rank(Us, Rank((pos.max_rank() - 1) / 2), pos.max_rank()));
-        while (pawns)
+        // Pawns need to be in promotion zone if there is more than one pawn
+        Bitboard promotionPawns = pos.count<PAWN>(Us) > 1 ? pawns & promotionZone : pawns;
+        while (promotionPawns)
         {
-            Square from = pop_lsb(pawns);
-            for (PieceType pt : pos.promotion_piece_types())
+            Square from = pop_lsb(promotionPawns);
+            for (PieceType pt : pos.promotion_piece_types(Us))
             {
                 if (pos.promotion_limit(pt) && pos.promotion_limit(pt) <= pos.count(Us, pt))
                     continue;
-                Bitboard b = (pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from;
-                if (Type == EVASIONS)
-                    b &= target;
-
+                Bitboard b = ((pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from) & target;
                 while (b)
                 {
                     Square to = pop_lsb(b);
@@ -222,35 +231,32 @@ namespace {
     // Standard and en passant captures
     if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
     {
-        Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
-        Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
-
-        while (b1)
+        while (brc)
         {
-            Square to = pop_lsb(b1);
+            Square to = pop_lsb(brc);
             moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - UpRight, to);
         }
 
-        while (b2)
+        while (blc)
         {
-            Square to = pop_lsb(b2);
+            Square to = pop_lsb(blc);
             moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - UpLeft, to);
         }
 
-        if (pos.ep_square() != SQ_NONE)
+        for (Bitboard epSquares = pos.ep_squares() & ~pos.pieces(); epSquares; )
         {
-            assert(relative_rank(Them, rank_of(pos.ep_square()), pos.max_rank()) <= Rank(pos.double_step_rank_max() + 1));
+            Square epSquare = pop_lsb(epSquares);
 
-            // An en passant capture cannot resolve a discovered check
-            if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
+            // An en passant capture cannot resolve a discovered check (unless there non-sliding riders)
+            if (Type == EVASIONS && (target & (epSquare + Up)) && !pos.non_sliding_riders())
                 return moveList;
 
-            b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
+            Bitboard b = pawns & pawn_attacks_bb(Them, epSquare);
 
-            assert(b1);
+            assert(b);
 
-            while (b1)
-                moveList = make_move_and_gating<EN_PASSANT>(pos, moveList, Us, pop_lsb(b1), pos.ep_square());
+            while (b)
+                moveList = make_move_and_gating<EN_PASSANT>(pos, moveList, Us, pop_lsb(b), epSquare);
         }
     }
 
@@ -258,7 +264,7 @@ namespace {
   }
 
 
-  template<Color Us, bool Checks>
+  template<Color Us, GenType Type>
   ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard target) {
 
     assert(Pt != KING && Pt != PAWN);
@@ -269,16 +275,25 @@ namespace {
     {
         Square from = pop_lsb(bb);
 
-        Bitboard b1 = (  (pos.attacks_from(Us, Pt, from) & pos.pieces())
-                       | (pos.moves_from(Us, Pt, from) & ~pos.pieces())) & target;
+        Bitboard attacks = pos.attacks_from(Us, Pt, from);
+        Bitboard quiets = pos.moves_from(Us, Pt, from);
+        Bitboard b = (  (attacks & pos.pieces())
+                       | (quiets & ~pos.pieces()));
+        Bitboard b1 = b & target;
+        Bitboard promotion_zone = pos.promotion_zone(Us);
         PieceType promPt = pos.promoted_piece_type(Pt);
         Bitboard b2 = promPt && (!pos.promotion_limit(promPt) || pos.promotion_limit(promPt) > pos.count(Us, promPt)) ? b1 : Bitboard(0);
         Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0);
+        Bitboard pawnPromotions = pos.variant()->promotionPawnTypes[Us] & Pt ? b & (Type == EVASIONS ? target : ~pos.pieces(Us)) & promotion_zone : Bitboard(0);
+        Bitboard epSquares = pos.variant()->enPassantTypes[Us] & Pt ? attacks & ~quiets & pos.ep_squares() & ~pos.pieces() : Bitboard(0);
+
+        // target squares considering pawn promotions
+        if (pawnPromotions && pos.mandatory_pawn_promotion())
+            b1 &= ~pawnPromotions;
 
         // Restrict target squares considering promotion zone
         if (b2 | b3)
         {
-            Bitboard promotion_zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank());
             if (pos.mandatory_piece_promotion())
                 b1 &= (promotion_zone & from ? Bitboard(0) : ~promotion_zone) | (pos.piece_promotion_on_capture() ? ~pos.pieces() : Bitboard(0));
             // Exclude quiet promotions/demotions
@@ -295,7 +310,7 @@ namespace {
             }
         }
 
-        if (Checks)
+        if (Type == QUIET_CHECKS)
         {
             b1 &= pos.check_squares(Pt);
             if (b2)
@@ -314,6 +329,18 @@ namespace {
         // Piece demotions
         while (b3)
             *moveList++ = make<PIECE_DEMOTION>(from, pop_lsb(b3));
+
+        // Pawn-style promotions
+        if ((Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) && pawnPromotions)
+            for (PieceType ptP : pos.promotion_piece_types(Us))
+                if (!pos.promotion_limit(ptP) || pos.promotion_limit(ptP) > pos.count(Us, ptP))
+                    for (Bitboard promotions = pawnPromotions; promotions; )
+                        moveList = make_move_and_gating<PROMOTION>(pos, moveList, pos.side_to_move(), from, pop_lsb(promotions), ptP);
+
+        // En passant captures
+        if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+            while (epSquares)
+                moveList = make_move_and_gating<EN_PASSANT>(pos, moveList, Us, from, pop_lsb(epSquares));
     }
 
     return moveList;
@@ -353,7 +380,7 @@ namespace {
         moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
         for (PieceType pt : pos.piece_types())
             if (pt != PAWN && pt != KING)
-                moveList = generate_moves<Us, Checks>(pos, moveList, pt, target);
+                moveList = generate_moves<Us, Type>(pos, moveList, pt, target);
         // generate drops
         if (pos.piece_drops() && Type != CAPTURES && (pos.can_drop(Us, ALL_PIECES) || pos.two_boards()))
             for (PieceType pt : pos.piece_types())
index 28104e7..90a5406 100644 (file)
@@ -123,14 +123,28 @@ namespace {
         return !ss.fail();
     }
 
+    template <typename T> void set(PieceType pt, T& target) {
+        target.insert(pt);
+    }
+
+    template <> void set(PieceType pt, PieceType& target) {
+        target = pt;
+    }
+
+    template <> void set(PieceType pt, PieceSet& target) {
+        target |= pt;
+    }
+
 } // namespace
 
 template <bool DoCheck>
-template <class T> void VariantParser<DoCheck>::parse_attribute(const std::string& key, T& target) {
+template <bool Current, class T> bool VariantParser<DoCheck>::parse_attribute(const std::string& key, T& target) {
     const auto& it = config.find(key);
     if (it != config.end())
     {
         bool valid = set(it->second, target);
+        if (DoCheck && !Current)
+            std::cerr << key << " - Deprecated option might be removed in future version." << std::endl;
         if (DoCheck && !valid)
         {
             std::string typeName =  std::is_same<T, int>() ? "int"
@@ -146,34 +160,40 @@ template <class T> void VariantParser<DoCheck>::parse_attribute(const std::strin
                                   : typeid(T).name();
             std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl;
         }
+        return valid;
     }
+    return false;
 }
 
 template <bool DoCheck>
-void VariantParser<DoCheck>::parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar) {
+template <bool Current, class T> bool VariantParser<DoCheck>::parse_attribute(const std::string& key, T& target, std::string pieceToChar) {
     const auto& it = config.find(key);
     if (it != config.end())
     {
+        target = T();
         char token;
         size_t idx;
         std::stringstream ss(it->second);
-        if (ss >> token && (idx = token == '-' ? 0 : pieceToChar.find(toupper(token))) != std::string::npos)
-            target = PieceType(idx);
-        else if (DoCheck)
+        while (ss >> token && (idx = pieceToChar.find(toupper(token))) != std::string::npos)
+            set(PieceType(idx), target);
+        if (DoCheck && idx == std::string::npos && token != '-')
             std::cerr << key << " - Invalid piece type: " << token << std::endl;
+        return idx != std::string::npos || token == '-';
     }
+    return false;
 }
 
 template <bool DoCheck>
 Variant* VariantParser<DoCheck>::parse() {
     Variant* v = new Variant();
     v->reset_pieces();
-    v->promotionPieceTypes = {};
     return parse(v);
 }
 
 template <bool DoCheck>
 Variant* VariantParser<DoCheck>::parse(Variant* v) {
+    parse_attribute("maxRank", v->maxRank);
+    parse_attribute("maxFile", v->maxFile);
     // piece types
     for (PieceType pt = PAWN; pt <= KING; ++pt)
     {
@@ -199,7 +219,15 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
             if (is_custom(pt))
             {
                 if (keyValue->second.size() > 1)
+                {
                     v->customPiece[pt - CUSTOM_PIECES] = keyValue->second.substr(2);
+                    // Is there an en passant flag in the Betza notation?
+                    if (v->customPiece[pt - CUSTOM_PIECES].find('e') != std::string::npos)
+                    {
+                        v->enPassantTypes[WHITE] |= piece_set(pt);
+                        v->enPassantTypes[BLACK] |= piece_set(pt);
+                    }
+                }
                 else if (DoCheck)
                     std::cerr << name << " - Missing Betza move notation" << std::endl;
             }
@@ -242,28 +270,58 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
                 std::cerr << optionName << " - Invalid piece value for type: " << v->pieceToChar[idx] << std::endl;
         }
     }
+
+    // Parse deprecate values for backwards compatibility
+    Rank promotionRank = RANK_8;
+    if (parse_attribute<false>("promotionRank", promotionRank))
+    {
+        for (Color c : {WHITE, BLACK})
+            v->promotionRegion[c] = zone_bb(c, promotionRank, v->maxRank);
+    }
+    Rank doubleStepRank = RANK_2;
+    Rank doubleStepRankMin = RANK_2;
+    if (   parse_attribute<false>("doubleStepRank", doubleStepRank)
+        || parse_attribute<false>("doubleStepRankMin", doubleStepRankMin))
+    {
+        for (Color c : {WHITE, BLACK})
+            v->doubleStepRegion[c] =   zone_bb(c, doubleStepRankMin, v->maxRank)
+                                    & ~forward_ranks_bb(c, relative_rank(c, doubleStepRank, v->maxRank));
+    }
+    parse_attribute<false>("whiteFlag", v->flagRegion[WHITE]);
+    parse_attribute<false>("blackFlag", v->flagRegion[BLACK]);
+
+    // Parse aliases
+    parse_attribute("pawnTypes", v->promotionPawnType[WHITE], v->pieceToChar);
+    parse_attribute("pawnTypes", v->promotionPawnType[BLACK], v->pieceToChar);
+    parse_attribute("pawnTypes", v->promotionPawnTypes[WHITE], v->pieceToChar);
+    parse_attribute("pawnTypes", v->promotionPawnTypes[BLACK], v->pieceToChar);
+    parse_attribute("pawnTypes", v->enPassantTypes[WHITE], v->pieceToChar);
+    parse_attribute("pawnTypes", v->enPassantTypes[BLACK], v->pieceToChar);
+    parse_attribute("pawnTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar);
+    parse_attribute("pawnTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar);
+
+    // Parse the official config options
     parse_attribute("variantTemplate", v->variantTemplate);
     parse_attribute("pieceToCharTable", v->pieceToCharTable);
     parse_attribute("pocketSize", v->pocketSize);
-    parse_attribute("maxRank", v->maxRank);
-    parse_attribute("maxFile", v->maxFile);
     parse_attribute("chess960", v->chess960);
     parse_attribute("twoBoards", v->twoBoards);
     parse_attribute("startFen", v->startFen);
-    parse_attribute("promotionRank", v->promotionRank);
-    // promotion piece types
-    const auto& it_prom = config.find("promotionPieceTypes");
-    if (it_prom != config.end())
-    {
-        v->promotionPieceTypes = {};
-        char token;
-        size_t idx = 0;
-        std::stringstream ss(it_prom->second);
-        while (ss >> token && ((idx = v->pieceToChar.find(toupper(token))) != std::string::npos))
-            v->promotionPieceTypes.insert(PieceType(idx));
-        if (DoCheck && idx == std::string::npos && token != '-')
-            std::cerr << "promotionPieceTypes - Invalid piece type: " << token << std::endl;
-    }
+    parse_attribute("promotionRegionWhite", v->promotionRegion[WHITE]);
+    parse_attribute("promotionRegionBlack", v->promotionRegion[BLACK]);
+    // Take the first promotionPawnTypes as the main promotionPawnType
+    parse_attribute("promotionPawnTypes", v->promotionPawnType[WHITE], v->pieceToChar);
+    parse_attribute("promotionPawnTypes", v->promotionPawnType[BLACK], v->pieceToChar);
+    parse_attribute("promotionPawnTypes", v->promotionPawnTypes[WHITE], v->pieceToChar);
+    parse_attribute("promotionPawnTypes", v->promotionPawnTypes[BLACK], v->pieceToChar);
+    parse_attribute("promotionPawnTypesWhite", v->promotionPawnType[WHITE], v->pieceToChar);
+    parse_attribute("promotionPawnTypesBlack", v->promotionPawnType[BLACK], v->pieceToChar);
+    parse_attribute("promotionPawnTypesWhite", v->promotionPawnTypes[WHITE], v->pieceToChar);
+    parse_attribute("promotionPawnTypesBlack", v->promotionPawnTypes[BLACK], v->pieceToChar);
+    parse_attribute("promotionPieceTypes", v->promotionPieceTypes[WHITE], v->pieceToChar);
+    parse_attribute("promotionPieceTypes", v->promotionPieceTypes[BLACK], v->pieceToChar);
+    parse_attribute("promotionPieceTypesWhite", v->promotionPieceTypes[WHITE], v->pieceToChar);
+    parse_attribute("promotionPieceTypesBlack", v->promotionPieceTypes[BLACK], v->pieceToChar);
     parse_attribute("sittuyinPromotion", v->sittuyinPromotion);
     // promotion limit
     const auto& it_prom_limit = config.find("promotionLimit");
@@ -299,9 +357,15 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("blastOnCapture", v->blastOnCapture);
     parse_attribute("petrifyOnCapture", v->petrifyOnCapture);
     parse_attribute("doubleStep", v->doubleStep);
-    parse_attribute("doubleStepRank", v->doubleStepRank);
-    parse_attribute("doubleStepRankMin", v->doubleStepRankMin);
+    parse_attribute("doubleStepRegionWhite", v->doubleStepRegion[WHITE]);
+    parse_attribute("doubleStepRegionBlack", v->doubleStepRegion[BLACK]);
+    parse_attribute("tripleStepRegionWhite", v->tripleStepRegion[WHITE]);
+    parse_attribute("tripleStepRegionBlack", v->tripleStepRegion[BLACK]);
     parse_attribute("enPassantRegion", v->enPassantRegion);
+    parse_attribute("enPassantTypes", v->enPassantTypes[WHITE], v->pieceToChar);
+    parse_attribute("enPassantTypes", v->enPassantTypes[BLACK], v->pieceToChar);
+    parse_attribute("enPassantTypesWhite", v->enPassantTypes[WHITE], v->pieceToChar);
+    parse_attribute("enPassantTypesBlack", v->enPassantTypes[BLACK], v->pieceToChar);
     parse_attribute("castling", v->castling);
     parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece);
     parse_attribute("castlingKingsideFile", v->castlingKingsideFile);
@@ -347,6 +411,10 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("soldierPromotionRank", v->soldierPromotionRank);
     parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces);
     // game end
+    parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar);
+    parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar);
+    parse_attribute("nMoveRuleTypesWhite", v->nMoveRuleTypes[WHITE], v->pieceToChar);
+    parse_attribute("nMoveRuleTypesBlack", v->nMoveRuleTypes[BLACK], v->pieceToChar);
     parse_attribute("nMoveRule", v->nMoveRule);
     parse_attribute("nFoldRule", v->nFoldRule);
     parse_attribute("nFoldValue", v->nFoldValue);
@@ -363,6 +431,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("extinctionValue", v->extinctionValue);
     parse_attribute("extinctionClaim", v->extinctionClaim);
     parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal);
+    parse_attribute("dupleCheck", v->dupleCheck);
     // extinction piece types
     const auto& it_ext = config.find("extinctionPieceTypes");
     if (it_ext != config.end())
@@ -379,8 +448,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("extinctionPieceCount", v->extinctionPieceCount);
     parse_attribute("extinctionOpponentPieceCount", v->extinctionOpponentPieceCount);
     parse_attribute("flagPiece", v->flagPiece, v->pieceToChar);
-    parse_attribute("whiteFlag", v->whiteFlag);
-    parse_attribute("blackFlag", v->blackFlag);
+    parse_attribute("flagRegionWhite", v->flagRegion[WHITE]);
+    parse_attribute("flagRegionBlack", v->flagRegion[BLACK]);
     parse_attribute("flagMove", v->flagMove);
     parse_attribute("checkCounting", v->checkCounting);
     parse_attribute("connectN", v->connectN);
@@ -435,8 +504,6 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
         // Contradictory options
         if (!v->checking && v->checkCounting)
             std::cerr << "checkCounting=true requires checking=true." << std::endl;
-        if (v->doubleStep && v->doubleStepRankMin > v->doubleStepRank)
-            std::cerr << "Inconsistent settings: doubleStepRankMin > doubleStepRank." << std::endl;
         if (v->castling && v->castlingRank > v->maxRank)
             std::cerr << "Inconsistent settings: castlingRank > maxRank." << std::endl;
         if (v->castling && v->castlingQueensideFile > v->castlingKingsideFile)
@@ -460,11 +527,11 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
             if (!is_custom(v->kingType))
             {
                 const PieceInfo* pi = pieceMap.find(v->kingType)->second;
-                if (   pi->hopper[MODALITY_QUIET].size()
-                    || pi->hopper[MODALITY_CAPTURE].size()
-                    || std::any_of(pi->steps[MODALITY_CAPTURE].begin(),
-                                pi->steps[MODALITY_CAPTURE].end(),
-                                [](const std::pair<const Direction, int>& d) { return d.second; }))
+                if (   pi->hopper[0][MODALITY_QUIET].size()
+                    || pi->hopper[0][MODALITY_CAPTURE].size()
+                    || std::any_of(pi->steps[0][MODALITY_CAPTURE].begin(),
+                                   pi->steps[0][MODALITY_CAPTURE].end(),
+                                   [](const std::pair<const Direction, int>& d) { return d.second; }))
                     std::cerr << piece_name(v->kingType) << " is not supported as kingType." << std::endl;
             }
         }
index dd81647..36b208e 100644 (file)
@@ -50,8 +50,8 @@ public:
 
 private:
     Config config;
-    template <class T> void parse_attribute(const std::string& key, T& target);
-    void parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar);
+    template <bool Current = true, class T> bool parse_attribute(const std::string& key, T& target);
+    template <bool Current = true, class T> bool parse_attribute(const std::string& key, T& target, std::string pieceToChar);
 };
 
 } // namespace Stockfish
index e4d6052..00a533e 100644 (file)
@@ -165,7 +165,7 @@ namespace {
 
         // Passed pawns will be properly scored later in evaluation when we have
         // full attack info.
-        if (passed && is_ok(s + Up) && !pos.sittuyin_promotion())
+        if (passed && pos.promotion_square(Us, s) != SQ_NONE && !pos.sittuyin_promotion())
             e->passedPawns[Us] |= s;
 
         // Score this pawn
index 4562738..bdbcf42 100644 (file)
@@ -29,7 +29,7 @@ PieceMap pieceMap; // Global object
 
 
 namespace {
-  std::map<char, std::vector<std::pair<int, int>>> leaperAtoms = {
+  const std::map<char, std::vector<std::pair<int, int>>> leaperAtoms = {
       {'W', {std::make_pair(1, 0)}},
       {'F', {std::make_pair(1, 1)}},
       {'D', {std::make_pair(2, 0)}},
@@ -43,7 +43,7 @@ namespace {
       {'G', {std::make_pair(3, 3)}},
       {'K', {std::make_pair(1, 0), std::make_pair(1, 1)}},
   };
-  std::map<char, std::vector<std::pair<int, int>>> riderAtoms = {
+  const std::map<char, std::vector<std::pair<int, int>>> riderAtoms = {
       {'R', {std::make_pair(1, 0)}},
       {'B', {std::make_pair(1, 1)}},
       {'Q', {std::make_pair(1, 0), std::make_pair(1, 1)}},
@@ -60,6 +60,7 @@ namespace {
       bool hopper = false;
       bool rider = false;
       bool lame = false;
+      bool initial = false;
       int distance = 0;
       std::vector<std::string> prelimDirections = {};
       for (std::string::size_type i = 0; i < betza.size(); i++)
@@ -79,6 +80,9 @@ namespace {
           // Lame leaper
           else if (c == 'n')
               lame = true;
+          // Initial move
+          else if (c == 'i')
+              initial = true;
           // Directional modifiers
           else if (verticals.find(c) != std::string::npos || horizontals.find(c) != std::string::npos)
           {
@@ -138,9 +142,9 @@ namespace {
                   // Add moves
                   for (auto modality : moveModalities)
                   {
-                      auto& v = hopper ? p->hopper[modality]
-                               : rider ? p->slider[modality]
-                                       : p->steps[modality];
+                      auto& v = hopper ? p->hopper[initial][modality]
+                               : rider ? p->slider[initial][modality]
+                                       : p->steps[initial][modality];
                       auto has_dir = [&](std::string s) {
                         return std::find(directions.begin(), directions.end(), s) != directions.end();
                       };
@@ -167,6 +171,9 @@ namespace {
               prelimDirections.clear();
               hopper = false;
               rider = false;
+              lame = false;
+              initial = false;
+              distance = 0;
           }
       }
       return p;
index f189b91..8d14dfd 100644 (file)
@@ -34,9 +34,9 @@ enum MoveModality {MODALITY_QUIET, MODALITY_CAPTURE, MOVE_MODALITY_NB};
 struct PieceInfo {
   std::string name = "";
   std::string betza = "";
-  std::map<Direction, int> steps[MOVE_MODALITY_NB] = {};
-  std::map<Direction, int> slider[MOVE_MODALITY_NB] = {};
-  std::map<Direction, int> hopper[MOVE_MODALITY_NB] = {};
+  std::map<Direction, int> steps[2][MOVE_MODALITY_NB] = {};
+  std::map<Direction, int> slider[2][MOVE_MODALITY_NB] = {};
+  std::map<Direction, int> hopper[2][MOVE_MODALITY_NB] = {};
 };
 
 struct PieceMap : public std::map<PieceType, const PieceInfo*> {
index f9813e6..99966b3 100644 (file)
@@ -332,7 +332,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
   ss >> token;
 
   // 3-4. Skip parsing castling and en passant flags if not present
-  st->epSquare = SQ_NONE;
+  st->epSquares = 0;
   st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE;
   if (!isdigit(ss.peek()) && !sfen)
   {
@@ -404,27 +404,33 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
 
       // 4. En passant square.
       // Ignore if square is invalid or not on side to move relative rank 6.
-      else if (   ((ss >> col) && (col >= 'a' && col <= 'a' + max_file()))
-               && ((ss >> row) && (row >= '1' && row <= '1' + max_rank())))
-      {
-          st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
+      else
+          while (   ((ss >> col) && (col >= 'a' && col <= 'a' + max_file()))
+                 && ((ss >> row) && (row >= '1' && row <= '1' + max_rank())))
+          {
+              Square epSquare = make_square(File(col - 'a'), Rank(row - '1'));
 #ifdef LARGEBOARDS
-          // Consider different rank numbering in CECP
-          if (max_rank() == RANK_10 && CurrentProtocol == XBOARD)
-              st->epSquare += NORTH;
+              // Consider different rank numbering in CECP
+              if (max_rank() == RANK_10 && CurrentProtocol == XBOARD)
+                  epSquare += NORTH;
 #endif
 
-          // En passant square will be considered only if
-          // a) side to move have a pawn threatening epSquare
-          // b) there is an enemy pawn in front of epSquare
-          // c) there is no piece on epSquare or behind epSquare
-          bool enpassant;
-          enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
-                  && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
-                  && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
-          if (!enpassant)
-              st->epSquare = SQ_NONE;
-      }
+              // En passant square will be considered only if
+              // epSquare is within enPassantRegion and
+              // 1) variant has non-standard rules
+              // or
+              // 2)
+              // a) side to move have a pawn threatening epSquare
+              // b) there is an enemy pawn one or two (for triple steps) squares in front of epSquare
+              // c) there is no piece on epSquare or behind epSquare
+              if (   (var->enPassantRegion & epSquare)
+                  && (   !var->fastAttacks
+                      || (   pawn_attacks_bb(~sideToMove, epSquare) & pieces(sideToMove, PAWN)
+                          && (   (pieces(~sideToMove, PAWN) & (epSquare + pawn_push(~sideToMove)))
+                              || (pieces(~sideToMove, PAWN) & (epSquare + 2 * pawn_push(~sideToMove))))
+                          && !(pieces() & (epSquare | (epSquare + pawn_push(sideToMove)))))))
+                  st->epSquares |= epSquare;
+          }
   }
 
   // Check counter for nCheck
@@ -560,9 +566,11 @@ void Position::set_check_info(StateInfo* si) const {
   si->legalCapture = NO_VALUE;
   if (var->extinctionPseudoRoyal)
   {
+      si->pseudoRoyalCandidates = 0;
       si->pseudoRoyals = 0;
       for (PieceType pt : extinction_piece_types())
       {
+          si->pseudoRoyalCandidates |= pieces(pt);
           if (count(sideToMove, pt) <= var->extinctionPieceCount + 1)
               si->pseudoRoyals |= pieces(sideToMove, pt);
           if (count(~sideToMove, pt) <= var->extinctionPieceCount + 1)
@@ -603,8 +611,8 @@ void Position::set_state(StateInfo* si) const {
           si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc];
   }
 
-  if (si->epSquare != SQ_NONE)
-      si->key ^= Zobrist::enpassant[file_of(si->epSquare)];
+  for (Bitboard b = si->epSquares; b; )
+      si->key ^= Zobrist::enpassant[file_of(pop_lsb(b))];
 
   if (sideToMove == BLACK)
       si->key ^= Zobrist::side;
@@ -774,8 +782,15 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
   // Counting limit or ep-square
   if (st->countingLimit)
       ss << " " << counting_limit(countStarted) << " ";
+  else if (!ep_squares())
+      ss << " - ";
   else
-      ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " ");
+  {
+      ss << " ";
+      for (Bitboard b = ep_squares(); b; )
+          ss << UCI::square(*this, pop_lsb(b));
+      ss << " ";
+  }
 
   // Check count
   if (check_counting())
@@ -1039,7 +1054,7 @@ bool Position::legal(Move m) const {
   }
 
   // No legal moves from target square
-  if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(PseudoMoves[us][type_of(moved_piece(m))][to] & board_bb()))
+  if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(PseudoMoves[0][us][type_of(moved_piece(m))][to] & board_bb()))
       return false;
 
   // Illegal king passing move
@@ -1071,7 +1086,7 @@ bool Position::legal(Move m) const {
           occupied = (pieces() ^ from ^ to) | kto | rto;
       }
       if (type_of(m) == EN_PASSANT)
-          occupied &= ~square_bb(kto - pawn_push(us));
+          occupied &= ~square_bb(capture_square(kto));
       if (capture(m) && blast_on_capture())
           occupied &= ~((attacks_bb<KING>(kto) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | kto);
       Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove);
@@ -1079,7 +1094,14 @@ bool Position::legal(Move m) const {
       if (is_ok(from) && (pseudoRoyals & from))
           pseudoRoyals ^= square_bb(from) ^ kto;
       if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end())
-          pseudoRoyals |= kto;
+      {
+          if (count(sideToMove, promotion_type(m)) > extinction_piece_count())
+              // increase in count leads to loss of pseudo-royalty
+              pseudoRoyals &= ~pieces(sideToMove, promotion_type(m));
+          else
+              // promoted piece is pseudo-royal
+              pseudoRoyals |= kto;
+      }
       // Self-explosions are illegal
       if (pseudoRoyals & ~occupied)
           return false;
@@ -1093,6 +1115,26 @@ bool Position::legal(Move m) const {
                   && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto))))
                   return false;
           }
+      // Look for duple check
+      if (var->dupleCheck)
+      {
+          Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(sideToMove);
+          if (is_ok(from) && (pseudoRoyalCandidates & from))
+              pseudoRoyalCandidates ^= square_bb(from) ^ kto;
+          if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end())
+              pseudoRoyalCandidates |= kto;
+          bool allCheck = bool(pseudoRoyalCandidates);
+          while (allCheck && pseudoRoyalCandidates)
+          {
+              Square sr = pop_lsb(pseudoRoyalCandidates);
+              // Touching pseudo-royal pieces are immune
+              if (!(  !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb<KING>(sr)))
+                    && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto)))))
+                  allCheck = false;
+          }
+          if (allCheck)
+              return false;
+      }
   }
 
   // Petrifying the king is illegal
@@ -1105,12 +1147,10 @@ bool Position::legal(Move m) const {
   if (type_of(m) == EN_PASSANT && count<KING>(us))
   {
       Square ksq = square<KING>(us);
-      Square capsq = to - pawn_push(us);
+      Square capsq = capture_square(to);
       Bitboard occupied = (pieces() ^ from ^ capsq) | to;
 
-      assert(to == ep_square());
-      assert(moved_piece(m) == make_piece(us, PAWN));
-      assert(piece_on(capsq) == make_piece(~us, PAWN));
+      assert(ep_squares() & to);
       assert(piece_on(to) == NO_PIECE);
 
       return !(attackers_to(ksq, occupied, ~us) & occupied);
@@ -1225,7 +1265,7 @@ bool Position::pseudo_legal(const Move m) const {
   // Handle the case where a mandatory piece promotion/demotion is not taken
   if (    mandatory_piece_promotion()
       && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE)
-      && (zone_bb(us, promotion_rank(), max_rank()) & (SquareBB[from] | to))
+      && (promotion_zone(us) & (SquareBB[from] | to))
       && (!piece_promotion_on_capture() || capture(m)))
       return false;
 
@@ -1247,17 +1287,17 @@ bool Position::pseudo_legal(const Move m) const {
   {
       // We have already handled promotion moves, so destination
       // cannot be on the 8th/1st rank.
-      if (mandatory_pawn_promotion() && rank_of(to) == relative_rank(us, promotion_rank(), max_rank()) && !sittuyin_promotion())
+      if (mandatory_pawn_promotion() && (promotion_zone(us) & to) && !sittuyin_promotion())
           return false;
 
       if (   !(pawn_attacks_bb(us, from) & pieces(~us) & to)     // Not a capture
           && !((from + pawn_push(us) == to) && !(pieces() & to)) // Not a single push
           && !(   (from + 2 * pawn_push(us) == to)               // Not a double push
-               && (   relative_rank(us, from, max_rank()) <= double_step_rank_max()
-                   && relative_rank(us, from, max_rank()) >= double_step_rank_min())
-               && !(pieces() & to)
-               && !(pieces() & (to - pawn_push(us)))
-               && double_step_enabled()))
+               && (double_step_region(us) & from)
+               && !(pieces() & (to | (to - pawn_push(us)))))
+          && !(   (from + 3 * pawn_push(us) == to)               // Not a triple push
+               && (triple_step_region(us) & from)
+               && !(pieces() & (to | (to - pawn_push(us)) | (to - 2 * pawn_push(us))))))
           return false;
   }
   else if (!((capture(m) ? attacks_from(us, type_of(pc), from) : moves_from(us, type_of(pc), from)) & to))
@@ -1379,7 +1419,7 @@ bool Position::gives_check(Move m) const {
   // the captured pawn.
   case EN_PASSANT:
   {
-      Square capsq = make_square(file_of(to), rank_of(from));
+      Square capsq = capture_square(to);
       Bitboard b = (pieces() ^ from ^ capsq) | to;
 
       return attackers_to(square<KING>(~sideToMove), b) & pieces(sideToMove) & b;
@@ -1445,7 +1485,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   Square from = from_sq(m);
   Square to = to_sq(m);
   Piece pc = moved_piece(m);
-  Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to);
+  Piece captured = piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to);
   if (to == from)
   {
       assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass()));
@@ -1478,25 +1518,20 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   {
       Square capsq = to;
 
+      if (type_of(m) == EN_PASSANT)
+      {
+          capsq = capture_square(to);
+          st->captureSquare = capsq;
+
+          assert(st->epSquares & to);
+          assert(var->enPassantRegion & to);
+          assert(piece_on(to) == NO_PIECE);
+      }
+
       // If the captured piece is a pawn, update pawn hash key, otherwise
       // update non-pawn material.
       if (type_of(captured) == PAWN)
-      {
-          if (type_of(m) == EN_PASSANT)
-          {
-              capsq -= pawn_push(us);
-
-              assert(pc == make_piece(us, PAWN));
-              assert(to == st->epSquare);
-              assert((var->enPassantRegion & to)
-                      && relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1)
-                      && relative_rank(~us, to, max_rank()) > double_step_rank_min());
-              assert(piece_on(to) == NO_PIECE);
-              assert(piece_on(capsq) == make_piece(them, PAWN));
-          }
-
           st->pawnKey ^= Zobrist::psq[captured][capsq];
-      }
       else
           st->nonPawnMaterial[them] -= PieceValue[MG][captured];
 
@@ -1519,7 +1554,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       {
           Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured
                              : unpromotedCaptured ? ~unpromotedCaptured
-                                                  : make_piece(~color_of(captured), PAWN);
+                                                  : make_piece(~color_of(captured), promotion_pawn_type(color_of(captured)));
           add_to_hand(pieceToHand);
           k ^=  Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1]
               ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]];
@@ -1554,12 +1589,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   else
       k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
 
-  // Reset en passant square
-  if (st->epSquare != SQ_NONE)
-  {
-      k ^= Zobrist::enpassant[file_of(st->epSquare)];
-      st->epSquare = SQ_NONE;
-  }
+  // Reset en passant squares
+  while (st->epSquares)
+      k ^= Zobrist::enpassant[file_of(pop_lsb(st->epSquares))];
 
   // Update castling rights if needed
   if (type_of(m) != DROP && !is_pass(m) && st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
@@ -1663,24 +1695,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   // If the moving piece is a pawn do some special extra work
   if (type_of(pc) == PAWN)
   {
-      // Set en passant square if the moved pawn can be captured
-      if (   type_of(m) != DROP
-          && std::abs(int(to) - int(from)) == 2 * NORTH
-          && (var->enPassantRegion & (to - pawn_push(us)))
-          && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))
-          && !(wall_gating() && gating_square(m) == to - pawn_push(us)))
-      {
-          st->epSquare = to - pawn_push(us);
-          k ^= Zobrist::enpassant[file_of(st->epSquare)];
-      }
-
-      else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION)
+      if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION)
       {
           Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(PAWN));
 
-          assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion());
+          assert((promotion_zone(us) & to) || sittuyin_promotion());
           assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING);
 
+          st->promotionPawn = piece_on(to);
           remove_piece(to);
           put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE);
 
@@ -1706,18 +1728,38 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
           st->nonPawnMaterial[us] += PieceValue[MG][promotion];
       }
 
+      // Set en passant square(s) if the moved pawn can be captured
+      else if (   type_of(m) != DROP
+          && (   std::abs(int(to) - int(from)) == 2 * NORTH
+              || std::abs(int(to) - int(from)) == 3 * NORTH))
+      {
+          if (   (var->enPassantRegion & (to - pawn_push(us)))
+              && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))
+              && !(wall_gating() && gating_square(m) == to - pawn_push(us)))
+          {
+              st->epSquares |= to - pawn_push(us);
+              k ^= Zobrist::enpassant[file_of(to)];
+          }
+          if (   std::abs(int(to) - int(from)) == 3 * NORTH
+              && (var->enPassantRegion & (to - 2 * pawn_push(us)))
+              && (pawn_attacks_bb(us, to - 2 * pawn_push(us)) & pieces(them, PAWN))
+              && !(wall_gating() && gating_square(m) == to - 2 * pawn_push(us)))
+          {
+              st->epSquares |= to - 2 * pawn_push(us);
+              k ^= Zobrist::enpassant[file_of(to)];
+          }
+      }
+
       // Update pawn hash key
       st->pawnKey ^= (type_of(m) != DROP ? Zobrist::psq[pc][from] : 0) ^ Zobrist::psq[pc][to];
-
-      // Reset rule 50 draw counter
-      st->rule50 = 0;
   }
-  else if (type_of(m) == PIECE_PROMOTION)
+  else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION)
   {
-      Piece promotion = make_piece(us, promoted_piece_type(type_of(pc)));
+      Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(type_of(pc)));
 
+      st->promotionPawn = piece_on(to);
       remove_piece(to);
-      put_piece(promotion, to, true, pc);
+      put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE);
 
       if (Eval::useNNUE)
       {
@@ -1766,6 +1808,19 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       // Update material
       st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc];
   }
+  // Set en passant square(s) if the moved piece can be captured
+  else if (   type_of(m) != DROP
+           && ((PseudoMoves[1][us][type_of(pc)][from] & ~PseudoMoves[0][us][type_of(pc)][from]) & to))
+  {
+      assert(type_of(pc) != PAWN);
+      st->epSquares = between_bb(from, to) & var->enPassantRegion;
+      for (Bitboard b = st->epSquares; b; )
+          k ^= Zobrist::enpassant[file_of(pop_lsb(b))];
+  }
+
+  // Reset rule 50 draw counter
+  if (var->nMoveRuleTypes[us] & type_of(pc))
+      st->rule50 = 0;
 
   // Set capture piece
   st->capturedPiece = captured;
@@ -2014,12 +2069,13 @@ void Position::undo_move(Move m) {
 
   if (type_of(m) == PROMOTION)
   {
-      assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion());
+      assert((promotion_zone(us) & to) || sittuyin_promotion());
       assert(type_of(pc) == promotion_type(m));
       assert(type_of(pc) >= KNIGHT && type_of(pc) < KING);
+      assert(type_of(st->promotionPawn) == promotion_pawn_type(us) || !captures_to_hand());
 
       remove_piece(to);
-      pc = make_piece(us, PAWN);
+      pc = st->promotionPawn;
       put_piece(pc, to);
   }
   else if (type_of(m) == PIECE_PROMOTION)
@@ -2055,19 +2111,17 @@ void Position::undo_move(Move m) {
 
           if (type_of(m) == EN_PASSANT)
           {
-              capsq -= pawn_push(us);
+              capsq = st->captureSquare;
 
-              assert(type_of(pc) == PAWN);
-              assert(to == st->previous->epSquare);
-              assert(relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1));
+              assert(st->previous->epSquares & to);
+              assert(var->enPassantRegion & to);
               assert(piece_on(capsq) == NO_PIECE);
-              assert(st->capturedPiece == make_piece(~us, PAWN));
           }
 
           put_piece(st->capturedPiece, capsq, st->capturedpromoted, st->unpromotedCapturedPiece); // Restore the captured piece
           if (captures_to_hand())
               remove_from_hand(!drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? ~st->unpromotedCapturedPiece
-                                                                                                   : make_piece(~color_of(st->capturedPiece), PAWN))
+                                                                                                   : make_piece(~color_of(st->capturedPiece), promotion_pawn_type(us)))
                                                                     : ~st->capturedPiece);
       }
   }
@@ -2145,11 +2199,8 @@ void Position::do_null_move(StateInfo& newSt) {
   st->accumulator.computed[WHITE] = false;
   st->accumulator.computed[BLACK] = false;
 
-  if (st->epSquare != SQ_NONE)
-  {
-      st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
-      st->epSquare = SQ_NONE;
-  }
+  while (st->epSquares)
+      st->key ^= Zobrist::enpassant[file_of(pop_lsb(st->epSquares))];
 
   st->key ^= Zobrist::side;
   prefetch(TT.first_entry(key()));
@@ -2195,7 +2246,7 @@ Key Position::key_after(Move m) const {
       k ^= Zobrist::psq[captured][to];
       if (captures_to_hand())
       {
-          Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured;
+          Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), promotion_pawn_type(color_of(captured))) : ~captured;
           k ^= Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)] + 1]
               ^ Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)]];
       }
@@ -2955,8 +3006,7 @@ bool Position::pos_is_ok() const {
   if (   (sideToMove != WHITE && sideToMove != BLACK)
       || (count<KING>(WHITE) && piece_on(square<KING>(WHITE)) != make_piece(WHITE, KING))
       || (count<KING>(BLACK) && piece_on(square<KING>(BLACK)) != make_piece(BLACK, KING))
-      || (   ep_square() != SQ_NONE
-          && relative_rank(~sideToMove, ep_square(), max_rank()) > Rank(double_step_rank_max() + 1)))
+      || (ep_squares() & ~var->enPassantRegion))
       assert(0 && "pos_is_ok: Default");
 
   if (Fast)
index fbdaff7..fa8ffd6 100644 (file)
@@ -52,7 +52,7 @@ struct StateInfo {
   int    countingPly;
   int    countingLimit;
   CheckCount checksRemaining[COLOR_NB];
-  Square epSquare;
+  Bitboard epSquares;
   Square castlingKingSquare[COLOR_NB];
   Bitboard wallSquares;
   Bitboard gatesBB[COLOR_NB];
@@ -69,8 +69,11 @@ struct StateInfo {
   Bitboard   pinners[COLOR_NB];
   Bitboard   checkSquares[PIECE_TYPE_NB];
   Piece      capturedPiece;
+  Square     captureSquare; // when != to_sq, e.g., en passant
+  Piece      promotionPawn;
   Bitboard   nonSlidingRiders;
   Bitboard   flippedPieces;
+  Bitboard   pseudoRoyalCandidates;
   Bitboard   pseudoRoyals;
   OptBool    legalCapture;
   bool       capturedpromoted;
@@ -125,8 +128,10 @@ public:
   const std::set<PieceType>& piece_types() const;
   const std::string& piece_to_char() const;
   const std::string& piece_to_char_synonyms() const;
-  Rank promotion_rank() const;
-  const std::set<PieceType, std::greater<PieceType> >& promotion_piece_types() const;
+  Bitboard promotion_zone(Color c) const;
+  Square promotion_square(Color c, Square s) const;
+  PieceType promotion_pawn_type(Color c) const;
+  const std::set<PieceType, std::greater<PieceType> >& promotion_piece_types(Color c) const;
   bool sittuyin_promotion() const;
   int promotion_limit(PieceType pt) const;
   PieceType promoted_piece_type(PieceType pt) const;
@@ -136,9 +141,8 @@ public:
   bool piece_demotion() const;
   bool blast_on_capture() const;
   bool endgame_eval() const;
-  bool double_step_enabled() const;
-  Rank double_step_rank_max() const;
-  Rank double_step_rank_min() const;
+  Bitboard double_step_region(Color c) const;
+  Bitboard triple_step_region(Color c) const;
   bool castling_enabled() const;
   bool castling_dropped_piece() const;
   File castling_kingside_file() const;
@@ -220,7 +224,7 @@ public:
   Bitboard non_sliding_riders() const;
   Piece piece_on(Square s) const;
   Piece unpromoted_piece_on(Square s) const;
-  Square ep_square() const;
+  Bitboard ep_squares() const;
   Square castling_king_square(Color c) const;
   Bitboard gates(Color c) const;
   bool empty(Square s) const;
@@ -260,6 +264,7 @@ public:
   bool virtual_drop(Move m) const;
   bool capture(Move m) const;
   bool capture_or_promotion(Move m) const;
+  Square capture_square(Square to) const;
   bool gives_check(Move m) const;
   Piece moved_piece(Move m) const;
   Piece captured_piece() const;
@@ -418,14 +423,25 @@ inline const std::string& Position::piece_to_char_synonyms() const {
   return var->pieceToCharSynonyms;
 }
 
-inline Rank Position::promotion_rank() const {
+inline Bitboard Position::promotion_zone(Color c) const {
   assert(var != nullptr);
-  return var->promotionRank;
+  return var->promotionRegion[c];
 }
 
-inline const std::set<PieceType, std::greater<PieceType> >& Position::promotion_piece_types() const {
+inline Square Position::promotion_square(Color c, Square s) const {
   assert(var != nullptr);
-  return var->promotionPieceTypes;
+  Bitboard b = promotion_zone(c) & forward_file_bb(c, s) & board_bb();
+  return !b ? SQ_NONE : c == WHITE ? lsb(b) : msb(b);
+}
+
+inline PieceType Position::promotion_pawn_type(Color c) const {
+  assert(var != nullptr);
+  return var->promotionPawnType[c];
+}
+
+inline const std::set<PieceType, std::greater<PieceType> >& Position::promotion_piece_types(Color c) const {
+  assert(var != nullptr);
+  return var->promotionPieceTypes[c];
 }
 
 inline bool Position::sittuyin_promotion() const {
@@ -473,19 +489,14 @@ inline bool Position::endgame_eval() const {
   return var->endgameEval && !count_in_hand(ALL_PIECES) && count<KING>() == 2;
 }
 
-inline bool Position::double_step_enabled() const {
-  assert(var != nullptr);
-  return var->doubleStep;
-}
-
-inline Rank Position::double_step_rank_max() const {
+inline Bitboard Position::double_step_region(Color c) const {
   assert(var != nullptr);
-  return var->doubleStepRank;
+  return var->doubleStepRegion[c];
 }
 
-inline Rank Position::double_step_rank_min() const {
+inline Bitboard Position::triple_step_region(Color c) const {
   assert(var != nullptr);
-  return var->doubleStepRankMin;
+  return var->tripleStepRegion[c];
 }
 
 inline bool Position::castling_enabled() const {
@@ -643,7 +654,7 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const {
   if (pt == PAWN)
   {
       if (!var->promotionZonePawnDrops)
-          b &= ~zone_bb(c, promotion_rank(), max_rank());
+          b &= ~promotion_zone(c);
       if (!first_rank_pawn_drops())
           b &= ~rank_bb(relative_rank(c, RANK_1, max_rank()));
   }
@@ -798,6 +809,22 @@ inline Value Position::stalemate_value(int ply) const {
               && attackers_to(sr, ~sideToMove))
               return convert_mate_value(var->checkmateValue, ply);
       }
+      // Look for duple check
+      if (var->dupleCheck)
+      {
+          Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(sideToMove);
+          bool allCheck = bool(pseudoRoyalCandidates);
+          while (allCheck && pseudoRoyalCandidates)
+          {
+              Square sr = pop_lsb(pseudoRoyalCandidates);
+              // Touching pseudo-royal pieces are immune
+              if (!(  !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb<KING>(sr)))
+                    && attackers_to(sr, ~sideToMove)))
+                  allCheck = false;
+          }
+          if (allCheck)
+              return convert_mate_value(var->checkmateValue, ply);
+      }
   }
   return convert_mate_value(var->stalemateValue, ply);
 }
@@ -894,7 +921,7 @@ inline PieceType Position::capture_the_flag_piece() const {
 
 inline Bitboard Position::capture_the_flag(Color c) const {
   assert(var != nullptr);
-  return c == WHITE ? var->whiteFlag : var->blackFlag;
+  return var->flagRegion[c];
 }
 
 inline bool Position::flag_move() const {
@@ -1022,8 +1049,8 @@ inline Square Position::square(Color c, PieceType pt) const {
   return lsb(pieces(c, pt));
 }
 
-inline Square Position::ep_square() const {
-  return st->epSquare;
+inline Bitboard Position::ep_squares() const {
+  return st->epSquares;
 }
 
 inline Square Position::castling_king_square(Color c) const {
@@ -1095,6 +1122,9 @@ inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const {
 
   PieceType movePt = pt == KING ? king_type() : pt;
   Bitboard b = moves_bb(c, movePt, s, byTypeBB[ALL_PIECES]);
+  // Add initial moves
+  if (double_step_region(c) & s)
+      b |= moves_bb<true>(c, movePt, s, byTypeBB[ALL_PIECES]);
   // Xiangqi soldier
   if (pt == SOLDIER && !(promoted_soldiers(c) & s))
       b &= file_bb(file_of(s));
@@ -1233,6 +1263,13 @@ inline bool Position::capture(Move m) const {
   return (!empty(to_sq(m)) && type_of(m) != CASTLING && from_sq(m) != to_sq(m)) || type_of(m) == EN_PASSANT;
 }
 
+inline Square Position::capture_square(Square to) const {
+  assert(is_ok(to));
+  // The capture square of en passant is either the marked ep piece or the closest piece behind the target square
+  Bitboard b = ep_squares() & pieces() ? ep_squares() & pieces() : pieces(~sideToMove) & forward_file_bb(~sideToMove, to);
+  return sideToMove == WHITE ? msb(b) : lsb(b);
+}
+
 inline bool Position::virtual_drop(Move m) const {
   assert(is_ok(m));
   return type_of(m) == DROP && !can_drop(side_to_move(), in_hand_piece_type(m));
index d674edc..afa1fde 100644 (file)
@@ -156,16 +156,16 @@ constexpr Score PBonus[RANK_NB][FILE_NB] =
 Value piece_value(Phase phase, PieceType pt)
 {
     const PieceInfo* pi = pieceMap.find(pt)->second;
-    int v0 =  (phase == MG ?  55 :  60) * pi->steps[MODALITY_CAPTURE].size()
-            + (phase == MG ?  30 :  40) * pi->steps[MODALITY_QUIET].size()
-            + (phase == MG ? 185 : 180) * pi->slider[MODALITY_CAPTURE].size()
-            + (phase == MG ?  55 :  50) * pi->slider[MODALITY_QUIET].size()
+    int v0 =  (phase == MG ?  55 :  60) * pi->steps[0][MODALITY_CAPTURE].size()
+            + (phase == MG ?  30 :  40) * pi->steps[0][MODALITY_QUIET].size()
+            + (phase == MG ? 185 : 180) * pi->slider[0][MODALITY_CAPTURE].size()
+            + (phase == MG ?  55 :  50) * pi->slider[0][MODALITY_QUIET].size()
             // Hoppers are more useful with more pieces on the board
-            + (phase == MG ? 100 :  80) * pi->hopper[MODALITY_CAPTURE].size()
-            + (phase == MG ?  80 :  60) * pi->hopper[MODALITY_QUIET].size()
+            + (phase == MG ? 100 :  80) * pi->hopper[0][MODALITY_CAPTURE].size()
+            + (phase == MG ?  80 :  60) * pi->hopper[0][MODALITY_QUIET].size()
             // Rook sliding directions are more valuable, especially in endgame
-            + (phase == MG ?  10 :  30) * std::count_if(pi->slider[MODALITY_CAPTURE].begin(), pi->slider[MODALITY_CAPTURE].end(), [](const std::pair<const Direction, int>& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; })
-            + (phase == MG ?  30 :  45) * std::count_if(pi->slider[MODALITY_QUIET].begin(), pi->slider[MODALITY_QUIET].end(), [](const std::pair<const Direction, int>& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; });
+            + (phase == MG ?  10 :  30) * std::count_if(pi->slider[0][MODALITY_CAPTURE].begin(), pi->slider[0][MODALITY_CAPTURE].end(), [](const std::pair<const Direction, int>& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; })
+            + (phase == MG ?  30 :  45) * std::count_if(pi->slider[0][MODALITY_QUIET].begin(), pi->slider[0][MODALITY_QUIET].end(), [](const std::pair<const Direction, int>& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; });
     return Value(v0 * exp(double(v0) / 10000));
 }
 
@@ -196,7 +196,7 @@ void init(const Variant* v) {
   }
 
   Value maxPromotion = VALUE_ZERO;
-  for (PieceType pt : v->promotionPieceTypes)
+  for (PieceType pt : v->promotionPieceTypes[WHITE])
       maxPromotion = std::max(maxPromotion, PieceValue[EG][pt]);
 
   for (PieceType pt = PAWN; pt <= KING; ++pt)
@@ -206,7 +206,7 @@ void init(const Variant* v) {
       Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
 
       // Consider promotion types in pawn score
-      if (pt == PAWN)
+      if (pt == v->promotionPawnType[WHITE])
       {
           score -= make_score(0, (QueenValueEg - maxPromotion) / 100);
           if (v->blastOnCapture)
@@ -214,9 +214,9 @@ void init(const Variant* v) {
       }
       
       const PieceInfo* pi = pieceMap.find(pt)->second;
-      bool isSlider = pi->slider[MODALITY_QUIET].size() || pi->slider[MODALITY_CAPTURE].size() || pi->hopper[MODALITY_QUIET].size() || pi->hopper[MODALITY_CAPTURE].size();
-      bool isPawn = !isSlider && pi->steps[MODALITY_QUIET].size() && !std::any_of(pi->steps[MODALITY_QUIET].begin(), pi->steps[MODALITY_QUIET].end(), [](const std::pair<const Direction, int>& d) { return d.first < SOUTH / 2; });
-      bool isSlowLeaper = !isSlider && !std::any_of(pi->steps[MODALITY_QUIET].begin(), pi->steps[MODALITY_QUIET].end(), [](const std::pair<const Direction, int>& d) { return dist(d.first) > 1; });
+      bool isSlider = pi->slider[0][MODALITY_QUIET].size() || pi->slider[0][MODALITY_CAPTURE].size() || pi->hopper[0][MODALITY_QUIET].size() || pi->hopper[0][MODALITY_CAPTURE].size();
+      bool isPawn = !isSlider && pi->steps[0][MODALITY_QUIET].size() && !std::any_of(pi->steps[0][MODALITY_QUIET].begin(), pi->steps[0][MODALITY_QUIET].end(), [](const std::pair<const Direction, int>& d) { return d.first < SOUTH / 2; });
+      bool isSlowLeaper = !isSlider && !std::any_of(pi->steps[0][MODALITY_QUIET].begin(), pi->steps[0][MODALITY_QUIET].end(), [](const std::pair<const Direction, int>& d) { return dist(d.first) > 1; });
 
       // Scale slider piece values with board size
       if (isSlider)
@@ -225,8 +225,8 @@ void init(const Variant* v) {
           constexpr int rm = 5;
           constexpr int r0 = rm + RANK_8;
           int r1 = rm + (v->maxRank + v->maxFile - 2 * v->capturesToHand) / 2;
-          int leaper = pi->steps[MODALITY_QUIET].size() + pi->steps[MODALITY_CAPTURE].size();
-          int slider = pi->slider[MODALITY_QUIET].size() + pi->slider[MODALITY_CAPTURE].size() + pi->hopper[MODALITY_QUIET].size() + pi->hopper[MODALITY_CAPTURE].size();
+          int leaper = pi->steps[0][MODALITY_QUIET].size() + pi->steps[0][MODALITY_CAPTURE].size();
+          int slider = pi->slider[0][MODALITY_QUIET].size() + pi->slider[0][MODALITY_CAPTURE].size() + pi->hopper[0][MODALITY_QUIET].size() + pi->hopper[0][MODALITY_CAPTURE].size();
           score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider),
                              eg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider));
       }
@@ -249,7 +249,7 @@ void init(const Variant* v) {
       // Increase leapers' value in makpong
       else if (v->makpongRule)
       {
-          if (std::any_of(pi->steps[MODALITY_CAPTURE].begin(), pi->steps[MODALITY_CAPTURE].end(), [](const std::pair<const Direction, int>& d) { return dist(d.first) > 1 && !d.second; }))
+          if (std::any_of(pi->steps[0][MODALITY_CAPTURE].begin(), pi->steps[0][MODALITY_CAPTURE].end(), [](const std::pair<const Direction, int>& d) { return dist(d.first) > 1 && !d.second; }))
               score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)),
                                  eg_value(score) * 4700 / (3500 + mg_value(score)));
       }
@@ -272,7 +272,7 @@ void init(const Variant* v) {
 
       // For antichess variants, use negative piece values
       if (v->extinctionValue == VALUE_MATE)
-          score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->slider[MODALITY_CAPTURE].size()));
+          score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->slider[0][MODALITY_CAPTURE].size()));
 
       // Override variant piece value
       if (v->pieceValue[MG][pt])
index 31449cf..67ee805 100644 (file)
@@ -936,7 +936,7 @@ namespace {
         && (ss-1)->statScore < 23767
         &&  eval >= beta
         &&  eval >= ss->staticEval
-        &&  ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 + 200 * (!pos.double_step_enabled() && pos.piece_to_char()[PAWN] != ' ')
+        &&  ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 + 200 * (!pos.double_step_region(pos.side_to_move()) && pos.piece_to_char()[PAWN] != ' ')
         && !excludedMove
         &&  pos.non_pawn_material(us)
         &&  pos.count<ALL_PIECES>(~us) != pos.count<PAWN>(~us)
index 261d49e..fbf7367 100644 (file)
@@ -419,6 +419,8 @@ enum Piece {
   PIECE_NB = 2 * PIECE_TYPE_NB
 };
 
+enum PieceSet : uint64_t {};
+
 enum RiderType : int {
   NO_RIDER = 0,
   RIDER_BISHOP = 1 << 0,
@@ -426,17 +428,18 @@ enum RiderType : int {
   RIDER_ROOK_V = 1 << 2,
   RIDER_CANNON_H = 1 << 3,
   RIDER_CANNON_V = 1 << 4,
-  RIDER_HORSE = 1 << 5,
-  RIDER_ELEPHANT = 1 << 6,
-  RIDER_JANGGI_ELEPHANT = 1 << 7,
-  RIDER_CANNON_DIAG = 1 << 8,
-  RIDER_NIGHTRIDER = 1 << 9,
-  RIDER_GRASSHOPPER_H = 1 << 10,
-  RIDER_GRASSHOPPER_V = 1 << 11,
-  RIDER_GRASSHOPPER_D = 1 << 12,
+  RIDER_LAME_DABBABA = 1 << 5,
+  RIDER_HORSE = 1 << 6,
+  RIDER_ELEPHANT = 1 << 7,
+  RIDER_JANGGI_ELEPHANT = 1 << 8,
+  RIDER_CANNON_DIAG = 1 << 9,
+  RIDER_NIGHTRIDER = 1 << 10,
+  RIDER_GRASSHOPPER_H = 1 << 11,
+  RIDER_GRASSHOPPER_V = 1 << 12,
+  RIDER_GRASSHOPPER_D = 1 << 13,
   HOPPING_RIDERS =  RIDER_CANNON_H | RIDER_CANNON_V | RIDER_CANNON_DIAG
                   | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D,
-  LAME_LEAPERS = RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT,
+  LAME_LEAPERS = RIDER_LAME_DABBABA | RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT,
   ASYMMETRICAL_RIDERS =  RIDER_HORSE | RIDER_JANGGI_ELEPHANT
                        | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D,
   NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER,
@@ -622,6 +625,19 @@ ENABLE_BASE_OPERATORS_ON(RiderType)
 #undef ENABLE_BASE_OPERATORS_ON
 #undef ENABLE_BIT_OPERATORS_ON
 
+constexpr PieceSet piece_set(PieceType pt) {
+  return PieceSet(1ULL << pt);
+}
+
+constexpr PieceSet operator| (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 | (uint64_t)ps2); }
+constexpr PieceSet operator& (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 & (uint64_t)ps2); }
+constexpr PieceSet operator& (PieceSet ps, PieceType pt) { return ps & piece_set(pt); }
+inline PieceSet& operator|= (PieceSet& ps1, PieceSet ps2) { return (PieceSet&)((uint64_t&)ps1 |= (uint64_t)ps2); }
+inline PieceSet& operator|= (PieceSet& ps, PieceType pt) { return ps |= piece_set(pt); }
+
+static_assert(piece_set(PAWN) & PAWN);
+static_assert(piece_set(KING) & KING);
+
 /// Additional operators to add a Direction to a Square
 constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
 constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
index ff9f22b..2d24c0f 100644 (file)
@@ -48,7 +48,8 @@ namespace UCI {
 std::set<string> standard_variants = {
     "normal", "nocastle", "fischerandom", "knightmate", "3check", "makruk", "shatranj",
     "asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers", "atomic",
-    "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck"
+    "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck",
+    "berolina", "spartan"
 };
 
 void init_variant(const Variant* v) {
@@ -145,7 +146,7 @@ void on_variant_change(const Option &o) {
                     suffix += std::string(v->dropNoDoubledCount, 'f');
                 else if (pt == BISHOP && v->dropOppositeColoredBishop)
                     suffix += "s";
-                suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops ? v->promotionRank : v->maxRank + 1);
+                suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops && v->promotionRegion[WHITE] ? rank_of(lsb(v->promotionRegion[WHITE])) : v->maxRank + 1);
             }
             sync_cout << "piece " << v->pieceToChar[pt] << "& " << pieceMap.find(pt == KING ? v->kingType : pt)->second->betza << suffix << sync_endl;
             PieceType promType = v->promotedPieceType[pt];
index 7bf4659..cb2ea69 100644 (file)
@@ -72,6 +72,67 @@ namespace {
         v->materialCounting = BLACK_DRAW_ODDS;
         return v;
     }
+    // Torpedo Chess
+    // https://arxiv.org/abs/2009.04374
+    Variant* torpedo_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->doubleStepRegion[WHITE] = AllSquares;
+        v->doubleStepRegion[BLACK] = AllSquares;
+        return v;
+    }
+    // Berolina Chess
+    // https://www.chessvariants.com/dpieces.dir/berlin.html
+    Variant* berolina_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->remove_piece(PAWN);
+        v->add_piece(CUSTOM_PIECES, 'p', "mfFcfeWimfnA");
+        v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES;
+        v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        return v;
+    }
+    // Pawnsideways
+    // https://arxiv.org/abs/2009.04374
+    Variant* pawnsideways_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->remove_piece(PAWN);
+        v->add_piece(CUSTOM_PIECES, 'p', "fsmWfceFifmnD");
+        v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES;
+        v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        return v;
+    }
+    // Pawnback
+    // https://arxiv.org/abs/2009.04374
+    Variant* pawnback_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->remove_piece(PAWN);
+        v->add_piece(CUSTOM_PIECES, 'p', "fbmWfceFifmnD");
+        v->mobilityRegion[WHITE][CUSTOM_PIECES] = (Rank2BB | Rank3BB | Rank4BB | Rank5BB | Rank6BB | Rank7BB | Rank8BB);
+        v->mobilityRegion[BLACK][CUSTOM_PIECES] = (Rank7BB | Rank6BB | Rank5BB | Rank4BB | Rank3BB | Rank2BB | Rank1BB);
+        v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES;
+        v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        return v;
+    }
+    // Legan Chess
+    // https://en.wikipedia.org/wiki/Legan_chess
+    Variant* legan_variant() {
+        Variant* v =  chess_variant_base()->init();
+        v->remove_piece(PAWN);
+        v->add_piece(CUSTOM_PIECES, 'p', "mflFcflW");
+        v->promotionRegion[WHITE] = make_bitboard(SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_A7, SQ_A6, SQ_A5);
+        v->promotionRegion[BLACK] = make_bitboard(SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_H2, SQ_H3, SQ_H4);
+        v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = CUSTOM_PIECES;
+        v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->startFen = "knbrp3/bqpp4/npp5/rp1p3P/p3P1PR/5PPN/4PPQB/3PRBNK w - - 0 1";
+        v->doubleStep = false;
+        return v;
+    }
     // Pseudo-variant only used for endgame initialization
     Variant* fairy_variant() {
         Variant* v = chess_variant_base()->init();
@@ -81,7 +142,7 @@ namespace {
     }
       // Raazuva (Maldivian Chess)
     Variant* raazuvaa_variant() {
-        Variant* v = chess_variant()->init();
+        Variant* v = chess_variant_base()->init();
         v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1";
         v->castling = false;
         v->doubleStep = false;
@@ -98,8 +159,10 @@ namespace {
         v->add_piece(KHON, 's');
         v->add_piece(MET, 'm');
         v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1";
-        v->promotionRank = RANK_6;
-        v->promotionPieceTypes = {MET};
+        v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB;
+        v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
+        v->promotionPieceTypes[WHITE] = {MET};
+        v->promotionPieceTypes[BLACK] = {MET};
         v->doubleStep = false;
         v->castling = false;
         v->nMoveRule = 0;
@@ -143,7 +206,8 @@ namespace {
         v->add_piece(KHON, 'b');
         v->add_piece(MET, 'q');
         v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1";
-        v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET};
+        v->promotionPieceTypes[WHITE] = {ROOK, KNIGHT, KHON, MET};
+        v->promotionPieceTypes[BLACK] = {ROOK, KNIGHT, KHON, MET};
         v->doubleStep = false;
         v->castling = false;
         v->countingRule = ASEAN_COUNTING;
@@ -157,7 +221,8 @@ namespace {
         v->remove_piece(MET);
         v->add_piece(AIWOK, 'a');
         v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1";
-        v->promotionPieceTypes = {AIWOK};
+        v->promotionPieceTypes[WHITE] = {AIWOK};
+        v->promotionPieceTypes[BLACK] = {AIWOK};
         return v;
     }
     // Shatranj
@@ -172,7 +237,8 @@ namespace {
         v->add_piece(ALFIL, 'b');
         v->add_piece(FERS, 'q');
         v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1";
-        v->promotionPieceTypes = {FERS};
+        v->promotionPieceTypes[WHITE] = {FERS};
+        v->promotionPieceTypes[BLACK] = {FERS};
         v->doubleStep = false;
         v->castling = false;
         v->extinctionValue = -VALUE_MATE;
@@ -203,7 +269,8 @@ namespace {
         v->remove_piece(QUEEN);
         v->add_piece(AMAZON, 'a');
         v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1";
-        v->promotionPieceTypes = {AMAZON, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {AMAZON, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {AMAZON, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Nightrider chess
@@ -213,7 +280,8 @@ namespace {
         Variant* v = chess_variant_base()->init();
         v->remove_piece(KNIGHT);
         v->add_piece(CUSTOM_PIECES, 'n', "NN");
-        v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES};
+        v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES};
+        v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES};
         return v;
     }
     // Grasshopper chess
@@ -221,7 +289,8 @@ namespace {
     Variant* grasshopper_variant() {
         Variant* v = chess_variant_base()->init();
         v->add_piece(CUSTOM_PIECES, 'g', "gQ");
-        v->promotionPieceTypes.insert(CUSTOM_PIECES);
+        v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES);
+        v->promotionPieceTypes[BLACK].insert(CUSTOM_PIECES);
         v->startFen = "rnbqkbnr/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/RNBQKBNR w KQkq - 0 1";
         v->doubleStep = false;
         return v;
@@ -235,7 +304,8 @@ namespace {
         v->remove_piece(BISHOP);
         v->add_piece(KNIBIS, 'n');
         v->add_piece(BISKNI, 'b');
-        v->promotionPieceTypes = {QUEEN, ROOK, BISKNI, KNIBIS};
+        v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, BISKNI, KNIBIS};
+        v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISKNI, KNIBIS};
         return v;
     }
     // New Zealand
@@ -247,7 +317,8 @@ namespace {
         v->add_piece(ROOKNI, 'r');
         v->add_piece(KNIROO, 'n');
         v->castlingRookPiece = ROOKNI;
-        v->promotionPieceTypes = {QUEEN, ROOKNI, BISHOP, KNIROO};
+        v->promotionPieceTypes[WHITE] = {QUEEN, ROOKNI, BISHOP, KNIROO};
+        v->promotionPieceTypes[BLACK] = {QUEEN, ROOKNI, BISHOP, KNIROO};
         return v;
     }
     // King of the Hill
@@ -255,8 +326,8 @@ namespace {
     Variant* kingofthehill_variant() {
         Variant* v = chess_variant_base()->init();
         v->flagPiece = KING;
-        v->whiteFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB);
-        v->blackFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB);
+        v->flagRegion[WHITE] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB);
+        v->flagRegion[BLACK] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB);
         v->flagMove = false;
         return v;
     }
@@ -266,8 +337,8 @@ namespace {
         Variant* v = chess_variant_base()->init();
         v->startFen = "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1";
         v->flagPiece = KING;
-        v->whiteFlag = Rank8BB;
-        v->blackFlag = Rank8BB;
+        v->flagRegion[WHITE] = Rank8BB;
+        v->flagRegion[BLACK] = Rank8BB;
         v->flagMove = true;
         v->castling = false;
         v->checking = false;
@@ -282,7 +353,8 @@ namespace {
         v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1";
         v->kingType = KNIGHT;
         v->castlingKingPiece = KING;
-        v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP};
+        v->promotionPieceTypes[WHITE] = {COMMONER, QUEEN, ROOK, BISHOP};
+        v->promotionPieceTypes[BLACK] = {COMMONER, QUEEN, ROOK, BISHOP};
         return v;
     }
     // Losers chess
@@ -306,7 +378,8 @@ namespace {
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
         v->castlingKingPiece = COMMONER;
-        v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
         v->stalemateValue = VALUE_MATE;
         v->extinctionValue = VALUE_MATE;
         v->extinctionPieceTypes = {ALL_PIECES};
@@ -336,7 +409,8 @@ namespace {
     // http://www.binnewirtz.com/Schlagschach1.htm
     Variant* codrus_variant() {
         Variant* v = giveaway_variant()->init();
-        v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISHOP, KNIGHT};
         v->extinctionPieceTypes = {COMMONER};
         return v;
     }
@@ -347,7 +421,8 @@ namespace {
         v->remove_piece(KING);
         v->add_piece(COMMONER, 'k');
         v->castlingKingPiece = COMMONER;
-        v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT};
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT, PAWN};
         return v;
@@ -356,7 +431,8 @@ namespace {
     // https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_chess
     Variant* kinglet_variant() {
         Variant* v = extinction_variant()->init();
-        v->promotionPieceTypes = {COMMONER};
+        v->promotionPieceTypes[WHITE] = {COMMONER};
+        v->promotionPieceTypes[BLACK] = {COMMONER};
         v->extinctionPieceTypes = {PAWN};
         return v;
     }
@@ -378,7 +454,7 @@ namespace {
     Variant* horde_variant() {
         Variant* v = chess_variant_base()->init();
         v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1";
-        v->doubleStepRankMin = RANK_1;
+        v->doubleStepRegion[WHITE] |= Rank1BB;
         v->enPassantRegion = Rank3BB | Rank6BB; // exclude en passant on second rank
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = {ALL_PIECES};
@@ -418,7 +494,7 @@ namespace {
         v->stalemateValue = VALUE_MATE;
         return v;
     }
-       
+    
     Variant* isolation_variant() { //https://boardgamegeek.com/boardgame/1875/isolation
         Variant* v = chess_variant_base()->init();
         v->maxRank = RANK_8;
@@ -572,7 +648,6 @@ namespace {
         v->variantTemplate = "bughouse";
         v->pieceToCharTable = "PN.R.F....SKpn.r.f....sk";
         v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1";
-        v->remove_piece(MET);
         v->add_piece(MET, 'f');
         v->mustDrop = true;
         v->pieceDrops = true;
@@ -581,6 +656,8 @@ namespace {
         v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB;
         v->sittuyinRookDrop = true;
         v->sittuyinPromotion = true;
+        v->promotionRegion[WHITE] = make_bitboard(SQ_A8, SQ_B7, SQ_C6, SQ_D5, SQ_E5, SQ_F6, SQ_G7, SQ_H8);
+        v->promotionRegion[BLACK] = make_bitboard(SQ_A1, SQ_B2, SQ_C3, SQ_D4, SQ_E4, SQ_F3, SQ_G2, SQ_H1);
         v->promotionLimit[FERS] = 1;
         v->immobilityIllegal = false;
         v->countingRule = ASEAN_COUNTING;
@@ -599,7 +676,8 @@ namespace {
         v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1";
         v->gating = true;
         v->seirawanGating = true;
-        v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // S-House
@@ -637,7 +715,8 @@ namespace {
         Variant *v = chess_variant_base()->init();
         v->remove_piece(BISHOP);
         v->add_piece(CUSTOM_PIECES, 'b', "BnN");
-        v->promotionPieceTypes = {QUEEN, CUSTOM_PIECES, ROOK, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {QUEEN, CUSTOM_PIECES, ROOK, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {QUEEN, CUSTOM_PIECES, ROOK, KNIGHT};
         return v;
     }
     // Base used for most shogi variants
@@ -658,8 +737,8 @@ namespace {
         v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1";
         v->pieceDrops = true;
         v->capturesToHand = true;
-        v->promotionRank = RANK_5;
-        v->promotionPieceTypes = {};
+        v->promotionRegion[WHITE] = Rank5BB;
+        v->promotionRegion[BLACK] = Rank1BB;
         v->doubleStep = false;
         v->castling = false;
         v->promotedPieceType[SHOGI_PAWN] = GOLD;
@@ -695,7 +774,8 @@ namespace {
         v->add_piece(LANCE, 'l');
         v->add_piece(SHOGI_KNIGHT, 'n');
         v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1";
-        v->promotionRank = RANK_1;
+        v->promotionRegion[WHITE] = AllSquares;
+        v->promotionRegion[BLACK] = AllSquares;
         v->mandatoryPiecePromotion = true;
         v->pieceDemotion = true;
         v->dropPromoted = true;
@@ -718,7 +798,8 @@ namespace {
         Variant* v = kyotoshogi_variant()->init();
         v->maxFile = FILE_D;
         v->startFen = "kb+r+l/p3/4/3P/+L+RBK[-] w 0 1";
-        v->promotionRank = RANK_1;
+        v->promotionRegion[WHITE] = AllSquares;
+        v->promotionRegion[BLACK] = AllSquares;
         v->piecePromotionOnCapture = true;
         v->promotedPieceType[LANCE]        = SILVER;
         v->promotedPieceType[BISHOP]       = GOLD;
@@ -745,13 +826,14 @@ namespace {
         v->add_piece(WAZIR, 'g');
         v->add_piece(KING, 'l');
         v->startFen = "gle/1c1/1C1/ELG[-] w 0 1";
-        v->promotionRank = RANK_4;
+        v->promotionRegion[WHITE] = Rank4BB;
+        v->promotionRegion[BLACK] = Rank1BB;
         v->mandatoryPiecePromotion = true;
         v->immobilityIllegal = false;
         v->shogiPawnDropMateIllegal = false;
         v->flagPiece = KING;
-        v->whiteFlag = Rank4BB;
-        v->blackFlag = Rank1BB;
+        v->flagRegion[WHITE] = Rank4BB;
+        v->flagRegion[BLACK] = Rank1BB;
         v->dropNoDoubled = NO_PIECE_TYPE;
         v->nFoldValue = VALUE_DRAW;
         v->perpetualCheckIllegal = false;
@@ -766,7 +848,8 @@ namespace {
         v->maxRank = RANK_6;
         v->maxFile = FILE_E;
         v->startFen = "sgkgs/5/1ppp1/1PPP1/5/SGKGS[-] w 0 1";
-        v->promotionRank = RANK_5;
+        v->promotionRegion[WHITE] = Rank5BB | Rank6BB;
+        v->promotionRegion[BLACK] = Rank2BB | Rank1BB;
         return v;
     }
     // Judkins shogi
@@ -778,7 +861,8 @@ namespace {
         v->maxFile = FILE_F;
         v->add_piece(SHOGI_KNIGHT, 'n');
         v->startFen = "rbnsgk/5p/6/6/P5/KGSNBR[-] w 0 1";
-        v->promotionRank = RANK_5;
+        v->promotionRegion[WHITE] = Rank5BB | Rank6BB;
+        v->promotionRegion[BLACK] = Rank2BB | Rank1BB;
         v->promotedPieceType[SHOGI_KNIGHT] = GOLD;
         return v;
     }
@@ -803,8 +887,8 @@ namespace {
         v->startFen = "rpckcpl/3f3/sssssss/2s1S2/SSSSSSS/3F3/LPCKCPR[-] w 0 1";
         v->pieceDrops = true;
         v->capturesToHand = true;
-        v->promotionRank = RANK_6;
-        v->promotionPieceTypes = {};
+        v->promotionRegion[WHITE] = Rank6BB | Rank7BB;
+        v->promotionRegion[BLACK] = Rank2BB | Rank1BB;
         v->doubleStep = false;
         v->castling = false;
         v->promotedPieceType[SHOGI_PAWN]    = CUSTOM_PIECES + 5; // swallow promotes to goose
@@ -830,7 +914,8 @@ namespace {
         v->maxFile = FILE_H;
         v->add_piece(CUSTOM_PIECES, 'n', "fNsW");
         v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1";
-        v->promotionRank = RANK_6;
+        v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB;
+        v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
         v->promotedPieceType[CUSTOM_PIECES] = GOLD;
         v->mandatoryPiecePromotion = true;
         return v;
@@ -844,8 +929,10 @@ namespace {
         v->maxFile = FILE_F;
         v->remove_piece(BISHOP);
         v->startFen = "rnqknr/pppppp/6/6/PPPPPP/RNQKNR w - - 0 1";
-        v->promotionRank = RANK_6;
-        v->promotionPieceTypes = {QUEEN, ROOK, KNIGHT};
+        v->promotionRegion[WHITE] = Rank6BB;
+        v->promotionRegion[BLACK] = Rank1BB;
+        v->promotionPieceTypes[WHITE] = {QUEEN, ROOK, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, KNIGHT};
         v->doubleStep = false;
         v->castling = false;
         return v;
@@ -857,7 +944,8 @@ namespace {
         v->maxRank = RANK_5;
         v->maxFile = FILE_E;
         v->startFen = "rnbqk/ppppp/5/PPPPP/RNBQK w - - 0 1";
-        v->promotionRank = RANK_5;
+        v->promotionRegion[WHITE] = Rank5BB;
+        v->promotionRegion[BLACK] = Rank1BB;
         v->doubleStep = false;
         v->castling = false;
         return v;
@@ -871,7 +959,8 @@ namespace {
         v->remove_piece(QUEEN);
         v->add_piece(CHANCELLOR, 'c');
         v->startFen = "rnbckbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBCKBNR w KQkq - 0 1";
-        v->promotionPieceTypes = {CHANCELLOR, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {CHANCELLOR, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {CHANCELLOR, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Chigorin chess
@@ -882,7 +971,29 @@ namespace {
         v->pieceToCharTable = "PNBR............CKpnbrq............k";
         v->add_piece(CHANCELLOR, 'c');
         v->startFen = "rbbqkbbr/pppppppp/8/8/8/8/PPPPPPPP/RNNCKNNR w KQkq - 0 1";
-        v->promotionPieceTypes = {QUEEN, CHANCELLOR, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {CHANCELLOR, ROOK, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {QUEEN, ROOK, BISHOP};
+        return v;
+    }
+    // Spartan chess
+    // https://www.chessvariants.com/rules/spartan-chess
+    Variant* spartan_variant() {
+        Variant* v = threekings_variant()->init();
+        v->add_piece(DRAGON, 'g');
+        v->add_piece(ARCHBISHOP, 'w');
+        v->add_piece(CUSTOM_PIECES, 'h', "fmFfcWimA");
+        v->add_piece(CUSTOM_PIECES + 1, 'l', "FAsmW");
+        v->add_piece(CUSTOM_PIECES + 2, 'c', "WD");
+        v->startFen = "lgkcckwl/hhhhhhhh/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1";
+        v->promotionPawnType[BLACK] = CUSTOM_PIECES;
+        v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECES);
+        v->promotionPieceTypes[BLACK] = {COMMONER, DRAGON, ARCHBISHOP, CUSTOM_PIECES + 1, CUSTOM_PIECES + 2};
+        v->promotionLimit[COMMONER] = 2;
+        v->enPassantRegion = 0;
+        v->extinctionPieceCount = 0;
+        v->extinctionPseudoRoyal = true;
+        v->dupleCheck = true;
         return v;
     }
     // Shatar (Mongolian chess)
@@ -893,7 +1004,8 @@ namespace {
         v->remove_piece(QUEEN);
         v->add_piece(BERS, 'j');
         v->startFen = "rnbjkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBJKBNR w - - 0 1";
-        v->promotionPieceTypes = {BERS};
+        v->promotionPieceTypes[WHITE] = {BERS};
+        v->promotionPieceTypes[BLACK] = {BERS};
         v->doubleStep = false;
         v->castling = false;
         v->extinctionValue = VALUE_DRAW; // Robado
@@ -923,7 +1035,6 @@ namespace {
         v->reset_pieces();
         v->add_piece(CLOBBER_PIECE, 'p');
         v->startFen = "PpPpP/pPpPp/PpPpP/pPpPp/PpPpP/pPpPp w 0 1";
-        v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
         v->stalemateValue = -VALUE_MATE;
@@ -938,13 +1049,12 @@ namespace {
         v->reset_pieces();
         v->add_piece(BREAKTHROUGH_PIECE, 'p');
         v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1";
-        v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
         v->stalemateValue = -VALUE_MATE;
         v->flagPiece = BREAKTHROUGH_PIECE;
-        v->whiteFlag = Rank8BB;
-        v->blackFlag = Rank1BB;
+        v->flagRegion[WHITE] = Rank8BB;
+        v->flagRegion[BLACK] = Rank1BB;
         return v;
     }
     // Ataxx
@@ -957,7 +1067,6 @@ namespace {
         v->reset_pieces();
         v->add_piece(CUSTOM_PIECES, 'p', "mDmNmA");
         v->startFen = "P5p/7/7/7/7/7/p5P w 0 1";
-        v->promotionPieceTypes = {};
         v->pieceDrops = true;
         v->doubleStep = false;
         v->castling = false;
@@ -982,7 +1091,6 @@ namespace {
         v->reset_pieces();
         v->add_piece(IMMOBILE_PIECE, 'p');
         v->startFen = "8/8/8/8/8/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1";
-        v->promotionPieceTypes = {};
         v->pieceDrops = true;
         v->doubleStep = false;
         v->castling = false;
@@ -1022,7 +1130,6 @@ namespace {
         v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileCBB | FileDBB | FileEBB);
         v->mobilityRegion[BLACK][KING] = (Rank5BB | Rank6BB | Rank7BB) & (FileCBB | FileDBB | FileEBB);
         v->kingType = WAZIR;
-        v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
         v->stalemateValue = -VALUE_MATE;
@@ -1041,7 +1148,8 @@ namespace {
         v->add_piece(LANCE, 'l');
         v->add_piece(SHOGI_KNIGHT, 'n');
         v->startFen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1";
-        v->promotionRank = RANK_7;
+        v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB;
+        v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
         v->promotedPieceType[LANCE]        = GOLD;
         v->promotedPieceType[SHOGI_KNIGHT] = GOLD;
         return v;
@@ -1084,14 +1192,14 @@ namespace {
         v->add_piece(CUSTOM_PIECES + 3, 'g', "WfFbR"); // Yari gold
         v->add_piece(CUSTOM_PIECES + 4, 's', "fKbR"); // Yari silver
         v->startFen = "rnnkbbr/7/ppppppp/7/7/7/PPPPPPP/7/RBBKNNR[-] w 0 1";
-        v->promotionRank = RANK_7;
+        v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB;
+        v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
         v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECES + 4;
         v->promotedPieceType[CUSTOM_PIECES] = CUSTOM_PIECES + 3;
         v->promotedPieceType[CUSTOM_PIECES + 1] = CUSTOM_PIECES + 3;
         v->promotedPieceType[CUSTOM_PIECES + 2] = ROOK;
         v->pieceDrops = true;
         v->capturesToHand = true;
-        v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
         v->dropNoDoubled = SHOGI_PAWN;
@@ -1113,7 +1221,8 @@ namespace {
         v->add_piece(KNIGHT, 'n');
         v->add_piece(QUEEN, 'q');
         v->startFen = "lnsgkqgsnl/1r6b1/pppppppppp/10/10/10/10/PPPPPPPPPP/1B6R1/LNSGQKGSNL[-] w 0 1";
-        v->promotionRank = RANK_8;
+        v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB;
+        v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
         v->promotedPieceType[CUSTOM_PIECES] = GOLD;
         v->promotedPieceType[KNIGHT] = GOLD;
         return v;
@@ -1130,7 +1239,8 @@ namespace {
         v->add_piece(ARCHBISHOP, 'a');
         v->add_piece(CHANCELLOR, 'c');
         v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1";
-        v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Capahouse
@@ -1173,7 +1283,8 @@ namespace {
         v->castlingQueensideFile = FILE_B;
         v->add_piece(ARCHBISHOP, 'j');
         v->startFen = "rjnbkqbnjr/pppppppppp/10/10/10/10/PPPPPPPPPP/RJNBKQBNJR w KQkq - 0 1";
-        v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Modern chess
@@ -1184,12 +1295,16 @@ namespace {
         v->pieceToCharTable = "PNBRQ..M.............Kpnbrq..m.............k";
         v->maxRank = RANK_9;
         v->maxFile = FILE_I;
-        v->promotionRank = RANK_9;
+        v->promotionRegion[WHITE] = Rank9BB;
+        v->promotionRegion[BLACK] = Rank1BB;
+        v->doubleStepRegion[WHITE] = Rank2BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
         v->castlingKingsideFile = FILE_G;
         v->castlingQueensideFile = FILE_C;
         v->add_piece(ARCHBISHOP, 'm');
         v->startFen = "rnbqkmbnr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBMKQBNR w KQkq - 0 1";
-        v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Chancellor chess
@@ -1200,12 +1315,16 @@ namespace {
         v->pieceToCharTable = "PNBRQ...........CKpnbrq...........ck";
         v->maxRank = RANK_9;
         v->maxFile = FILE_I;
-        v->promotionRank = RANK_9;
+        v->promotionRegion[WHITE] = Rank9BB;
+        v->promotionRegion[BLACK] = Rank1BB;
+        v->doubleStepRegion[WHITE] = Rank2BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
         v->castlingKingsideFile = FILE_G;
         v->castlingQueensideFile = FILE_C;
         v->add_piece(CHANCELLOR, 'c');
         v->startFen = "rnbqkcnbr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBQKCNBR w KQkq - 0 1";
-        v->promotionPieceTypes = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Embassy chess
@@ -1231,7 +1350,8 @@ namespace {
         v->castlingQueensideFile = FILE_C;
         v->add_piece(CENTAUR, 'c');
         v->startFen = "rcnbqkbncr/pppppppppp/10/10/10/10/PPPPPPPPPP/RCNBQKBNCR w KQkq - 0 1";
-        v->promotionPieceTypes = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Gustav III chess
@@ -1246,7 +1366,8 @@ namespace {
         v->castlingQueensideFile = FILE_D;
         v->add_piece(AMAZON, 'a');
         v->startFen = "arnbqkbnra/*pppppppp*/*8*/*8*/*8*/*8*/*PPPPPPPP*/ARNBQKBNRA w KQkq - 0 1";
-        v->promotionPieceTypes = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[WHITE] = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {AMAZON, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
     // Jeson mor
@@ -1259,13 +1380,12 @@ namespace {
         v->reset_pieces();
         v->add_piece(KNIGHT, 'n');
         v->startFen = "nnnnnnnnn/9/9/9/9/9/9/9/NNNNNNNNN w - - 0 1";
-        v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
         v->stalemateValue = -VALUE_MATE;
         v->flagPiece = KNIGHT;
-        v->whiteFlag = make_bitboard(SQ_E5);
-        v->blackFlag = make_bitboard(SQ_E5);
+        v->flagRegion[WHITE] = make_bitboard(SQ_E5);
+        v->flagRegion[BLACK] = make_bitboard(SQ_E5);
         v->flagMove = true;
         return v;
     }
@@ -1282,7 +1402,8 @@ namespace {
         v->add_piece(COMMONER, 'm');
         v->add_piece(WAZIR, 'w');
         v->startFen = "rnebmk1wbenr/1ppppp1pppp1/6f5/p5p4p/P5P4P/6F5/1PPPPP1PPPP1/RNEBMK1WBENR w - - 0 1";
-        v->promotionPieceTypes = {FERS};
+        v->promotionPieceTypes[WHITE] = {FERS};
+        v->promotionPieceTypes[BLACK] = {FERS};
         v->doubleStep = false;
         v->castling = false;
         v->extinctionValue = -VALUE_MATE;
@@ -1305,8 +1426,10 @@ namespace {
         v->add_piece(ARCHBISHOP, 'a');
         v->add_piece(CHANCELLOR, 'c');
         v->startFen = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1";
-        v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
-        v->promotionRank = RANK_8;
+        v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB;
+        v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB;
         v->promotionLimit[ARCHBISHOP] = 1;
         v->promotionLimit[CHANCELLOR] = 1;
         v->promotionLimit[QUEEN] = 1;
@@ -1315,8 +1438,8 @@ namespace {
         v->promotionLimit[KNIGHT] = 2;
         v->mandatoryPawnPromotion = false;
         v->immobilityIllegal = true;
-        v->doubleStepRank = RANK_3;
-        v->doubleStepRankMin = RANK_3;
+        v->doubleStepRegion[WHITE] = Rank3BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
         v->castling = false;
         return v;
     }
@@ -1331,10 +1454,11 @@ namespace {
         v->add_piece(CUSTOM_PIECES + 1, 'w', "CF");
         v->add_piece(CUSTOM_PIECES + 2, 'l', "FDH");
         v->startFen = "rw6wr/clbnqknbla/pppppppppp/10/10/10/10/PPPPPPPPPP/CLBNQKNBLA/RW6WR w - - 0 1";
-        v->promotionPieceTypes.erase(KNIGHT);
-        v->promotionPieceTypes.insert(CUSTOM_PIECES);
-        v->promotionPieceTypes.insert(CUSTOM_PIECES + 1);
-        v->promotionPieceTypes.insert(CUSTOM_PIECES + 2);
+        v->promotionPieceTypes[WHITE].erase(KNIGHT);
+        v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES);
+        v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES + 1);
+        v->promotionPieceTypes[WHITE].insert(CUSTOM_PIECES + 2);
+        v->promotionPieceTypes[BLACK] = v->promotionPieceTypes[WHITE];
         v->promotionLimit[CUSTOM_PIECES] = 2;
         v->promotionLimit[CUSTOM_PIECES + 1] = 2;
         v->promotionLimit[CUSTOM_PIECES + 2] = 2;
@@ -1352,10 +1476,12 @@ namespace {
         v->add_piece(CHANCELLOR, 'm');
         v->add_piece(CUSTOM_PIECES, 'c', "DAW"); // Champion
         v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); // Wizard
-        v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN};
-        v->promotionRank = RANK_10;
-        v->doubleStepRank = RANK_3;
-        v->doubleStepRankMin = RANK_3;
+        v->promotionPieceTypes[WHITE] = {ARCHBISHOP, CHANCELLOR, QUEEN};
+        v->promotionPieceTypes[BLACK] = {ARCHBISHOP, CHANCELLOR, QUEEN};
+        v->promotionRegion[WHITE] = Rank10BB;
+        v->promotionRegion[BLACK] = Rank1BB;
+        v->doubleStepRegion[WHITE] = Rank3BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
         v->castling = false;
         return v;
     }
@@ -1373,10 +1499,52 @@ namespace {
         v->castlingKingsideFile = FILE_I;
         v->castlingQueensideFile = FILE_E;
         v->castlingRank = RANK_2;
-        v->promotionRank = RANK_9;
-        v->promotionPieceTypes = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT};
-        v->doubleStepRank = RANK_3;
-        v->doubleStepRankMin = RANK_3;
+        v->promotionRegion[WHITE] = Rank9BB | Rank10BB;
+        v->promotionRegion[BLACK] = Rank2BB | Rank1BB;
+        v->promotionPieceTypes[WHITE] = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->promotionPieceTypes[BLACK] = {CUSTOM_PIECES + 1, CUSTOM_PIECES, QUEEN, ROOK, BISHOP, KNIGHT};
+        v->doubleStepRegion[WHITE] = Rank3BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
+        return v;
+    }
+    // Troitzky Chess
+    // https://www.chessvariants.com/play/troitzky-chess
+    Variant* troitzky_variant() {
+        Variant* v =  chess_variant_base()->init();
+        v->maxRank = RANK_10;
+        v->maxFile = FILE_J;
+        v->startFen = "****qk****/**rnbbnr**/*pppppppp*/*8*/10/10/*8*/*PPPPPPPP*/**RNBBNR**/****QK**** w - - 0 1";
+        v->promotionRegion[WHITE] = make_bitboard(SQ_A6, SQ_B8, SQ_C9, SQ_D9, SQ_E10, SQ_F10, SQ_G9, SQ_H9, SQ_I8, SQ_J6);
+        v->promotionRegion[BLACK] = make_bitboard(SQ_A5, SQ_B3, SQ_C2, SQ_D2, SQ_E1, SQ_F1, SQ_G2, SQ_H2, SQ_I3, SQ_J5);
+        v->doubleStepRegion[WHITE] = Rank3BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
+        v->castling = false;
+        return v;
+    }
+    // Wolf chess
+    // https://en.wikipedia.org/wiki/Wolf_chess
+    Variant* wolf_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->maxRank = RANK_10;
+        v->remove_piece(KNIGHT);
+        v->add_piece(CHANCELLOR, 'w'); // wolf
+        v->add_piece(ARCHBISHOP, 'f'); // fox
+        v->add_piece(CUSTOM_PIECES, 's', "fKifmnD"); // seargent
+        v->add_piece(CUSTOM_PIECES + 1, 'n', "NN"); // nightrider
+        v->add_piece(CUSTOM_PIECES + 2, 'e', "NNQ"); // elephant
+        v->startFen = "qwfrbbnk/pssppssp/1pp2pp1/8/8/8/8/1PP2PP1/PSSPPSSP/KNBBRFWQ w - - 0 1";
+        v->promotionPawnType[WHITE] = v->promotionPawnType[BLACK] = PAWN;
+        v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(PAWN) | piece_set(CUSTOM_PIECES);
+        v->promotionPieceTypes[WHITE] = {QUEEN, CHANCELLOR, ARCHBISHOP, ROOK, BISHOP};
+        v->promotionPieceTypes[BLACK] = {QUEEN, CHANCELLOR, ARCHBISHOP, ROOK, BISHOP};
+        v->promotedPieceType[PAWN] = CUSTOM_PIECES + 2;
+        v->promotionRegion[WHITE] = Rank10BB;
+        v->promotionRegion[BLACK] = Rank1BB;
+        v->doubleStepRegion[WHITE] = Rank2BB | make_bitboard(SQ_B3, SQ_C3, SQ_F3, SQ_G3);
+        v->doubleStepRegion[BLACK] = Rank9BB | make_bitboard(SQ_B8, SQ_C8, SQ_F8, SQ_G8);
+        v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(PAWN);
+        v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(PAWN) | piece_set(CUSTOM_PIECES);
+        v->castling = false;
         return v;
     }
     // Shako
@@ -1390,13 +1558,15 @@ namespace {
         v->add_piece(FERS_ALFIL, 'e');
         v->add_piece(CANNON, 'c');
         v->startFen = "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/ERNBQKBNRE/C8C w KQkq - 0 1";
-        v->promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL };
-        v->promotionRank = RANK_10;
+        v->promotionPieceTypes[WHITE] = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL };
+        v->promotionPieceTypes[BLACK] = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL };
+        v->promotionRegion[WHITE] = Rank10BB;
+        v->promotionRegion[BLACK] = Rank1BB;
         v->castlingKingsideFile = FILE_H;
         v->castlingQueensideFile = FILE_D;
         v->castlingRank = RANK_2;
-        v->doubleStepRank = RANK_3;
-        v->doubleStepRankMin = RANK_3;
+        v->doubleStepRegion[WHITE] = Rank3BB;
+        v->doubleStepRegion[BLACK] = Rank8BB;
         return v;
     }
     // Clobber 10x10
@@ -1554,6 +1724,11 @@ void VariantMap::init() {
     add("fischerandom", chess960_variant());
     add("nocastle", nocastle_variant());
     add("armageddon", armageddon_variant());
+    add("torpedo", torpedo_variant());
+    add("berolina", berolina_variant());
+    add("pawnsideways", pawnsideways_variant());
+    add("pawnback", pawnback_variant());
+    add("legan", legan_variant());
     add("fairy", fairy_variant()); // fairy variant used for endgame code initialization
     add("makruk", makruk_variant());
     add("makpong", makpong_variant());
@@ -1616,6 +1791,7 @@ void VariantMap::init() {
     add("gardner", gardner_variant());
     add("almost", almost_variant());
     add("chigorin", chigorin_variant());
+    add("spartan", spartan_variant());
     add("shatar", shatar_variant());
     add("coregal", coregal_variant());
     add("clobber", clobber_variant());
@@ -1646,6 +1822,8 @@ void VariantMap::init() {
     add("opulent", opulent_variant());
     add("tencubed", tencubed_variant());
     add("omicron", omicron_variant());
+    add("troitzky", troitzky_variant());
+    add("wolf", wolf_variant());
     add("shako", shako_variant());
     add("clobber10", clobber10_variant());
     add("flipello10", flipello10_variant());
index 3ac95de..2a9d277 100644 (file)
@@ -50,8 +50,11 @@ struct Variant {
   std::string pieceToCharSynonyms = std::string(PIECE_NB, ' ');
   std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
   Bitboard mobilityRegion[COLOR_NB][PIECE_TYPE_NB] = {};
-  Rank promotionRank = RANK_8;
-  std::set<PieceType, std::greater<PieceType> > promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT };
+  Bitboard promotionRegion[COLOR_NB] = {Rank8BB, Rank1BB};
+  PieceType promotionPawnType[COLOR_NB] = {PAWN, PAWN};
+  PieceSet promotionPawnTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)};
+  std::set<PieceType, std::greater<PieceType> > promotionPieceTypes[COLOR_NB] = {{ QUEEN, ROOK, BISHOP, KNIGHT },
+                                                                                 { QUEEN, ROOK, BISHOP, KNIGHT }};
   bool sittuyinPromotion = false;
   int promotionLimit[PIECE_TYPE_NB] = {}; // 0 means unlimited
   PieceType promotedPieceType[PIECE_TYPE_NB] = {};
@@ -62,9 +65,10 @@ struct Variant {
   bool blastOnCapture = false;
   bool petrifyOnCapture = false;
   bool doubleStep = true;
-  Rank doubleStepRank = RANK_2;
-  Rank doubleStepRankMin = RANK_2;
+  Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB};
+  Bitboard tripleStepRegion[COLOR_NB] = {};
   Bitboard enPassantRegion = AllSquares;
+  PieceSet enPassantTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)};
   bool castling = true;
   bool castlingDroppedPiece = false;
   File castlingKingsideFile = FILE_G;
@@ -113,6 +117,7 @@ struct Variant {
   bool freeDrops = false;
 
   // game end
+  PieceSet nMoveRuleTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)};
   int nMoveRule = 50;
   int nFoldRule = 3;
   Value nFoldValue = VALUE_DRAW;
@@ -129,12 +134,12 @@ struct Variant {
   Value extinctionValue = VALUE_NONE;
   bool extinctionClaim = false;
   bool extinctionPseudoRoyal = false;
+  bool dupleCheck = false;
   std::set<PieceType> extinctionPieceTypes = {};
   int extinctionPieceCount = 0;
   int extinctionOpponentPieceCount = 0;
   PieceType flagPiece = NO_PIECE_TYPE;
-  Bitboard whiteFlag = 0;
-  Bitboard blackFlag = 0;
+  Bitboard flagRegion[COLOR_NB] = {};
   bool flagMove = false;
   bool checkCounting = false;
   int connectN = 0;
@@ -156,6 +161,11 @@ struct Variant {
   bool shogiStylePromotions = false;
 
   void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') {
+      // Avoid ambiguous definition by removing existing piece with same letter
+      size_t idx;
+      if ((idx = pieceToChar.find(toupper(c))) != std::string::npos)
+          remove_piece(PieceType(idx));
+      // Now add new piece
       pieceToChar[make_piece(WHITE, pt)] = toupper(c);
       pieceToChar[make_piece(BLACK, pt)] = tolower(c);
       pieceToCharSynonyms[make_piece(WHITE, pt)] = toupper(c2);
@@ -176,12 +186,18 @@ struct Variant {
       pieceToCharSynonyms[make_piece(WHITE, pt)] = ' ';
       pieceToCharSynonyms[make_piece(BLACK, pt)] = ' ';
       pieceTypes.erase(pt);
+      // erase from promotion types to ensure consistency
+      promotionPieceTypes[WHITE].erase(pt);
+      promotionPieceTypes[BLACK].erase(pt);
   }
 
   void reset_pieces() {
       pieceToChar = std::string(PIECE_NB, ' ');
       pieceToCharSynonyms = std::string(PIECE_NB, ' ');
       pieceTypes.clear();
+      // clear promotion types to ensure consistency
+      promotionPieceTypes[WHITE].clear();
+      promotionPieceTypes[BLACK].clear();
   }
 
   // Reset values that always need to be redefined
@@ -192,6 +208,12 @@ struct Variant {
 
   // Pre-calculate derived properties
   Variant* conclude() {
+      // Enforce consistency to allow runtime optimizations
+      if (!doubleStep)
+          doubleStepRegion[WHITE] = doubleStepRegion[BLACK] = 0;
+      if (!doubleStepRegion[WHITE] && !doubleStepRegion[BLACK])
+          doubleStep = false;
+
       fastAttacks = std::all_of(pieceTypes.begin(), pieceTypes.end(), [this](PieceType pt) {
                                     return (   pt < FAIRY_PIECES
                                             || pt == COMMONER || pt == IMMOBILE_PIECE
index d7b3099..a0710fc 100644 (file)
 # [File]: denotes a file of the board [1-12, a-i]
 # [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]
 # [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 couting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none]
 # startFen: FEN of starting position (default: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1)
 # mobilityRegion: the mobility area can be defined via options specific to color and piece,
 #                 .e.g., mobilityRegionWhiteRook, mobilityRegionBlackJanggiElephant, etc. [Bitboard]
-# promotionRank: relative rank required to reach for promotion [Rank] (default: 8)
-# promotionPieceTypes: pawn promotion options using their one-letter representations (default: nbrq)
+# pawnTypes: define pieces considered as "pawns" for promotion, en passant, and n move rule [PieceSet] (default: p)
+#            see promotionPawnTypes, enPassantTypes, and nMoveRuleTypes for more specific overrides.
+# promotionRegionWhite: region where promotions are allowed for white [Bitboard] (default: *8)
+# promotionRegionBlack: region where promotions are allowed for black [Bitboard] (default: *1)
+# promotionPawnTypes: promotion pawn types for both colors [PieceSet] (default: p)
+# promotionPawnTypesWhite: white promotion pawn types [PieceSet] (default: p)
+# promotionPawnTypesBlack: black promotion pawn types [PieceSet] (default: p)
+# promotionPieceTypes: pawn promotion options [PieceSet] (default: nbrq)
+# promotionPieceTypesWhite: white pawn promotion options [PieceSet] (default: nbrq)
+# promotionPieceTypesBlack: black pawn promotion options [PieceSet] (default: nbrq)
 # sittuyinPromotion: enable Sittuyin-style pawn promotion [bool] (default: false)
 # promotionLimit: maximum number of pieces of a type, e.g., q:1 r:2 (default: )
 # promotedPieceType: mapping between unpromoted and promoted non-pawn piece types, e.g., p:g s:g (default: )
 # blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false)
 # petrifyOnCapture: non-pawn pieces are turned into wall squares when capturing [bool] (default: false)
 # doubleStep: enable pawn double step [bool] (default: true)
-# doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2)
-# doubleStepRankMin: earlist relative rank from where pawn double steps are allowed [Rank] (default: 2)
+# doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2)
+# doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *2)
+# tripleStepRegionWhite: region where pawn triple steps are allowed for white [Bitboard] (default: -)
+# tripleStepRegionBlack: region where pawn triple steps are allowed for black [Bitboard] (default: -)
 # enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard]
+# enPassantTypes: define pieces able to capture en passant [PieceSet] (default: p)
+# enPassantTypesWhite: define white pieces able to capture en passant [PieceSet] (default: p)
+# enPassantTypesBlack: define black pieces able to capture en passant [PieceSet] (default: p)
 # castling: enable castling [bool] (default: true)
 # castlingDroppedPiece: enable castling with dropped rooks/kings [bool] (default: false)
 # castlingKingsideFile: destination file of king after kingside castling [File] (default: g)
 # dropOppositeColoredBishop: dropped bishops have to be on opposite-colored squares [bool] (default: false)
 # dropPromoted: pieces may be dropped in promoted state [bool] (default: false)
 # dropNoDoubled: specified piece type can not be dropped to the same file (e.g. shogi pawn) [PieceType] (default: -)
-# dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [PieceType] (default: 1)
+# dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [int] (default: 1)
 # immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false)
 # gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false)
 # arrowGating: gating of wall squares in Game of the Amazons style [bool] (default: false)
 # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false)
 # soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [bool] (default: 1)
 # flipEnclosedPieces: change color of pieces that are enclosed by a drop [EnclosingRule] (default: none)
+# nMoveRuleTypes: define pieces resetting n move rule [PieceSet] (default: p)
+# nMoveRuleTypesWhite: define white pieces resetting n move rule [PieceSet] (default: p)
+# nMoveRuleTypesBlack: define black pieces resetting n move rule [PieceSet] (default: p)
 # nMoveRule: move count for 50/n-move rule [int] (default: 50)
 # nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3)
 # nFoldValue: result in case of 3/n-fold repetition [Value] (default: draw)
 # extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: none)
 # extinctionClaim: extinction of opponent pieces can only be claimed as side to move [bool] (default: false)
 # extinctionPseudoRoyal: treat the last extinction piece like a royal piece [bool] (default: false)
+# dupleCheck: when all pseudo-royal pieces are attacked, it counts as a check [bool] (default: false)
 # extinctionPieceTypes: list of piece types for extinction rules, e.g., pnbrq (* means all) (default: )
 # extinctionPieceCount: piece count at which the game is decided by extinction rule (default: 0)
 # extinctionOpponentPieceCount: opponent piece count required to adjudicate by extinction rule (default: 0)
 # flagPiece: piece type for capture the flag win rule [PieceType] (default: -)
-# whiteFlag: white's target region for capture the flag win rule [Bitboard] (default: )
-# blackFlag: black's target region for capture the flag win rule [Bitboard] (default: )
+# flagRegionWhite: white's target region for capture the flag win rule [Bitboard] (default: )
+# flagRegionBlack: black's target region for capture the flag win rule [Bitboard] (default: )
 # flagMove: black gets one more move after white captures the flag [bool] (default: false)
 # checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false)
 # connectN: number of aligned pieces for win [int] (default: 0)
 # startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1
 # pieceDrops = true
 # capturesToHand = true
-# promotionRank = 5
+# promotionRegionWhite = *5
+# promotionRegionBlack = *1
 # doubleStep = false
 # castling = false
 # promotedPieceType = p:g s:g b:h r:d
 # nFoldValue = loss
 # nFoldValueAbsolute = true
 
+# pawns with extra sideways and backwards movement
+# example for defining a custom pawn type
+# resetting the original "p" piece with "pawn = -" is optional as already done implicitly
+[allwayspawns:chess]
+customPiece1 = p:mWfceFifmnD
+pawnTypes = p
+
 # Hybrid variant of three-check chess and crazyhouse, using crazyhouse as a template
 [3check-crazyhouse:crazyhouse]
 startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1
@@ -280,8 +306,8 @@ blastOnCapture = true
 [atomic-giveaway-hill:giveaway]
 blastOnCapture = true
 flagPiece = k
-whiteFlag = d4 e4 d5 e5
-blackFlag = d4 e4 d5 e5
+flagRegionWhite = d4 e4 d5 e5
+flagRegionBlack = d4 e4 d5 e5
 
 # Crazyhouse with mandatory captures, using crazyhouse as a template
 [coffeehouse:crazyhouse]
@@ -337,7 +363,8 @@ amazon = g
 maxRank = 10
 maxFile = 10
 startFen = rnbqkgvbnr/ppppwwpppp/4pp4/10/10/10/10/4PP4/PPPPWWPPPP/RNBVGKQBNR w - - 0 1
-promotionRank = 10
+promotionRegionWhite = *10
+promotionRegionBlack = *1
 promotionPieceTypes = q
 doubleStep = false
 castling = false
@@ -387,7 +414,8 @@ startFen = nnnnknnn/pppppppp/2p2p2/1pppppp1/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1
 
 # Semi-torpedo chess
 [semitorpedo:chess]
-doubleStepRank = 3
+doubleStepRegionWhite = *2 *3
+doubleStepRegionBlack = *7 *6
 
 # This variant is similar to Capablanca Chess, but with two archbishops and no chancellor piece.
 [gemini:janus]
@@ -406,8 +434,8 @@ promotionPieceTypes = q
 castling = false
 stalemateValue = loss
 flagPiece = q
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 
 [tictactoe]
 maxRank = 3
@@ -456,7 +484,8 @@ centaur = g
 archbishop = a
 chancellor = m
 fers = f
-promotionRank = 6
+promotionRegionWhite = *6 *7 *8
+promotionRegionBlack = *3 *2 *1
 promotionLimit = g:1 a:1 m:1 q:1
 promotionPieceTypes = -
 promotedPieceType = p:c n:g b:a r:m f:q
@@ -477,8 +506,8 @@ silver = y
 promotionPieceTypes = qh
 startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1
 flagPiece = k
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 
 # Ordamirror
 # https://vchess.club/#/variants/Ordamirror
@@ -491,8 +520,8 @@ customPiece1 = f:mQcN
 promotionPieceTypes = lhaf
 startFen = lhafkahl/8/pppppppp/8/8/PPPPPPPP/8/LHAFKAHL w - - 0 1
 flagPiece = k
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 
 # Hybrid variant of Gothic-chess and crazyhouse, using Capablanca as a template
 [gothhouse:capablanca]
@@ -517,8 +546,8 @@ flyingGeneral = true
 capturesToHand = false
 blackDropRegion = *5
 flagPiece = k
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 
 # Capture chess
 # https://vchess.club/#/variants/Capture
@@ -548,8 +577,8 @@ blackDropRegion = *8 *7 *6 *5
 # Crossing chess
 # https://vchess.club/#/variants/Crossing
 [crossing:kingofthehill]
-whiteFlag = *5
-blackFlag = *4
+flagRegionWhite = *5
+flagRegionBlack = *4
 
 # 4x5 Chess
 # https://greenchess.net/rules.php?v=4x5-chess --> Solved draw
@@ -563,7 +592,8 @@ startFen = rnbk/pppp/4/PPPP/RNBK w - - 0 1
 [4x6chess:gardner]
 maxRank = 6
 maxFile = d
-promotionRank = 6
+promotionRegionWhite = *6
+promotionRegionBlack = *1
 startFen = rnbk/pppp/4/4/PPPP/RNBK w - - 0 1
 
 # 5x6 chess
@@ -571,7 +601,8 @@ startFen = rnbk/pppp/4/4/PPPP/RNBK w - - 0 1
 [5x6chess:gardner]
 maxRank = 6
 maxFile = e
-promotionRank = 6
+promotionRegionWhite = *6
+promotionRegionBlack = *1
 startFen = rnbqk/ppppp/5/5/PPPPP/RNBQK w - - 0 1
 
 # Active chess
@@ -585,7 +616,8 @@ startFen = rnbkqbnrq/ppppppppp/9/9/9/9/PPPPPPPPP/RNBKQBNRQ w KQkq - 0 1
 extinctionPseudoRoyal = true
 maxRank = 6
 maxFile = f
-promotionRank = 6
+promotionRegionWhite = *6
+promotionRegionBlack = *1
 doubleStep = false
 startFen = rbqkbr/pppppp/6/6/PPPPPP/RBQKBR w - - 0 1
 
@@ -639,8 +671,8 @@ startFen = rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w kq - 0 1
 stalemateValue = loss
 nFoldValue = loss
 flagPiece = k
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 flyingGeneral = true
 
 # Shinobi Chess
@@ -655,7 +687,8 @@ archbishop = j
 fers = m
 shogiKnight = h
 lance = l
-promotionRank = 7
+promotionRegionWhite = *7 *8
+promotionRegionBlack = *2 *1
 promotionPieceTypes = -
 promotedPieceType = p:c m:b h:n l:r
 mandatoryPiecePromotion = true
@@ -667,8 +700,8 @@ whiteDropRegion = *1 *2 *3 *4
 blackDropRegion = *5 *6 *7 *8
 immobilityIllegal = true
 flagPiece = k
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 
 # Wildebeest
 # https://vchess.club/#/variants/Wildebeest
@@ -678,10 +711,15 @@ maxRank = 10
 maxFile = k
 customPiece1 = c:C
 customPiece2 = w:NC
+doubleStepRegionWhite = *2 *3
+doubleStepRegionBlack = *9 *8
+tripleStepRegionWhite = *2
+tripleStepRegionBlack = *9
 pieceToCharTable = PNBRQ.......C....WKpnbrq.......c....wk
 startFen = rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w KQkq - 0 1
 promotionPieceTypes = qw
-promotionRank = 9
+promotionRegionWhite = *9 *10
+promotionRegionBlack = *2 *1
 mandatoryPawnPromotion = false
 castling = false
 
@@ -718,7 +756,8 @@ chancellor = m
 fers = f
 wazir = v
 centaur = t
-promotionRank = 7
+promotionRegionWhite = *7 *8 *9
+promotionRegionBlack = *3 *2 *1
 promotedPieceType = p:g n:o b:h r:d a:c v:m f:q s:w u:t
 doubleStep = false
 perpetualCheckIllegal = true
@@ -726,32 +765,32 @@ nMoveRule = 0
 nFoldValue = loss
 stalemateValue = loss
 flagPiece = k
-whiteFlag = *9
-blackFlag = *1
+flagRegionWhite = *9
+flagRegionBlack = *1
 
 # 5x5 breakthrough
 [breakthrough5:breakthrough]
 maxFile = 5
 maxRank = 5
 startFen = ppppp/ppppp/5/PPPPP/PPPPP w 0 1
-whiteFlag = *5
-blackFlag = *1
+flagRegionWhite = *5
+flagRegionBlack = *1
 
 # 6x6 breakthrough
 [breakthrough6:breakthrough]
 maxFile = 6
 maxRank = 6
 startFen = pppppp/pppppp/6/6/PPPPPP/PPPPPP w 0 1
-whiteFlag = *6
-blackFlag = *1
+flagRegionWhite = *6
+flagRegionBlack = *1
 
 # 7x7 breakthrough
 [breakthrough7:breakthrough]
 maxFile = 7
 maxRank = 7
 startFen = ppppppp/ppppppp/7/7/7/PPPPPPP/PPPPPPP w 0 1
-whiteFlag = *7
-blackFlag = *1
+flagRegionWhite = *7
+flagRegionBlack = *1
 
 # Mansindam (Pantheon tale)
 # A variant that combines drop rule and powerful pieces, and there is no draw
@@ -779,7 +818,8 @@ dragonHorse = h
 bers = t
 customPiece1 = i:BNW
 customPiece2 = s:RNF
-promotionRank = 7
+promotionRegionWhite = *7 *8 *9
+promotionRegionBlack = *3 *2 *1
 doubleStep = false
 castling = false
 promotedPieceType = p:g n:e b:h r:t c:i m:s
@@ -788,8 +828,8 @@ stalemateValue = loss
 nMoveRule = 0
 nFoldValue = loss
 flagPiece = k
-whiteFlag = *9
-blackFlag = *1
+flagRegionWhite = *9
+flagRegionBlack = *1
 immobilityIllegal = true
 mandatoryPiecePromotion = true
 
diff --git a/test.py b/test.py
index c8101dc..3294bb5 100644 (file)
--- a/test.py
+++ b/test.py
@@ -38,7 +38,8 @@ centaur = g
 archbishop = a
 chancellor = m
 fers = f
-promotionRank = 6
+promotionRegionWhite = *6 *7 *8
+promotionRegionBlack = *3 *2 *1
 promotionLimit = g:1 a:1 m:1 q:1
 promotionPieceTypes = -
 promotedPieceType = p:c n:g b:a r:m f:q
@@ -58,8 +59,8 @@ kniroo = l
 silver = y
 promotionPieceTypes = qh
 flagPiece = k
-whiteFlag = *8
-blackFlag = *1
+flagRegionWhite = *8
+flagRegionBlack = *1
 
 [diana:losalamos]
 pieceToCharTable = PNBRQ................Kpnbrq................k
index 085bc02..e11d01e 100755 (executable)
@@ -39,6 +39,14 @@ if [[ $1 == "all" || $1 == "variant" ]]; then
   expect perft.exp losalamos startpos 5 191846 > /dev/null
   expect perft.exp losalamos "fen 6/2P3/6/1K1k2/6/6 w - - 0 1" 6 187431 > /dev/null
   # fairy
+  expect perft.exp torpedo startpos 4 209719 > /dev/null
+  expect perft.exp torpedo "fen rnbqkbnr/1ppppppp/8/6P1/p7/8/PPPPPP1P/RNBQKBNR w KQkq - 0 1" 4 232819 > /dev/null
+  expect perft.exp berolina "fen rnbqkbnr/pppp1ppp/8/2p5/5P2/8/PPP1PPPP/RNBQKBNR w KQkq c5d6 2 2" 3 46643 > /dev/null
+  expect perft.exp berolina "fen k7/6P1/8/8/8/2K2p2/4p3/8 w - - 0 1" 3 1983 > /dev/null
+  expect perft.exp berolina "fen rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PP1PPPPP/RNBQKBNR w KQkq d6c5 0 1" 2 1047 > /dev/null
+  expect perft.exp pawnsideways startpos 3 10022 > /dev/null
+  expect perft.exp pawnback startpos 3 9222 > /dev/null
+  expect perft.exp legan startpos 4 8138 > /dev/null
   expect perft.exp makruk startpos 4 273026 > /dev/null
   expect perft.exp cambodian startpos 4 361719 > /dev/null
   expect perft.exp cambodian "fen r1s1ks1r/3nm3/pppNpppp/3n4/5P2/PPPPPNPP/8/R1SKMS1R b DEe 0 0 5" 2 72 > /dev/null
@@ -52,6 +60,12 @@ if [[ $1 == "all" || $1 == "variant" ]]; then
   expect perft.exp sittuyin "fen 2r5/6k1/6p1/3s2P1/3npR2/8/p2N2F1/3K4[] w - - 1 50" 4 373984 > /dev/null
   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 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
+  # self duple check with promotions
+  expect perft.exp spartan "fen 8/8/8/8/6Q1/8/2h3h1/4K1k1 b - - 0 1" 3 3456 > /dev/null
   expect perft.exp shatar startpos 4 177344 > /dev/null
   expect perft.exp shatranj startpos 4 68122 > /dev/null
   expect perft.exp amazon startpos 4 318185 > /dev/null
@@ -90,6 +104,7 @@ if [[ $1 == "all" || $1 == "variant" ]]; then
   expect perft.exp losers startpos 4 152955 > /dev/null
   expect perft.exp kinglet startpos 4 197742 > /dev/null
   expect perft.exp threekings startpos 4 199514 > /dev/null
+
   # pockets
   expect perft.exp crazyhouse startpos 4 197281 > /dev/null
   expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3[QRBNPqrbnp] w - - 0 1" 2 75353 > /dev/null
@@ -149,6 +164,9 @@ if [[ $1 == "all" ||  $1 == "largeboard" ]]; then
   expect perft.exp centaur startpos 3 24490 > /dev/null
   expect perft.exp gustav3 startpos 4 331659 > /dev/null
   expect perft.exp omicron startpos 4 967381 > /dev/null
+  expect perft.exp troitzky startpos 3 8766 > /dev/null
+  expect perft.exp wolf startpos 3 13722 > /dev/null
+  expect perft.exp wolf "fen 8/k5SP/8/8/8/8/8/8/8/7K w - - 0 1" 4 10587 > /dev/null
   expect perft.exp shako "fen 4kc3c/ernbq1b1re/ppp3p1pp/3p2pp2/4p5/5P4/2PN2P3/PP1PP2PPP/ER1BQKBNR1/5C3C w KQ - 0 9" 3 26325 > /dev/null
   expect perft.exp shako "fen 4ncr1k1/1cr2P4/pp2p2pp1/P7PN/2Ep1p4/B3P1eN2/2P1n1P3/1B1P1K4/9p/5C2CR w - - 0 1" 3 180467 > /dev/null
   expect perft.exp shako "fen r5k3/4q2c2/1ebppnp3/1pp3BeEQ/10/2PE2P3/1P3P4/5NP2P/rR3KB3/7C2 w Q - 3 35" 2 4940 > /dev/null