From 873e76a4983af174019413a3d940a0ecd0933587 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 14 Jan 2023 16:37:01 +0100 Subject: [PATCH] Support wall squares (#565) 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. --- README.md | 4 +- setup.py | 2 +- src/Makefile | 2 +- src/Makefile_js | 6 ++ src/apiutil.h | 14 +++- src/movegen.cpp | 44 +++++++----- src/movepick.cpp | 14 ++-- src/movepick.h | 7 ++- src/nnue/features/half_ka_v2_variants.cpp | 2 +- src/parser.cpp | 7 ++- src/position.cpp | 109 +++++++++++++++++++++++----- src/position.h | 9 ++- src/search.cpp | 34 ++++++++-- src/thread.cpp | 1 + src/thread.h | 1 + src/types.h | 2 +- src/uci.cpp | 8 ++ src/ucioption.cpp | 2 +- src/variant.cpp | 59 +++++++++++++++- src/variant.h | 4 +- src/variants.ini | 4 +- tests/perft.sh | 3 + 22 files changed, 267 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 1fd8227..b2ed1c4 100644 --- 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) diff --git a/setup.py b/setup.py index 9a25103..7dee176 100644 --- 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") diff --git a/src/Makefile b/src/Makefile index 3584386..602beef 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/Makefile_js b/src/Makefile_js index 9933922..79a7695 100644 --- a/src/Makefile_js +++ b/src/Makefile_js @@ -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 diff --git a/src/apiutil.h b/src/apiutil.h index 86bd208..f75d830 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -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; diff --git a/src/movegen.cpp b/src/movegen.cpp index fe90154..416f992 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -26,22 +26,29 @@ namespace Stockfish { namespace { template - 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(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(from, to, pt, pop_lsb(b)); return moveList; } - *moveList++ = make(from, to); + *moveList++ = make(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(to - D, to, pt); + moveList = make_move_and_gating(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(to - D, to); + moveList = make_move_and_gating(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(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(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(pos, moveList, Us, to - UpRight, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - UpLeft, to); + moveList = make_move_and_gating(pos, moveList, Us, to - UpLeft, to); } if (pos.ep_square() != SQ_NONE) @@ -238,7 +245,7 @@ namespace { assert(b1); while (b1) - *moveList++ = make(pop_lsb(b1), pos.ep_square()); + moveList = make_move_and_gating(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(pos, moveList, target); diff --git a/src/movepick.cpp b/src/movepick.cpp index b16c309..eac77e8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -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)] diff --git a/src/movepick.h b/src/movepick.h index d19af6a..9047ee7 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -88,6 +88,8 @@ enum StatsType { NoCaptures, Captures }; /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards typedef Stats ButterflyHistory; +typedef Stats 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; diff --git a/src/nnue/features/half_ka_v2_variants.cpp b/src/nnue/features/half_ka_v2_variants.cpp index 8551ebf..0807241 100644 --- a/src/nnue/features/half_ka_v2_variants.cpp +++ b/src/nnue/features/half_ka_v2_variants.cpp @@ -53,7 +53,7 @@ namespace Stockfish::Eval::NNUE::Features { ValueListInserter 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); diff --git a/src/parser.cpp b/src/parser.cpp index 196cfca..79c838f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -293,6 +293,7 @@ Variant* VariantParser::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::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::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::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. diff --git a/src/position.cpp b/src/position.cpp index 6cbdaa9..b62a803 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -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(); + for (Square s = SQ_A1; s <= SQ_MAX; ++s) + Zobrist::wall[s] = rng.rand(); + // 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(). - 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(*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(~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(~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(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(to) & (pieces() ^ pieces(PAWN))) | to; + Bitboard blast = blast_on_capture() ? (attacks_bb(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(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(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; diff --git a/src/position.h b/src/position.h index 68275f5..2ee2aee 100644 --- a/src/position.h +++ b/src/position.h @@ -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 { diff --git a/src/search.cpp b/src/search.cpp index b7947eb..fe4d6fb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -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 diff --git a/src/thread.cpp b/src/thread.cpp index ffaaba2..32aaf58 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -61,6 +61,7 @@ void Thread::clear() { counterMoves.fill(MOVE_NONE); mainHistory.fill(0); + gateHistory.fill(0); lowPlyHistory.fill(0); captureHistory.fill(0); diff --git a/src/thread.h b/src/thread.h index bfb0d33..464be79 100644 --- a/src/thread.h +++ b/src/thread.h @@ -71,6 +71,7 @@ public: Depth rootDepth, completedDepth; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; + GateHistory gateHistory; LowPlyHistory lowPlyHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; diff --git a/src/types.h b/src/types.h index 95b0516..4231ede 100644 --- a/src/types.h +++ b/src/types.h @@ -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 diff --git a/src/uci.cpp b/src/uci.cpp index 34b7e0c..4f41b3f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -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; } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 782d51d..ff9f22b 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -48,7 +48,7 @@ namespace UCI { std::set 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) { diff --git a/src/variant.cpp b/src/variant.cpp index 0b6be09..13a29b4 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -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()); diff --git a/src/variant.h b/src/variant.h index d76e908..9b41538 100644 --- a/src/variant.h +++ b/src/variant.h @@ -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; diff --git a/src/variants.ini b/src/variants.ini index 5253d6d..82ed5e7 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -157,6 +157,7 @@ # 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) @@ -191,7 +192,8 @@ # 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] diff --git a/tests/perft.sh b/tests/perft.sh index 2b71476..085bc02 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -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 -- 1.7.0.4