From 0907b7f75899949b421ab5b64a4cff8488f504c3 Mon Sep 17 00:00:00 2001 From: RainRat Date: Fri, 1 Sep 2023 07:19:21 -0700 Subject: [PATCH 01/39] add Quad Wrangle (#704) --- src/parser.cpp | 3 ++- src/position.cpp | 7 +++++-- src/types.h | 2 +- src/variants.ini | 19 ++++++++++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 43d2bc82..348f17ff 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -105,8 +105,9 @@ namespace { template <> bool set(const std::string& value, EnclosingRule& target) { target = value == "reversi" ? REVERSI : value == "ataxx" ? ATAXX + : value == "quadwrangle" ? QUADWRANGLE : NO_ENCLOSING; - return value == "reversi" || value == "ataxx" || value == "none"; + return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value == "none"; } template <> bool set(const std::string& value, Bitboard& target) { diff --git a/src/position.cpp b/src/position.cpp index 7363fc28..1b8ae90c 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1675,8 +1675,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } else { - assert(flip_enclosed_pieces() == ATAXX); - st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); + assert((flip_enclosed_pieces() == ATAXX) || (flip_enclosed_pieces() == QUADWRANGLE)); + if ((flip_enclosed_pieces() == ATAXX) || (flip_enclosed_pieces() == QUADWRANGLE && (PseudoAttacks[us][KING][to] & pieces(us) || type_of(m) == NORMAL))) + { + st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); + } } // Flip pieces diff --git a/src/types.h b/src/types.h index 12de0c6d..a59f8678 100644 --- a/src/types.h +++ b/src/types.h @@ -302,7 +302,7 @@ enum ChasingRule { }; enum EnclosingRule { - NO_ENCLOSING, REVERSI, ATAXX + NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE }; enum OptBool { diff --git a/src/variants.ini b/src/variants.ini index 63bd4da6..c048132a 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -129,7 +129,7 @@ # [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, none] +# [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, quadwrangle, none] ### Additional options relevant for usage in Winboard/XBoard # A few options only have the purpose of improving compatibility with Winboard/Xboard. @@ -1594,3 +1594,20 @@ customPiece1 = p:fmWfceFifmnD pawnTypes = p petrifyOnCapture = true enPassantRegion = - + +#https://www.ludii.games/details.php?keyword=Quad%20Wrangle +[quadwrangle:ataxx] +#different sources give different info on whether it is a 7x7 or 8x8 board. +#could offer both if wanted. +maxRank = 8 +maxFile = 8 +startFen = 1PPPPPP1/p6P/p6P/p6P/p6P/p6P/p6P/1pppppp1 +customPiece1 = p:mQ +flipEnclosedPieces = quadwrangle +#override rule from ataxx since drops can be done freely +enclosingDrop = none + +[quadwrangle7x7:quadwrangle] +maxRank = 7 +maxFile = 7 +startFen = 1PPPPP1/p5P/p5P/p5P/p5P/p5P/1ppppp1 From 12422d2ed8f6ecaee69f11bc17af6f3afa295172 Mon Sep 17 00:00:00 2001 From: RainRat Date: Mon, 4 Sep 2023 13:27:18 -0700 Subject: [PATCH 02/39] add Caught in a Snag, Minihouse, Pawnsking, Rookmate (#707) --- src/variants.ini | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/variants.ini b/src/variants.ini index c048132a..d9ec7d8a 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -455,6 +455,15 @@ flagPiece = q flagRegionWhite = *8 flagRegionBlack = *1 +#https://github.com/yagu0/vchess/blob/master/client/src/translations/rules/Pawnsking/en.pug +[pawnsking:pawnsonly] +commoner = k +flagPiece = * +startFen = 4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1 +extinctionValue = loss +extinctionPieceTypes = k +stalemateValue = draw + [tictactoe] maxRank = 3 maxFile = 3 @@ -514,6 +523,38 @@ whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *4 *5 *6 *7 *8 immobilityIllegal = true +#https://www.chess.com/variants/caught-in-a-snag +[caught-in-a-snag:chess] +pieceToCharTable = P..........GUFW......Mp..........gufw......m +maxRank = 6 +pawnTypes = p +fers = f +horse = u +wazir = w +centaur = m +king = g:gBgR +customPiece1 = p:fmWfcF +startFen = fuwmgwuf/pppppppp/8/8/PPPPPPPP/FUWGMWUF w - - 0 1 +stalemateValue = win + +#https://www.chess.com/variants/minihouse +[minihouse:crazyhouse] +maxRank = 6 +maxFile = 6 +startFen = 2bnrk/5p/6/6/P5/KRNB2 + +#https://www.chess.com/variants/rookmate +[rookmate:chess] +#in this variant, one rook is royal, one isn't +#to define this, the royal rook will be a king(K) with custom moves, and the non-royal king a commoner(C) +pieceToCharTable = PNBRQC...............Kpnbrqc...............k +startFen = rnbqcbnk/pppppppp/8/8/8/8/PPPPPPPP/RNBQCBNK w 7+7 0 1 +king = k:R +commoner = c +extinctionPieceCount = 1 +extinctionValue = loss +extinctionPieceTypes = * + # Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) [orda:chess] pieceToCharTable = PNBRQ..AH...........LKp...q..ah.y.........lk @@ -1598,7 +1639,7 @@ enPassantRegion = - #https://www.ludii.games/details.php?keyword=Quad%20Wrangle [quadwrangle:ataxx] #different sources give different info on whether it is a 7x7 or 8x8 board. -#could offer both if wanted. +#7x7 is below maxRank = 8 maxFile = 8 startFen = 1PPPPPP1/p6P/p6P/p6P/p6P/p6P/p6P/1pppppp1 From a080a673d795fd451b22c62e993945426bec7f48 Mon Sep 17 00:00:00 2001 From: Belzedar94 <54615238+Belzedar94@users.noreply.github.com> Date: Tue, 5 Sep 2023 18:59:16 +0200 Subject: [PATCH 03/39] Fix promotion rules of Caught-in-a-snag variant --- src/variants.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/src/variants.ini b/src/variants.ini index d9ec7d8a..60c701cf 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -536,6 +536,7 @@ king = g:gBgR customPiece1 = p:fmWfcF startFen = fuwmgwuf/pppppppp/8/8/PPPPPPPP/FUWGMWUF w - - 0 1 stalemateValue = win +promotionPawnTypes = - #https://www.chess.com/variants/minihouse [minihouse:crazyhouse] From 60953929ca5ca691e5fb9bbb32bb56afd50d68a8 Mon Sep 17 00:00:00 2001 From: RainRat Date: Wed, 6 Sep 2023 00:34:28 -0700 Subject: [PATCH 04/39] add Duck region mask. add forward/checkers (#705) --- src/apiutil.h | 2 +- src/movegen.cpp | 10 +++++----- src/parser.cpp | 21 ++++++++++++--------- src/position.cpp | 28 ++++++++++++++-------------- src/position.h | 6 +++--- src/search.cpp | 20 ++++++++++---------- src/uci.cpp | 4 ++-- src/variant.cpp | 16 ++++++++-------- src/variant.h | 10 +++++----- src/variants.ini | 31 +++++++++++++++++++++++++------ 10 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/apiutil.h b/src/apiutil.h index ca5858e9..0794abc3 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -346,7 +346,7 @@ inline const std::string move_to_san(Position& pos, Move m, Notation n) { } // Wall square - if (pos.wall_gating()) + if (pos.walling()) san += "," + square(pos, gating_square(m), n); // Check and checkmate diff --git a/src/movegen.cpp b/src/movegen.cpp index b33d9864..0c14dd75 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -29,7 +29,7 @@ namespace { ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to, PieceType pt = NO_PIECE_TYPE) { // Wall placing moves - if (pos.wall_gating()) + if (pos.walling()) { Bitboard b = pos.board_bb() & ~((pos.pieces() ^ from) | to); if (T == CASTLING) @@ -41,11 +41,11 @@ namespace { } if (T == EN_PASSANT) b ^= pos.capture_square(to); - if (pos.variant()->arrowGating) + if (pos.variant()->arrowWalling) b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from); - if (pos.variant()->staticGating) - b &= pos.variant()->staticGatingRegion; - if (pos.variant()->pastGating) + if ((pos.variant()->staticWalling)||(pos.variant()->duckWalling)) + b &= pos.variant()->wallingRegion[us]; + if (pos.variant()->pastWalling) b &= square_bb(from); while (b) diff --git a/src/parser.cpp b/src/parser.cpp index 348f17ff..4b074409 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -410,11 +410,14 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("dropNoDoubledCount", v->dropNoDoubledCount); parse_attribute("immobilityIllegal", v->immobilityIllegal); parse_attribute("gating", v->gating); - parse_attribute("arrowGating", v->arrowGating); - parse_attribute("duckGating", v->duckGating); - parse_attribute("staticGating", v->staticGating); - parse_attribute("pastGating", v->pastGating); - parse_attribute("staticGatingRegion", v->staticGatingRegion); + parse_attribute("arrowWalling", v->arrowWalling); + parse_attribute("duckWalling", v->duckWalling); + 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); @@ -525,8 +528,8 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl; // Check for limitations - if (v->pieceDrops && (v->arrowGating || v->duckGating || v->staticGating || v->pastGating)) - std::cerr << "pieceDrops and arrowGating/duckGating are incompatible." << std::endl; + if (v->pieceDrops && (v->arrowWalling || v->duckWalling || v->staticWalling || v->pastWalling)) + std::cerr << "pieceDrops and arrowWalling/duckWalling are incompatible." << std::endl; // Options incompatible with royal kings if (v->pieceTypes & KING) @@ -535,8 +538,8 @@ Variant* VariantParser::parse(Variant* v) { std::cerr << "Can not use kings with blastOnCapture." << std::endl; if (v->flipEnclosedPieces) std::cerr << "Can not use kings with flipEnclosedPieces." << std::endl; - if (v->duckGating) - std::cerr << "Can not use kings with duckGating." << std::endl; + if (v->duckWalling) + std::cerr << "Can not use kings with duckWalling." << std::endl; // We can not fully check support for custom king movements at this point, // since custom pieces are only initialized on loading of the variant. // We will assume this is valid, but it might cause problems later if it's not. diff --git a/src/position.cpp b/src/position.cpp index 1b8ae90c..a87ce1d6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1099,9 +1099,9 @@ bool Position::legal(Move m) const { { Square kto = to; Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()); - if (var->duckGating) + if (var->duckWalling) occupied ^= st->wallSquares; - if (wall_gating() || is_gating(m)) + if (walling() || is_gating(m)) occupied |= gating_square(m); if (type_of(m) == CASTLING) { @@ -1296,15 +1296,15 @@ bool Position::pseudo_legal(const Move m) const { : MoveList(*this).contains(m); // Illegal wall square placement - if (wall_gating() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) + if (walling() && !((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) return false; - if (var->arrowGating && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) + if (var->arrowWalling && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) return false; - if (var->pastGating && (from != gating_square(m))) + if (var->pastWalling && (from != gating_square(m))) return false; - if (var->staticGating && !(var->staticGatingRegion & gating_square(m))) + if ((var->staticWalling || var->duckWalling) && !(var->wallingRegion[us] & gating_square(m))) return false; - + // Handle the case where a mandatory piece promotion/demotion is not taken if ( mandatory_piece_promotion() && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE) @@ -1803,7 +1803,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { if ( (var->enPassantRegion & (to - pawn_push(us))) && ((pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN)) - && !(wall_gating() && gating_square(m) == to - pawn_push(us))) + && !(walling() && gating_square(m) == to - pawn_push(us))) { st->epSquares |= to - pawn_push(us); k ^= Zobrist::enpassant[file_of(to)]; @@ -1811,7 +1811,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if ( std::abs(int(to) - int(from)) == 3 * NORTH && (var->enPassantRegion & (to - 2 * pawn_push(us))) && ((pawn_attacks_bb(us, to - 2 * pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN)) - && !(wall_gating() && gating_square(m) == to - 2 * pawn_push(us))) + && !(walling() && gating_square(m) == to - 2 * pawn_push(us))) { st->epSquares |= to - 2 * pawn_push(us); k ^= Zobrist::enpassant[file_of(to)]; @@ -2016,10 +2016,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Add gated wall square - if (wall_gating()) + if (walling()) { - // Reset wall squares for duck gating - if (var->duckGating) + // Reset wall squares for duck walling + if (var->duckWalling) { Bitboard b = st->previous->wallSquares; byTypeBB[ALL_PIECES] ^= b; @@ -2457,7 +2457,7 @@ bool Position::see_ge(Move m, Value threshold) const { stmAttackers &= ~blockers_for_king(stm); // Ignore distant sliders - if (var->duckGating) + if (var->duckWalling) stmAttackers &= attacks_bb(to) | ~(pieces(BISHOP, ROOK) | pieces(QUEEN)); if (!stmAttackers) @@ -2924,7 +2924,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->duckGating) + if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || var->duckWalling) return false; Key originalKey = st->key; diff --git a/src/position.h b/src/position.h index fcdd6cae..22791350 100644 --- a/src/position.h +++ b/src/position.h @@ -178,7 +178,7 @@ class Position { PieceType drop_no_doubled() const; bool immobility_illegal() const; bool gating() const; - bool wall_gating() const; + bool walling() const; bool seirawan_gating() const; bool cambodian_moves() const; Bitboard diagonal_lines() const; @@ -753,9 +753,9 @@ inline bool Position::gating() const { return var->gating; } -inline bool Position::wall_gating() const { +inline bool Position::walling() const { assert(var != nullptr); - return var->arrowGating || var->duckGating || var->staticGating || var->pastGating; + return var->arrowWalling || var->duckWalling || var->staticWalling || var->pastWalling; } inline bool Position::seirawan_gating() const { diff --git a/src/search.cpp b/src/search.cpp index 38d72f52..2502fcc2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -80,7 +80,7 @@ namespace { } int futility_move_count(bool improving, Depth depth, const Position& pos) { - return (3 + depth * depth * (1 + pos.wall_gating()) + 2 * pos.blast_on_capture()) / (2 - improving + pos.blast_on_capture()); + return (3 + depth * depth * (1 + pos.walling()) + 2 * pos.blast_on_capture()) / (2 - improving + pos.blast_on_capture()); } // History and stats update bonus, based on depth @@ -281,7 +281,7 @@ void MainThread::search() { if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true)) { std::string move = UCI::move(rootPos, bestMove); - if (rootPos.wall_gating()) + if (rootPos.walling()) { sync_cout << "move " << move.substr(0, move.find(",")) << "," << sync_endl; sync_cout << "move " << move.substr(move.find(",") + 1) << sync_endl; @@ -800,7 +800,7 @@ namespace { { int penalty = -stat_bonus(depth); thisThread->mainHistory[us][from_to(ttMove)] << penalty; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(ttMove)] << penalty; update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); } @@ -1185,7 +1185,7 @@ namespace { continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.variant()->duckGating && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) + if (!pos.variant()->duckWalling && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) continue; } } @@ -1823,9 +1823,9 @@ namespace { // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - if (!(pos.wall_gating() && from_to(quietsSearched[i]) == from_to(bestMove))) + if (!(pos.walling() && from_to(quietsSearched[i]) == from_to(bestMove))) thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(quietsSearched[i])] << -bonus2; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); } @@ -1834,7 +1834,7 @@ namespace { { // Increase stats for the best move in case it was a capture move captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(bestMove)] << bonus1; } @@ -1849,9 +1849,9 @@ namespace { { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - if (!(pos.wall_gating() && from_to(capturesSearched[i]) == from_to(bestMove))) + if (!(pos.walling() && from_to(capturesSearched[i]) == from_to(bestMove))) captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(capturesSearched[i])] << -bonus1; } } @@ -1887,7 +1887,7 @@ namespace { Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); thisThread->mainHistory[us][from_to(move)] << bonus; - if (pos.wall_gating()) + if (pos.walling()) thisThread->gateHistory[us][gating_square(move)] << bonus; update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); diff --git a/src/uci.cpp b/src/uci.cpp index 9902afd3..584977aa 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -535,7 +535,7 @@ string UCI::move(const Position& pos, Move m) { : UCI::square(pos, from)) + UCI::square(pos, to); // Wall square - if (pos.wall_gating() && CurrentProtocol == XBOARD) + if (pos.walling() && CurrentProtocol == XBOARD) move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m)); if (type_of(m) == PROMOTION) @@ -552,7 +552,7 @@ string UCI::move(const Position& pos, Move m) { } // Wall square - if (pos.wall_gating() && CurrentProtocol != XBOARD) + if (pos.walling() && CurrentProtocol != XBOARD) move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m)); return move; diff --git a/src/variant.cpp b/src/variant.cpp index a0ce8156..311651c9 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -500,7 +500,7 @@ namespace { v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = piece_set(COMMONER); - v->duckGating = true; + v->duckWalling = true; v->stalemateValue = VALUE_MATE; return v; } @@ -512,10 +512,10 @@ namespace { v->maxFile = FILE_F; v->reset_pieces(); v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture - v->startFen = "2p3/6/6/6/6/6/6/3P2 w - - 0 1"; + v->startFen = "3p2/6/6/6/6/6/6/2P3 w - - 0 1"; v->stalemateValue = -VALUE_MATE; - v->staticGating = true; - v->staticGatingRegion = AllSquares ^ make_bitboard(SQ_C1, SQ_D8); + v->staticWalling = true; + v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_C1, SQ_D8); return v; } @@ -524,7 +524,7 @@ namespace { v->maxRank = RANK_7; v->maxFile = FILE_G; v->startFen = "3p3/7/7/7/7/7/3P3 w - - 0 1"; - v->staticGatingRegion = AllSquares ^ make_bitboard(SQ_D1, SQ_D7); + v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_D1, SQ_D7); return v; } @@ -536,7 +536,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->pastGating = true; + v->pastWalling = true; return v; } @@ -547,7 +547,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->pastGating = true; + v->pastWalling = true; return v; } @@ -1653,7 +1653,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->arrowGating = true; + v->arrowWalling = true; return v; } #endif diff --git a/src/variant.h b/src/variant.h index 0492dccc..37de4203 100644 --- a/src/variant.h +++ b/src/variant.h @@ -106,11 +106,11 @@ struct Variant { int dropNoDoubledCount = 1; bool immobilityIllegal = false; bool gating = false; - bool arrowGating = false; - bool duckGating = false; - bool staticGating = false; - bool pastGating = false; - Bitboard staticGatingRegion = AllSquares; + bool arrowWalling = false; + bool duckWalling = false; + bool staticWalling = false; + bool pastWalling = false; + Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares}; bool seirawanGating = false; bool cambodianMoves = false; Bitboard diagonalLines = 0; diff --git a/src/variants.ini b/src/variants.ini index 60c701cf..71cb8d2d 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -211,11 +211,12 @@ # 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) -# arrowGating: gating of wall squares in Game of the Amazons style [bool] (default: false) -# duckGating: gating of a wall square in Duck chess style [bool] (default: false) -# staticGating: gating of wall squares in Isolation style [bool] (default: false) -# staticGatingRegion: mask where wall squares can be placed [Bitboard] (default: all squares) -# pastGating: gating of previous square [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) +# 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] @@ -1345,9 +1346,27 @@ pieceToCharTable = P...Q..AH..ECTDY....LKp...q..ah..ectdy....lk # Atomic + duck chess hybrid. # Playable as a custom variant in chess.com [atomicduck:atomic] -duckGating = true +duckWalling = true stalemateValue = win +#https://www.chessvariants.com/diffmove.dir/checkers.html +[forward:chess] +pieceToCharTable = PNBRQGE......S.F..LKpnbrqge......s.f..lk +#pieces move only forward. Not even sideways, so no castling. +castling = false +#I tried to pick letters that would remind people of the full piece. +startFen = lesfgsel/pppppppp/8/8/8/8/PPPPPPPP/LESFGSEL w KQkq - 0 1 +lance = l +customPiece1 = g:fFfW +customPiece2 = e:ffNfsN +customPiece3 = s:fB +customPiece4 = f:fBfR +promotedPieceType = l:r e:n s:b f:q g:k +#since the king changes forms, should be an extinction variant +extinctionValue = loss +extinctionPieceTypes = kg +extinctionPseudoRoyal = true + [kono] maxRank = 5 maxFile = e From 7015c1d0f3de6de8e80446db7f09d2c17ca9107c Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Wed, 6 Sep 2023 23:15:17 +0200 Subject: [PATCH 05/39] Fix Betza parsing of half-plane modifiers And add unit tests. Closes #709. --- src/piece.cpp | 4 ++-- test.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/piece.cpp b/src/piece.cpp index 39bec12b..1a48f802 100644 --- a/src/piece.cpp +++ b/src/piece.cpp @@ -152,9 +152,9 @@ namespace { v[Direction(atom.first * FILE_NB + atom.second)] = distance; if (directions.size() == 0 || has_dir("bb") || has_dir("vv") || has_dir("lb") || has_dir("lv") || has_dir("bh") || has_dir("lh") || has_dir("hr")) v[Direction(-atom.first * FILE_NB - atom.second)] = distance; - if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("br") || has_dir("bs") || has_dir("bh") || has_dir("lh") || has_dir("hr")) + if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("br") || has_dir("bs") || has_dir("bh") || has_dir("rh") || has_dir("hr")) v[Direction(-atom.second * FILE_NB + atom.first)] = distance; - if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("fl") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hr")) + if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("fl") || has_dir("fs") || has_dir("fh") || has_dir("lh") || has_dir("hr")) v[Direction(atom.second * FILE_NB - atom.first)] = distance; if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("fr") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hl")) v[Direction(atom.second * FILE_NB + atom.first)] = distance; diff --git a/test.py b/test.py index 11558177..b2d953a5 100644 --- a/test.py +++ b/test.py @@ -91,6 +91,15 @@ startFen = 7k/5Kq1/8/8/8/8/8/8 w - - 0 1 stalemateValue = loss nFoldValue = loss + +[betzatest] +maxRank = 7 +maxFile = 7 +customPiece1 = a:lhN +customPiece2 = b:rhN +customPiece3 = c:hlN +customPiece4 = d:hrN +startFen = 7/7/7/3A3/7/7/7 w - - 0 1 """ sf.load_variant_config(ini_text) @@ -330,6 +339,17 @@ def test_legal_moves(self): result = sf.legal_moves("yarishogi", sf.start_fen("yarishogi"), []) self.assertCountEqual(legals, result) + # Test betza parsing + result = sf.legal_moves("betzatest", "7/7/7/3A3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4c2', 'd4b3', 'd4b5', 'd4c6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3B3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4e2', 'd4f3', 'd4f5', 'd4e6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3C3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4e2', 'd4b3', 'd4f5', 'd4c6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3D3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4c2', 'd4f3', 'd4b5', 'd4e6'], result) + + def test_short_castling(self): legals = ['f5f4', 'a7a6', 'b7b6', 'c7c6', 'd7d6', 'e7e6', 'i7i6', 'j7j6', 'a7a5', 'b7b5', 'c7c5', 'e7e5', 'i7i5', 'j7j5', 'b8a6', 'b8c6', 'h6g4', 'h6i4', 'h6j5', 'h6f7', 'h6g8', 'h6i8', 'd5a2', 'd5b3', 'd5f3', 'd5c4', 'd5e4', 'd5c6', 'd5e6', 'd5f7', 'd5g8', 'j8g8', 'j8h8', 'j8i8', 'e8f7', 'c8b6', 'c8d6', 'g6g2', 'g6g3', 'g6f4', 'g6g4', 'g6h4', 'g6e5', 'g6g5', 'g6i5', 'g6a6', 'g6b6', 'g6c6', 'g6d6', 'g6e6', 'g6f6', 'g6h8', 'f8f7', 'f8g8', 'f8i8'] moves = ['b2b4', 'f7f5', 'c2c3', 'g8d5', 'a2a4', 'h8g6', 'f2f3', 'i8h6', 'h2h3'] From 6e37b8c3a42e2306a88ae0ea2609dfaa6a21ca3f Mon Sep 17 00:00:00 2001 From: RainRat Date: Thu, 7 Sep 2023 11:50:32 -0700 Subject: [PATCH 06/39] Opposite castling (#710) --- src/parser.cpp | 1 + src/position.cpp | 7 +++++++ src/variant.h | 1 + src/variants.ini | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/src/parser.cpp b/src/parser.cpp index 4b074409..47fd5d23 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -388,6 +388,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("castlingRookPieces", v->castlingRookPieces[BLACK], v->pieceToChar); parse_attribute("castlingRookPiecesWhite", v->castlingRookPieces[WHITE], v->pieceToChar); parse_attribute("castlingRookPiecesBlack", v->castlingRookPieces[BLACK], v->pieceToChar); + parse_attribute("oppositeCastling", v->oppositeCastling); parse_attribute("checking", v->checking); parse_attribute("dropChecks", v->dropChecks); parse_attribute("mustCapture", v->mustCapture); diff --git a/src/position.cpp b/src/position.cpp index a87ce1d6..5d504882 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1659,6 +1659,13 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { k ^= Zobrist::castling[st->castlingRights]; st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + + // Remove castling rights from opponent on the same side if oppositeCastling + if ((var->oppositeCastling) && (type_of(m) == CASTLING)) + { + bool kingSide = to > from; + st->castlingRights &= ~(~us & (kingSide ? KING_SIDE : QUEEN_SIDE)); + } k ^= Zobrist::castling[st->castlingRights]; } diff --git a/src/variant.h b/src/variant.h index 37de4203..94f67b37 100644 --- a/src/variant.h +++ b/src/variant.h @@ -83,6 +83,7 @@ struct Variant { File castlingRookKingsideFile = FILE_MAX; // only has to match if rook is not in corner in non-960 variants File castlingRookQueensideFile = FILE_A; // only has to match if rook is not in corner in non-960 variants PieceSet castlingRookPieces[COLOR_NB] = {piece_set(ROOK), piece_set(ROOK)}; + bool oppositeCastling = false; PieceType kingType = KING; bool checking = true; bool dropChecks = true; diff --git a/src/variants.ini b/src/variants.ini index 71cb8d2d..45327f55 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -189,6 +189,7 @@ # castlingRookKingsideFile: starting file of castlingRookPieces on kingside (if not in corner) [File] (default: l) # castlingRookQueensideFile: starting file of castlingRookPieces on queenside (if not in corner) [File] (default: a) # castlingRookPieces: second piece type that participates in castling [PieceSet] (default: r) +# oppositeCastling: can't castle same side as opponent [bool] (default: false) # checking: allow checks [bool] (default: true) # dropChecks: allow checks by piece drops [bool] (default: true) # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) @@ -1672,3 +1673,6 @@ enclosingDrop = none maxRank = 7 maxFile = 7 startFen = 1PPPPP1/p5P/p5P/p5P/p5P/p5P/1ppppp1 + +[opposite-castling:chess] +oppositeCastling = true From ceedd61356ff1e0850d98307dde38721dea1afe4 Mon Sep 17 00:00:00 2001 From: rainrat Date: Wed, 6 Sep 2023 20:31:03 -0700 Subject: [PATCH 07/39] Add Nim, Roll-ing to Four --- src/variants.ini | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/variants.ini b/src/variants.ini index 45327f55..10283790 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -1657,6 +1657,26 @@ pawnTypes = p petrifyOnCapture = true enPassantRegion = - +#https://en.wikipedia.org/wiki/Nim +#FSF can be used to analyse Nim. The number of empty squares between the pieces is the number of items in the stack, +#the number in the custom piece is the number of items you can nim. This is the popular 1-3-5-7 stacks layout. +[nim] +maxRank = 9 +maxFile = d +#if the Nim variant has special rules, ie.nimming<=3 pieces then: +#customPiece1 = p:mfR3 +customPiece1 = p:mfR +startFen = 3p/4/2p1/4/1p2/4/p3/4/PPPP +stalemateValue = loss + +#https://www.ludii.games/details.php?keyword=Roll-Ing%20to%20Four +[roll-ing-to-four] +maxRank = 10 +maxFile = d +customPiece1 = p:mfFmfW +startFen = 1ppp/4/4/4/1PPP/ppp1/4/4/4/PPP1 +connectN = 4 + #https://www.ludii.games/details.php?keyword=Quad%20Wrangle [quadwrangle:ataxx] #different sources give different info on whether it is a 7x7 or 8x8 board. From 25bd564eef4cc07c736633cf57fc669f5315d9ee Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Thu, 7 Sep 2023 22:46:29 +0200 Subject: [PATCH 08/39] Handle FENs with wrong board size (#712) Try to avoid crashing on FENs with wrong board size and parse them to the extent possible. --- src/position.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 5d504882..de08457e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -264,7 +264,8 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, ss >> std::noskipws; - Square sq = SQ_A1 + max_rank() * NORTH; + Rank r = max_rank(); + Square sq = SQ_A1 + r * NORTH; // 1. Piece placement while ((ss >> token) && !isspace(token)) @@ -283,11 +284,19 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, else if (token == '/') { - sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST; + sq = SQ_A1 + --r * NORTH; if (!is_ok(sq)) break; } + // Stop before pieces in hand + else if (token == '[') + break; + + // Ignore pieces outside the board and wait for next / or [ to return to a valid state + else if (!is_ok(sq) || file_of(sq) > max_file() || rank_of(sq) > r) + continue; + // Wall square else if (token == '*') { @@ -311,10 +320,6 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx)); ++sq; } - - // Stop before pieces in hand - else if (token == '[') - break; } // Pieces in hand if (!isspace(token)) From 00b9087199d3c6f5a3c0f543b32f41ad142f906b Mon Sep 17 00:00:00 2001 From: RainRat Date: Sat, 9 Sep 2023 00:36:27 -0700 Subject: [PATCH 09/39] add Crusade, Blackletter Chess (#714) --- src/variants.ini | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/variants.ini b/src/variants.ini index 10283790..86309a5c 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -172,10 +172,10 @@ # petrifyBlastPieces: if petrify and blast combined, should pieces destroyed in the blast be petrified? [bool] (default: false) # doubleStep: enable pawn double step [bool] (default: true) # doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2) -# doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *2) +# doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *7) # tripleStepRegionWhite: region where pawn triple steps are allowed for white [Bitboard] (default: -) # tripleStepRegionBlack: region where pawn triple steps are allowed for black [Bitboard] (default: -) -# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] +# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] (default: AllSquares) # enPassantTypes: define pieces able to capture en passant [PieceSet] (default: p) # enPassantTypesWhite: define white pieces able to capture en passant [PieceSet] (default: p) # enPassantTypesBlack: define black pieces able to capture en passant [PieceSet] (default: p) @@ -1694,5 +1694,30 @@ maxRank = 7 maxFile = 7 startFen = 1PPPPP1/p5P/p5P/p5P/p5P/p5P/1ppppp1 +#https://www.ludii.games/details.php?keyword=Crusade +[crusade:quadwrangle] +startFen = PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP +customPiece1 = p:cK +flipEnclosedPieces = ataxx +pieceDrops = false +passOnStalemate = false + +#https://www.chess.com/variants/blackletter-chess +[blackletter:chess] +pieceToCharTable = PNBRQ.......E...M...HKpnbrq.......e...m...hk +maxRank = 10 +maxFile = 9 +promotionRegionWhite = *10 +promotionRegionBlack = *1 +archbishop = e +chancellor = m +centaur = h +startFen = 1ebq1meb1/rn2k2nr/ppppppppp/9/9/9/9/PPPPPPPPP/RN2K2NR/1EBQ1MEB1[HHhh] w KQkq - 0 1 +promotionPieceTypes = behmnqr +doubleStepRegionWhite = *3 +doubleStepRegionBlack = *8 +pieceDrops = true +castlingRank = 2 + [opposite-castling:chess] oppositeCastling = true From e408eb87cfcb18529603e22ec8c59caf8183a3dd Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sat, 9 Sep 2023 14:17:36 +0200 Subject: [PATCH 10/39] Do not print info string in CECP --- src/evaluate.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c6c7ef3d..396d5ff6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -162,10 +162,13 @@ namespace Eval { exit(EXIT_FAILURE); } - if (useNNUE) - sync_cout << "info string NNUE evaluation using " << eval_file_loaded << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; + if (CurrentProtocol != XBOARD) + { + if (useNNUE) + sync_cout << "info string NNUE evaluation using " << eval_file_loaded << " enabled" << sync_endl; + else + sync_cout << "info string classical evaluation enabled" << sync_endl; + } } } From 390bf976841da4a6682cba05b14d7b2ded1bd85a Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 10 Sep 2023 00:30:05 +0200 Subject: [PATCH 11/39] Add perft tests for atomic variants --- src/variant.cpp | 2 +- tests/perft.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/variant.cpp b/src/variant.cpp index 311651c9..e25d7ba0 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -1807,11 +1807,11 @@ void VariantMap::init() { add("horde", horde_variant()); add("nocheckatomic", nocheckatomic_variant()); add("atomic", atomic_variant()); + add("atomar", atomar_variant()); add("isolation", isolation_variant()); add("isolation7x7", isolation7x7_variant()); add("snailtrail", snailtrail_variant()); add("fox-and-hounds", fox_and_hounds_variant()); - add("atomar", atomar_variant()); #ifdef ALLVARS add("duck", duck_variant()); #endif diff --git a/tests/perft.sh b/tests/perft.sh index 1cc3886f..f6845f9a 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -92,6 +92,10 @@ if [[ $1 == "all" || $1 == "variant" ]]; then expect perft.exp atomic "fen rn2kb1r/1pp1p2p/p2q1pp1/3P4/2P3b1/4PN2/PP3PPP/R2QKB1R b KQkq - 0 1" 4 1434825 > /dev/null expect perft.exp atomic "fen rn1qkb1r/p5pp/2p5/3p4/N3P3/5P2/PPP4P/R1BQK3 w Qkq - 0 1" 4 714499 > /dev/null expect perft.exp atomic "fen r4b1r/2kb1N2/p2Bpnp1/8/2Pp3p/1P1PPP2/P5PP/R3K2R b KQ - 0 1" 2 148 > /dev/null + expect perft.exp atomar startpos 4 197779 > /dev/null + expect perft.exp atomar "fen 7r/6Pb/1np5/1p2kp2/P1P1n3/1P2KP1B/5N2/8 w - - 0 1" 3 24644 > /dev/null + expect perft.exp nocheckatomic startpos 4 197779 > /dev/null + expect perft.exp nocheckatomic "fen 7r/6Pb/1np5/1p2kp2/P1P1n3/1P2KP1B/5N2/8 w - - 0 1" 3 21347 > /dev/null expect perft.exp antichess startpos 4 153299 > /dev/null expect perft.exp giveaway startpos 4 153299 > /dev/null expect perft.exp giveaway "fen 8/1p6/8/8/8/8/P7/8 w - - 0 1" 4 3 > /dev/null From 8e3fe58f4401245b02e4a8be8726f08a503d5959 Mon Sep 17 00:00:00 2001 From: RainRat Date: Sun, 10 Sep 2023 04:23:42 -0700 Subject: [PATCH 12/39] add Castle Chess (#717) --- src/parser.cpp | 3 ++- src/position.cpp | 30 ++++++++++++++++++++++++++++++ src/variant.cpp | 9 +++++++++ src/variant.h | 2 ++ src/variants.ini | 5 +++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/parser.cpp b/src/parser.cpp index 47fd5d23..df0ce18f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -472,7 +472,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("connectDiagonal", v->connectDiagonal); parse_attribute("materialCounting", v->materialCounting); parse_attribute("countingRule", v->countingRule); - + parse_attribute("castlingWins", v->castlingWins); + // Report invalid options if (DoCheck) { diff --git a/src/position.cpp b/src/position.cpp index de08457e..8f7185cf 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2736,6 +2736,36 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { return true; } } + + // Castle chess + if (var->castlingWinConditions) { + if (type_of(st->move) == CASTLING) + { + //check for victory first, because castling also removes castling rights. + CastlingRights justCastled = static_cast((sideToMove == BLACK ? WHITE_OOO : BLACK_OOO) + >> ((from_sq(st->move) < to_sq(st->move)) ? 1 : 0)); + if (var->castlingWinConditions & justCastled) + { + result = mated_in(ply); + return true; + } + } + if ((var->castlingWinConditions & BLACK_CASTLING) && (!(var->castlingWinConditions & BLACK_CASTLING & st->castlingRights))) + { + //black permanently losing castling rights. either through moving a castling piece, + //or having their rook captured. Either way, black lost. + result = sideToMove == WHITE ? mate_in(ply) : mated_in(ply); + return true; + } + if ((var->castlingWinConditions & WHITE_CASTLING) && (!(var->castlingWinConditions & WHITE_CASTLING & st->castlingRights))) + { + //white permanently losing castling rights. either through moving a castling piece, + //or having their rook captured. Either way, white lost. + result = sideToMove == BLACK ? mate_in(ply) : mated_in(ply); + return true; + } + } + // nCheck if (check_counting() && checks_remaining(~sideToMove) == 0) { diff --git a/src/variant.cpp b/src/variant.cpp index e25d7ba0..6162a4b7 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -2041,6 +2041,15 @@ Variant* Variant::conclude() { connect_directions.push_back(SOUTH_EAST); } + for (char c : castlingWins) { + switch (c) { + case 'K': castlingWinConditions = static_cast(castlingWinConditions | WHITE_OO); break; + case 'Q': castlingWinConditions = static_cast(castlingWinConditions | WHITE_OOO); break; + case 'k': castlingWinConditions = static_cast(castlingWinConditions | BLACK_OO); break; + case 'q': castlingWinConditions = static_cast(castlingWinConditions | BLACK_OOO); break; + } + } + return this; } diff --git a/src/variant.h b/src/variant.h index 94f67b37..449407ef 100644 --- a/src/variant.h +++ b/src/variant.h @@ -157,6 +157,7 @@ struct Variant { bool connectDiagonal = true; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; + std::string castlingWins = ""; // Derived properties bool fastAttacks = true; @@ -172,6 +173,7 @@ struct Variant { bool endgameEval = false; bool shogiStylePromotions = false; std::vector connect_directions; + CastlingRights castlingWinConditions = NO_CASTLING; void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') { // Avoid ambiguous definition by removing existing piece with same letter diff --git a/src/variants.ini b/src/variants.ini index 86309a5c..8e7881a7 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -266,6 +266,7 @@ # connectDiagonal: connectN looks at Diagonal rows [bool] (default: true) # 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. (ie. KQkq) [string] (default: "") ################################################ ### Example for minishogi configuration that would be equivalent to the built-in variant: @@ -1719,5 +1720,9 @@ doubleStepRegionBlack = *8 pieceDrops = true castlingRank = 2 +#https://www.chessvariants.com/winning.dir/castle.html +[castle:chess] +castlingWins = q + [opposite-castling:chess] oppositeCastling = true From 5f986594105aa766f7d085fb744c9e5fa209e99a Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 10 Sep 2023 14:10:44 +0200 Subject: [PATCH 13/39] Add CastlingRights as dedicated config type No functional change. --- src/parser.cpp | 22 ++++++++++++++++++++++ src/position.cpp | 36 ++++++++++++++++-------------------- src/variant.cpp | 9 --------- src/variant.h | 3 +-- src/variants.ini | 3 ++- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index df0ce18f..078c3387 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -124,6 +124,27 @@ namespace { return !ss.fail(); } + template <> bool set(const std::string& value, CastlingRights& target) { + char c; + CastlingRights castlingRight; + std::stringstream ss(value); + target = NO_CASTLING; + bool valid = true; + while (ss >> c && c != '-') + { + castlingRight = c == 'K' ? WHITE_OO + : c == 'Q' ? WHITE_OOO + : c == 'k' ? BLACK_OO + : c == 'q' ? BLACK_OOO + : NO_CASTLING; + if (castlingRight) + target = CastlingRights(target | castlingRight); + else + valid = false; + } + return valid; + } + template void set(PieceType pt, T& target) { target.insert(pt); } @@ -158,6 +179,7 @@ template bool VariantParser::parse_attribute(co : std::is_same() ? "ChasingRule" : std::is_same() ? "EnclosingRule" : std::is_same() ? "Bitboard" + : std::is_same() ? "CastlingRights" : typeid(T).name(); std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; } diff --git a/src/position.cpp b/src/position.cpp index 8f7185cf..a55d50b9 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2738,32 +2738,28 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } // Castle chess - if (var->castlingWinConditions) { - if (type_of(st->move) == CASTLING) + if (var->castlingWins) + { + if (st->pliesFromNull > 0 && type_of(st->move) == CASTLING) { - //check for victory first, because castling also removes castling rights. - CastlingRights justCastled = static_cast((sideToMove == BLACK ? WHITE_OOO : BLACK_OOO) - >> ((from_sq(st->move) < to_sq(st->move)) ? 1 : 0)); - if (var->castlingWinConditions & justCastled) + // check for victory first, because castling also removes castling rights. + CastlingRights justCastled = ~sideToMove & ((from_sq(st->move) < to_sq(st->move)) ? KING_SIDE : QUEEN_SIDE); + if (var->castlingWins & justCastled) { result = mated_in(ply); return true; } } - if ((var->castlingWinConditions & BLACK_CASTLING) && (!(var->castlingWinConditions & BLACK_CASTLING & st->castlingRights))) - { - //black permanently losing castling rights. either through moving a castling piece, - //or having their rook captured. Either way, black lost. - result = sideToMove == WHITE ? mate_in(ply) : mated_in(ply); - return true; - } - if ((var->castlingWinConditions & WHITE_CASTLING) && (!(var->castlingWinConditions & WHITE_CASTLING & st->castlingRights))) - { - //white permanently losing castling rights. either through moving a castling piece, - //or having their rook captured. Either way, white lost. - result = sideToMove == BLACK ? mate_in(ply) : mated_in(ply); - return true; - } + // We check the opponent side first, because a rook capturing a rook could remove both sides castling rights, + // which should likely be seen as losing, analogous to extinction rules. + for (Color c : { ~sideToMove, sideToMove }) + if ((c & var->castlingWins) && !(c & var->castlingWins & st->castlingRights)) + { + // player permanently losing castling rights. either through moving a castling piece, + // or having their rook captured. + result = c == sideToMove ? mated_in(ply) : mate_in(ply); + return true; + } } // nCheck diff --git a/src/variant.cpp b/src/variant.cpp index 6162a4b7..e25d7ba0 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -2041,15 +2041,6 @@ Variant* Variant::conclude() { connect_directions.push_back(SOUTH_EAST); } - for (char c : castlingWins) { - switch (c) { - case 'K': castlingWinConditions = static_cast(castlingWinConditions | WHITE_OO); break; - case 'Q': castlingWinConditions = static_cast(castlingWinConditions | WHITE_OOO); break; - case 'k': castlingWinConditions = static_cast(castlingWinConditions | BLACK_OO); break; - case 'q': castlingWinConditions = static_cast(castlingWinConditions | BLACK_OOO); break; - } - } - return this; } diff --git a/src/variant.h b/src/variant.h index 449407ef..dd6036a3 100644 --- a/src/variant.h +++ b/src/variant.h @@ -157,7 +157,7 @@ struct Variant { bool connectDiagonal = true; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; - std::string castlingWins = ""; + CastlingRights castlingWins = NO_CASTLING; // Derived properties bool fastAttacks = true; @@ -173,7 +173,6 @@ struct Variant { bool endgameEval = false; bool shogiStylePromotions = false; std::vector connect_directions; - CastlingRights castlingWinConditions = NO_CASTLING; void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') { // Avoid ambiguous definition by removing existing piece with same letter diff --git a/src/variants.ini b/src/variants.ini index 8e7881a7..7d0b36ff 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -124,6 +124,7 @@ # [int]: any natural number [0, 1, ...] # [PieceType]: a piece type [letters defined for pieces, e.g., p] # [PieceSet]: multiple piece types [letters defined for pieces, e.g., nbrq] +# [CastlingRights]: set of castling rights [letters for castling rights as in FEN, e.g., KQkq] # [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) # [Value]: game result for the side to move [win, loss, draw] # [MaterialCounting]: material counting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] @@ -266,7 +267,7 @@ # connectDiagonal: connectN looks at Diagonal rows [bool] (default: true) # 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. (ie. KQkq) [string] (default: "") +# castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -) ################################################ ### Example for minishogi configuration that would be equivalent to the built-in variant: From 1355f7ee1e33910102e84dac0328933c4dd3a802 Mon Sep 17 00:00:00 2001 From: Brian Svoboda Date: Tue, 12 Sep 2023 01:11:14 -0600 Subject: [PATCH 14/39] Add Gethenian variant (#720) --- src/variants.ini | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/variants.ini b/src/variants.ini index 7d0b36ff..60ccfafe 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -1727,3 +1727,33 @@ castlingWins = q [opposite-castling:chess] oppositeCastling = true + +# A Kyoto Shogi variant with a left/right theme. +[gethenian] +maxRank = 7 +maxFile = 7 +king = - +customPiece1 = k:K +customPiece2 = q:mW +customPiece3 = b:lfrbB +customPiece4 = i:rflbB +customPiece5 = r:lrR +customPiece6 = n:hlN +customPiece7 = t:hrN +customPiece8 = m:WfF +customPiece9 = s:FfW +startFen = 2ikb2/2mnm2/7/7/7/2MNM2/2B+KI2[] w - - 0 1 +promotionPieceTypes = - +promotedPieceType = k:q b:r i:r n:t m:s +promotionRegionWhite = *1 *2 *3 *4 *5 *6 *7 +promotionRegionBlack = *7 *6 *5 *4 *3 *2 *1 +mandatoryPiecePromotion = true +pieceDemotion = true +pieceDrops = true +capturesToHand = true +dropPromoted = true +immobilityIllegal = false +extinctionValue = loss +extinctionPieceTypes = kq +extinctionPseudoRoyal = true +stalemateValue = loss From cd412cc7d4b83b5f61dab0ea491d9507e965deff Mon Sep 17 00:00:00 2001 From: RainRat Date: Fri, 15 Sep 2023 10:10:53 -0700 Subject: [PATCH 15/39] add Snort (#721) --- src/bitboard.cpp | 4 ++-- src/parser.cpp | 3 ++- src/position.h | 6 ++++++ src/types.h | 2 +- src/variants.ini | 14 +++++++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index b4671f12..3939296c 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -64,7 +64,7 @@ namespace { // Some magics need to be split in order to reduce memory consumption. // Otherwise on a 12x10 board they can be >100 MB. #ifdef LARGEBOARDS - Bitboard RookTableH[0x11800]; // To store horizontalrook attacks + Bitboard RookTableH[0x11800]; // To store horizontal rook attacks Bitboard RookTableV[0x4800]; // To store vertical rook attacks Bitboard BishopTable[0x33C00]; // To store bishop attacks Bitboard CannonTableH[0x11800]; // To store horizontal cannon attacks @@ -220,7 +220,7 @@ std::string Bitboards::pretty(Bitboard b) { s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+---+---+---+---+\n"; } - s += " a b c d e f g h i j k\n"; + s += " a b c d e f g h i j k l\n"; return s; } diff --git a/src/parser.cpp b/src/parser.cpp index 078c3387..f222a6bc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -106,8 +106,9 @@ namespace { target = value == "reversi" ? REVERSI : value == "ataxx" ? ATAXX : value == "quadwrangle" ? QUADWRANGLE + : value == "snort" ? SNORT : NO_ENCLOSING; - return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value == "none"; + return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value =="snort" || value == "none"; } template <> bool set(const std::string& value, Bitboard& target) { diff --git a/src/position.h b/src/position.h index 22791350..34b09763 100644 --- a/src/position.h +++ b/src/position.h @@ -708,6 +708,12 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const { b ^= s; } } + else if (enclosing_drop() == SNORT) + { + Bitboard theirs = pieces(~c); + b &= ~(shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs)); + } else { assert(enclosing_drop() == ATAXX); diff --git a/src/types.h b/src/types.h index a59f8678..99c5e1aa 100644 --- a/src/types.h +++ b/src/types.h @@ -302,7 +302,7 @@ enum ChasingRule { }; enum EnclosingRule { - NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE + NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE, SNORT }; enum OptBool { diff --git a/src/variants.ini b/src/variants.ini index 60ccfafe..b5c03537 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -130,7 +130,7 @@ # [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, none] +# [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, quadwrangle, snort, none] ### Additional options relevant for usage in Winboard/XBoard # A few options only have the purpose of improving compatibility with Winboard/Xboard. @@ -1704,6 +1704,18 @@ flipEnclosedPieces = ataxx pieceDrops = false passOnStalemate = false +#https://www.ludii.games/details.php?keyword=Snort +#also known as Cats & Dogs +[snort:ataxx] +immobile = p +startFen = 8/8/8/8/8/8/8/8 +enclosingDrop = snort +flipEnclosedPieces = none +maxRank = 8 +maxFile = 8 +passOnStalemate = false +stalematePieceCount = false + #https://www.chess.com/variants/blackletter-chess [blackletter:chess] pieceToCharTable = PNBRQ.......E...M...HKpnbrq.......e...m...hk From 1b5a7893d112a95c728878de14500e940708a5bd Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 15 Sep 2023 19:26:24 +0200 Subject: [PATCH 16/39] Support petrified (#713) --- src/parser.cpp | 2 +- src/position.cpp | 31 +++++++++++++++++++------------ src/variant.cpp | 17 +++++++++++++++++ src/variant.h | 2 +- src/variants.ini | 4 ++-- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index f222a6bc..4871446b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -383,7 +383,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("blastOnCapture", v->blastOnCapture); parse_attribute("blastImmuneTypes", v->blastImmuneTypes, v->pieceToChar); parse_attribute("mutuallyImmuneTypes", v->mutuallyImmuneTypes, v->pieceToChar); - parse_attribute("petrifyOnCapture", v->petrifyOnCapture); + parse_attribute("petrifyOnCaptureTypes", v->petrifyOnCaptureTypes, v->pieceToChar); parse_attribute("petrifyBlastPieces", v->petrifyBlastPieces); parse_attribute("doubleStep", v->doubleStep); parse_attribute("doubleStepRegionWhite", v->doubleStepRegion[WHITE]); diff --git a/src/position.cpp b/src/position.cpp index a55d50b9..70912854 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1129,6 +1129,9 @@ bool Position::legal(Move m) const { occupied &= ~square_bb(capture_square(kto)); if (capture(m) && blast_on_capture()) occupied &= ~((attacks_bb(kto) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | kto); + // Petrifying a pseudo-royal piece is illegal + if (capture(m) && (var->petrifyOnCaptureTypes & type_of(moved_piece(m))) && (st->pseudoRoyals & from)) + return false; Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); if (is_ok(from) && (pseudoRoyals & from)) @@ -1145,6 +1148,10 @@ bool Position::legal(Move m) const { // Self-explosions are illegal if (pseudoRoyals & ~occupied) return false; + // Petrifiable pseudo-royals can't capture + Bitboard attackerCandidatesTheirs = occupied & ~square_bb(kto); + for (PieceSet ps = var->petrifyOnCaptureTypes & extinction_piece_types(); ps;) + attackerCandidatesTheirs &= ~pieces(~us, pop_lsb(ps)); // Check for legality unless we capture a pseudo-royal piece if (!(pseudoRoyalsTheirs & ~occupied)) while (pseudoRoyals) @@ -1152,7 +1159,7 @@ bool Position::legal(Move m) const { Square sr = pop_lsb(pseudoRoyals); // Touching pseudo-royal pieces are immune if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) - && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto)))) + && (attackers_to(sr, occupied, ~us) & attackerCandidatesTheirs)) return false; } // Look for duple check @@ -1169,7 +1176,7 @@ bool Position::legal(Move m) const { Square sr = pop_lsb(pseudoRoyalCandidates); // Touching pseudo-royal pieces are immune if (!( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) - && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto))))) + && (attackers_to(sr, occupied, ~us) & attackerCandidatesTheirs))) allCheck = false; } if (allCheck) @@ -1177,10 +1184,6 @@ bool Position::legal(Move m) const { } } - // Petrifying the king is illegal - if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) == KING) - return false; - // mutuallyImmuneTypes (diplomacy in Atomar)-- In no-check Atomic, kings can be beside each other, but in Atomar, this prevents them from actually taking. // Generalized to allow a custom set of pieces that can't capture a piece of the same type. if (capture(m) && @@ -1405,7 +1408,7 @@ bool Position::gives_check(Move m) const { // Is there a direct check? if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && type_of(m) != CASTLING - && !(var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) != PAWN)) + && !((var->petrifyOnCaptureTypes & type_of(moved_piece(m))) && capture(m))) { PieceType pt = type_of(moved_piece(m)); if (pt == JANGGI_CANNON) @@ -1433,7 +1436,7 @@ bool Position::gives_check(Move m) const { return true; // Petrified piece can't give check - if (var->petrifyOnCapture && capture(m) && type_of(moved_piece(m)) != PAWN) + if ((var->petrifyOnCaptureTypes & type_of(moved_piece(m))) && capture(m)) return false; // Is there a check by special diagonal moves? @@ -1946,7 +1949,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Remove the blast pieces - if (captured && (blast_on_capture() || var->petrifyOnCapture)) + if (captured && (blast_on_capture() || var->petrifyOnCaptureTypes)) { std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch)); st->demotedBycatch = st->promotedBycatch = 0; @@ -1956,7 +1959,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { blastImmune |= pieces(pt); }; Bitboard blast = blast_on_capture() ? ((attacks_bb(to) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | to) - & (pieces() ^ blastImmune) : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0); + & (pieces() ^ blastImmune) : var->petrifyOnCaptureTypes & type_of(pc) ? square_bb(to) : Bitboard(0); while (blast) { Square bsq = pop_lsb(blast); @@ -2018,7 +2021,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Make a wall square where the piece was - if (bsq == to ? var->petrifyOnCapture : var->petrifyBlastPieces) + if (bsq == to ? bool(var->petrifyOnCaptureTypes & type_of(bpc)) : var->petrifyBlastPieces) { st->wallSquares |= bsq; byTypeBB[ALL_PIECES] |= bsq; @@ -2116,7 +2119,7 @@ void Position::undo_move(Move m) { byTypeBB[ALL_PIECES] ^= st->wallSquares ^ st->previous->wallSquares; // Add the blast pieces - if (st->capturedPiece && (blast_on_capture() || var->petrifyOnCapture)) + if (st->capturedPiece && (blast_on_capture() || var->petrifyOnCaptureTypes)) { Bitboard blast = attacks_bb(to) | to; while (blast) @@ -2435,6 +2438,10 @@ bool Position::see_ge(Move m, Value threshold) const { if (swap <= 0) return true; + // Petrification ends SEE + if (var->petrifyOnCaptureTypes & type_of(moved_piece(m)) && capture(m)) + return false; + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) ^ to; Color stm = color_of(moved_piece(m)); Bitboard attackers = attackers_to(to, occupied); diff --git a/src/variant.cpp b/src/variant.cpp index e25d7ba0..2fc1c9c4 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -460,6 +460,20 @@ namespace { v->extinctionPieceTypes = piece_set(ALL_PIECES); return v; } + // Petrified + // Sideways pawns + petrification on capture + // https://www.chess.com/variants/petrified + Variant* petrified_variant() { + Variant* v = pawnsideways_variant()->init(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->extinctionPseudoRoyal = true; + v->petrifyOnCaptureTypes = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } // Atomic chess without checks (ICC rules) // https://www.chessclub.com/help/atomic Variant* nocheckatomic_variant() { @@ -493,6 +507,7 @@ namespace { #ifdef ALLVARS // Duck chess + // https://duckchess.com/ Variant* duck_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(KING); @@ -1805,6 +1820,7 @@ void VariantMap::init() { add("kinglet", kinglet_variant()); add("threekings", threekings_variant()); add("horde", horde_variant()); + add("petrified", petrified_variant()); add("nocheckatomic", nocheckatomic_variant()); add("atomic", atomic_variant()); add("atomar", atomar_variant()); @@ -2013,6 +2029,7 @@ Variant* Variant::conclude() { && !makpongRule && !connectN && !blastOnCapture + && !petrifyOnCaptureTypes && !capturesToHand && !twoBoards && !restrictedMobility diff --git a/src/variant.h b/src/variant.h index dd6036a3..2a026958 100644 --- a/src/variant.h +++ b/src/variant.h @@ -66,7 +66,7 @@ struct Variant { bool blastOnCapture = false; PieceSet blastImmuneTypes = NO_PIECE_SET; PieceSet mutuallyImmuneTypes = NO_PIECE_SET; - bool petrifyOnCapture = false; + PieceSet petrifyOnCaptureTypes = NO_PIECE_SET; bool petrifyBlastPieces = false; bool doubleStep = true; Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB}; diff --git a/src/variants.ini b/src/variants.ini index b5c03537..8afb5561 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -169,7 +169,7 @@ # blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false) # blastImmuneTypes: pieces completely immune to explosions (even at ground zero) [PieceSet] (default: none) # mutuallyImmuneTypes: pieces that can't capture another piece of same types (e.g., kings (commoners) in atomar) [PieceSet] (default: none) -# petrifyOnCapture: non-pawn pieces are turned into wall squares when capturing [bool] (default: false) +# petrifyOnCaptureTypes: defined pieces are turned into wall squares when capturing [PieceSet] (default: -) # petrifyBlastPieces: if petrify and blast combined, should pieces destroyed in the blast be petrified? [bool] (default: false) # doubleStep: enable pawn double step [bool] (default: true) # doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2) @@ -1656,7 +1656,7 @@ startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] pawn = - customPiece1 = p:fmWfceFifmnD pawnTypes = p -petrifyOnCapture = true +petrifyOnCaptureTypes = pnbrq enPassantRegion = - #https://en.wikipedia.org/wiki/Nim From 994eb7bd07ea72ee4c25b3f35af50245bf11fbff Mon Sep 17 00:00:00 2001 From: RainRat Date: Thu, 21 Sep 2023 09:47:31 -0700 Subject: [PATCH 17/39] add Edge Walling rule, suggesting refactor of Walling Rule, (#722) --- src/movegen.cpp | 18 ++++++++++++++---- src/parser.cpp | 24 ++++++++++++++++-------- src/position.cpp | 40 +++++++++++++++++++++++++++------------- src/position.h | 8 +++++++- src/search.cpp | 2 +- src/types.h | 4 ++++ src/variant.cpp | 10 +++++----- src/variant.h | 5 +---- src/variants.ini | 20 +++++++++++++++----- 9 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 0c14dd75..b658619d 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 4871446b..3d822ebb 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) { char file; int rank; @@ -181,6 +191,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; } @@ -434,14 +445,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); @@ -553,8 +561,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) @@ -563,8 +571,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. diff --git a/src/position.cpp b/src/position.cpp index 70912854..39f4888e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1104,7 +1104,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); @@ -1303,15 +1303,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() @@ -2034,7 +2048,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; @@ -2476,7 +2490,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) @@ -2969,7 +2983,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 34b09763..2311776d 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 { diff --git a/src/search.cpp b/src/search.cpp index 2502fcc2..77686ed0 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 99c5e1aa..2fa03e7c 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 2fc1c9c4..8a424681 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 2a026958..cb2fe051 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; diff --git a/src/variants.ini b/src/variants.ini index 8afb5561..e3bdf0da 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. @@ -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] @@ -1349,7 +1352,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 @@ -1769,3 +1772,10 @@ extinctionValue = loss extinctionPieceTypes = kq extinctionPseudoRoyal = true stalemateValue = loss + +#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 \ No newline at end of file From b8274a7ac6cc850e712ad8fb25397d090a511a86 Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 26 Sep 2023 02:44:24 -0700 Subject: [PATCH 18/39] 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 3d822ebb..48b8cc9a 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 2311776d..7b9b4cd8 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 cb2fe051..e8899f39 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 e3bdf0da..73bf9e99 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 19/39] 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 73bf9e99..104b038c 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 20/39] 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 48b8cc9a..1c4b4faf 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 39f4888e..c72a07f9 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 7b9b4cd8..81c61dd4 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 2fa03e7c..5abfac80 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 e8899f39..79cdc5a6 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 104b038c..38c6cc7f 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 21/39] 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 1eb31a56..ed14ee97 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 22/39] 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 38c6cc7f..926fa423 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 23/39] 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 1c4b4faf..c925b2de 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 c72a07f9..9b8ce391 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 79cdc5a6..49bc5440 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 926fa423..b7dd011f 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 24/39] 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 b658619d..43807bb0 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 c925b2de..7535e686 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 9b8ce391..a7ada05c 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 81c61dd4..4ee93665 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 8a424681..cd565baf 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 49bc5440..b9174ad0 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 b7dd011f..6b3ea740 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 482b5e47..09228edb 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 25/39] 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 602beefa..8572369b 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 26/39] 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 a03b8328..ab253c9a 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 7535e686..48aa6870 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 a7ada05c..bf6e60f2 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 537148c4..d321a57c 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 cd565baf..386c21d5 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 b9174ad0..ec199185 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 6b3ea740..abc2c709 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 b2d953a5..9587501d 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 27/39] 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 9587501d..9cf4c539 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 28/39] 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 c073aa73..d6f5226e 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." From 6c8fb4630cc3a3771b6f7e7fa723464c1795a037 Mon Sep 17 00:00:00 2001 From: RainRat Date: Wed, 24 Jan 2024 15:14:46 -0800 Subject: [PATCH 29/39] fix typos --- src/apiutil.h | 2 +- src/parser.cpp | 2 +- src/parser.h | 2 +- tests/js/test.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apiutil.h b/src/apiutil.h index 0794abc3..4b364f63 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -237,7 +237,7 @@ inline Disambiguation disambiguation_level(const Position& pos, Move m, Notation return SQUARE_DISAMBIGUATION; } - // A disambiguation occurs if we have more then one piece of type 'pt' + // A disambiguation occurs if we have more than one piece of type 'pt' // that can reach 'to' with a legal move. Bitboard b = pos.pieces(us, pt) ^ from; Bitboard others = 0; diff --git a/src/parser.cpp b/src/parser.cpp index 48aa6870..9d2b55b9 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -544,7 +544,7 @@ Variant* VariantParser::parse(Variant* v) { // Report invalid options if (DoCheck) { - const std::set& parsedKeys = config.get_comsumed_keys(); + const std::set& parsedKeys = config.get_consumed_keys(); for (const auto& it : config) if (parsedKeys.find(it.first) == parsedKeys.end()) std::cerr << "Invalid option: " << it.first << std::endl; diff --git a/src/parser.h b/src/parser.h index 36b208e0..04610b2f 100644 --- a/src/parser.h +++ b/src/parser.h @@ -34,7 +34,7 @@ class Config : public std::map { consumedKeys.insert(s); return std::map::find(s); } - const std::set& get_comsumed_keys() { + const std::set& get_consumed_keys() { return consumedKeys; } private: diff --git a/tests/js/test.js b/tests/js/test.js index 88f4c59e..3c82032c 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -768,7 +768,7 @@ describe('ffish.setOption(name, value)', function () { }); describe('ffish.setOptionInt(name, value)', function () { - it("it sets a int uci option value pair", () => { + it("it sets an int uci option value pair", () => { ffish.setOptionInt("Threads", 4); chai.expect(true).to.equal(true); }); From 052dce6a150c5dfc4a4775fc23319028904a86ab Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sat, 11 Mar 2023 22:08:35 +0100 Subject: [PATCH 30/39] Fix Makefile for clang 16 The clang 16 release will remove the -fexperimental-new-pass-manager flag (see https://github.com/llvm/llvm-project/commit/69b2b7282e92a1b576b7bd26f3b16716a5027e8e). Thus, the commit adapts the Makefile to use this flag only for older clang versions. closes https://github.com/official-stockfish/Stockfish/pull/4437 No functional change --- src/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 8572369b..4fe40955 100644 --- a/src/Makefile +++ b/src/Makefile @@ -539,7 +539,10 @@ ifeq ($(optimize),yes) endif ifeq ($(comp),clang) - CXXFLAGS += -fexperimental-new-pass-manager + clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + ifeq ($(shell expr $(clangmajorversion) \< 16),1) + CXXFLAGS += -fexperimental-new-pass-manager + endif endif endif From 7d42030f631551af033ada63c6eaa2d9ab922ddb Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 13 Feb 2024 04:49:27 -0800 Subject: [PATCH 31/39] add Three Musketeers. needs collinearN and connectPieceTypes. (#755) --- src/parser.cpp | 2 ++ src/position.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++------ src/position.h | 12 ++++++++++++ src/variant.cpp | 11 +++++++++++ src/variant.h | 2 ++ src/variants.ini | 18 ++++++++++++++++- 6 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 9d2b55b9..2a4db2d2 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -527,6 +527,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("flagPieceSafe", v->flagPieceSafe); parse_attribute("checkCounting", v->checkCounting); parse_attribute("connectN", v->connectN); + parse_attribute("connectPieceTypes", v->connectPieceTypes, v->pieceToChar); parse_attribute("connectHorizontal", v->connectHorizontal); parse_attribute("connectVertical", v->connectVertical); parse_attribute("connectDiagonal", v->connectDiagonal); @@ -535,6 +536,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("collinearN", v->collinearN); parse_attribute("connectValue", v->connectValue); parse_attribute("materialCounting", v->materialCounting); parse_attribute("adjudicateFullBoard", v->adjudicateFullBoard); diff --git a/src/position.cpp b/src/position.cpp index bf6e60f2..c449b6a9 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2703,7 +2703,7 @@ bool Position::is_optional_game_end(Value& result, int ply, int countStarted) co /// Position::is_immediate_game_end() tests whether the position ends the game /// immediately by a variant rule, i.e., there are no more legal moves. -/// It does not not detect stalemates. +/// It does not detect stalemates. bool Position::is_immediate_game_end(Value& result, int ply) const { @@ -2789,6 +2789,15 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { result = mated_in(ply); return true; } + + //Calculate eligible pieces for connection once. + Bitboard connectPieces = 0; + for (PieceSet ps = connect_piece_types(); ps;){ + PieceType pt = pop_lsb(ps); + connectPieces |= pieces(pt); + }; + connectPieces &= pieces(~sideToMove); + // Connect-n if (connect_n() > 0) { @@ -2796,7 +2805,7 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { for (Direction d : var->connect_directions) { - b = pieces(~sideToMove); + b = connectPieces; for (int i = 1; i < connect_n() && b; i++) b &= shift(d, b); if (b) @@ -2807,15 +2816,15 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } - if ((var->connectRegion1[~sideToMove] & pieces(~sideToMove)) && (var->connectRegion2[~sideToMove] & pieces(~sideToMove))) + if ((var->connectRegion1[~sideToMove] & connectPieces) && (var->connectRegion2[~sideToMove] & connectPieces)) { Bitboard target = var->connectRegion2[~sideToMove]; - Bitboard current = var->connectRegion1[~sideToMove] & pieces(~sideToMove); + Bitboard current = var->connectRegion1[~sideToMove] & connectPieces; 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 + newBitboard |= shift(d, current | newBitboard) & connectPieces; // the "| newBitboard" here probably saves a few loops } if (newBitboard & target) { @@ -2835,7 +2844,7 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { if (connect_nxn()) { - Bitboard connectors = pieces(~sideToMove); + Bitboard connectors = connectPieces; for (int i = 1; i < connect_nxn() && connectors; i++) connectors &= shift(connectors) & shift(connectors) & shift(connectors); if (connectors) @@ -2845,6 +2854,36 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } + // Collinear-n + if (collinear_n() > 0) { + Bitboard allPieces = connectPieces; + for (Direction d : var->connect_directions) { + Bitboard b = allPieces; + while (b) { + Square s = pop_lsb(b); + + int total_count = 1; // Start with the current piece + + // Check in both directions + for (int sign : {-1, 1}) { + Bitboard shifted = shift(sign * d, square_bb(s)); + while (shifted) { + if (shifted & b) { + total_count++; + b &= ~shifted; // Remove this piece from further consideration + } + shifted = shift(sign * d, shifted); + } + } + + if (total_count >= collinear_n()) { + result = convert_mate_value(-var->connectValue, ply); + return true; + } + } + } + } + // 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()))) diff --git a/src/position.h b/src/position.h index 4ee93665..adaeeb6a 100644 --- a/src/position.h +++ b/src/position.h @@ -205,11 +205,13 @@ class Position { bool flag_reached(Color c) const; bool check_counting() const; int connect_n() const; + PieceSet connect_piece_types() const; bool connect_horizontal() const; bool connect_vertical() const; bool connect_diagonal() const; const std::vector& getConnectDirections() const; int connect_nxn() const; + int collinear_n() const; CheckCount checks_remaining(Color c) const; MaterialCounting material_counting() const; @@ -1035,6 +1037,11 @@ inline int Position::connect_n() const { return var->connectN; } +inline PieceSet Position::connect_piece_types() const { + assert(var != nullptr); + return var->connectPieceTypes; +} + inline bool Position::connect_horizontal() const { assert(var != nullptr); return var->connectHorizontal; @@ -1058,6 +1065,11 @@ inline int Position::connect_nxn() const { return var->connectNxN; } +inline int Position::collinear_n() const { + assert(var != nullptr); + return var->collinearN; +} + inline CheckCount Position::checks_remaining(Color c) const { return st->checksRemaining[c]; } diff --git a/src/variant.cpp b/src/variant.cpp index 386c21d5..5b21b53d 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -2064,6 +2064,17 @@ Variant* Variant::conclude() { connect_directions.push_back(SOUTH_EAST); } + // If not a connect variant, set connectPieceTypes to no pieces. + if ( !(connectRegion1[WHITE] || connectRegion1[BLACK] || connectN || connectNxN || collinearN) ) + { + connectPieceTypes = NO_PIECE_SET; + } + //Otherwise optimize to pieces actually in the game. + else + { + connectPieceTypes = connectPieceTypes & pieceTypes; + }; + return this; } diff --git a/src/variant.h b/src/variant.h index ec199185..a34b37de 100644 --- a/src/variant.h +++ b/src/variant.h @@ -149,12 +149,14 @@ struct Variant { bool flagPieceSafe = false; bool checkCounting = false; int connectN = 0; + PieceSet connectPieceTypes = ~NO_PIECE_SET; bool connectHorizontal = true; bool connectVertical = true; bool connectDiagonal = true; Bitboard connectRegion1[COLOR_NB] = {}; Bitboard connectRegion2[COLOR_NB] = {}; int connectNxN = 0; + int collinearN = 0; Value connectValue = VALUE_MATE; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; bool adjudicateFullBoard = false; diff --git a/src/variants.ini b/src/variants.ini index abc2c709..aaf40810 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -280,6 +280,7 @@ # 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) +# connectPieceTypes: pieces evaluated for connection rule [PieceSet] (default: *) # 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) @@ -288,6 +289,7 @@ # connectRegion1Black: " # connectRegion2Black: " # connectNxN: connect a tight NxN square for win [int] (default: 0) +# collinearN: arrange N pieces collinearly (other squares can be between pieces) [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) @@ -1546,7 +1548,7 @@ nMoveRule = 0 #https://ludii.games/details.php?keyword=Djara-Badakh #https://ludii.games/details.php?keyword=Tuk%20Tak customPiece1 = p:mKmNmAmD -#moves anywhere on the board, KNAD is an list of all possible moves on a 3x3 +#moves anywhere on the board, KNAD is a list of all possible moves on a 3x3 startFen = 3/3/3[PPPppp] w - - 0 1 mustDrop = true nMoveRule = 0 @@ -1912,3 +1914,17 @@ enclosingDrop = anyside #http://gamescrafters.berkeley.edu/games.php?game=connect4 [cfour-misere:cfour] connectValue = loss + +#https://www.ludii.games/details.php?keyword=Three%20Musketeers +[three-musketeers] +pieceToCharTable = P......M..............p......m.............. +startFen = ppppM/ppppp/ppMpp/ppppp/Mpppp +maxRank = 5 +maxFile = 5 +collinearN = 3 +connectDiagonal = false +customPiece1 = m:cW +customPiece2 = p:mW +connectValue = loss +stalemateValue = win +connectPieceTypes = m From 0bfa4fcdfe9889bdf1b136f4739a3e21e25fb7d8 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 16 Feb 2024 19:42:26 +0100 Subject: [PATCH 32/39] Fix cannonshogi (#757) --- src/bitboard.cpp | 10 ++++++---- src/variants.ini | 4 ++-- test.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 3939296c..4f005759 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -111,7 +111,7 @@ namespace { const std::map GrasshopperDirectionsH { {EAST, 1}, {WEST, 1} }; const std::map GrasshopperDirectionsD { {NORTH_EAST, 1}, {SOUTH_EAST, 1}, {SOUTH_WEST, 1}, {NORTH_WEST, 1} }; - enum MovementType { RIDER, HOPPER, LAME_LEAPER, UNLIMITED_RIDER }; + enum MovementType { RIDER, HOPPER, LAME_LEAPER, HOPPER_RANGE }; template #ifdef PRECOMPUTED_MAGICS @@ -137,7 +137,9 @@ namespace { if (MT != HOPPER || hurdle) { attack |= s; - if (limit && MT != UNLIMITED_RIDER && ++count >= limit) + // For hoppers we consider limit == 1 as a grasshopper, + // but limit > 1 as a limited distance hopper + if (limit && !(MT == HOPPER_RANGE && limit == 1) && ++count >= limit) break; } @@ -300,7 +302,7 @@ void Bitboards::init_pieces() { leaper |= safe_destination(s, c == WHITE ? d : -d); } pseudo |= sliding_attack(pi->slider[initial][modality], s, 0, c); - pseudo |= sliding_attack(pi->hopper[initial][modality], s, 0, c); + pseudo |= sliding_attack(pi->hopper[initial][modality], s, 0, c); } } } @@ -420,7 +422,7 @@ namespace { // apply to the 64 or 32 bits word to get the index. Magic& m = magics[s]; // The mask for hoppers is unlimited distance, even if the hopper is limited distance (e.g., grasshopper) - m.mask = (MT == LAME_LEAPER ? lame_leaper_path(directions, s) : sliding_attack(directions, s, 0)) & ~edges; + m.mask = (MT == LAME_LEAPER ? lame_leaper_path(directions, s) : sliding_attack(directions, s, 0)) & ~edges; #ifdef LARGEBOARDS m.shift = 128 - popcount(m.mask); #else diff --git a/src/variants.ini b/src/variants.ini index aaf40810..09be0e40 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -1662,8 +1662,8 @@ cannon = u customPiece1 = a:pR customPiece2 = c:mBcpB customPiece3 = i:pB -customPiece4 = w:mRpRFAcpR -customPiece5 = f:mBpBWDcpB +customPiece4 = w:mRpRmFpB2 +customPiece5 = f:mBpBmWpR2 promotedPieceType = u:w a:w c:f i:f startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 diff --git a/test.py b/test.py index 9cf4c539..7a5aa456 100644 --- a/test.py +++ b/test.py @@ -100,6 +100,19 @@ customPiece3 = c:hlN customPiece4 = d:hrN startFen = 7/7/7/3A3/7/7/7 w - - 0 1 + +[cannonshogi:shogi] +dropNoDoubled = - +shogiPawnDropMateIllegal = false +soldier = p +cannon = u +customPiece1 = a:pR +customPiece2 = c:mBcpB +customPiece3 = i:pB +customPiece4 = w:mRpRmFpB2 +customPiece5 = f:mBpBmWpR2 +promotedPieceType = u:w a:w c:f i:f +startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 """ sf.load_variant_config(ini_text) @@ -317,6 +330,24 @@ def test_legal_moves(self): result = sf.legal_moves("shogun", SHOGUN, ["c2c4", "b8c6", "b2b4", "b7b5", "c4b5", "c6b8"]) self.assertIn("b5b6+", result) + # In Cannon Shogi the FGC and FSC can also move one square diagonally and, besides, + # move or capture two squares diagonally, by leaping an adjacent piece. + fen = "lnsg1gsnl/1rc1kuab1/p1+A1p1p1p/3P5/6i2/6P2/P1P1P3P/1B1U1ICR1/LNSGKGSNL[] w - - 1 3" + result = sf.legal_moves("cannonshogi", fen, []) + # mF + self.assertIn("c7b6", result) + self.assertIn("c7d8", result) + self.assertNotIn("c7d6", result) + self.assertNotIn("c7b8", result) + # pB2 + self.assertIn("c7a9", result) + self.assertIn("c7e5", result) + self.assertNotIn("c7a5", result) + self.assertNotIn("c7e9", result) + # verify distance limited to 2 + self.assertNotIn("c7f4", result) + self.assertNotIn("c7g3", result) + # Cambodian queen cannot capture with its leap # Cambodian king cannot leap to escape check result = sf.legal_moves("cambodian", CAMBODIAN, ["b1d2", "g8e7", "d2e4", "d6d5", "e4d6"]) From 203f8f8c1b6ed935e62f5e7d54fc655a951774b1 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 16 Feb 2024 20:10:00 +0100 Subject: [PATCH 33/39] Bump pyffish version --- setup.py | 2 +- src/pyffish.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ab253c9a..357001db 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ sources=sources, extra_compile_args=args) -setup(name="pyffish", version="0.0.80", +setup(name="pyffish", version="0.0.81", description="Fairy-Stockfish Python wrapper", long_description=long_description, long_description_content_type="text/markdown", diff --git a/src/pyffish.cpp b/src/pyffish.cpp index d321a57c..8439acd0 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, 80); + return Py_BuildValue("(iii)", 0, 0, 81); } extern "C" PyObject* pyffish_info(PyObject* self) { From 2c22a948ed309c4c15f3534818a8d2935812f898 Mon Sep 17 00:00:00 2001 From: QueensGambit Date: Sat, 17 Feb 2024 10:51:15 +0100 Subject: [PATCH 34/39] Bump version --- tests/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/js/package.json b/tests/js/package.json index cc960f14..aa744fd5 100644 --- a/tests/js/package.json +++ b/tests/js/package.json @@ -1,6 +1,6 @@ { "name": "ffish", - "version": "0.7.4", + "version": "0.7.5", "description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish", "main": "ffish.js", "types": "ffish.d.ts", From 69cdd8257634612c52e4c032c50f3d5026b4710f Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 23 Feb 2024 15:25:11 +0100 Subject: [PATCH 35/39] Update reference bench bench: 6180480 From def2560cb130cd2b8eab4bbaf56fa91b14b4dd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bajusz=20Tam=C3=A1s?= Date: Mon, 26 Feb 2024 15:53:51 +0100 Subject: [PATCH 36/39] Add missing soldier promotion to cannonshogi --- src/variants.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variants.ini b/src/variants.ini index 09be0e40..0b27fc2d 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -1664,7 +1664,7 @@ customPiece2 = c:mBcpB customPiece3 = i:pB customPiece4 = w:mRpRmFpB2 customPiece5 = f:mBpBmWpR2 -promotedPieceType = u:w a:w c:f i:f +promotedPieceType = u:w a:w c:f i:f p:g startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 #https://www.chessvariants.com/difftaking.dir/deadsquare.html From fb8cf35d9dbd9278f00c772100abf5888e444c1a Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 1 Mar 2024 11:55:17 +0100 Subject: [PATCH 37/39] Validate max one king per side --- src/apiutil.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/apiutil.h b/src/apiutil.h index 4b364f63..d39102fc 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -821,6 +821,16 @@ inline Validation check_number_of_kings(const std::string& fenBoard, const std:: int nbWhiteKingsStart = piece_count(startFenBoard, WHITE, KING, v); int nbBlackKingsStart = piece_count(startFenBoard, BLACK, KING, v); + if (nbWhiteKings > 1) + { + std::cerr << "Invalid number of white kings. Maximum: 1. Given: " << nbWhiteKings << std::endl; + return NOK; + } + if (nbBlackKings > 1) + { + std::cerr << "Invalid number of black kings. Maximum: 1. Given: " << nbBlackKings << std::endl; + return NOK; + } if (nbWhiteKings != nbWhiteKingsStart) { std::cerr << "Invalid number of white kings. Expected: " << nbWhiteKingsStart << ". Given: " << nbWhiteKings << std::endl; From bbe0d954a18a258cff38c155e8b62925cd80f0de Mon Sep 17 00:00:00 2001 From: RainRat Date: Fri, 1 Mar 2024 05:01:13 -0800 Subject: [PATCH 38/39] add 'wall or move' rule (for Atlantis) (#728) --- src/movegen.cpp | 9 ++++++++- src/parser.cpp | 1 + src/position.cpp | 6 ++++-- src/variant.h | 1 + src/variants.ini | 5 ++--- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 43807bb0..f5a16eb4 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -29,7 +29,8 @@ namespace { ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to, PieceType pt = NO_PIECE_TYPE) { // Wall placing moves - if (pos.walling()) + //if it's "wall or move", and they chose non-null move, skip even generating wall move + if (pos.walling() && !(pos.variant()->wallOrMove && (from!=to))) { Bitboard b = pos.board_bb() & ~((pos.pieces() ^ from) | to); if (T == CASTLING) @@ -443,6 +444,12 @@ namespace { // Workaround for passing: Execute a non-move with any piece if (pos.pass(Us) && !pos.count(Us) && pos.pieces(Us)) *moveList++ = make(lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); + + //if "wall or move", generate walling action with null move + if (pos.variant()->wallOrMove) + { + moveList = make_move_and_gating(pos, moveList, Us, lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); + } } // King moves diff --git a/src/parser.cpp b/src/parser.cpp index 2a4db2d2..3ebf1b16 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -472,6 +472,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("wallingRegionBlack", v->wallingRegion[BLACK]); parse_attribute("wallingRegion", v->wallingRegion[WHITE]); parse_attribute("wallingRegion", v->wallingRegion[BLACK]); + parse_attribute("wallOrMove", v->wallOrMove); parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("diagonalLines", v->diagonalLines); diff --git a/src/position.cpp b/src/position.cpp index c449b6a9..023cdfa7 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1303,7 +1303,8 @@ bool Position::pseudo_legal(const Move m) const { return checkers() ? MoveList< EVASIONS>(*this).contains(m) : MoveList(*this).contains(m); - if (walling()) + //if walling, and walling is not optional, or they didn't move, do the checks. + if (walling() && (!var->wallOrMove || (from==to))) { Bitboard wallsquares = st->wallSquares; @@ -2045,7 +2046,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Add gated wall square - if (walling()) + // if wallOrMove, only actually place the wall if they gave up their move + if (walling() && (!var->wallOrMove || (from==to))) { // Reset wall squares for duck walling if (walling_rule() == DUCK) diff --git a/src/variant.h b/src/variant.h index a34b37de..aa273835 100644 --- a/src/variant.h +++ b/src/variant.h @@ -108,6 +108,7 @@ struct Variant { bool gating = false; WallingRule wallingRule = NO_WALLING; Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares}; + bool wallOrMove = false; bool seirawanGating = false; bool cambodianMoves = false; Bitboard diagonalLines = 0; diff --git a/src/variants.ini b/src/variants.ini index 0b27fc2d..fd5b5049 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -232,6 +232,7 @@ # 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) +# 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) # cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false) # diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard] @@ -1811,9 +1812,7 @@ 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 +wallOrMove = true #https://www.chessvariants.com/rules/ajax-orthodox-chess [ajax-orthodox:chess] From 62577a4039e48c55a579edac78dbdbec5d3bf71e Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Fri, 1 Mar 2024 15:28:21 +0100 Subject: [PATCH 39/39] Handle invalid promoted piece (#416) --- src/position.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index 023cdfa7..ade4798d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -314,7 +314,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, } // Promoted shogi pieces - else if (token == '+' && (idx = piece_to_char().find(ss.peek())) != string::npos) + 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));