From 7d42030f631551af033ada63c6eaa2d9ab922ddb Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 13 Feb 2024 04:49:27 -0800 Subject: [PATCH] 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 9d2b55b98..2a4db2d2b 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 bf6e60f28..c449b6a94 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 4ee936657..adaeeb6ad 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 386c21d5d..5b21b53dd 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 ec1991859..a34b37de3 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 abc2c7099..aaf40810f 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