Support wall squares (#565)
authorFabian Fichter <ianfab@users.noreply.github.com>
Sat, 14 Jan 2023 15:37:01 +0000 (16:37 +0100)
committerGitHub <noreply@github.com>
Sat, 14 Jan 2023 15:37:01 +0000 (16:37 +0100)
Add support for squares that are neither accessible
nor traversable, using the standard `*` notation.

* Add support for duck chess, closing #528.
* Add support for wall squares, e.g., Omicron and Gustav III chess, closing #53.
* Support petrification on capture.
* Refactor game of the amazons to use wall squares.
  * This changes both the FEN and move representation.
* Enable -DALLVARS for both pyffish and ffishjs.

Note: With this commit `pieces() == pieces(WHITE) | pieces(BLACK)`
can no longer be assumed due to the possibility of wall squares.

22 files changed:
README.md
setup.py
src/Makefile
src/Makefile_js
src/apiutil.h
src/movegen.cpp
src/movepick.cpp
src/movepick.h
src/nnue/features/half_ka_v2_variants.cpp
src/parser.cpp
src/position.cpp
src/position.h
src/search.cpp
src/thread.cpp
src/thread.h
src/types.h
src/uci.cpp
src/ucioption.cpp
src/variant.cpp
src/variant.h
src/variants.ini
tests/perft.sh

index 1fd8227..b2ed1c4 100644 (file)
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca
 - [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess)
 - [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse
 - [Bughouse](https://en.wikipedia.org/wiki/Bughouse_chess), [Koedem](http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf)
-- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse
+- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse, [Dragon Chess](https://www.edami.com/dragonchess/)
 - [Amazon](https://www.chessvariants.com/diffmove.dir/amazone.html), [Chigorin](https://www.chessvariants.com/diffsetup.dir/chigorin.html), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess)
 - [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html), New Zealand
 - [Antichess](https://lichess.org/variant/antichess), [Giveaway](http://www.chessvariants.com/diffobjective.dir/giveaway.old.html), [Suicide](https://www.freechess.org/Help/HelpFiles/suicide_chess.html), [Losers](https://www.chessclub.com/help/Wild17), [Codrus](http://www.binnewirtz.com/Schlagschach1.htm)
@@ -56,7 +56,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca
 - [Atomic](https://en.wikipedia.org/wiki/Atomic_chess)
 - [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess), [Maharajah and the Sepoys](https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys)
 - [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html), [Nightrider](https://en.wikipedia.org/wiki/Nightrider_(chess)), [Grasshopper](https://en.wikipedia.org/wiki/Grasshopper_chess)
-- [Dragon Chess](https://www.edami.com/dragonchess/)
+- [Duck chess](https://duckchess.com/), [Omicron](http://www.eglebbk.dds.nl/program/chess-omicron.html), [Gustav III](https://www.chessvariants.com/play/gustav-iiis-chess)
 
 ### Shogi variants
 - [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi)
index 9a25103..7dee176 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ if platform.python_compiler().startswith("MSC"):
 else:
     args = ["-std=c++17", "-flto", "-Wno-date-time"]
 
-args.extend(["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"])
+args.extend(["-DLARGEBOARDS", "-DALLVARS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"])
 
 if "64bit" in platform.architecture():
     args.append("-DIS_64BIT")
index 3584386..602beef 100644 (file)
@@ -348,7 +348,7 @@ ifeq ($(nnue),no)
        CXXFLAGS += -DNNUE_EMBEDDING_OFF
 endif
 
-# Enable all variants, even heavyweight ones like amazons
+# Enable all variants, even heavyweight ones like duck and amazons
 ifneq ($(all),no)
        CXXFLAGS += -DALLVARS
 endif
index 9933922..79a7695 100644 (file)
@@ -27,6 +27,7 @@ CXX=emcc
 CXXFLAGS += --bind -DNNUE_EMBEDDING_OFF -DNO_THREADS -std=c++17 -Wall
 
 largeboards = yes
+all = yes
 optimize = yes
 debug = no
 
@@ -48,6 +49,11 @@ ifneq ($(largeboards),no)
        CXXFLAGS += -DLARGEBOARDS -DPRECOMPUTED_MAGICS -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB
 endif
 
+# Enable all variants, even heavyweight ones like duck and amazons
+ifneq ($(all),no)
+       CXXFLAGS += -DALLVARS
+endif
+
 ### Compile as ES6/ES2015 module
 ifeq ($(es6),yes)
        CXXFLAGS += -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0
index 86bd208..f75d830 100644 (file)
@@ -345,6 +345,10 @@ inline const std::string move_to_san(Position& pos, Move m, Notation n) {
             san += std::string("/") + (char)toupper(pos.piece_to_char()[make_piece(us, gating_type(m))]);
     }
 
+    // Wall square
+    if (pos.wall_gating())
+        san += "," + square(pos, gating_square(m), n);
+
     // Check and checkmate
     if (pos.gives_check(m) && !is_shogi(n) && n != NOTATION_XIANGQI_WXF)
     {
@@ -551,7 +555,9 @@ inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard,
     {
         if (c == ' ' || c == '[')
             break;
-        if (isdigit(c))
+        if (c == '*')
+            ++fileIdx;
+        else if (isdigit(c))
         {
             fileIdx += c - '0';
             // if we have multiple digits attached we can add multiples of 9 to compute the resulting number (e.g. -> 21 = 2 + 2 * 9 + 1)
@@ -885,13 +891,13 @@ inline Validation check_digit_field(const std::string& field) {
 }
 
 inline std::string get_valid_special_chars(const Variant* v) {
-    std::string validSpecialCharactersFirstField = "/";
+    std::string validSpecialCharactersFirstField = "/*";
     // Whether or not '-', '+', '~', '[', ']' are valid depends on the variant being played.
     if (v->shogiStylePromotions)
         validSpecialCharactersFirstField += '+';
     if (!v->promotionPieceTypes.empty())
         validSpecialCharactersFirstField += '~';
-    if (!v->freeDrops && (v->pieceDrops || v->seirawanGating || v->arrowGating))
+    if (!v->freeDrops && (v->pieceDrops || v->seirawanGating))
         validSpecialCharactersFirstField += "[-]";
     return validSpecialCharactersFirstField;
 }
@@ -935,7 +941,7 @@ inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool
 
     // check for pocket
     std::string pocket = "";
-    if (v->pieceDrops || v->seirawanGating || v->arrowGating)
+    if (v->pieceDrops || v->seirawanGating)
     {
         if (check_pocket_info(fenParts[0], nbRanks, v, pocket) == NOK)
             return FEN_INVALID_POCKET_INFO;
index fe90154..416f992 100644 (file)
@@ -26,22 +26,29 @@ namespace Stockfish {
 namespace {
 
   template<MoveType T>
-  ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) {
+  ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to, PieceType pt = NO_PIECE_TYPE) {
 
-    // Arrow gating moves
-    if (pos.arrow_gating())
+    // Wall placing moves
+    if (pos.wall_gating())
     {
-        for (PieceType pt_gating : pos.piece_types())
-            if (pos.can_drop(us, pt_gating))
-            {
-                Bitboard b = pos.drop_region(us, pt_gating) & moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from) & ~(pos.pieces() ^ from);
-                while (b)
-                    *moveList++ = make_gating<T>(from, to, pt_gating, pop_lsb(b));
-            }
+        Bitboard b = pos.board_bb() & ~((pos.pieces() ^ from) | to);
+        if (T == CASTLING)
+        {
+            Square kto = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), pos.castling_rank(us));
+            Direction step = kto > from ? EAST : WEST;
+            Square rto = kto - step;
+            b ^= square_bb(to) ^ kto ^ rto;
+        }
+        if (T == EN_PASSANT)
+            b ^= to - pawn_push(us);
+        if (pos.variant()->arrowGating)
+            b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from);
+        while (b)
+            *moveList++ = make_gating<T>(from, to, pt, pop_lsb(b));
         return moveList;
     }
 
-    *moveList++ = make<T>(from, to);
+    *moveList++ = make<T>(from, to, pt);
 
     // Gating moves
     if (pos.seirawan_gating() && (pos.gates(us) & from))
@@ -63,10 +70,10 @@ namespace {
     {
         for (PieceType pt : pos.promotion_piece_types())
             if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt))
-                *moveList++ = make<PROMOTION>(to - D, to, pt);
+                moveList = make_move_and_gating<PROMOTION>(pos, moveList, pos.side_to_move(), to - D, to, pt);
         PieceType pt = pos.promoted_piece_type(PAWN);
         if (pt && !(pos.piece_promotion_on_capture() && pos.empty(to)))
-            *moveList++ = make<PIECE_PROMOTION>(to - D, to);
+            moveList = make_move_and_gating<PIECE_PROMOTION>(pos, moveList, pos.side_to_move(), to - D, to);
     }
 
     return moveList;
@@ -147,13 +154,13 @@ namespace {
         while (b1)
         {
             Square to = pop_lsb(b1);
-            *moveList++ = make_move(to - Up, to);
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - Up, to);
         }
 
         while (b2)
         {
             Square to = pop_lsb(b2);
-            *moveList++ = make_move(to - Up - Up, to);
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - Up - Up, to);
         }
     }
 
@@ -216,13 +223,13 @@ namespace {
         while (b1)
         {
             Square to = pop_lsb(b1);
-            *moveList++ = make_move(to - UpRight, to);
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - UpRight, to);
         }
 
         while (b2)
         {
             Square to = pop_lsb(b2);
-            *moveList++ = make_move(to - UpLeft, to);
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, to - UpLeft, to);
         }
 
         if (pos.ep_square() != SQ_NONE)
@@ -238,7 +245,7 @@ namespace {
             assert(b1);
 
             while (b1)
-                *moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
+                moveList = make_move_and_gating<EN_PASSANT>(pos, moveList, Us, pop_lsb(b1), pos.ep_square());
         }
     }
 
@@ -335,6 +342,7 @@ namespace {
                 target = pos.checkers();
         }
 
+        // Remove inaccesible squares (outside board + wall squares)
         target &= pos.board_bb();
 
         moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
index b16c309..eac77e8 100644 (file)
@@ -63,9 +63,9 @@ namespace {
 /// ordering is at the current node.
 
 /// MovePicker constructor for the main search
-MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp,
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const GateHistory* dh, const LowPlyHistory* lp,
                        const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl)
-           : pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
+           : pos(p), mainHistory(mh), gateHistory(dh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
              ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {
 
   assert(d > 0);
@@ -75,9 +75,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist
 }
 
 /// MovePicker constructor for quiescence search
-MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const GateHistory* dh,
                        const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
-           : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
+           : pos(p), mainHistory(mh), gateHistory(dh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
 
   assert(d <= 0);
 
@@ -89,8 +89,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist
 
 /// MovePicker constructor for ProbCut: we generate captures with SEE greater
 /// than or equal to the given threshold.
-MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
-           : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) {
+MovePicker::MovePicker(const Position& p, Move ttm, Value th, const GateHistory* dh, const CapturePieceToHistory* cph)
+           : pos(p), gateHistory(dh), captureHistory(cph), ttMove(ttm), threshold(th) {
 
   assert(!pos.checkers());
 
@@ -110,10 +110,12 @@ void MovePicker::score() {
   for (auto& m : *this)
       if constexpr (Type == CAPTURES)
           m.value =  int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6
+                   + (*gateHistory)[pos.side_to_move()][gating_square(m)]
                    + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
 
       else if constexpr (Type == QUIETS)
           m.value =      (*mainHistory)[pos.side_to_move()][from_to(m)]
+                   +     (*gateHistory)[pos.side_to_move()][gating_square(m)]
                    + 2 * (*continuationHistory[0])[history_slot(pos.moved_piece(m))][to_sq(m)]
                    +     (*continuationHistory[1])[history_slot(pos.moved_piece(m))][to_sq(m)]
                    +     (*continuationHistory[3])[history_slot(pos.moved_piece(m))][to_sq(m)]
index d19af6a..9047ee7 100644 (file)
@@ -88,6 +88,8 @@ enum StatsType { NoCaptures, Captures };
 /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
 typedef Stats<int16_t, 13365, COLOR_NB, int(SQUARE_NB + 1) * int(1 << SQUARE_BITS)> ButterflyHistory;
 
+typedef Stats<int16_t, 13365, COLOR_NB, SQUARE_NB> GateHistory;
+
 /// At higher depths LowPlyHistory records successful quiet moves near the root
 /// and quiet moves which are/were in the PV (ttPv). It is cleared with each new
 /// search and filled during iterative deepening.
@@ -124,12 +126,14 @@ class MovePicker {
 public:
   MovePicker(const MovePicker&) = delete;
   MovePicker& operator=(const MovePicker&) = delete;
-  MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
+  MovePicker(const Position&, Move, Value, const GateHistory*, const CapturePieceToHistory*);
   MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
+                                           const GateHistory*,
                                            const CapturePieceToHistory*,
                                            const PieceToHistory**,
                                            Square);
   MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
+                                           const GateHistory*,
                                            const LowPlyHistory*,
                                            const CapturePieceToHistory*,
                                            const PieceToHistory**,
@@ -146,6 +150,7 @@ private:
 
   const Position& pos;
   const ButterflyHistory* mainHistory;
+  const GateHistory* gateHistory;
   const LowPlyHistory* lowPlyHistory;
   const CapturePieceToHistory* captureHistory;
   const PieceToHistory** continuationHistory;
index 8551ebf..0807241 100644 (file)
@@ -53,7 +53,7 @@ namespace Stockfish::Eval::NNUE::Features {
     ValueListInserter<IndexType> active
   ) {
     Square oriented_ksq = orient(perspective, pos.nnue_king_square(perspective), pos);
-    Bitboard bb = pos.pieces();
+    Bitboard bb = pos.pieces(WHITE) | pos.pieces(BLACK);
     while (bb)
     {
       Square s = pop_lsb(bb);
index 196cfca..79c838f 100644 (file)
@@ -293,6 +293,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion);
     parse_attribute("pieceDemotion", v->pieceDemotion);
     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);
@@ -328,6 +329,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("immobilityIllegal", v->immobilityIllegal);
     parse_attribute("gating", v->gating);
     parse_attribute("arrowGating", v->arrowGating);
+    parse_attribute("duckGating", v->duckGating);
     parse_attribute("seirawanGating", v->seirawanGating);
     parse_attribute("cambodianMoves", v->cambodianMoves);
     parse_attribute("diagonalLines", v->diagonalLines);
@@ -434,7 +436,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
             std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl;
 
         // Check for limitations
-
+        if (v->pieceDrops && (v->arrowGating || v->duckGating))
+            std::cerr << "pieceDrops and arrowGating/duckGating are incompatible." << std::endl;
         // Options incompatible with royal kings
         if (v->pieceTypes.find(KING) != v->pieceTypes.end())
         {
@@ -442,6 +445,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
                 std::cerr << "Can not use kings with blastOnCapture." << std::endl;
             if (v->flipEnclosedPieces)
                 std::cerr << "Can not use kings with flipEnclosedPieces." << std::endl;
+            if (v->duckGating)
+                std::cerr << "Can not use kings with duckGating." << std::endl;
             // We can not fully check support for custom king movements at this point,
             // since custom pieces are only initialized on loading of the variant.
             // We will assume this is valid, but it might cause problems later if it's not.
index 6cbdaa9..b62a803 100644 (file)
@@ -44,6 +44,7 @@ namespace Zobrist {
   Key side, noPawns;
   Key inHand[PIECE_NB][SQUARE_NB];
   Key checks[COLOR_NB][CHECKS_NB];
+  Key wall[SQUARE_NB];
 }
 
 
@@ -59,7 +60,9 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
   for (Rank r = pos.max_rank(); r >= RANK_1; --r)
   {
       for (File f = FILE_A; f <= pos.max_file(); ++f)
-          if (pos.unpromoted_piece_on(make_square(f, r)))
+          if (pos.state()->wallSquares & make_square(f, r))
+              os << " | *";
+          else if (pos.unpromoted_piece_on(make_square(f, r)))
               os << " |+" << pos.piece_to_char()[pos.unpromoted_piece_on(make_square(f, r))];
           else
               os << " | " << pos.piece_to_char()[pos.piece_on(make_square(f, r))];
@@ -72,7 +75,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
               os << " *";
           else
               os << "  ";
-          if (!pos.variant()->freeDrops && (pos.piece_drops() || pos.seirawan_gating() || pos.arrow_gating()))
+          if (!pos.variant()->freeDrops && (pos.piece_drops() || pos.seirawan_gating()))
           {
               os << " [";
               for (PieceType pt = KING; pt >= PAWN; --pt)
@@ -172,6 +175,9 @@ void Position::init() {
           for (int n = 0; n < SQUARE_NB; ++n)
               Zobrist::inHand[make_piece(c, pt)][n] = rng.rand<Key>();
 
+  for (Square s = SQ_A1; s <= SQ_MAX; ++s)
+      Zobrist::wall[s] = rng.rand<Key>();
+
   // Prepare the cuckoo tables
   std::memset(cuckoo, 0, sizeof(cuckoo));
   std::memset(cuckooMove, 0, sizeof(cuckooMove));
@@ -282,6 +288,14 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
               break;
       }
 
+      // Wall square
+      else if (token == '*')
+      {
+          st->wallSquares |= sq;
+          byTypeBB[ALL_PIECES] |= sq;
+          ++sq;
+      }
+
       else if ((idx = piece_to_char().find(token)) != string::npos || (idx = piece_to_char_synonyms().find(token)) != string::npos)
       {
           if (ss.peek() == '~')
@@ -579,7 +593,10 @@ void Position::set_state(StateInfo* si) const {
       Piece pc = piece_on(s);
       si->key ^= Zobrist::psq[pc][s];
 
-      if (type_of(pc) == PAWN)
+      if (!pc)
+          si->key ^= Zobrist::wall[s];
+
+      else if (type_of(pc) == PAWN)
           si->pawnKey ^= Zobrist::psq[pc][s];
 
       else if (type_of(pc) != KING)
@@ -602,7 +619,7 @@ void Position::set_state(StateInfo* si) const {
           for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
               si->materialKey ^= Zobrist::psq[pc][cnt];
 
-          if (piece_drops() || seirawan_gating() || arrow_gating())
+          if (piece_drops() || seirawan_gating())
               si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]];
       }
 
@@ -648,7 +665,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
   {
       for (File f = FILE_A; f <= max_file(); ++f)
       {
-          for (emptyCnt = 0; f <= max_file() && empty(make_square(f, r)); ++f)
+          for (emptyCnt = 0; f <= max_file() && !(pieces() & make_square(f, r)); ++f)
               ++emptyCnt;
 
           if (emptyCnt)
@@ -656,7 +673,10 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
 
           if (f <= max_file())
           {
-              if (unpromoted_piece_on(make_square(f, r)))
+              if (empty(make_square(f, r)))
+                  // Wall square
+                  ss << "*";
+              else if (unpromoted_piece_on(make_square(f, r)))
                   // Promoted shogi pieces, e.g., +r for dragon
                   ss << "+" << piece_to_char()[unpromoted_piece_on(make_square(f, r))];
               else
@@ -693,7 +713,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string
   }
 
   // pieces in hand
-  if (!variant()->freeDrops && (piece_drops() || seirawan_gating() || arrow_gating()))
+  if (!variant()->freeDrops && (piece_drops() || seirawan_gating()))
   {
       ss << '[';
       if (holdings != "-")
@@ -1075,6 +1095,10 @@ bool Position::legal(Move m) const {
           }
   }
 
+  // Petrifying the king is illegal
+  if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) == KING)
+      return false;
+
   // En passant captures are a tricky special case. Because they are rather
   // uncommon, we do it simply by testing whether the king is attacked after
   // the move is made.
@@ -1168,7 +1192,7 @@ bool Position::pseudo_legal(const Move m) const {
   Square to = to_sq(m);
   Piece pc = moved_piece(m);
 
-  // Illegal moves to squares outside of board
+  // Illegal moves to squares outside of board or to wall squares
   if (!(board_bb() & to))
       return false;
 
@@ -1184,10 +1208,16 @@ bool Position::pseudo_legal(const Move m) const {
 
   // Use a slower but simpler function for uncommon cases
   // yet we skip the legality check of MoveList<LEGAL>().
-  if (type_of(m) != NORMAL || is_gating(m) || arrow_gating())
+  if (type_of(m) != NORMAL || is_gating(m))
       return checkers() ? MoveList<    EVASIONS>(*this).contains(m)
                         : MoveList<NON_EVASIONS>(*this).contains(m);
 
+  // Illegal wall square placement
+  if (wall_gating() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m)))
+      return false;
+  if (var->arrowGating && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m)))
+      return false;
+
   // 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)
@@ -1216,13 +1246,13 @@ bool Position::pseudo_legal(const Move m) const {
       if (mandatory_pawn_promotion() && rank_of(to) == relative_rank(us, promotion_rank(), max_rank()) && !sittuyin_promotion())
           return false;
 
-      if (   !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
-          && !((from + pawn_push(us) == to) && empty(to))       // Not a single push
-          && !(   (from + 2 * pawn_push(us) == to)              // Not a double push
+      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())
-               && empty(to)
-               && empty(to - pawn_push(us))
+               && !(pieces() & to)
+               && !(pieces() & (to - pawn_push(us)))
                && double_step_enabled()))
           return false;
   }
@@ -1275,7 +1305,8 @@ bool Position::gives_check(Move m) const {
       return false;
 
   // Is there a direct check?
-  if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && type_of(m) != CASTLING)
+  if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && type_of(m) != CASTLING
+      && !(var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) != PAWN))
   {
       PieceType pt = type_of(moved_piece(m));
       if (AttackRiderTypes[pt] & (HOPPING_RIDERS | ASYMMETRICAL_RIDERS))
@@ -1304,6 +1335,10 @@ bool Position::gives_check(Move m) const {
       && attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square<KING>(~sideToMove))
       return true;
 
+  // Petrified piece can't give check
+  if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) != PAWN)
+      return false;
+
   // Is there a check by special diagonal moves?
   if (more_than_one(diagonal_lines() & (to | square<KING>(~sideToMove))))
   {
@@ -1628,7 +1663,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       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)))
+          && (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)];
@@ -1774,11 +1810,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       st->gatesBB[them] ^= square<KING>(them);
 
   // Remove the blast pieces
-  if (captured && blast_on_capture())
+  if (captured && (blast_on_capture() || var->petrifyOnCapture))
   {
       std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch));
       st->demotedBycatch = st->promotedBycatch = 0;
-      Bitboard blast = (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to;
+      Bitboard blast =  blast_on_capture() ? (attacks_bb<KING>(to) & (pieces() ^ pieces(PAWN))) | to
+                      : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0);
       while (blast)
       {
           Square bsq = pop_lsb(blast);
@@ -1838,9 +1875,34 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
              st->castlingRights &= ~castlingRightsMask[bsq];
              k ^= Zobrist::castling[st->castlingRights];
           }
+
+          // Make a wall square where the piece was
+          if (var->petrifyOnCapture)
+          {
+              st->wallSquares |= bsq;
+              byTypeBB[ALL_PIECES] |= bsq;
+              k ^= Zobrist::wall[bsq];
+          }
       }
   }
 
+  // Add gated wall square
+  if (wall_gating())
+  {
+      // Reset wall squares for duck gating
+      if (var->duckGating)
+      {
+          Bitboard b = st->previous->wallSquares;
+          byTypeBB[ALL_PIECES] ^= b;
+          while (b)
+              k ^= Zobrist::wall[pop_lsb(b)];
+          st->wallSquares = 0;
+      }
+      st->wallSquares |= gating_square(m);
+      byTypeBB[ALL_PIECES] |= gating_square(m);
+      k ^= Zobrist::wall[gating_square(m)];
+  }
+
   // Update the key with the final value
   st->key = k;
   // Calculate checkers bitboard (if move gives check)
@@ -1908,8 +1970,11 @@ void Position::undo_move(Move m) {
          || (is_pass(m) && pass()));
   assert(type_of(st->capturedPiece) != KING);
 
+  // Reset wall squares
+  byTypeBB[ALL_PIECES] ^= st->wallSquares ^ st->previous->wallSquares;
+
   // Add the blast pieces
-  if (st->capturedPiece && blast_on_capture())
+  if (st->capturedPiece && (blast_on_capture() || var->petrifyOnCapture))
   {
       Bitboard blast = attacks_bb<KING>(to) | to;
       while (blast)
@@ -2265,6 +2330,10 @@ bool Position::see_ge(Move m, Value threshold) const {
       if (pinners(~stm) & occupied)
           stmAttackers &= ~blockers_for_king(stm);
 
+      // Ignore distant sliders
+      if (var->duckGating)
+          stmAttackers &= attacks_bb<KING>(to) | ~(pieces(BISHOP, ROOK) | pieces(QUEEN));
+
       if (!stmAttackers)
           break;
 
@@ -2724,7 +2793,7 @@ bool Position::has_game_cycle(int ply) const {
 
   int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull);
 
-  if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal)
+  if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || var->duckGating)
     return false;
 
   Key originalKey = st->key;
index 68275f5..2ee2aee 100644 (file)
@@ -54,6 +54,7 @@ struct StateInfo {
   CheckCount checksRemaining[COLOR_NB];
   Square epSquare;
   Square castlingKingSquare[COLOR_NB];
+  Bitboard wallSquares;
   Bitboard gatesBB[COLOR_NB];
 
   // Not copied when making a move (will be recomputed anyhow)
@@ -171,7 +172,7 @@ public:
   PieceType drop_no_doubled() const;
   bool immobility_illegal() const;
   bool gating() const;
-  bool arrow_gating() const;
+  bool wall_gating() const;
   bool seirawan_gating() const;
   bool cambodian_moves() const;
   Bitboard diagonal_lines() const;
@@ -394,7 +395,7 @@ inline bool Position::two_boards() const {
 
 inline Bitboard Position::board_bb() const {
   assert(var != nullptr);
-  return board_size_bb(var->maxFile, var->maxRank);
+  return board_size_bb(var->maxFile, var->maxRank) & ~st->wallSquares;
 }
 
 inline Bitboard Position::board_bb(Color c, PieceType pt) const {
@@ -723,9 +724,9 @@ inline bool Position::gating() const {
   return var->gating;
 }
 
-inline bool Position::arrow_gating() const {
+inline bool Position::wall_gating() const {
   assert(var != nullptr);
-  return var->arrowGating;
+  return var->arrowGating || var->duckGating;
 }
 
 inline bool Position::seirawan_gating() const {
index b7947eb..fe4d6fb 100644 (file)
@@ -280,7 +280,14 @@ void MainThread::search() {
       // Send move only when not in analyze mode and not at game end
       if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true))
       {
-          sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl;
+          std::string move = UCI::move(rootPos, bestMove);
+          if (rootPos.wall_gating())
+          {
+              sync_cout << "move " << move.substr(0, move.find(",")) << "," << sync_endl;
+              sync_cout << "move " << move.substr(move.find(",") + 1) << sync_endl;
+          }
+          else
+              sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl;
           if (XBoard::stateMachine->moveAfterSearch)
           {
               XBoard::stateMachine->do_move(bestMove);
@@ -793,6 +800,8 @@ namespace {
             {
                 int penalty = -stat_bonus(depth);
                 thisThread->mainHistory[us][from_to(ttMove)] << penalty;
+                if (pos.wall_gating())
+                    thisThread->gateHistory[us][gating_square(ttMove)] << penalty;
                 update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
             }
         }
@@ -992,7 +1001,7 @@ namespace {
     {
         assert(probCutBeta < VALUE_INFINITE);
 
-        MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
+        MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->gateHistory, &captureHistory);
         int probCutCount = 0;
         bool ttPv = ss->ttPv;
         ss->ttPv = false;
@@ -1071,6 +1080,7 @@ moves_loop: // When in check, search starts from here
     Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
 
     MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+                                      &thisThread->gateHistory,
                                       &thisThread->lowPlyHistory,
                                       &captureHistory,
                                       contHist,
@@ -1175,7 +1185,7 @@ moves_loop: // When in check, search starts from here
                   continue;
 
               // Prune moves with negative SEE (~20 Elo)
-              if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth)))
+              if (!pos.variant()->duckGating && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth)))
                   continue;
           }
       }
@@ -1315,6 +1325,7 @@ moves_loop: // When in check, search starts from here
                   r++;
 
               ss->statScore =  thisThread->mainHistory[us][from_to(move)]
+                             + thisThread->gateHistory[us][gating_square(move)] * 2
                              + (*contHist[0])[history_slot(movedPiece)][to_sq(move)]
                              + (*contHist[1])[history_slot(movedPiece)][to_sq(move)]
                              + (*contHist[3])[history_slot(movedPiece)][to_sq(move)]
@@ -1615,6 +1626,7 @@ moves_loop: // When in check, search starts from here
     // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
     // will be generated.
     MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+                                      &thisThread->gateHistory,
                                       &thisThread->captureHistory,
                                       contHist,
                                       to_sq((ss-1)->currentMove));
@@ -1811,13 +1823,20 @@ moves_loop: // When in check, search starts from here
         // Decrease stats for all non-best quiet moves
         for (int i = 0; i < quietCount; ++i)
         {
-            thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
+            if (!(pos.wall_gating() && from_to(quietsSearched[i]) == from_to(bestMove)))
+                thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
+            if (pos.wall_gating())
+                thisThread->gateHistory[us][gating_square(quietsSearched[i])] << -bonus2;
             update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
         }
     }
     else
