add Three Musketeers. needs collinearN and connectPieceTypes. (#755)
authorRainRat <rainrat78@yahoo.ca>
Tue, 13 Feb 2024 12:49:27 +0000 (04:49 -0800)
committerGitHub <noreply@github.com>
Tue, 13 Feb 2024 12:49:27 +0000 (13:49 +0100)
src/parser.cpp
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
src/variants.ini

index 9d2b55b..2a4db2d 100644 (file)
@@ -527,6 +527,7 @@ Variant* VariantParser<DoCheck>::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<DoCheck>::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);
index bf6e60f..c449b6a 100644 (file)
@@ -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<SOUTH>(connectors) & shift<EAST>(connectors) & shift<SOUTH_EAST>(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())))
index 4ee9366..adaeeb6 100644 (file)
@@ -205,11 +205,13 @@ public:
   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<Direction>& 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];
 }
index 386c21d..5b21b53 100644 (file)
@@ -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;
 }
 
index ec19918..a34b37d 100644 (file)
@@ -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;
index abc2c70..aaf4081 100644 (file)
 # 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)
 # 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