add Atomar. Optimize connect. (#695)
authorRainRat <rainrat78@yahoo.ca>
Wed, 23 Aug 2023 12:02:05 +0000 (05:02 -0700)
committerGitHub <noreply@github.com>
Wed, 23 Aug 2023 12:02:05 +0000 (14:02 +0200)
src/evaluate.cpp
src/parser.cpp
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
src/variants.ini

index 734d117..c6c7ef3 100644 (file)
@@ -1280,22 +1280,8 @@ namespace {
     // Connect-n
     if (pos.connect_n() > 0)
     {
-        std::vector<Direction> connect_directions;
+        for (const Direction& d : pos.getConnectDirections())
 
-        if (pos.connect_horizontal())
-        {
-            connect_directions.push_back(EAST);
-        }
-        if (pos.connect_vertical())
-        {
-            connect_directions.push_back(NORTH);
-        }
-        if (pos.connect_diagonal())
-        {
-            connect_directions.push_back(NORTH_EAST);
-            connect_directions.push_back(SOUTH_EAST);
-        }
-        for (Direction d : connect_directions)
         {
             // Find sufficiently large gaps
             Bitboard b = pos.board_bb() & ~pos.pieces(Them);
index 7623e5a..9be7e00 100644 (file)
@@ -357,6 +357,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion);
     parse_attribute("pieceDemotion", v->pieceDemotion);
     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("doubleStep", v->doubleStep);
     parse_attribute("doubleStepRegionWhite", v->doubleStepRegion[WHITE]);
@@ -523,6 +525,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
         // Check for limitations
         if (v->pieceDrops && (v->arrowGating || v->duckGating || v->staticGating || v->pastGating))
             std::cerr << "pieceDrops and arrowGating/duckGating are incompatible." << std::endl;
+
         // Options incompatible with royal kings
         if (v->pieceTypes & KING)
         {
@@ -546,6 +549,17 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
                     std::cerr << piece_name(v->kingType) << " is not supported as kingType." << std::endl;
             }
         }
+        // Options incompatible with royal kings OR pseudo-royal kings. Possible in theory though:
+        // 1. In blast variants, moving a (pseudo-)royal blastImmuneType into another piece is legal.
+        // 2. In blast variants, capturing a piece next to a (pseudo-)royal blastImmuneType is legal.
+        // 3. Moving a (pseudo-)royal mutuallyImmuneType into a square threatened by the same type is legal.
+        if ((v->extinctionPseudoRoyal) || (v->pieceTypes & KING))
+        {
+            if (v->blastImmuneTypes)
+                std::cerr << "Can not use kings or pseudo-royal with blastImmuneTypes." << std::endl;
+            if (v->mutuallyImmuneTypes)
+                std::cerr << "Can not use kings or pseudo-royal with mutuallyImmuneTypes." << std::endl;
+        }
     }
     return v;
 }
index c1734e4..21ff18c 100644 (file)
@@ -1176,6 +1176,14 @@ bool Position::legal(Move m) const {
   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) &&
+      (mutually_immune_types() & type_of(moved_piece(m))) &&
+      (type_of(moved_piece(m)) == type_of(piece_on(to)))
+  )
+  return false;
+
   // En passant captures are a tricky special case. Because they are rather
   // uncommon, we do it simply by testing whether the king is attacked after
   // the move is made.
@@ -1921,13 +1929,19 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   if (cambodian_moves() && type_of(pc) == ROOK && (square<KING>(them) & gates(them) & attacks_bb<ROOK>(to)))
       st->gatesBB[them] ^= square<KING>(them);
 
+
   // Remove the blast pieces
   if (captured && (blast_on_capture() || var->petrifyOnCapture))
   {
       std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch));
       st->demotedBycatch = st->promotedBycatch = 0;