+    {
         // Increase stats for the best move in case it was a capture move
         captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+        if (pos.wall_gating())
+            thisThread->gateHistory[us][gating_square(bestMove)] << bonus1;
+    }
 
     // Extra penalty for a quiet early move that was not a TT move or
     // main killer move in previous ply when it gets refuted.
@@ -1830,7 +1849,10 @@ moves_loop: // When in check, search starts from here
     {
         moved_piece = pos.moved_piece(capturesSearched[i]);
         captured = type_of(pos.piece_on(to_sq(capturesSearched[i])));
-        captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
+        if (!(pos.wall_gating() && from_to(capturesSearched[i]) == from_to(bestMove)))
+            captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
+        if (pos.wall_gating())
+            thisThread->gateHistory[us][gating_square(capturesSearched[i])] << -bonus1;
     }
   }
 
@@ -1865,6 +1887,8 @@ moves_loop: // When in check, search starts from here
     Color us = pos.side_to_move();
     Thread* thisThread = pos.this_thread();
     thisThread->mainHistory[us][from_to(move)] << bonus;
+    if (pos.wall_gating())
+        thisThread->gateHistory[us][gating_square(move)] << bonus;
     update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
 
     // Penalty for reversed move in case of moved piece not being a pawn
index ffaaba2..32aaf58 100644 (file)
@@ -61,6 +61,7 @@ void Thread::clear() {
 
   counterMoves.fill(MOVE_NONE);
   mainHistory.fill(0);
+  gateHistory.fill(0);
   lowPlyHistory.fill(0);
   captureHistory.fill(0);
 
index bfb0d33..464be79 100644 (file)
@@ -71,6 +71,7 @@ public:
   Depth rootDepth, completedDepth;
   CounterMoveHistory counterMoves;
   ButterflyHistory mainHistory;
+  GateHistory gateHistory;
   LowPlyHistory lowPlyHistory;
   CapturePieceToHistory captureHistory;
   ContinuationHistory continuationHistory[2][2];
index 95b0516..4231ede 100644 (file)
@@ -228,7 +228,7 @@ constexpr int SQUARE_BITS = 6;
 #endif
 
 #ifdef ALLVARS
-constexpr int MAX_MOVES = 4096;
+constexpr int MAX_MOVES = 8192;
 #else
 constexpr int MAX_MOVES = 1024;
 #endif
index 34b7e0c..4f41b3f 100644 (file)
@@ -531,6 +531,10 @@ string UCI::move(const Position& pos, Move m) {
   string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (CurrentProtocol == USI ? '*' : '@')
                                     : UCI::square(pos, from)) + UCI::square(pos, to);
 
+  // Wall square
+  if (pos.wall_gating() && CurrentProtocol == XBOARD)
+      move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m));
+
   if (type_of(m) == PROMOTION)
       move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))];
   else if (type_of(m) == PIECE_PROMOTION)
