From b8274a7ac6cc850e712ad8fb25397d090a511a86 Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 26 Sep 2023 02:44:24 -0700 Subject: [PATCH 01/11] flagPieceSafe, Squatter (#719) --- src/parser.cpp | 3 +++ src/position.h | 33 ++++++++++++++++++++++++++++++++- src/variant.h | 1 + src/variants.ini | 25 ++++++++++++------------- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 3d822ebb4..48b8cc9af 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -496,6 +496,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); @@ -598,6 +599,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.h b/src/position.h index 2311776da..7b9b4cd8d 100644 --- a/src/position.h +++ b/src/position.h @@ -962,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/variant.h b/src/variant.h index cb2fe0514..e8899f397 100644 --- a/src/variant.h +++ b/src/variant.h @@ -147,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 e3bdf0dad..73bf9e99c 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -263,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) @@ -1558,26 +1559,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 @@ -1740,6 +1733,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 From 5d3af0900b842f81097d0c0f7a2efb9cf2af2c5a Mon Sep 17 00:00:00 2001 From: RainRat Date: Fri, 29 Sep 2023 01:43:19 -0700 Subject: [PATCH 02/11] add Ajax Orthodox, Petty, Haynie (#730) --- src/variants.ini | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/variants.ini b/src/variants.ini index 73bf9e99c..104b038c9 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -204,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) @@ -1777,4 +1777,37 @@ stalemateValue = loss 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 \ No newline at end of file +#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 From 3f40adab6b506d02172291ad36bbab8dd1c105ac Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 24 Oct 2023 13:03:24 -0700 Subject: [PATCH 03/11] Add Cfour-anyside, Symphony, Teeko (#731) --- src/parser.cpp | 10 ++++++++-- src/position.cpp | 13 ++++++++++++ src/position.h | 52 ++++++++++++++++++++++++++++++++++++++---------- src/types.h | 2 +- src/variant.h | 2 +- src/variants.ini | 42 +++++++++++++++++++++++++++++++++++--- 6 files changed, 104 insertions(+), 17 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 48b8cc9af..1c4b4faf3 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -107,8 +107,10 @@ namespace { : value == "ataxx" ? ATAXX : value == "quadwrangle" ? QUADWRANGLE : value == "snort" ? SNORT + : value == "anyside" ? ANYSIDE + : value == "top" ? TOP : NO_ENCLOSING; - return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value =="snort" || value == "none"; + return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value =="snort" || value =="anyside" || value =="top" || value == "none"; } template <> bool set(const std::string& value, WallingRule& target) { @@ -327,6 +329,10 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("castlingRookPiece", v->castlingRookPieces[WHITE], v->pieceToChar); parse_attribute("castlingRookPiece", v->castlingRookPieces[BLACK], v->pieceToChar); + bool dropOnTop = false; + parse_attribute("dropOnTop", dropOnTop); + if (dropOnTop) v->enclosingDrop=TOP; + // Parse aliases parse_attribute("pawnTypes", v->promotionPawnType[WHITE], v->pieceToChar); parse_attribute("pawnTypes", v->promotionPawnType[BLACK], v->pieceToChar); @@ -433,7 +439,6 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("capturesToHand", v->capturesToHand); parse_attribute("firstRankPawnDrops", v->firstRankPawnDrops); parse_attribute("promotionZonePawnDrops", v->promotionZonePawnDrops); - parse_attribute("dropOnTop", v->dropOnTop); parse_attribute("enclosingDrop", v->enclosingDrop); parse_attribute("enclosingDropStart", v->enclosingDropStart); parse_attribute("whiteDropRegion", v->whiteDropRegion); @@ -502,6 +507,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("connectHorizontal", v->connectHorizontal); parse_attribute("connectVertical", v->connectVertical); parse_attribute("connectDiagonal", v->connectDiagonal); + parse_attribute("connectNxN", v->connectNxN); parse_attribute("materialCounting", v->materialCounting); parse_attribute("countingRule", v->countingRule); parse_attribute("castlingWins", v->castlingWins); diff --git a/src/position.cpp b/src/position.cpp index 39f4888e2..c72a07f90 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2806,6 +2806,19 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } } + + if (connect_nxn()) + { + Bitboard connectors = pieces(~sideToMove); + for (int i = 1; i < connect_nxn() && connectors; i++) + connectors &= shift(connectors) & shift(connectors) & shift(connectors); + if (connectors) + { + result = mated_in(ply); + return true; + } + } + // Check for bikjang rule (Janggi) and double passing if (st->pliesFromNull > 0 && ((st->bikjang && st->previous->bikjang) || (st->pass && st->previous->pass))) { diff --git a/src/position.h b/src/position.h index 7b9b4cd8d..81c61dd49 100644 --- a/src/position.h +++ b/src/position.h @@ -167,7 +167,6 @@ class Position { bool drop_loop() const; bool captures_to_hand() const; bool first_rank_pawn_drops() const; - bool drop_on_top() const; bool can_drop(Color c, PieceType pt) const; EnclosingRule enclosing_drop() const; Bitboard drop_region(Color c) const; @@ -210,6 +209,7 @@ class Position { bool connect_vertical() const; bool connect_diagonal() const; const std::vector& getConnectDirections() const; + int connect_nxn() const; CheckCount checks_remaining(Color c) const; MaterialCounting material_counting() const; @@ -373,6 +373,7 @@ class Position { 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); + Bitboard find_drop_region(Direction dir, Square s, Bitboard occupied) const; }; extern std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -648,11 +649,6 @@ inline bool Position::first_rank_pawn_drops() const { return var->firstRankPawnDrops; } -inline bool Position::drop_on_top() const { - assert(var != nullptr); - return var->dropOnTop; -} - inline EnclosingRule Position::enclosing_drop() const { assert(var != nullptr); return var->enclosingDrop; @@ -666,9 +662,6 @@ inline Bitboard Position::drop_region(Color c) const { inline Bitboard Position::drop_region(Color c, PieceType pt) const { Bitboard b = drop_region(c) & board_bb(c, pt); - // Connect4-style drops - if (drop_on_top()) - b &= shift(pieces()) | Rank1BB; // Pawns on back ranks if (pt == PAWN) { @@ -686,7 +679,6 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const { if (pt == ROOK && sittuyin_rook_drop()) b &= rank_bb(relative_rank(c, RANK_1, max_rank())); - // Filter out squares where the drop does not enclose at least one opponent's piece if (enclosing_drop()) { // Reversi start @@ -694,6 +686,7 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const { b &= var->enclosingDropStart; else { + // Filter out squares where the drop does not enclose at least one opponent's piece if (enclosing_drop() == REVERSI) { Bitboard theirs = pieces(~c); @@ -715,6 +708,40 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const { b &= ~(shift(theirs) | shift(theirs) | shift(theirs) | shift(theirs)); } + else if (enclosing_drop() == ANYSIDE) + { + Bitboard occupied = pieces(); + b = 0ULL; + Bitboard candidates = (shift(occupied) | file_bb(max_file())) & ~occupied; + + for (Rank r = RANK_1; r <= max_rank(); ++r) { + if (!(occupied & make_square(FILE_A, r))) { + b |= lsb(candidates & rank_bb(r)); + } + } + candidates = (shift(occupied) | rank_bb(max_rank())) & ~occupied; + for (File f = FILE_A; f <= max_file(); ++f) { + if (!(occupied & make_square(f, RANK_1))) { + b |= lsb(candidates & file_bb(f)); + } + } + candidates = (shift(occupied) | rank_bb(RANK_1)) & ~occupied; + for (File f = FILE_A; f <= max_file(); ++f) { + if (!(occupied & make_square(f, max_rank()))) { + b |= lsb(candidates & file_bb(f)); + } + } + candidates = (shift(occupied) | file_bb(FILE_A)) & ~occupied; + for (Rank r = RANK_1; r <= max_rank(); ++r) { + if (!(occupied & make_square(max_file(), r))) { + b |= lsb(candidates & rank_bb(r)); + } + } + } + else if (enclosing_drop() == TOP) + { + b &= shift(pieces()) | Rank1BB; + } else { assert(enclosing_drop() == ATAXX); @@ -1026,6 +1053,11 @@ inline const std::vector& Position::getConnectDirections() const { return var->connect_directions; } +inline int Position::connect_nxn() const { + assert(var != nullptr); + return var->connectNxN; +} + inline CheckCount Position::checks_remaining(Color c) const { return st->checksRemaining[c]; } diff --git a/src/types.h b/src/types.h index 2fa03e7c4..5abfac80b 100644 --- a/src/types.h +++ b/src/types.h @@ -302,7 +302,7 @@ enum ChasingRule { }; enum EnclosingRule { - NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE, SNORT + NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE, SNORT, ANYSIDE, TOP }; enum WallingRule { diff --git a/src/variant.h b/src/variant.h index e8899f397..79cdc5a6e 100644 --- a/src/variant.h +++ b/src/variant.h @@ -95,7 +95,6 @@ struct Variant { bool capturesToHand = false; bool firstRankPawnDrops = false; bool promotionZonePawnDrops = false; - bool dropOnTop = false; EnclosingRule enclosingDrop = NO_ENCLOSING; Bitboard enclosingDropStart = 0; Bitboard whiteDropRegion = AllSquares; @@ -153,6 +152,7 @@ struct Variant { bool connectHorizontal = true; bool connectVertical = true; bool connectDiagonal = true; + int connectNxN = 0; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; CastlingRights castlingWins = NO_CASTLING; diff --git a/src/variants.ini b/src/variants.ini index 104b038c9..38c6cc7f8 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -130,7 +130,17 @@ # [MaterialCounting]: material counting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] # [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] +# [EnclosingRule]: reversi, ataxx, etc. enclosing rules [reversi, ataxx, quadwrangle, snort, anyside, top, none] +# - in enclosingDrop: +# - reversi: must enclose opponent's pieces between yours by Queen move +# - ataxx: must be adjacent to own piece by King move +# - snort: most *not* be adjacent to opponent's piece by Wazir move +# - anyside: must be reached by inserting from an edge and sliding to opposite edge +# - top: must be reached by inserting from top and sliding to bottom (ie. Connect 4) +# - in flipEnclosedPieces: +# - reversi: flip opponent's pieces enclosed between yours by Queen move +# - quadwrangle: if a normal move *or* a drop with a friendly piece adjacent by King move, then flip opponent's pieces adjacent by King move +# - ataxx: flip opponent's pieces adjacent by King move # [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) @@ -207,7 +217,7 @@ # 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) +# dropOnTop: DEPRECATED, use "enclosingDrop = top" # enclosingDrop: require piece drop to enclose pieces [EnclosingRule] (default: none) # enclosingDropStart: drop region for starting phase disregarding enclosingDrop (e.g., for reversi) [Bitboard] # whiteDropRegion: restrict region for piece drops of all white pieces [Bitboard] @@ -269,6 +279,7 @@ # connectVertical: connectN looks at Vertical rows [bool] (default: true) # connectHorizontal: connectN looks at Horizontal rows [bool] (default: true) # connectDiagonal: connectN looks at Diagonal rows [bool] (default: true) +# connectNxN: connect a tight NxN square for win [int] (default: 0) # materialCounting: enable material counting rules [MaterialCounting] (default: none) # countingRule: enable counting rules [CountingRule] (default: none) # castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -) @@ -490,7 +501,7 @@ maxFile = 7 immobile = p startFen = 7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w - - 0 1 pieceDrops = true -dropOnTop = true +enclosingDrop = top doubleStep = false castling = false stalemateValue = draw @@ -1801,6 +1812,15 @@ castling = false doubleStep = false promotionRegionWhite = *6 +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=655 +[teeko:picaria] +maxRank = 5 +maxFile = 5 +connectN = 4 +connectNxN = 2 +customPiece1 = p:mK +startFen = 5/5/5/5/5[PPPPpppp] w - - 0 1 + #https://www.chessvariants.com/small.dir/haynie.html [haynie:chess] maxRank = 6 @@ -1811,3 +1831,19 @@ promotionPieceTypes = rbq castlingQueensideFile = c castlingKingsideFile = e promotionRegionWhite = *6 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1723 +[symphony:tictactoe] +maxRank = 8 +maxFile = 8 +connectN = 5 +customPiece1 = p:mfsW +nFoldRule = 3 +startFen = 8/8/8/8/8/8/8/8[PPPPPPpppppp] w - - 0 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=734 +#am calling it cfour-anyside so it's less confusable with roll-ing-to-four +[cfour-anyside:cfour] +maxRank = 7 +startFen = 7/7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppp] w - - 0 1 +enclosingDrop = anyside From 88f4b3d1505d6405910e1f41bf3341b5c6f0cb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bajusz=20Tam=C3=A1s?= Date: Wed, 25 Oct 2023 14:29:05 +0200 Subject: [PATCH 04/11] Update wheels.yml (#742) --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1eb31a56f..ed14ee97a 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/setup-python@v3 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.11.2 + run: python -m pip install cibuildwheel==2.16.2 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse @@ -31,7 +31,7 @@ jobs: env: MACOSX_DEPLOYMENT_TARGET: "10.14" CIBW_ARCHS_MACOS: "x86_64 arm64" - CIBW_SKIP: "pp* *-win32 *-manylinux_i686 *-musllinux_* cp36-*" + CIBW_SKIP: "pp* *-win32 *-manylinux_i686 *-musllinux_* cp36-* cp37-*" CIBW_TEST_COMMAND: python {project}/test.py - uses: actions/upload-artifact@v3 From a621470b91757699f935ba06d5f4bf48a60574b1 Mon Sep 17 00:00:00 2001 From: RainRat Date: Wed, 25 Oct 2023 13:20:50 -0700 Subject: [PATCH 05/11] add La Mancha, Argess, 4 Kings Quasi Shatranj (#739) --- src/variants.ini | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/variants.ini b/src/variants.ini index 38c6cc7f8..926fa4231 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -1832,6 +1832,44 @@ castlingQueensideFile = c castlingKingsideFile = e promotionRegionWhite = *6 +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1367 +[la-mancha-squeez:snailtrail] +pieceToCharTable = P.....................p..................... +maxRank = 9 +maxFile = 9 +startFen = p7P/9/9/9/3p*P3/9/9/9/p7P w 0 1 + +[la-mancha-duel:la-mancha-squeez] +customPiece1 = p:K + +#https://www.chessvariants.com/diffsetup.dir/argess.html +[argess:chess] +pawnTypes = p +customPiece1 = p:mWcF +#yes, black moves first +startFen = rppppnbk/6qb/7n/7p/PPPP3p/RNPP3p/BQNP3p/KBRP3r b 0 1 +castling = false +promotionRegionWhite = g8 h8 h7 +promotionRegionBlack = a1 a2 b1 + +#https://www.chessvariants.com/rules/4-kings-quasi-shatranj +[quasi-shatranj:twokings2] +pieceToCharTable = PN....E...G..FZ....IAKpn....e...g..fz....iak +maxRank = 10 +maxFile = 10 +customPiece1 = a:AD +customPiece2 = e:AF +customPiece3 = f:AW +customPiece4 = i:DF +customPiece5 = z:DW +customPiece6 = g:K +extinctionPieceCount = 3 +startFen = kifkaakfik/znegaagenz/pppppppppp/10/10/10/10/PPPPPPPPPP/ZNEGAAGENZ/KIFKAAKFIK w 0 1 +promotionRegionWhite = *10 +promotionPieceTypes = zangief +doubleStepRegionWhite = *3 +doubleStepRegionBlack = *8 + #https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1723 [symphony:tictactoe] maxRank = 8 @@ -1847,3 +1885,4 @@ startFen = 8/8/8/8/8/8/8/8[PPPPPPpppppp] w - - 0 maxRank = 7 startFen = 7/7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppp] w - - 0 1 enclosingDrop = anyside + From dc28770df1a9394e231f2985a7b188571df9655a Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 31 Oct 2023 03:26:38 -0700 Subject: [PATCH 06/11] add Gale (#724) and improve bitboard parsing --- src/parser.cpp | 31 ++++++++++++++++++++++++++----- src/position.cpp | 26 ++++++++++++++++++++++++++ src/variant.h | 2 ++ src/variants.ini | 17 +++++++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 1c4b4faf3..c925b2dee 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -124,19 +124,36 @@ namespace { } template <> bool set(const std::string& value, Bitboard& target) { - char file; - int rank; + std::string symbol; std::stringstream ss(value); target = 0; - while (!ss.eof() && ss >> file && file != '-' && ss >> rank) + while (!ss.eof() && ss >> symbol && symbol != "-") { - if (Rank(rank - 1) > RANK_MAX || (file != '*' && File(tolower(file) - 'a') > FILE_MAX)) + if (symbol.back() == '*') { + if (isalpha(symbol[0]) && symbol.length() == 2) { + char file = tolower(symbol[0]); + if (File(file - 'a') > FILE_MAX) return false; + target |= file_bb(File(file - 'a')); + } else { + return false; + } + } else if (symbol[0] == '*') { + int rank = std::stoi(symbol.substr(1)); + if (Rank(rank - 1) > RANK_MAX) return false; + target |= rank_bb(Rank(rank - 1)); + } else if (isalpha(symbol[0]) && symbol.length() > 1) { + char file = tolower(symbol[0]); + int rank = std::stoi(symbol.substr(1)); + if (Rank(rank - 1) > RANK_MAX || File(file - 'a') > FILE_MAX) return false; + target |= square_bb(make_square(File(file - 'a'), Rank(rank - 1))); + } else { return false; - target |= file == '*' ? rank_bb(Rank(rank - 1)) : square_bb(make_square(File(tolower(file) - 'a'), Rank(rank - 1))); + } } return !ss.fail(); } + template <> bool set(const std::string& value, CastlingRights& target) { char c; CastlingRights castlingRight; @@ -507,6 +524,10 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("connectHorizontal", v->connectHorizontal); parse_attribute("connectVertical", v->connectVertical); parse_attribute("connectDiagonal", v->connectDiagonal); + parse_attribute("connectRegion1White", v->connectRegion1[WHITE]); + parse_attribute("connectRegion2White", v->connectRegion2[WHITE]); + parse_attribute("connectRegion1Black", v->connectRegion1[BLACK]); + parse_attribute("connectRegion2Black", v->connectRegion2[BLACK]); parse_attribute("connectNxN", v->connectNxN); parse_attribute("materialCounting", v->materialCounting); parse_attribute("countingRule", v->countingRule); diff --git a/src/position.cpp b/src/position.cpp index c72a07f90..9b8ce391d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2807,6 +2807,32 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } + if ((var->connectRegion1[~sideToMove] & pieces(~sideToMove)) && (var->connectRegion2[~sideToMove] & pieces(~sideToMove))) + { + Bitboard target = var->connectRegion2[~sideToMove]; + Bitboard current = var->connectRegion1[~sideToMove] & pieces(~sideToMove); + + while (true) { + Bitboard newBitboard = 0; + for (Direction d : var->connect_directions) { + newBitboard |= shift(d, current | newBitboard) & pieces(~sideToMove); // the "| newBitboard" here probably saves a few loops + } + + if (newBitboard & target) { + // A connection has been made + result = mated_in(ply); + return true; + } + + if (!(newBitboard & ~current)) { + // The expansion got stuck; no further squares to explore + break; + } + + current |= newBitboard; + } + } + if (connect_nxn()) { Bitboard connectors = pieces(~sideToMove); diff --git a/src/variant.h b/src/variant.h index 79cdc5a6e..49bc54404 100644 --- a/src/variant.h +++ b/src/variant.h @@ -152,6 +152,8 @@ struct Variant { bool connectHorizontal = true; bool connectVertical = true; bool connectDiagonal = true; + Bitboard connectRegion1[COLOR_NB] = {}; + Bitboard connectRegion2[COLOR_NB] = {}; int connectNxN = 0; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; diff --git a/src/variants.ini b/src/variants.ini index 926fa4231..b7dd011f9 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -279,6 +279,10 @@ # connectVertical: connectN looks at Vertical rows [bool] (default: true) # connectHorizontal: connectN looks at Horizontal rows [bool] (default: true) # connectDiagonal: connectN looks at Diagonal rows [bool] (default: true) +# connectRegion1White: connect Region 1 to Region 2 for win. obeys connectVertical, connectHorizontal, connectDiagonal [Bitboard] (default: -) +# connectRegion2White: " +# connectRegion1Black: " +# connectRegion2Black: " # connectNxN: connect a tight NxN square for win [int] (default: 0) # materialCounting: enable material counting rules [MaterialCounting] (default: none) # countingRule: enable counting rules [CountingRule] (default: none) @@ -1783,6 +1787,19 @@ extinctionPieceTypes = kq extinctionPseudoRoyal = true stalemateValue = loss +#https://www.ludii.games/details.php?keyword=Gale +[gale:snort] +maxRank = 9 +maxFile = 9 +startFen = 1p1p1p1p1/P1P1P1P1P/1p1p1p1p1/P1P1P1P1P/1p1p1p1p1/P1P1P1P1P/1p1p1p1p1/P1P1P1P1P/1p1p1p1p1 +enclosingDrop = none +connectRegion1White = a* +connectRegion2White = i* +connectRegion1Black = *1 +connectRegion2Black = *9 +#should be impossible anyway +connectDiagonal = false + #https://www.chessvariants.com/boardrules.dir/atlantis.html [atlantis:chess] wallingRule = edge From ae72c8e94f52804c5a325a7197e349d3a236e512 Mon Sep 17 00:00:00 2001 From: RainRat Date: Sun, 26 Nov 2023 13:30:48 -0800 Subject: [PATCH 07/11] Cfour-misere, per-color passing (#746) --- src/movegen.cpp | 4 ++-- src/parser.cpp | 11 +++++++++-- src/position.cpp | 14 +++++++------- src/position.h | 12 ++++++------ src/variant.cpp | 12 ++++++++---- src/variant.h | 5 +++-- src/variants.ini | 8 ++++++++ src/xboard.cpp | 2 +- 8 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index b658619d3..43807bb0b 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -441,7 +441,7 @@ namespace { } // Workaround for passing: Execute a non-move with any piece - if (pos.pass() && !pos.count(Us) && pos.pieces(Us)) + if (pos.pass(Us) && !pos.count(Us) && pos.pieces(Us)) *moveList++ = make(lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); } @@ -454,7 +454,7 @@ namespace { moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(b)); // Passing move by king - if (pos.pass()) + if (pos.pass(Us)) *moveList++ = make(ksq, ksq); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) diff --git a/src/parser.cpp b/src/parser.cpp index c925b2dee..7535e686e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -475,8 +475,14 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("diagonalLines", v->diagonalLines); - parse_attribute("pass", v->pass); - parse_attribute("passOnStalemate", v->passOnStalemate); + parse_attribute("pass", v->pass[WHITE]); + parse_attribute("pass", v->pass[BLACK]); + parse_attribute("passWhite", v->pass[WHITE]); + parse_attribute("passBlack", v->pass[BLACK]); + parse_attribute("passOnStalemate", v->passOnStalemate[WHITE]); + parse_attribute("passOnStalemate", v->passOnStalemate[BLACK]); + parse_attribute("passOnStalemateWhite", v->passOnStalemate[WHITE]); + parse_attribute("passOnStalemateBlack", v->passOnStalemate[BLACK]); parse_attribute("makpongRule", v->makpongRule); parse_attribute("flyingGeneral", v->flyingGeneral); parse_attribute("soldierPromotionRank", v->soldierPromotionRank); @@ -529,6 +535,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("connectRegion1Black", v->connectRegion1[BLACK]); parse_attribute("connectRegion2Black", v->connectRegion2[BLACK]); parse_attribute("connectNxN", v->connectNxN); + parse_attribute("connectValue", v->connectValue); parse_attribute("materialCounting", v->materialCounting); parse_attribute("countingRule", v->countingRule); parse_attribute("castlingWins", v->castlingWins); diff --git a/src/position.cpp b/src/position.cpp index 9b8ce391d..a7ada05c6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1092,7 +1092,7 @@ bool Position::legal(Move m) const { return false; // Illegal king passing move - if (pass_on_stalemate() && is_pass(m) && !checkers()) + if (pass_on_stalemate(us) && is_pass(m) && !checkers()) { for (const auto& move : MoveList(*this)) if (!is_pass(move) && legal(move)) @@ -1557,7 +1557,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Piece captured = piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to); if (to == from) { - assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass())); + assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass(us))); captured = NO_PIECE; } st->capturedpromoted = is_promoted(to); @@ -2126,7 +2126,7 @@ 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())); + || (is_pass(m) && pass(us))); assert(type_of(st->capturedPiece) != KING); // Reset wall squares @@ -2564,7 +2564,7 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } -/// Position::is_optinal_game_end() tests whether the position may end the game by +/// Position::is_optional_game_end() tests whether the position may end the game by /// 50-move rule, by repetition, or a variant rule that allows a player to claim a game result. bool Position::is_optional_game_end(Value& result, int ply, int countStarted) const { @@ -2801,7 +2801,7 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { b &= shift(d, b); if (b) { - result = mated_in(ply); + result = convert_mate_value(-var->connectValue, ply); return true; } } @@ -2820,7 +2820,7 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { if (newBitboard & target) { // A connection has been made - result = mated_in(ply); + result = convert_mate_value(-var->connectValue, ply); return true; } @@ -2840,7 +2840,7 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { connectors &= shift(connectors) & shift(connectors) & shift(connectors); if (connectors) { - result = mated_in(ply); + result = convert_mate_value(-var->connectValue, ply); return true; } } diff --git a/src/position.h b/src/position.h index 81c61dd49..4ee936657 100644 --- a/src/position.h +++ b/src/position.h @@ -182,8 +182,8 @@ class Position { bool seirawan_gating() const; bool cambodian_moves() const; Bitboard diagonal_lines() const; - bool pass() const; - bool pass_on_stalemate() const; + bool pass(Color c) const; + bool pass_on_stalemate(Color c) const; Bitboard promoted_soldiers(Color c) const; bool makpong() const; EnclosingRule flip_enclosed_pieces() const; @@ -812,14 +812,14 @@ inline Bitboard Position::diagonal_lines() const { return var->diagonalLines; } -inline bool Position::pass() const { +inline bool Position::pass(Color c) const { assert(var != nullptr); - return var->pass || var->passOnStalemate; + return var->pass[c] || var->passOnStalemate[c]; } -inline bool Position::pass_on_stalemate() const { +inline bool Position::pass_on_stalemate(Color c) const { assert(var != nullptr); - return var->passOnStalemate; + return var->passOnStalemate[c]; } inline Bitboard Position::promoted_soldiers(Color c) const { diff --git a/src/variant.cpp b/src/variant.cpp index 8a4246817..cd565baf2 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -1136,7 +1136,8 @@ namespace { v->immobilityIllegal = false; v->stalemateValue = -VALUE_MATE; v->stalematePieceCount = true; - v->passOnStalemate = true; + v->passOnStalemate[WHITE] = true; + v->passOnStalemate[BLACK] = true; v->enclosingDrop = ATAXX; v->flipEnclosedPieces = ATAXX; v->materialCounting = UNWEIGHTED_MATERIAL; @@ -1160,7 +1161,8 @@ namespace { v->immobilityIllegal = false; v->stalemateValue = -VALUE_MATE; v->stalematePieceCount = true; - v->passOnStalemate = false; + v->passOnStalemate[WHITE] = false; + v->passOnStalemate[BLACK] = false; v->enclosingDrop = REVERSI; v->enclosingDropStart = make_bitboard(SQ_D4, SQ_E4, SQ_D5, SQ_E5); v->flipEnclosedPieces = REVERSI; @@ -1172,7 +1174,8 @@ namespace { Variant* flipello_variant() { Variant* v = flipersi_variant()->init(); v->startFen = "8/8/8/3pP3/3Pp3/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1"; - v->passOnStalemate = true; + v->passOnStalemate[WHITE] = true; + v->passOnStalemate[BLACK] = true; return v; } // Minixiangqi @@ -1742,7 +1745,8 @@ namespace { v->materialCounting = JANGGI_MATERIAL; v->diagonalLines = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3, SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10); - v->pass = true; + v->pass[WHITE] = true; + v->pass[BLACK] = true; v->nFoldValue = VALUE_DRAW; v->perpetualCheckIllegal = true; return v; diff --git a/src/variant.h b/src/variant.h index 49bc54404..b9174ad07 100644 --- a/src/variant.h +++ b/src/variant.h @@ -111,8 +111,8 @@ struct Variant { bool seirawanGating = false; bool cambodianMoves = false; Bitboard diagonalLines = 0; - bool pass = false; - bool passOnStalemate = false; + bool pass[COLOR_NB] = {false, false}; + bool passOnStalemate[COLOR_NB] = {false, false}; bool makpongRule = false; bool flyingGeneral = false; Rank soldierPromotionRank = RANK_1; @@ -155,6 +155,7 @@ struct Variant { Bitboard connectRegion1[COLOR_NB] = {}; Bitboard connectRegion2[COLOR_NB] = {}; int connectNxN = 0; + Value connectValue = VALUE_MATE; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; CastlingRights castlingWins = NO_CASTLING; diff --git a/src/variants.ini b/src/variants.ini index b7dd011f9..6b3ea7407 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -236,7 +236,11 @@ # 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) +# passWhite: allow passing for white [bool] (default: false) +# passBlack: allow passing for black [bool] (default: false) # passOnStalemate: allow passing in case of stalemate [bool] (default: false) +# passOnStalemateWhite: allow passing in case of stalemate for white [bool] (default: false) +# passOnStalemateBlack: allow passing in case of stalemate for black [bool] (default: false) # makpongRule: the king may not move away from check [bool] (default: false) # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) # soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [Rank] (default: 1) @@ -284,6 +288,7 @@ # connectRegion1Black: " # connectRegion2Black: " # connectNxN: connect a tight NxN square for win [int] (default: 0) +# connectValue: result in case of connect [Value] (default: win) # materialCounting: enable material counting rules [MaterialCounting] (default: none) # countingRule: enable counting rules [CountingRule] (default: none) # castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -) @@ -1903,3 +1908,6 @@ maxRank = 7 startFen = 7/7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppp] w - - 0 1 enclosingDrop = anyside +#http://gamescrafters.berkeley.edu/games.php?game=connect4 +[cfour-misere:cfour] +connectValue = loss diff --git a/src/xboard.cpp b/src/xboard.cpp index 482b5e47f..09228edb3 100644 --- a/src/xboard.cpp +++ b/src/xboard.cpp @@ -308,7 +308,7 @@ void StateMachine::process_command(std::string token, std::istringstream& is) { std::getline(is >> std::ws, fen); // Check if setboard actually indicates a passing move // to avoid unnecessarily clearing the move history - if (pos.pass()) + if (pos.pass(~pos.side_to_move())) { StateInfo st; Position p; From 3b71d409ff5e66576387174bb4e1983a89869349 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 3 Sep 2022 11:03:09 +0200 Subject: [PATCH 08/11] Provide network download fallback in case the base infrastructure for providing the networks https://tests.stockfishchess.org/nns is down, use an alternate github repo for downloading networks during the build. No functional change --- src/Makefile | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Makefile b/src/Makefile index 602beefae..8572369b1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -821,25 +821,35 @@ clean: objclean profileclean net: $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) @echo "Default net: $(nnuenet)" - $(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) - @if test -f "$(nnuenet)"; then \ - echo "Already available."; \ - else \ - if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ - else \ - echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\ - fi; \ - fi; + @if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ + fi $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) - @if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \ - fi \ - else \ + @if [ "x$(shasum_command)" = "x" ]; then \ echo "shasum / sha256sum not found, skipping net validation"; \ fi + @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ + if test -f "$(nnuenet)"; then \ + echo "$(nnuenet) available."; \ + else \ + if [ "x$(curl_or_wget)" != "x" ]; then \ + echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + fi; \ + fi; \ + if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing failed download"; rm -f $(nnuenet); \ + else \ + echo "Network validated"; break; \ + fi; \ + fi; \ + done + @if ! test -f "$(nnuenet)"; then \ + echo "Failed to download $(nnuenet)."; \ + fi # clean binaries and objects objclean: From 8a4d0421de9d9f66a3e89d6892db82b4b00fbfe4 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 23 Dec 2023 15:02:28 +0100 Subject: [PATCH 09/11] Adjudicate when board is full (#750) Closes #749. --- setup.py | 2 +- src/parser.cpp | 1 + src/position.cpp | 7 +++++-- src/pyffish.cpp | 2 +- src/variant.cpp | 2 ++ src/variant.h | 1 + src/variants.ini | 1 + test.py | 12 ++++++++++++ 8 files changed, 24 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index a03b8328e..ab253c9ac 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ sources=sources, extra_compile_args=args) -setup(name="pyffish", version="0.0.78", +setup(name="pyffish", version="0.0.80", description="Fairy-Stockfish Python wrapper", long_description=long_description, long_description_content_type="text/markdown", diff --git a/src/parser.cpp b/src/parser.cpp index 7535e686e..48aa6870f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -537,6 +537,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("connectNxN", v->connectNxN); parse_attribute("connectValue", v->connectValue); parse_attribute("materialCounting", v->materialCounting); + parse_attribute("adjudicateFullBoard", v->adjudicateFullBoard); parse_attribute("countingRule", v->countingRule); parse_attribute("castlingWins", v->castlingWins); diff --git a/src/position.cpp b/src/position.cpp index a7ada05c6..bf6e60f28 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2845,18 +2845,21 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } - // Check for bikjang rule (Janggi) and double passing - if (st->pliesFromNull > 0 && ((st->bikjang && st->previous->bikjang) || (st->pass && st->previous->pass))) + // Check for bikjang rule (Janggi), double passing, or board running full + if ( (st->pliesFromNull > 0 && ((st->bikjang && st->previous->bikjang) || (st->pass && st->previous->pass))) + || (var->adjudicateFullBoard && !(~pieces() & board_bb()))) { result = var->materialCounting ? convert_mate_value(material_counting_result(), ply) : VALUE_DRAW; return true; } + // Tsume mode: Assume that side with king wins when not in check if (tsumeMode && !count(~sideToMove) && count(sideToMove) && !checkers()) { result = mate_in(ply); return true; } + // Failing to checkmate with virtual pieces is a loss if (two_boards() && !checkers()) { diff --git a/src/pyffish.cpp b/src/pyffish.cpp index 537148c4d..d321a57cc 100644 --- a/src/pyffish.cpp +++ b/src/pyffish.cpp @@ -54,7 +54,7 @@ void buildPosition(Position& pos, StateListPtr& states, const char *variant, con } extern "C" PyObject* pyffish_version(PyObject* self) { - return Py_BuildValue("(iii)", 0, 0, 78); + return Py_BuildValue("(iii)", 0, 0, 80); } extern "C" PyObject* pyffish_info(PyObject* self) { diff --git a/src/variant.cpp b/src/variant.cpp index cd565baf2..386c21d5d 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -1141,6 +1141,7 @@ namespace { v->enclosingDrop = ATAXX; v->flipEnclosedPieces = ATAXX; v->materialCounting = UNWEIGHTED_MATERIAL; + v->adjudicateFullBoard = true; v->nMoveRule = 0; v->freeDrops = true; return v; @@ -1167,6 +1168,7 @@ namespace { v->enclosingDropStart = make_bitboard(SQ_D4, SQ_E4, SQ_D5, SQ_E5); v->flipEnclosedPieces = REVERSI; v->materialCounting = UNWEIGHTED_MATERIAL; + v->adjudicateFullBoard = true; return v; } // Flipello diff --git a/src/variant.h b/src/variant.h index b9174ad07..ec1991859 100644 --- a/src/variant.h +++ b/src/variant.h @@ -157,6 +157,7 @@ struct Variant { int connectNxN = 0; Value connectValue = VALUE_MATE; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; + bool adjudicateFullBoard = false; CountingRule countingRule = NO_COUNTING; CastlingRights castlingWins = NO_CASTLING; diff --git a/src/variants.ini b/src/variants.ini index 6b3ea7407..abc2c7099 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -290,6 +290,7 @@ # connectNxN: connect a tight NxN square for win [int] (default: 0) # connectValue: result in case of connect [Value] (default: win) # materialCounting: enable material counting rules [MaterialCounting] (default: none) +# adjudicateFullBoard: apply material counting immediately when board is full [bool] (default: false) # countingRule: enable counting rules [CountingRule] (default: none) # castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -) diff --git a/test.py b/test.py index b2d953a5c..9587501da 100644 --- a/test.py +++ b/test.py @@ -977,6 +977,13 @@ def test_game_result(self): result = sf.game_result("royalduck", "rnbqk1nr/pppp1ppp/4p3/8/7P/5Pb1/PPPPP*P1/RNBQKBNR w KQkq - 1 4", []) self.assertEqual(result, sf.VALUE_MATE) + def _check_immediate_game_end(self, variant, fen, moves, game_end, game_result=None): + with self.subTest(variant=variant, fen=fen, game_end=game_end, game_result=game_result): + result = sf.is_immediate_game_end(variant, fen, moves) + self.assertEqual(result[0], game_end) + if game_result is not None: + self.assertEqual(result[1], game_result) + def test_is_immediate_game_end(self): result = sf.is_immediate_game_end("capablanca", CAPA, []) self.assertFalse(result[0]) @@ -991,6 +998,11 @@ def test_is_immediate_game_end(self): self.assertTrue(result[0]) self.assertEqual(result[1], -sf.VALUE_MATE) + # full board adjudication + self._check_immediate_game_end("flipello", "pppppppp/pppppppp/pppPpppp/pPpPpppp/pppppppp/pPpPPPPP/ppPpPPpp/pppppppp[PPpp] b - - 63 32", [], True, sf.VALUE_MATE) + self._check_immediate_game_end("ataxx", "PPPpppp/pppPPPp/pPPPPPP/PPPPPPp/ppPPPpp/pPPPPpP/pPPPPPP b - - 99 50", [], True, -sf.VALUE_MATE) + self._check_immediate_game_end("ataxx", "PPPpppp/pppPPPp/pPP*PPP/PP*P*Pp/ppP*Ppp/pPPPPpP/pPPPPPP b - - 99 50", [], True, -sf.VALUE_MATE) + def test_is_optional_game_end(self): result = sf.is_optional_game_end("capablanca", CAPA, []) self.assertFalse(result[0]) From 1466609faa66655b557fff990a44ba21c5476e5d Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 23 Dec 2023 15:26:05 +0100 Subject: [PATCH 10/11] Use subtests for better testing output --- test.py | 174 ++++++++++++++++++-------------------------------------- 1 file changed, 56 insertions(+), 118 deletions(-) diff --git a/test.py b/test.py index 9587501da..9cf4c5393 100644 --- a/test.py +++ b/test.py @@ -985,181 +985,119 @@ def _check_immediate_game_end(self, variant, fen, moves, game_end, game_result=N self.assertEqual(result[1], game_result) def test_is_immediate_game_end(self): - result = sf.is_immediate_game_end("capablanca", CAPA, []) - self.assertFalse(result[0]) + self._check_immediate_game_end("capablanca", CAPA, [], False) # bikjang (facing kings) moves = "e2e3 e9f9 h3d3 e7f7 i1i3 h10i8 i3h3 c10e7 h3h8 i10i9 h8b8 i9g9 d3f3 f9e9 f3f10 e7c10 f10c10 b10c8 c10g10 g9f9 b8c8 a10b10 b3f3 f9h9 a1a2 h9f9 a2d2 b10b9 d2d10 e9d10 c8c10 d10d9 f3f9 i8g9 f9b9 a7a6 g10g7 f7f6 e4e5 c7d7 g1e4 i7i6 e4b6 d9d8 c10c8 d8d9 b9g9 d7d6 b6e8 i6h6 e5e6 f6e6 c1e4 a6b6 e4b6 d6d5 c4c5 d9d10 e3d3 h6i6 c5c6 d5c5" - result = sf.is_immediate_game_end("janggi", JANGGI, moves.split()) - self.assertFalse(result[0]) + self._check_immediate_game_end("janggi", JANGGI, moves.split(), False) moves = "e2e3 e9f9 h3d3 e7f7 i1i3 h10i8 i3h3 c10e7 h3h8 i10i9 h8b8 i9g9 d3f3 f9e9 f3f10 e7c10 f10c10 b10c8 c10g10 g9f9 b8c8 a10b10 b3f3 f9h9 a1a2 h9f9 a2d2 b10b9 d2d10 e9d10 c8c10 d10d9 f3f9 i8g9 f9b9 a7a6 g10g7 f7f6 e4e5 c7d7 g1e4 i7i6 e4b6 d9d8 c10c8 d8d9 b9g9 d7d6 b6e8 i6h6 e5e6 f6e6 c1e4 a6b6 e4b6 d6d5 c4c5 d9d10 e3d3 h6i6 c5c6 d5c5 d3d3" - result = sf.is_immediate_game_end("janggi", JANGGI, moves.split()) - self.assertTrue(result[0]) - self.assertEqual(result[1], -sf.VALUE_MATE) + self._check_immediate_game_end("janggi", JANGGI, moves.split(), True, -sf.VALUE_MATE) # full board adjudication self._check_immediate_game_end("flipello", "pppppppp/pppppppp/pppPpppp/pPpPpppp/pppppppp/pPpPPPPP/ppPpPPpp/pppppppp[PPpp] b - - 63 32", [], True, sf.VALUE_MATE) self._check_immediate_game_end("ataxx", "PPPpppp/pppPPPp/pPPPPPP/PPPPPPp/ppPPPpp/pPPPPpP/pPPPPPP b - - 99 50", [], True, -sf.VALUE_MATE) self._check_immediate_game_end("ataxx", "PPPpppp/pppPPPp/pPP*PPP/PP*P*Pp/ppP*Ppp/pPPPPpP/pPPPPPP b - - 99 50", [], True, -sf.VALUE_MATE) + def _check_optional_game_end(self, variant, fen, moves, game_end, game_result=None): + with self.subTest(variant=variant, fen=fen, game_end=game_end, game_result=game_result): + result = sf.is_optional_game_end(variant, fen, moves) + self.assertEqual(result[0], game_end) + if game_result is not None: + self.assertEqual(result[1], game_result) + def test_is_optional_game_end(self): - result = sf.is_optional_game_end("capablanca", CAPA, []) - self.assertFalse(result[0]) + self._check_optional_game_end("capablanca", CAPA, [], False) # sittuyin stalemate due to optional promotion - result = sf.is_optional_game_end("sittuyin", "1k4PK/3r4/8/8/8/8/8/8[] w - - 0 1", []) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("sittuyin", "1k4PK/3r4/8/8/8/8/8/8[] w - - 0 1", [], True, sf.VALUE_DRAW) # Xiangqi chasing rules # Also see http://www.asianxiangqi.org/English/AXF_rules_Eng.pdf # Direct chase by cannon - result = sf.is_optional_game_end("xiangqi", "2bakabnr/9/r1n1c4/2p1p1p1p/PP7/9/4P1P1P/2C3NC1/9/1NBAKAB1R w - - 0 1", ["c3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8", "b3a3"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "2bakabnr/9/r1n1c4/2p1p1p1p/PP7/9/4P1P1P/2C3NC1/9/1NBAKAB1R w - - 0 1", ["c3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8", "b3a3"], True, sf.VALUE_MATE) # Chase with chasing side to move - result = sf.is_optional_game_end("xiangqi", "2bakabnr/9/r1n1c4/2p1p1p1p/PP7/9/4P1P1P/2C3NC1/9/1NBAKAB1R w - - 0 1", ["c3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], -sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "2bakabnr/9/r1n1c4/2p1p1p1p/PP7/9/4P1P1P/2C3NC1/9/1NBAKAB1R w - - 0 1", ["c3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8"], True, -sf.VALUE_MATE) # Discovered chase by cannon (including pawn capture) - result = sf.is_optional_game_end("xiangqi", "2bakabr1/9/9/r1p1p1p2/p7R/P8/9/9/9/CC1AKA3 w - - 0 1", ["a5a6", "a7b7", "a6b6", "b7a7", "b6a6", "a7b7", "a6b6", "b7a7", "b6a6"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "2bakabr1/9/9/r1p1p1p2/p7R/P8/9/9/9/CC1AKA3 w - - 0 1", ["a5a6", "a7b7", "a6b6", "b7a7", "b6a6", "a7b7", "a6b6", "b7a7", "b6a6"], True, sf.VALUE_MATE) # Chase by soldier (draw) - result = sf.is_optional_game_end("xiangqi", "2bakabr1/9/9/r1p1p1p2/p7R/P8/9/9/9/1C1AKA3 w - - 0 1", ["a5a6", "a7b7", "a6b6", "b7a7", "b6a6", "a7b7", "a6b6", "b7a7", "b6a6"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "2bakabr1/9/9/r1p1p1p2/p7R/P8/9/9/9/1C1AKA3 w - - 0 1", ["a5a6", "a7b7", "a6b6", "b7a7", "b6a6", "a7b7", "a6b6", "b7a7", "b6a6"], True, sf.VALUE_DRAW) # Discovered and anti-discovered chase by cannon - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/5C3/5c3/5C3/9/9/5p3/4K4 w - - 0 1", ["f5d5", "f6d6", "d5f5", "d6f6", "f5d5", "f6d6", "d5f5", "d6f6"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], -sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/5C3/5c3/5C3/9/9/5p3/4K4 w - - 0 1", ["f5d5", "f6d6", "d5f5", "d6f6", "f5d5", "f6d6", "d5f5", "d6f6"], True, -sf.VALUE_MATE) # Mutual chase (draw) - result = sf.is_optional_game_end("xiangqi", "4k4/7n1/9/4pR3/9/9/4P4/9/9/4K4 w - - 0 1", ["f7h7"] + 2 * ["h9f8", "h7h8", "f8g6", "h8g8", "g6i7", "g8g7", "i7h9", "g7h7"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "4k4/7n1/9/4pR3/9/9/4P4/9/9/4K4 w - - 0 1", ["f7h7"] + 2 * ["h9f8", "h7h8", "f8g6", "h8g8", "g6i7", "g8g7", "i7h9", "g7h7"], True, sf.VALUE_DRAW) # Perpetual check vs. intermittent checks - result = sf.is_optional_game_end("xiangqi", "9/3kc4/3a5/3P5/9/4p4/9/4K4/9/3C5 w - - 0 1", 2 * ['d7e7', 'e5d5', 'e7d7', 'd5e5']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "9/3kc4/3a5/3P5/9/4p4/9/4K4/9/3C5 w - - 0 1", 2 * ['d7e7', 'e5d5', 'e7d7', 'd5e5'], True, sf.VALUE_MATE) # Perpetual check by soldier - result = sf.is_optional_game_end("xiangqi", "3k5/9/9/9/9/5p3/9/5p3/5K3/5C3 w - - 0 1", 2 * ['f2e2', 'f3e3', 'e2f2', 'e3f3']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "3k5/4P4/4b4/3C5/4c4/9/9/9/9/5K3 w - - 0 1", 2 * ['d7e7', 'e8g6', 'e7d7', 'g6e8']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "3k5/9/9/9/9/9/9/9/cr1CAK3/9 w - - 0 1", 2 * ['d2d4', 'b2b4', 'd4d2', 'b4b2']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/5C3/5c3/5C3/9/9/5p3/4K4 w - - 0 1", 2 * ['f5d5', 'f6d6', 'd5f5', 'd6f6']) - self.assertTrue(result[0]) - self.assertEqual(result[1], -sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "4k4/9/4b4/2c2nR2/9/9/9/9/9/3K5 w - - 0 1", 2 * ['g7g6', 'f7g9', 'g6g7', 'g9f7']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "3P5/3k5/3nn4/9/9/9/9/9/9/5K3 w - - 0 1", 2 * ['d10e10', 'd9e9', 'e10d10', 'e9d9']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "4ck3/9/9/9/9/2r1R4/9/9/4A4/3AK4 w - - 0 1", 2 * ['e5e4', 'c5c4', 'e4e5', 'c4c5']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) - result = sf.is_optional_game_end("xiangqi", "4k4/9/9/c1c6/9/r8/9/9/C8/3K5 w - - 0 1", 2 * ['a2c2', 'a5c5', 'c2a2', 'c5a5']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3k5/9/9/9/9/5p3/9/5p3/5K3/5C3 w - - 0 1", 2 * ['f2e2', 'f3e3', 'e2f2', 'e3f3'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3k5/4P4/4b4/3C5/4c4/9/9/9/9/5K3 w - - 0 1", 2 * ['d7e7', 'e8g6', 'e7d7', 'g6e8'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3k5/9/9/9/9/9/9/9/cr1CAK3/9 w - - 0 1", 2 * ['d2d4', 'b2b4', 'd4d2', 'b4b2'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/5C3/5c3/5C3/9/9/5p3/4K4 w - - 0 1", 2 * ['f5d5', 'f6d6', 'd5f5', 'd6f6'], True, -sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4k4/9/4b4/2c2nR2/9/9/9/9/9/3K5 w - - 0 1", 2 * ['g7g6', 'f7g9', 'g6g7', 'g9f7'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3P5/3k5/3nn4/9/9/9/9/9/9/5K3 w - - 0 1", 2 * ['d10e10', 'd9e9', 'e10d10', 'e9d9'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4ck3/9/9/9/9/2r1R4/9/9/4A4/3AK4 w - - 0 1", 2 * ['e5e4', 'c5c4', 'e4e5', 'c4c5'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4k4/9/9/c1c6/9/r8/9/9/C8/3K5 w - - 0 1", 2 * ['a2c2', 'a5c5', 'c2a2', 'c5a5'], True, sf.VALUE_MATE) # Mutual perpetual check - result = sf.is_optional_game_end("xiangqi", "9/4c4/3k5/3r5/9/9/4C4/9/4K4/3R5 w - - 0 1", 2 * ['e4d4', 'd7e7', 'd4e4', 'e7d7']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) - result = sf.is_optional_game_end("xiangqi", "3k5/6c2/9/7P1/6c2/6P2/9/9/9/5K3 w - - 0 1", 2 * ['h7g7', 'g6h6', 'g7h7', 'h6g6']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) - result = sf.is_optional_game_end("xiangqi", "4ck3/9/9/9/9/2r1R1N2/6N2/9/4A4/3AK4 w - - 0 1", 2 * ['e5e4', 'c5c4', 'e4e5', 'c4c5']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/c8/9/P1P6/9/2C6/9/3K5 w - - 0 1", 2 * ['c3a3', 'a7c7', 'a3c3', 'c7a7']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) - result = sf.is_optional_game_end("xiangqi", "4k4/9/r1r6/9/PPPP5/9/9/9/1C7/5K3 w - - 0 1", ['b2a2'] + 2 * ['a8b8', 'a2c2', 'c8d8', 'c2b2', 'b8a8', 'b2d2', 'd8c8', 'd2a2']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "9/4c4/3k5/3r5/9/9/4C4/9/4K4/3R5 w - - 0 1", 2 * ['e4d4', 'd7e7', 'd4e4', 'e7d7'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "3k5/6c2/9/7P1/6c2/6P2/9/9/9/5K3 w - - 0 1", 2 * ['h7g7', 'g6h6', 'g7h7', 'h6g6'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "4ck3/9/9/9/9/2r1R1N2/6N2/9/4A4/3AK4 w - - 0 1", 2 * ['e5e4', 'c5c4', 'e4e5', 'c4c5'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "5k3/9/9/c8/9/P1P6/9/2C6/9/3K5 w - - 0 1", 2 * ['c3a3', 'a7c7', 'a3c3', 'c7a7'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "4k4/9/r1r6/9/PPPP5/9/9/9/1C7/5K3 w - - 0 1", ['b2a2'] + 2 * ['a8b8', 'a2c2', 'c8d8', 'c2b2', 'b8a8', 'b2d2', 'd8c8', 'd2a2'], True, sf.VALUE_DRAW) # Corner cases # D106: Chariot chases cannon, but attack actually does not change (draw) - result = sf.is_optional_game_end("xiangqi", "3k2b2/4P4/4b4/9/8p/6Bc1/6P1P/3AB4/4pp3/1p1K3R1[] w - - 0 1", 2 * ["h1h2", "h5h4", "h2h1", "h4h5"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "3k2b2/4P4/4b4/9/8p/6Bc1/6P1P/3AB4/4pp3/1p1K3R1[] w - - 0 1", 2 * ["h1h2", "h5h4", "h2h1", "h4h5"], True, sf.VALUE_DRAW) # D39: Chased chariot pinned by horse + mutual chase (controversial if pinned chariot chases) - result = sf.is_optional_game_end("xiangqi", "2baka1r1/C4rN2/9/1Rp1p4/9/9/4P4/9/4A4/4KA3 w - - 0 1", ["b7b9"] + 2 * ["f10e9", "b9b10", "e9f10", "b10b9"]) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "2baka1r1/C4rN2/9/1Rp1p4/9/9/4P4/9/4A4/4KA3 w - - 0 1", ["b7b9"] + 2 * ["f10e9", "b9b10", "e9f10", "b10b9"], True, sf.VALUE_MATE) # D39: Chased chariot pinned by horse + mutual chase (controversial if pinned chariot chases) - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/9/9/9/7r1/9/2nRA3c/4K4 w - - 0 1", 2 * ['e2f1', 'h4h2', 'f1e2', 'h2h4']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/9/7r1/9/2nRA3c/4K4 w - - 0 1", 2 * ['e2f1', 'h4h2', 'f1e2', 'h2h4'], True, sf.VALUE_MATE) # Creating pins to undermine root - result = sf.is_optional_game_end("xiangqi", "4k4/4c4/9/4p4/9/9/3rn4/3NR4/4K4/9 b - - 0 1", 2 * ['e4g5', 'e2f2', 'g5e4', 'f2e2']) - self.assertTrue(result[0]) - self.assertEqual(result[1], -sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4k4/4c4/9/4p4/9/9/3rn4/3NR4/4K4/9 b - - 0 1", 2 * ['e4g5', 'e2f2', 'g5e4', 'f2e2'], True, -sf.VALUE_MATE) # Discovered check capture threat by rook - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/9/9/1N2P1C2/9/4BC3/9/cr1RK4 w - - 0 1", 2 * ['b5c3', 'b1c1', 'c3b5', 'c1b1']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/1N2P1C2/9/4BC3/9/cr1RK4 w - - 0 1", 2 * ['b5c3', 'b1c1', 'c3b5', 'c1b1'], True, sf.VALUE_MATE) # Creating a pin to undermine root + discovered check threat by horse - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/9/9/4c4/3n5/3NBA3/4A4/4K4 w - - 0 1", 2 * ['e1d1', 'e5d5', 'd1e1', 'd5e5']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/4c4/3n5/3NBA3/4A4/4K4 w - - 0 1", 2 * ['e1d1', 'e5d5', 'd1e1', 'd5e5'], True, sf.VALUE_MATE) # Creating a pin to undermine root + discovered check threat by rook - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/9/9/4c4/3r5/3NB4/4A4/4K4 w - - 0 1", 2 * ['e1d1', 'e5d5', 'd1e1', 'd5e5']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/4c4/3r5/3NB4/4A4/4K4 w - - 0 1", 2 * ['e1d1', 'e5d5', 'd1e1', 'd5e5'], True, sf.VALUE_MATE) # X-Ray protected discovered check - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/9/9/9/9/9/9/3NK1cr1 w - - 0 1", 2 * ['d1c3', 'h1h3', 'c3d1', 'h3h1']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/9/9/9/9/3NK1cr1 w - - 0 1", 2 * ['d1c3', 'h1h3', 'c3d1', 'h3h1'], True, sf.VALUE_MATE) # No overprotection by king - result = sf.is_optional_game_end("xiangqi", "3k5/9/9/3n5/9/9/3r5/9/9/3NK4 w - - 0 1", 2 * ['d1c3', 'd4c4', 'c3d1', 'c4d4']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "3k5/9/9/3n5/9/9/3r5/9/9/3NK4 w - - 0 1", 2 * ['d1c3', 'd4c4', 'c3d1', 'c4d4'], True, sf.VALUE_DRAW) # Overprotection by king - result = sf.is_optional_game_end("xiangqi", "3k5/9/9/9/9/9/3r5/9/9/3NK4 w - - 0 1", 2 * ['d1c3', 'd4c4', 'c3d1', 'c4d4']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3k5/9/9/9/9/9/3r5/9/9/3NK4 w - - 0 1", 2 * ['d1c3', 'd4c4', 'c3d1', 'c4d4'], True, sf.VALUE_MATE) # Mutual pins by flying generals - result = sf.is_optional_game_end("xiangqi", "4k4/9/9/9/4n4/9/5C3/9/4N4/4K4 w - - 0 1", 2 * ['e2g1', 'e10f10', 'g1e2', 'f10e10']) - self.assertTrue(result[0]) - #self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4k4/9/9/9/4n4/9/5C3/9/4N4/4K4 w - - 0 1", 2 * ['e2g1', 'e10f10', 'g1e2', 'f10e10'], True) #, sf.VALUE_MATE) # Fake protection by cannon - result = sf.is_optional_game_end("xiangqi", "5k3/9/9/9/9/1C7/1r7/9/1C7/4K4 w - - 0 1", 2 * ['b5c5', 'b4c4', 'c5b5', 'c4b4']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/1C7/1r7/9/1C7/4K4 w - - 0 1", 2 * ['b5c5', 'b4c4', 'c5b5', 'c4b4'], True, sf.VALUE_MATE) # Fake protection by cannon + mutual chase - result = sf.is_optional_game_end("xiangqi", "4ka3/c2R1R2c/4b4/9/9/9/9/9/9/4K4 w - - 0 1", 2 * ['f9f7', 'f10e9', 'f7f9', 'e9f10']) - self.assertTrue(result[0]) - self.assertEqual(result[1], sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "4ka3/c2R1R2c/4b4/9/9/9/9/9/9/4K4 w - - 0 1", 2 * ['f9f7', 'f10e9', 'f7f9', 'e9f10'], True, sf.VALUE_DRAW) def test_has_insufficient_material(self): for variant, positions in variant_positions.items(): for fen, expected_result in positions.items(): - result = sf.has_insufficient_material(variant, fen, []) - self.assertEqual(result, expected_result, "{}: {}".format(variant, fen)) + with self.subTest(variant=variant, fen=fen): + result = sf.has_insufficient_material(variant, fen, []) + self.assertEqual(result, expected_result) def test_validate_fen(self): # valid for variant, positions in variant_positions.items(): for fen in positions: - self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen)) + with self.subTest(variant=variant, fen=fen): + self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK) # invalid for variant, positions in invalid_variant_positions.items(): for fen in positions: - self.assertNotEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen)) + with self.subTest(variant=variant, fen=fen): + self.assertNotEqual(sf.validate_fen(fen, variant), sf.FEN_OK) # chess960 - self.assertEqual(sf.validate_fen(CHESS960, "chess", True), sf.FEN_OK, "{}: {}".format(variant, fen)) + self.assertEqual(sf.validate_fen(CHESS960, "chess", True), sf.FEN_OK) self.assertEqual(sf.validate_fen("nrbqbkrn/pppppppp/8/8/8/8/PPPPPPPP/NRBQBKRN w BGbg - 0 1", "newzealand", True), sf.FEN_OK, "{}: {}".format(variant, fen)) # all variants starting positions for variant in sf.variants(): - fen = sf.start_fen(variant) - self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen)) + with self.subTest(variant=variant): + fen = sf.start_fen(variant) + self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK) if __name__ == '__main__': unittest.main(verbosity=2) From cf7570938c03447a802df106775cbe2a5110c96f Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 23 Dec 2023 17:03:57 +0100 Subject: [PATCH 11/11] Use NNUE fallback URL for appveyor (#752) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c073aa73f..d6f5226e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -70,7 +70,7 @@ build_script: $dummy = $nnuenet -match "(?nn-[a-z0-9]{12}.nnue)" $nnuenet = $Matches.nnuenet Write-Host "Default net:" $nnuenet - $nnuedownloadurl = "https://tests.stockfishchess.org/api/nn/$nnuenet" + $nnuedownloadurl = "https://github.com/official-stockfish/networks/raw/master/$nnuenet" $nnuefilepath = "src\${env:CONFIGURATION}\$nnuenet" if (Test-Path -Path $nnuefilepath) { Write-Host "Already available."