-      Bitboard blast =  blast_on_capture() ? (attacks_bb<KING>(to) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | to
-                      : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0);
+      Bitboard blastImmune = 0;
+      for (PieceSet ps = blast_immune_types(); ps;){
+          PieceType pt = pop_lsb(ps);
+          blastImmune |= pieces(pt);
+      };
+      Bitboard blast = blast_on_capture() ? ((attacks_bb<KING>(to) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | to)
+                       & (pieces() ^ blastImmune) : type_of(pc) != PAWN ? square_bb(to) : Bitboard(0);
       while (blast)
       {
           Square bsq = pop_lsb(blast);
@@ -2717,22 +2731,8 @@ bool Position::is_immediate_game_end(Value& result, int ply) const {
   if (connect_n() > 0)
   {
       Bitboard b;
-      std::vector<Direction> connect_directions;
 
-      if (connect_horizontal())
-      {
-          connect_directions.push_back(EAST);
-      }
-      if (connect_vertical())
-      {
-          connect_directions.push_back(NORTH);
-      }
-      if (connect_diagonal())
-      {
-          connect_directions.push_back(NORTH_EAST);
-          connect_directions.push_back(SOUTH_EAST);
-      }
-      for (Direction d : connect_directions)
+      for (Direction d : var->connect_directions)
       {
           b = pieces(~sideToMove);
           for (int i = 1; i < connect_n() && b; i++)
index f0a5073..fcdd6ca 100644 (file)
@@ -140,6 +140,8 @@ public:
   bool mandatory_piece_promotion() const;
   bool piece_demotion() const;
   bool blast_on_capture() const;
+  PieceSet blast_immune_types() const;
+  PieceSet mutually_immune_types() const;
   bool endgame_eval() const;
   Bitboard double_step_region(Color c) const;
   Bitboard triple_step_region(Color c) const;
@@ -206,6 +208,7 @@ public:
   bool connect_horizontal() const;
   bool connect_vertical() const;
   bool connect_diagonal() const;
+  const std::vector<Direction>& getConnectDirections() const;
 
   CheckCount checks_remaining(Color c) const;
   MaterialCounting material_counting() const;
@@ -489,6 +492,16 @@ inline bool Position::blast_on_capture() const {
   return var->blastOnCapture;
 }
 
+inline PieceSet Position::blast_immune_types() const {
+  assert(var != nullptr);
+  return var->blastImmuneTypes;
+}
+
+inline PieceSet Position::mutually_immune_types() const {
+  assert(var != nullptr);
+  return var->mutuallyImmuneTypes;
+}
+
 inline bool Position::endgame_eval() const {
   assert(var != nullptr);
   return var->endgameEval && !count_in_hand(ALL_PIECES) && count<KING>() == 2;
@@ -965,6 +978,10 @@ inline bool Position::connect_diagonal() const {
   return var->connectDiagonal;
 }
 
+inline const std::vector<Direction>& Position::getConnectDirections() const {
+    assert(var != nullptr);
+    return var->connect_directions;
+}
 
 inline CheckCount Position::checks_remaining(Color c) const {
   return st->checksRemaining[c];
@@ -1391,18 +1408,18 @@ inline bool Position::allow_virtual_drop(Color c, PieceType pt) const {
 }
 
 inline Value Position::material_counting_result() const {
-  auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); };
+  auto weight_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); };
   int materialCount;
   Value result;
   switch (var->materialCounting)
   {
   case JANGGI_MATERIAL:
-      materialCount =  weigth_count(ROOK, 13)
-                     + weigth_count(JANGGI_CANNON, 7)
-                     + weigth_count(HORSE, 5)
-                     + weigth_count(JANGGI_ELEPHANT, 3)
-                     + weigth_count(WAZIR, 3)
-                     + weigth_count(SOLDIER, 2)
+      materialCount =  weight_count(ROOK, 13)
+                     + weight_count(JANGGI_CANNON, 7)
+                     + weight_count(HORSE, 5)
+                     + weight_count(JANGGI_ELEPHANT, 3)
+                     + weight_count(WAZIR, 3)
+                     + weight_count(SOLDIER, 2)
                      - 1;
       result = materialCount > 0 ? VALUE_MATE : -VALUE_MATE;
       break;
index d477eb6..a0ce815 100644 (file)
@@ -482,6 +482,15 @@ namespace {
         return v;
     }
 
+    // Atomar chess
+    // https://web.archive.org/web/20230519082613/https://chronatog.com/wp-content/uploads/2021/09/atomar-chess-rules.pdf
+    Variant* atomar_variant() {
+        Variant* v = nocheckatomic_variant()->init();
+        v->blastImmuneTypes = piece_set(COMMONER);
+        v->mutuallyImmuneTypes = piece_set(COMMONER);
+        return v;
+    }
+
 #ifdef ALLVARS
     // Duck chess
     Variant* duck_variant() {
@@ -1802,6 +1811,7 @@ void VariantMap::init() {
     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
@@ -2016,6 +2026,21 @@ Variant* Variant::conclude() {
             break;
         }
 
+    connect_directions.clear();
+    if (connectHorizontal)
+    {
+        connect_directions.push_back(EAST);
+    }
+    if (connectVertical)
+    {
+        connect_directions.push_back(NORTH);
+    }
+    if (connectDiagonal)
+    {
+        connect_directions.push_back(NORTH_EAST);
+        connect_directions.push_back(SOUTH_EAST);
+    }
+
     return this;
 }
 
index bc4862d..77c3443 100644 (file)
@@ -64,6 +64,8 @@ struct Variant {
   bool mandatoryPiecePromotion = false;
   bool pieceDemotion = false;
   bool blastOnCapture = false;
+  PieceSet blastImmuneTypes = NO_PIECE_SET;
+  PieceSet mutuallyImmuneTypes = NO_PIECE_SET;
   bool petrifyOnCapture = false;
   bool doubleStep = true;
   Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB};
@@ -167,6 +169,7 @@ struct Variant {
   int nnueMaxPieces;
   bool endgameEval = false;
   bool shogiStylePromotions = false;
+  std::vector<Direction> connect_directions;
 
   void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') {
       // Avoid ambiguous definition by removing existing piece with same letter
index 5b139d8..b4c3097 100644 (file)
 # mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false)
 # pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false)
 # 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)
 # doubleStep: enable pawn double step [bool] (default: true)
 # doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2)