@@ -544,6 +548,10 @@ string UCI::move(const Position& pos, Move m) {
           move += UCI::square(pos, gating_square(m));
   }
 
+  // Wall square
+  if (pos.wall_gating() && CurrentProtocol != XBOARD)
+      move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m));
+
   return move;
 }
 
index 782d51d..ff9f22b 100644 (file)
@@ -48,7 +48,7 @@ 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"
+    "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck"
 };
 
 void init_variant(const Variant* v) {
index 0b6be09..13a29b4 100644 (file)
@@ -405,6 +405,20 @@ namespace {
         v->extinctionPseudoRoyal = true;
         return v;
     }
+#ifdef ALLVARS
+    // Duck chess
+    Variant* duck_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->remove_piece(KING);
+        v->add_piece(COMMONER, 'k');
+        v->castlingKingPiece = COMMONER;
+        v->extinctionValue = -VALUE_MATE;
+        v->extinctionPieceTypes = {COMMONER};
+        v->duckGating = true;
+        v->stalemateValue = VALUE_MATE;
+        return v;
+    }
+#endif
     // Three-check chess
     // Check the king three times to win
     // https://lichess.org/variant/threeCheck
@@ -1175,6 +1189,21 @@ namespace {
         v->promotionPieceTypes = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
+    // Gustav III chess
+    // 10x8 variant with an amazon piece and wall squares
+    // https://www.chessvariants.com/play/gustav-iiis-chess
+    Variant* gustav3_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->pieceToCharTable = "PNBRQ.............AKpnbrq.............ak";
+        v->maxRank = RANK_8;
+        v->maxFile = FILE_J;
+        v->castlingKingsideFile = FILE_H;
+        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};
+        return v;
+    }
     // Jeson mor
     // Mongolian chess variant with knights only and a king of the hill like goal
     // https://en.wikipedia.org/wiki/Jeson_Mor
