diff --git a/src/movegen.cpp b/src/movegen.cpp index 0c14dd75b..b658619d3 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -41,13 +41,23 @@ namespace { } if (T == EN_PASSANT) b ^= pos.capture_square(to); - if (pos.variant()->arrowWalling) + + if (pos.walling_rule() == ARROW) b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from); - if ((pos.variant()->staticWalling)||(pos.variant()->duckWalling)) - b &= pos.variant()->wallingRegion[us]; - if (pos.variant()->pastWalling) + + //Any current or future wall variant must follow the walling region rule if set: + b &= pos.variant()->wallingRegion[us]; + + if (pos.walling_rule() == PAST) b &= square_bb(from); + if (pos.walling_rule() == EDGE) + { + Bitboard wallsquares = pos.state()->wallSquares; + b &= (FileABB | file_bb(pos.max_file()) | Rank1BB | rank_bb(pos.max_rank())) | + ( shift(wallsquares) | shift(wallsquares) + | shift(wallsquares) | shift(wallsquares)); + } while (b) *moveList++ = make_gating(from, to, pt, pop_lsb(b)); return moveList; diff --git a/src/parser.cpp b/src/parser.cpp index 3dbceb6a1..6b0c9bb31 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -111,6 +111,16 @@ namespace { return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value =="snort" || value == "none"; } + template <> bool set(const std::string& value, WallingRule& target) { + target = value == "arrow" ? ARROW + : value == "duck" ? DUCK + : value == "edge" ? EDGE + : value == "past" ? PAST + : value == "static" ? STATIC + : NO_WALLING; + return value == "arrow" || value == "duck" || value == "edge" || value =="past" || value == "static" || value == "none"; + } + template <> bool set(const std::string& value, Bitboard& target) { std::string symbol; std::stringstream ss(value); @@ -198,6 +208,7 @@ template bool VariantParser::parse_attribute(co : std::is_same() ? "EnclosingRule" : std::is_same() ? "Bitboard" : std::is_same() ? "CastlingRights" + : std::is_same() ? "WallingRule" : typeid(T).name(); std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; } @@ -451,14 +462,11 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("dropNoDoubledCount", v->dropNoDoubledCount); parse_attribute("immobilityIllegal", v->immobilityIllegal); parse_attribute("gating", v->gating); - parse_attribute("arrowWalling", v->arrowWalling); - parse_attribute("duckWalling", v->duckWalling); + parse_attribute("wallingRule", v->wallingRule); parse_attribute("wallingRegionWhite", v->wallingRegion[WHITE]); parse_attribute("wallingRegionBlack", v->wallingRegion[BLACK]); parse_attribute("wallingRegion", v->wallingRegion[WHITE]); parse_attribute("wallingRegion", v->wallingRegion[BLACK]); - parse_attribute("staticWalling", v->staticWalling); - parse_attribute("pastWalling", v->pastWalling); parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("diagonalLines", v->diagonalLines); @@ -505,6 +513,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("flagPieceCount", v->flagPieceCount); parse_attribute("flagPieceBlockedWin", v->flagPieceBlockedWin); parse_attribute("flagMove", v->flagMove); + parse_attribute("flagPieceSafe", v->flagPieceSafe); parse_attribute("checkCounting", v->checkCounting); parse_attribute("connectN", v->connectN); parse_attribute("connectHorizontal", v->connectHorizontal); @@ -574,8 +583,8 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl; // Check for limitations - if (v->pieceDrops && (v->arrowWalling || v->duckWalling || v->staticWalling || v->pastWalling)) - std::cerr << "pieceDrops and arrowWalling/duckWalling are incompatible." << std::endl; + if (v->pieceDrops && v->wallingRule) + std::cerr << "pieceDrops and any walling are incompatible." << std::endl; // Options incompatible with royal kings if (v->pieceTypes & KING) @@ -584,8 +593,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->duckWalling) - std::cerr << "Can not use kings with duckWalling." << std::endl; + if (v->wallingRule==DUCK) + std::cerr << "Can not use kings with wallingRule = duck." << 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. @@ -611,6 +620,8 @@ Variant* VariantParser::parse(Variant* v) { if (v->mutuallyImmuneTypes) std::cerr << "Can not use kings or pseudo-royal with mutuallyImmuneTypes." << std::endl; } + if (v->flagPieceSafe && v->blastOnCapture) + std::cerr << "Can not use flagPieceSafe with blastOnCapture (flagPieceSafe uses simple assessment that does not see blast)." << std::endl; } return v; } diff --git a/src/position.cpp b/src/position.cpp index 4933a25ca..439bbd260 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1105,7 +1105,7 @@ bool Position::legal(Move m) const { { Square kto = to; Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()); - if (var->duckWalling) + if (walling_rule() == DUCK) occupied ^= st->wallSquares; if (walling() || is_gating(m)) occupied |= gating_square(m); @@ -1304,15 +1304,29 @@ bool Position::pseudo_legal(const Move m) const { return checkers() ? MoveList< EVASIONS>(*this).contains(m) : MoveList(*this).contains(m); - // Illegal wall square placement - if (walling() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) - return false; - if (var->arrowWalling && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) - return false; - if (var->pastWalling && (from != gating_square(m))) - return false; - if ((var->staticWalling || var->duckWalling) && !(var->wallingRegion[us] & gating_square(m))) - return false; + if (walling()) + { + Bitboard wallsquares = st->wallSquares; + + // Illegal wall square placement + if (!((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) + return false; + if (!(var->wallingRegion[us] & gating_square(m)) || //putting a wall on disallowed square + wallsquares & gating_square(m)) //or square already with a wall + return false; + if (walling_rule() == ARROW && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) + return false; + if (walling_rule() == PAST && (from != gating_square(m))) + return false; + if (walling_rule() == EDGE) + { + Bitboard validsquares = board_bb() & + ((FileABB | file_bb(max_file()) | Rank1BB | rank_bb(max_rank())) | + ( shift(wallsquares) | shift(wallsquares) + | shift(wallsquares) | shift(wallsquares))); + if (!(validsquares & gating_square(m))) return false; + }; + } // Handle the case where a mandatory piece promotion/demotion is not taken if ( mandatory_piece_promotion() @@ -2035,7 +2049,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if (walling()) { // Reset wall squares for duck walling - if (var->duckWalling) + if (walling_rule() == DUCK) { Bitboard b = st->previous->wallSquares; byTypeBB[ALL_PIECES] ^= b; @@ -2477,7 +2491,7 @@ bool Position::see_ge(Move m, Value threshold) const { stmAttackers &= ~blockers_for_king(stm); // Ignore distant sliders - if (var->duckWalling) + if (walling_rule() == DUCK) stmAttackers &= attacks_bb(to) | ~(pieces(BISHOP, ROOK) | pieces(QUEEN)); if (!stmAttackers) @@ -3001,7 +3015,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 || var->duckWalling) + if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || walling_rule() == DUCK) return false; Key originalKey = st->key; diff --git a/src/position.h b/src/position.h index 34b09763b..7b9b4cd8d 100644 --- a/src/position.h +++ b/src/position.h @@ -179,6 +179,7 @@ class Position { bool immobility_illegal() const; bool gating() const; bool walling() const; + WallingRule walling_rule() const; bool seirawan_gating() const; bool cambodian_moves() const; Bitboard diagonal_lines() const; @@ -761,7 +762,12 @@ inline bool Position::gating() const { inline bool Position::walling() const { assert(var != nullptr); - return var->arrowWalling || var->duckWalling || var->staticWalling || var->pastWalling; + return var->wallingRule != NO_WALLING; +} + +inline WallingRule Position::walling_rule() const { + assert(var != nullptr); + return var->wallingRule; } inline bool Position::seirawan_gating() const { @@ -956,9 +962,40 @@ inline bool Position::flag_move() const { inline bool Position::flag_reached(Color c) const { assert(var != nullptr); - return (flag_region(c) & pieces(c, flag_piece(c))) + bool simpleResult = + (flag_region(c) & pieces(c, flag_piece(c))) && ( popcount(flag_region(c) & pieces(c, flag_piece(c))) >= var->flagPieceCount || (var->flagPieceBlockedWin && !(flag_region(c) & ~pieces()))); + + if (simpleResult&&var->flagPieceSafe) + { + Bitboard piecesInFlagZone = flag_region(c) & pieces(c, flag_piece(c)); + int potentialPieces = (popcount(piecesInFlagZone)); + /* + There isn't a variant that uses it, but in the hypothetical game where the rules say I need 3 + pieces in the flag zone and they need to be safe: If I have 3 pieces there, but one is under + threat, I don't think I can declare victory. If I have 4 there, but one is under threat, I + think that's victory. + */ + while (piecesInFlagZone) + { + Square sr = pop_lsb(piecesInFlagZone); + Bitboard flagAttackers = attackers_to(sr, ~c); + + if ((potentialPieces < var->flagPieceCount) || (potentialPieces >= var->flagPieceCount + 1)) break; + while (flagAttackers) + { + Square currentAttack = pop_lsb(flagAttackers); + if (legal(make_move(currentAttack, sr))) + { + potentialPieces--; + break; + } + } + } + return potentialPieces >= var->flagPieceCount; + } + return simpleResult; } inline bool Position::check_counting() const { diff --git a/src/search.cpp b/src/search.cpp index 2502fcc2c..77686ed0d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1185,7 +1185,7 @@ namespace { continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.variant()->duckWalling && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) + if (!(pos.walling_rule() == DUCK) && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) continue; } } diff --git a/src/types.h b/src/types.h index 99c5e1aa3..2fa03e7c4 100644 --- a/src/types.h +++ b/src/types.h @@ -305,6 +305,10 @@ enum EnclosingRule { NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE, SNORT }; +enum WallingRule { + NO_WALLING, ARROW, DUCK, EDGE, PAST, STATIC +}; + enum OptBool { NO_VALUE, VALUE_FALSE, VALUE_TRUE }; diff --git a/src/variant.cpp b/src/variant.cpp index 2fc1c9c49..8a4246817 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -515,7 +515,7 @@ namespace { v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); - v->duckWalling = true; + v->wallingRule = DUCK; v->stalemateValue = VALUE_MATE; return v; } @@ -529,7 +529,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture v->startFen = "3p2/6/6/6/6/6/6/2P3 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->staticWalling = true; + v->wallingRule = STATIC; v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_C1, SQ_D8); return v; } @@ -551,7 +551,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture v->startFen = "6p/7/7/7/7/7/P6 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->pastWalling = true; + v->wallingRule = PAST; return v; } @@ -562,7 +562,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'n', "mN"); //move as a Knight, but can't capture v->startFen = "8/8/8/4n3/3N4/8/8/8 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->pastWalling = true; + v->wallingRule = PAST; return v; } @@ -1668,7 +1668,7 @@ namespace { v->add_piece(CUSTOM_PIECE_1, 'q', "mQ"); v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->arrowWalling = true; + v->wallingRule = ARROW; return v; } #endif diff --git a/src/variant.h b/src/variant.h index 1114fd8cb..4d3297791 100644 --- a/src/variant.h +++ b/src/variant.h @@ -107,10 +107,7 @@ struct Variant { int dropNoDoubledCount = 1; bool immobilityIllegal = false; bool gating = false; - bool arrowWalling = false; - bool duckWalling = false; - bool staticWalling = false; - bool pastWalling = false; + WallingRule wallingRule = NO_WALLING; Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares}; bool seirawanGating = false; bool cambodianMoves = false; @@ -150,6 +147,7 @@ struct Variant { int flagPieceCount = 1; bool flagPieceBlockedWin = false; bool flagMove = false; + bool flagPieceSafe = false; bool checkCounting = false; int connectN = 0; bool connectHorizontal = true; diff --git a/src/variants.ini b/src/variants.ini index 51b152e30..0a5679426 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -131,6 +131,12 @@ # [CountingRule]: makruk, cambodian, or ASEAN counting rules [makruk, cambodian, asean, none] # [ChasingRule]: xiangqi chasing rules [axf, none] # [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, quadwrangle, snort, none] +# [WallingRule]: wall-placing rule [arrow, duck, edge, past, static, none] +# - arrow: copies piece movement (ie. Game of the Amazons) +# - duck: mobile square (ie. Duck chess) +# - edge: edges of board, opening up new edges (ie. Atlantis) +# - past: previous square (ie. Snailtrail) +# - static: unchanging mask (ie. Isolation) ### Additional options relevant for usage in Winboard/XBoard # A few options only have the purpose of improving compatibility with Winboard/Xboard. @@ -198,7 +204,7 @@ # mustDropType: piece type for which piece drops are mandatory [PieceType] (default: *) # pieceDrops: enable piece drops [bool] (default: false) # dropLoop: captures promoted pieces are not demoted [bool] (default: false) -# capturesToHand: captured pieces are go to opponent's hand [bool] (default: false) +# capturesToHand: captured pieces go to opponent's hand [bool] (default: false) # firstRankPawnDrops: allow pawn drops to first rank [bool] (default: false) # promotionZonePawnDrops: allow pawn drops in promotion zone [bool] (default: false) # dropOnTop: piece drops need to be on top of pieces on board (e.g., for connect4) [bool] (default: false) @@ -213,12 +219,9 @@ # dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [int] (default: 1) # immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false) # gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) -# arrowWalling: walling squares in Game of the Amazons style [bool] (default: false) -# duckWalling: walling square in Duck chess style [bool] (default: false) -# staticWalling: walling squares in Isolation style [bool] (default: false) +# wallingRule: rule on where wall can be placed [WallingRule] (default: none) # wallingRegionWhite: mask where wall squares (including duck) can be placed by white [Bitboard] (default: all squares) # wallingRegionBlack: mask where wall squares (including duck) can be placed by black [Bitboard] (default: all squares) -# pastWalling: walling of previous square in Snailtrail 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] @@ -260,6 +263,7 @@ # flagPieceCount: number of flag pieces that have to be in the flag zone [int] (default: 1) # flagPieceBlockedWin: for flagPieceCount > 1, win if at least one flag piece in flag zone and all others occupied by pieces [bool] (default: false) # flagMove: the other side gets one more move after one reaches the flag zone [bool] (default: false) +# flagPieceSafe: the flag piece must be safe to win [bool] (default: false) # checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) # connectN: number of aligned pieces for win [int] (default: 0) # connectVertical: connectN looks at Vertical rows [bool] (default: true) @@ -1353,7 +1357,7 @@ pieceToCharTable = P...Q..AH..ECTDY....LKp...q..ah..ectdy....lk # Atomic + duck chess hybrid. # Playable as a custom variant in chess.com [atomicduck:atomic] -duckWalling = true +wallingRule = duck stalemateValue = win #https://www.chessvariants.com/diffmove.dir/checkers.html @@ -1559,26 +1563,18 @@ nFoldRule = 2 [alapo:chess] #https://www.chessvariants.org/small.dir/alapo.html -#Reaching the opponent's back row such that the piece isn't immediately -#captured is a win. Let's promote to a victory piece (Amazon), then, moving -#that piece to anywhere not on the back row is a victory. There's nothing about -#the Amazon in the rules, just a powerful piece. -pieceToCharTable = ..BRQ........AFW.....K..brq........afw.....k +pieceToCharTable = ..BRQ.........FW.....K..brq.........fw.....k maxRank = 6 maxFile = f wazir = w fers = f -amazon = a king = - commoner = k startFen = rbqqbr/wfkkfw/6/6/WFKKFW/RBQQBR -promotionRegionWhite = *6 -promotionRegionBlack = *1 -promotedPieceType = w:a r:a f:a b:a k:a q:a -mandatoryPiecePromotion = true -flagPiece = a -flagRegionWhite = *5 *4 *3 *2 *1 -flagRegionBlack = *6 *5 *4 *3 *2 +flagRegionWhite = *6 +flagRegionBlack = *1 +flagPieceSafe = true +flagMove = true stalemateValue = loss nMoveRule = 0 nFoldRule = 0 @@ -1741,6 +1737,12 @@ castlingRank = 2 [castle:chess] castlingWins = q +#https://github.com/yagu0/vchess/blob/master/client/src/translations/rules/Squatter1/en.pug +[squatter:chess] +flagRegionWhite = *8 +flagRegionBlack = *1 +flagPieceSafe = true + [opposite-castling:chess] oppositeCastling = true @@ -1786,3 +1788,43 @@ connectRegion1Black = *1 connectRegion2Black = *9 #should be impossible anyway connectDiagonal = false + +#https://www.chessvariants.com/boardrules.dir/atlantis.html +[atlantis:chess] +wallingRule = edge +#not ready yet. Other wall variants are "move and wall", this is "move or wall". +#need to figure out way to do this ie. write code for: +#wallOrMove = true + +#https://www.chessvariants.com/rules/ajax-orthodox-chess +[ajax-orthodox:chess] +pieceToCharTable = PNBRQ.............MKpnbrq.............mk +customPiece1 = r:RmF +customPiece2 = n:NmK +customPiece3 = b:BmW +customPiece1 = m:KAD +promotionPieceTypes = mqnbr +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[MMmm] w KQkq - 0 1 +pieceDrops = true +whiteDropRegion = *1 +blackDropRegion = *8 + +#https://www.chessvariants.com/small.dir/petty.html +[petty:chess] +maxRank = 6 +maxFile = 5 +startFen = qkbnr/ppppp/5/5/PPPPP/QKBNR w - 0 1 +castling = false +doubleStep = false +promotionRegionWhite = *6 + +#https://www.chessvariants.com/small.dir/haynie.html +[haynie:chess] +maxRank = 6 +maxFile = 6 +startFen = rbqkbr/pppppp/6/6/PPPPPP/RBQKBR w KQkq - 0 1 +doubleStep = false +promotionPieceTypes = rbq +castlingQueensideFile = c +castlingKingsideFile = e +promotionRegionWhite = *6