From 40885b42c30eabaa4f875fcd323b998135e37944 Mon Sep 17 00:00:00 2001 From: alperatalayn Date: Mon, 12 Feb 2024 18:06:44 +0300 Subject: [PATCH] musketeer variant --- README.md | 1 + src/apiutil.h | 34 +++++++--- src/parser.cpp | 1 + src/position.cpp | 157 +++++++++++++++++++++++++++++++++++++++++----- src/position.h | 45 +++++++++++++ src/uci.cpp | 31 ++++++++- src/ucioption.cpp | 2 +- src/variant.cpp | 31 +++++++++ src/variant.h | 1 + src/variants.ini | 1 + src/xboard.cpp | 6 ++ 11 files changed, 284 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index f118cf304..f6057f10b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca - [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, [Dragon Chess](https://www.edami.com/dragonchess/) +- [Musketeer](https://www.musketeerchess.net) - [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) diff --git a/src/apiutil.h b/src/apiutil.h index d39102fcd..98b3d1b54 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -558,14 +558,22 @@ inline std::vector get_fen_parts(const std::string& fullFen, char d inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard, const std::string& validSpecialCharactersFirstField, const Variant* v) { int rankIdx = 0; int fileIdx = 0; + bool firstRankSkipped = false; char prevChar = '?'; for (char c : fenBoard) { if (c == ' ' || c == '[') break; - if (c == '*') - ++fileIdx; + if (c == '*') { + if (v->commitGates) + { + // just ignore? + } + else { + ++fileIdx; + } + } else if (isdigit(c)) { fileIdx += c - '0'; @@ -575,14 +583,26 @@ inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard, } else if (c == '/') { - ++rankIdx; - if (fileIdx != board.get_nb_files()) - { - std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl; - return NOK; + if (v->commitGates && rankIdx == 0 && !firstRankSkipped) { + firstRankSkipped = true; + // ignore starting 'xx******/' + } + else { + ++rankIdx; + if (fileIdx != board.get_nb_files()) + { + std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl; + return NOK; + } } if (rankIdx == board.get_nb_ranks()) + { + if (v->commitGates) + { + rankIdx--; // pretend we didn't see the ending '/xx******' + } break; + } fileIdx = 0; } else if (!contains(validSpecialCharactersFirstField, c)) diff --git a/src/parser.cpp b/src/parser.cpp index 3ebf1b16f..62dd6e0bd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -474,6 +474,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("wallingRegion", v->wallingRegion[BLACK]); parse_attribute("wallOrMove", v->wallOrMove); parse_attribute("seirawanGating", v->seirawanGating); + parse_attribute("commitGates", v->commitGates); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("diagonalLines", v->diagonalLines); parse_attribute("pass", v->pass[WHITE]); diff --git a/src/position.cpp b/src/position.cpp index b9f34be44..2c9aa4214 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -267,10 +267,13 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, Rank r = max_rank(); Square sq = SQ_A1 + r * NORTH; + int commitFile = 0; + int rank = 0; + // 1. Piece placement while ((ss >> token) && !isspace(token)) { - if (isdigit(token)) + if (isdigit(token) && (!commit_gates() || (rank != 0 && rank != max_rank() + 2))) { #ifdef LARGEBOARDS if (isdigit(ss.peek())) @@ -284,7 +287,20 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, else if (token == '/') { - sq = SQ_A1 + --r * NORTH; + if(commit_gates()) + { + if(rank != 0 && rank <= max_rank()){ + sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST; + } + else if (rank == max_rank() + 1) { + sq = SQ_A1; // dummy to proceed with white musketeer pieces setup + } + ++rank; + commitFile = 0; + } + else { + sq = SQ_A1 + --r * NORTH; + } if (!is_ok(sq)) break; } @@ -297,28 +313,49 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, else if (!is_ok(sq) || file_of(sq) > max_file() || rank_of(sq) > r) continue; - // Wall square else if (token == '*') { - st->wallSquares |= sq; - byTypeBB[ALL_PIECES] |= sq; - ++sq; + if(commit_gates()) + { + // musketeer + ++commitFile; + } + else { + // Wall square + 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() == '~') ss >> token; - put_piece(Piece(idx), sq, token == '~'); - ++sq; + + if(v->commitGates && (rank == 0 || rank == max_rank() + 2)) + { + commit_piece(Piece(idx), File(commitFile)); + ++commitFile; + } + else{ + put_piece(Piece(idx), sq, token == '~'); + ++sq; + } } // Promoted shogi pieces else if (token == '+' && (idx = piece_to_char().find(ss.peek())) != string::npos && promoted_piece_type(type_of(Piece(idx)))) { ss >> token; - put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx)); - ++sq; + if(v->commitGates && (rank == 0 || rank == max_rank() + 2)){ + commit_piece(Piece(idx), File(commitFile)); + ++commitFile; + } + else { + put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx)); + ++sq; + } } } // Pieces in hand @@ -377,7 +414,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, } // Set gates (and skip castling rights) - if (gating()) + if (gating() && !commit_gates()) { st->gatesBB[c] |= rsq; if (token == 'K' || token == 'Q') @@ -604,6 +641,9 @@ void Position::set_state(StateInfo* si) const { si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; si->checkersBB = count(sideToMove) ? attackers_to(square(sideToMove), ~sideToMove) : Bitboard(0); si->move = MOVE_NONE; + si->removedGatingType = NO_PIECE_TYPE; + si->removedCastlingGatingType = NO_PIECE_TYPE; + si->capturedGatingType = NO_PIECE_TYPE; set_check_info(si); @@ -680,6 +720,13 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string int emptyCnt; std::ostringstream ss; + if(commit_gates()){ + for(File f = FILE_A; f <= max_file(); ++f){ + if(has_committed_piece(BLACK, f)) ss << piece_to_char()[make_piece(BLACK, committedGates[BLACK][f])]; + else ss << "*"; + } + ss << "/"; + } for (Rank r = max_rank(); r >= RANK_1; --r) { @@ -713,7 +760,13 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string if (r > RANK_1) ss << '/'; } - + if(commit_gates()){ + ss << "/"; + for(File f = FILE_A; f <= max_file(); ++f){ + if(has_committed_piece(WHITE, f)) ss << piece_to_char()[make_piece(WHITE, committedGates[WHITE][f])]; + else ss << "*"; + } + } // SFEN if (sfen) { @@ -733,7 +786,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string } // pieces in hand - if (!variant()->freeDrops && (piece_drops() || seirawan_gating())) + if (!variant()->freeDrops && (piece_drops() || seirawan_gating()) && !commit_gates()) { ss << '['; if (holdings != "-") @@ -760,7 +813,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string if (can_castle(WHITE_OOO)) ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); - if (gating() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) > 0 || captures_to_hand())) + if (gating() && !commit_gates() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) > 0 || captures_to_hand())) for (File f = FILE_A; f <= max_file(); ++f) if ( (gates(WHITE) & file_bb(f)) // skip gating flags redundant with castling flags @@ -788,7 +841,7 @@ string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string && !(can_castle(BLACK_OOO) && f == file_of(castling_rook_square(BLACK_OOO)))) ss << char('a' + f); - if (!can_castle(ANY_CASTLING) && !(gating() && (gates(WHITE) | gates(BLACK)))) + if (!can_castle(ANY_CASTLING) && !(gating() && !commit_gates() && (gates(WHITE) | gates(BLACK)))) ss << '-'; // Counting limit or ep-square @@ -1536,6 +1589,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st = &newSt; st->move = m; + if (commit_gates()) { + st->removedGatingType = NO_PIECE_TYPE; + st->removedCastlingGatingType = NO_PIECE_TYPE; + st->capturedGatingType = NO_PIECE_TYPE; + } // Increment ply counters. In particular, rule50 will be reset to zero later on // in case of a capture or a pawn move. ++gamePly; @@ -1945,6 +2003,27 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[MG][gating_piece]; } + // Musketeer gating + if(commit_gates()){ + { + Rank r = rank_of(from); + if(r == RANK_1 && has_committed_piece(WHITE, file_of(from))){ + st->removedGatingType = drop_committed_piece(WHITE, file_of(from)); + } else if(r == max_rank() && has_committed_piece(BLACK, file_of(from))){ + st->removedGatingType = drop_committed_piece(BLACK, file_of(from)); + } + } + if (captured) { + // remove uncommitted musketeer piece if piece at the front row is captured + Rank r = rank_of(to); + if (r == RANK_1 && color_of(captured) == WHITE){ + st->capturedGatingType = uncommit_piece(WHITE, file_of(to)); + } else if (r == max_rank() && color_of(captured) == BLACK) { + st->capturedGatingType = uncommit_piece(BLACK, file_of(to)); + } + } + else st->removedGatingType = NO_PIECE_TYPE; + } // Remove gates if (gating()) { @@ -2128,7 +2207,9 @@ void Position::undo_move(Move m) { assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) || (type_of(m) == PROMOTION && sittuyin_promotion()) - || (is_pass(m) && (pass(us) || var->wallOrMove))); + || (is_pass(m) && (pass(us) || var->wallOrMove)) + || (commit_gates() && st->removedGatingType > NO_PIECE_TYPE) + ); assert(type_of(st->capturedPiece) != KING); // Reset wall squares @@ -2169,6 +2250,15 @@ void Position::undo_move(Move m) { st->gatesBB[us] |= gating_square(m); } + if(commit_gates() && st->removedGatingType > NO_PIECE_TYPE){ + commit_piece(piece_on(from), file_of(from)); + remove_piece( from ); + } + if (commit_gates() && st->capturedPiece && st->capturedGatingType > NO_PIECE_TYPE){ + // return musketeer piece fronted by the captured piece + commit_piece(make_piece(color_of(st->capturedPiece), st->capturedGatingType), file_of(to)); + } + if (type_of(m) == PROMOTION) { assert((promotion_zone(us) & to) || sittuyin_promotion()); @@ -2259,6 +2349,11 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ to = make_square(kingSide ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); rto = to + (kingSide ? WEST : EAST); + if (!Do && commit_gates() && st->removedCastlingGatingType > NO_PIECE_TYPE) { + commit_piece(piece_on(rfrom), file_of(rfrom)); + remove_piece(rfrom); + } + Piece castlingKingPiece = piece_on(Do ? from : to); Piece castlingRookPiece = piece_on(Do ? rfrom : rto); @@ -2280,6 +2375,11 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us put_piece(castlingKingPiece, Do ? to : from); put_piece(castlingRookPiece, Do ? rto : rfrom); + + if (Do && commit_gates() && has_committed_piece(us, file_of(rfrom))) { + st->removedCastlingGatingType = drop_committed_piece(us, file_of(rfrom)); + } + } @@ -3280,4 +3380,29 @@ bool Position::pos_is_ok() const { return true; } +PieceType Position::committed_piece_type(Move m, bool castlingRook) const { + PieceType result = NO_PIECE_TYPE; + if (commit_gates()) { + Square from = from_sq(m); + Rank r = rank_of(from); + if (castlingRook){ + if (type_of(m) == CASTLING){ + from = to_sq(m); + } else { + from = SQ_NONE; + } + } + if (from != SQ_NONE){ + if (r == RANK_1){ + result = committed_piece_type(WHITE, file_of(from)); + } else if (r == max_rank()){ + result = committed_piece_type(BLACK, file_of(from)); + } else{ + assert(false); + } + } + } + return result; +} + } // namespace Stockfish diff --git a/src/position.h b/src/position.h index adaeeb6ad..40bd05cec 100644 --- a/src/position.h +++ b/src/position.h @@ -83,6 +83,9 @@ struct StateInfo { bool pass; Move move; int repetition; + PieceType removedGatingType; + PieceType removedCastlingGatingType; + PieceType capturedGatingType; // Used by NNUE Eval::NNUE::Accumulator accumulator; @@ -180,6 +183,7 @@ class Position { bool walling() const; WallingRule walling_rule() const; bool seirawan_gating() const; + bool commit_gates() const; bool cambodian_moves() const; Bitboard diagonal_lines() const; bool pass(Color c) const; @@ -279,6 +283,7 @@ class Position { bool gives_check(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; + PieceType committed_piece_type(Move m, bool castlingRook) const; // Piece specific bool pawn_passed(Color c, Square s) const; @@ -369,12 +374,18 @@ class Position { bool tsumeMode; bool chess960; int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; + PieceType committedGates[COLOR_NB][FILE_NB]; int virtualPieces; Bitboard promotedPieces; void add_to_hand(Piece pc); void remove_from_hand(Piece pc); void drop_piece(Piece pc_hand, Piece pc_drop, Square s); void undrop_piece(Piece pc_hand, Square s); + void commit_piece(Piece pc, File fl); + PieceType uncommit_piece(Color cl, File fl); + PieceType committed_piece_type(Color cl, File fl) const; + bool has_committed_piece(Color cl, File fl) const; + PieceType drop_committed_piece(Color cl, File fl); Bitboard find_drop_region(Direction dir, Square s, Bitboard occupied) const; }; @@ -799,6 +810,11 @@ inline WallingRule Position::walling_rule() const { return var->wallingRule; } +inline bool Position::commit_gates() const { + assert(var != nullptr); + return var->commitGates; +} + inline bool Position::seirawan_gating() const { assert(var != nullptr); return var->seirawanGating; @@ -1561,6 +1577,35 @@ inline bool Position::can_drop(Color c, PieceType pt) const { return variant()->freeDrops || count_in_hand(c, pt) > 0; } +inline void Position::commit_piece(Piece pc, File fl){ + committedGates[color_of(pc)][fl] = type_of(pc); +} + +inline PieceType Position::uncommit_piece(Color cl, File fl){ + PieceType committedPieceType = committedGates[cl][fl]; + committedGates[cl][fl] = NO_PIECE_TYPE; + return committedPieceType; +} + +inline PieceType Position::committed_piece_type(Color cl, File fl) const { + return committedGates[cl][fl]; +} + +inline bool Position::has_committed_piece(Color cl, File fl) const { + return committed_piece_type(cl,fl) > NO_PIECE_TYPE; +} + +inline PieceType Position::drop_committed_piece(Color cl, File fl){ + if(has_committed_piece(cl, fl)){ + Square dropSquare = make_square(fl, (cl == WHITE)? RANK_1 : max_rank()); + PieceType committedPieceType = committedGates[cl][fl]; + put_piece(make_piece(cl, committedPieceType), dropSquare, false, NO_PIECE); + uncommit_piece(cl, fl); + return committedPieceType; + } + else return NO_PIECE_TYPE; +} + } // namespace Stockfish #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/uci.cpp b/src/uci.cpp index 584977aad..e62cc9439 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -574,9 +574,36 @@ Move UCI::to_move(const Position& pos, string& str) { str[4] = char(tolower(str[4])); } - for (const auto& m : MoveList(pos)) - if (str == UCI::move(pos, m) || (is_pass(m) && str == UCI::square(pos, from_sq(m)) + UCI::square(pos, to_sq(m)))) + for (const auto& m : MoveList(pos)) { + auto move_str = UCI::move(pos, m); + + // special processing of optional gating suffix from xboard + // like "b1c3o" => "b1c3" + if (CurrentProtocol == XBOARD && str.length() == 5 && move_str.length() == 4) { + if (memcmp(str.c_str(), move_str.c_str(), 4) == 0){ + PieceType pt = pos.committed_piece_type(m, false); + PieceType ptCastling = pos.committed_piece_type(m, true); + if ( + ( + pt != NO_PIECE_TYPE + && + pos.piece_to_char()[make_piece(BLACK, pt)] == str[4] + ) + || + ( + ptCastling != NO_PIECE_TYPE + && + pos.piece_to_char()[make_piece(BLACK, ptCastling)] == str[4] + ) + ) { + return m; + } + } + } + + if (str == move_str || (is_pass(m) && str == UCI::square(pos, from_sq(m)) + UCI::square(pos, to_sq(m)))) return m; + } return MOVE_NONE; } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 0326e047f..7f9fc1af5 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -109,7 +109,7 @@ void on_variant_change(const Option &o) { } // Send setup command sync_cout << "setup (" << v->pieceToCharTable << ") " - << v->maxFile + 1 << "x" << v->maxRank + 1 + << v->maxFile + 1 << "x" << v->maxRank + 1 + ( v->commitGates ? 2 : 0 ) << "+" << pocketsize << "_" << v->variantTemplate << " " << v->startFen << sync_endl; diff --git a/src/variant.cpp b/src/variant.cpp index bf61a0493..16db69b45 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -1209,6 +1209,36 @@ namespace { return v; } #ifdef LARGEBOARDS + // Musketeer Chess + // https://musketeerchess.net + // A Seirawan-inspired variant with unique gating mechanics. + // Pieces are introduced to predefined squares, chosen before game start, this is named Gating Selection = Where the chosen piece is going to be gated + // Gating of the additional pieces is activated when first-rank pieces move for the first time. Only the additional piece waiting to be gated on that specific square can be introduced. + // Features a variety of new pieces, thus there is a piece selection step where both players must agree to chose the additional piece combination. + // In Fairy Stockfish the Piece Selection is determined at the PieceToCharTable, this default combination can be changed in variant.ini + Variant* musketeer_variant() { + Variant* v = chess_variant(); + v->variantTemplate = "seirawan"; + v->pieceToCharTable = "PNBRQ.E....C.AF.MH.SU........D............LKpnbrq.e....c.af.mh.su........d............lk"; // The default piece combo in Musketeer Chess is Leopard L and Musketeer Cannon O + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'm'); + v->add_piece(AMAZON, 'd'); // also called Dragon in Musketeer, but Amazon is the most accurate + v->add_piece(CUSTOM_PIECE_1, 'l', "B2N"); // Leopard + v->add_piece(CUSTOM_PIECE_2, 'h', "ADGH"); // Hawk + v->add_piece(CUSTOM_PIECE_3, 'u', "NC"); // Unicorn + v->add_piece(CUSTOM_PIECE_4, 's', "B2ND"); // Spider + v->add_piece(CUSTOM_PIECE_5, 'f', "B3vND"); // Fortress + v->add_piece(CUSTOM_PIECE_6, 'e', "FWDA"); // Musketeer Elephant + v->add_piece(CUSTOM_PIECE_7, 'c', "FWDsN"); // Musketeer Cannon + + //"********/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/******** w KQkq - 0 1" + v->startFen = "lc******/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/LC****** w KQkq - 0 1"; + v->gating = true; + v->commitGates = true; + v->promotionPieceTypes[BLACK] = piece_set(CUSTOM_PIECE_1) | CUSTOM_PIECE_7 | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[WHITE] = piece_set(CUSTOM_PIECE_1) | CUSTOM_PIECE_7 | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } // Shogi (Japanese chess) // https://en.wikipedia.org/wiki/Shogi Variant* shogi_variant() { @@ -1880,6 +1910,7 @@ void VariantMap::init() { add("minixiangqi", minixiangqi_variant()); add("raazuvaa", raazuvaa_variant()); #ifdef LARGEBOARDS + add("musketeer", musketeer_variant()); add("shogi", shogi_variant()); add("shoshogi", shoshogi_variant()); add("yarishogi", yarishogi_variant()); diff --git a/src/variant.h b/src/variant.h index aa2738356..7dea254fd 100644 --- a/src/variant.h +++ b/src/variant.h @@ -110,6 +110,7 @@ struct Variant { Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares}; bool wallOrMove = false; bool seirawanGating = false; + bool commitGates = false; bool cambodianMoves = false; Bitboard diagonalLines = 0; bool pass[COLOR_NB] = {false, false}; diff --git a/src/variants.ini b/src/variants.ini index 688cce0d3..c25d953d3 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -234,6 +234,7 @@ # wallingRegionBlack: mask where wall squares (including duck) can be placed by black [Bitboard] (default: all squares) # wallOrMove: can wall or move, but not both [bool] (default: false) # seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false) +# commitGates: gating pieces are committed to gating squares like in Musketeer chess [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] # pass: allow passing [bool] (default: false) diff --git a/src/xboard.cpp b/src/xboard.cpp index 09228edb3..5b3600963 100644 --- a/src/xboard.cpp +++ b/src/xboard.cpp @@ -146,6 +146,9 @@ namespace XBoard { // Generate color FEN int emptyCnt; std::ostringstream ss; + if (pos.variant()->commitGates) { + ss << pos.max_file() + 1 << "/"; + } for (Rank r = pos.max_rank(); r >= RANK_1; --r) { for (File f = FILE_A; f <= pos.max_file(); ++f) @@ -163,6 +166,9 @@ namespace XBoard { if (r > RANK_1) ss << '/'; } + if (pos.variant()->commitGates) { + ss << "/" << pos.max_file() + 1; + } return ss.str(); }