@@ -1285,6 +1314,26 @@ namespace {
         v->castling = false;
         return v;
     }
+    // Omicron chess
+    // Omega chess on a 12x10 board
+    // http://www.eglebbk.dds.nl/program/chess-omicron.html
+    Variant* omicron_variant() {
+        Variant* v = chess_variant_base()->init();
+        v->pieceToCharTable = "PNBRQ..C.W...........Kpnbrq..c.w...........k";
+        v->maxRank = RANK_10;
+        v->maxFile = FILE_L;
+        v->startFen = "w**********w/*crnbqkbnrc*/*pppppppppp*/*10*/*10*/*10*/*10*/*PPPPPPPPPP*/*CRNBQKBNRC*/W**********W w KQkq - 0 1";
+        v->add_piece(CUSTOM_PIECES, 'c', "DAW"); // Champion
+        v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); // Wizard
+        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;
+        return v;
+    }
     // Shako
     // 10x10 variant with cannons by Jean-Louis Cazaux
     // https://www.chessvariants.com/large.dir/shako.html
@@ -1332,13 +1381,12 @@ namespace {
     // https://en.wikipedia.org/wiki/Game_of_the_Amazons
     Variant* amazons_variant() {
         Variant* v = chess_variant_base()->init();
-        v->pieceToCharTable = "P...Q.................p...q.................";
+        v->pieceToCharTable = "....Q.....................q.................";
         v->maxRank = RANK_10;
         v->maxFile = FILE_J;
         v->reset_pieces();
         v->add_piece(CUSTOM_PIECES, 'q', "mQ");
-        v->add_piece(IMMOBILE_PIECE, 'p');
-        v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppppppppppppppppp] w - - 0 1";
+        v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3 w - - 0 1";
         v->stalemateValue = -VALUE_MATE;
         v->arrowGating = true;
         return v;
@@ -1489,6 +1537,9 @@ void VariantMap::init() {
     add("horde", horde_variant());
     add("nocheckatomic", nocheckatomic_variant());
     add("atomic", atomic_variant());
+#ifdef ALLVARS
+    add("duck", duck_variant());
+#endif
     add("3check", threecheck_variant());
     add("5check", fivecheck_variant());
     add("crazyhouse", crazyhouse_variant());
@@ -1539,11 +1590,13 @@ void VariantMap::init() {
     add("chancellor", chancellor_variant());
     add("embassy", embassy_variant());
     add("centaur", centaur_variant());
+    add("gustav3", gustav3_variant());
     add("jesonmor", jesonmor_variant());
     add("courier", courier_variant());
     add("grand", grand_variant());
     add("opulent", opulent_variant());
     add("tencubed", tencubed_variant());
+    add("omicron", omicron_variant());
     add("shako", shako_variant());
     add("clobber10", clobber10_variant());
     add("flipello10", flipello10_variant());
index d76e908..9b41538 100644 (file)
@@ -60,6 +60,7 @@ struct Variant {
   bool mandatoryPiecePromotion = false;
   bool pieceDemotion = false;
   bool blastOnCapture = false;
+  bool petrifyOnCapture = false;
   bool doubleStep = true;
   Rank doubleStepRank = RANK_2;
   Rank doubleStepRankMin = RANK_2;
@@ -96,6 +97,7 @@ struct Variant {
   bool immobilityIllegal = false;
   bool gating = false;
   bool arrowGating = false;
+  bool duckGating = false;
   bool seirawanGating = false;
   bool cambodianMoves = false;
   Bitboard diagonalLines = 0;
@@ -220,7 +222,7 @@ struct Variant {
               nnueKing = NO_PIECE_TYPE;
       }
       int nnueSquares = (maxRank + 1) * (maxFile + 1);
-      nnueUsePockets = (pieceDrops && (capturesToHand || (!mustDrop && !arrowGating && pieceTypes.size() != 1))) || seirawanGating;
+      nnueUsePockets = (pieceDrops && (capturesToHand || (!mustDrop && pieceTypes.size() != 1))) || seirawanGating;
       int nnuePockets = nnueUsePockets ? 2 * int(maxFile + 1) : 0;
       int nnueNonDropPieceIndices = (2 * pieceTypes.size() - (nnueKing != NO_PIECE_TYPE)) * nnueSquares;
       int nnuePieceIndices = nnueNonDropPieceIndices + 2 * (pieceTypes.size() - (nnueKing != NO_PIECE_TYPE)) * nnuePockets;
index 5253d6d..82ed5e7 100644 (file)
 # mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false)
 # pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false)
 # 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)
 # dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [PieceType] (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: allow gating in Game of the Amazons style [bool] (default: false)
+# arrowGating: gating of wall squares in Game of the Amazons style [bool] (default: false)
+# duckGating: gating of a wall square in Duck chess style [bool] (default: false)
 # seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false)
 # cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false)
 # diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard]
index 2b71476..085bc02 100755 (executable)
@@ -147,6 +147,8 @@ if [[ $1 == "all" ||  $1 == "largeboard" ]]; then
   expect perft.exp opulent startpos 3 133829 > /dev/null
   expect perft.exp tencubed startpos 3 68230 > /dev/null
   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 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
@@ -165,6 +167,7 @@ fi
 
 # special variants
 if [[ $1 == "all" ]]; then
+  expect perft.exp duck startpos 1 640 > /dev/null
   expect perft.exp amazons startpos 1 2176 > /dev/null
 fi