Support counting rules
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 27 Jan 2019 16:46:48 +0000 (17:46 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 27 Jan 2019 16:46:48 +0000 (17:46 +0100)
Counting rules are used for south-east asian chess variants:
- Makruk
- ASEAN
- Sittuyin

Standard FENs and (cutechess-style) FENs including counting rules are both supported.

src/position.cpp
src/position.h
src/types.h
src/variant.cpp
src/variant.h

index 29ce04d..2b1863b 100644 (file)
@@ -376,9 +376,13 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
           set_castling_right(c, rsq);
       }
 
+      // counting limit
+      if (counting_rule() && isdigit(ss.peek()))
+          ss >> st->countingLimit;
+
       // 4. En passant square. Ignore if no pawn capture is possible
-      if (   ((ss >> col) && (col >= 'a' && col <= 'a' + max_file()))
-          && ((ss >> row) && (row >= '1' && row <= '1' + max_rank())))
+      else if (   ((ss >> col) && (col >= 'a' && col <= 'a' + max_file()))
+               && ((ss >> row) && (row >= '1' && row <= '1' + max_rank())))
       {
           st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
 
@@ -424,6 +428,13 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
       gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
   }
 
+  // counting rules
+  if (st->countingLimit && st->rule50)
+  {
+      st->countingPly = st->rule50;
+      st->rule50 = 0;
+  }
+
   chess960 = isChess960;
   thisThread = th;
   set_state(st);
@@ -633,8 +644,14 @@ const string Position::fen() const {
   if (max_check_count())
       ss << " " << (max_check_count() - st->checksGiven[WHITE]) << "+" << (max_check_count() - st->checksGiven[BLACK]);
 
-  ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " ")
-     << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
+  // Counting limit and counting ply, or ep-square and 50-move rule counter
+  if (st->countingLimit)
+      ss << " " << st->countingLimit << " " << st->countingPly;
+  else
+      ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " ")
+         << st->rule50;
+
+  ss << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
 
   return ss.str();
 }
@@ -970,6 +987,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   ++gamePly;
   ++st->rule50;
   ++st->pliesFromNull;
+  if (st->countingLimit)
+      ++st->countingPly;
 
   Color us = sideToMove;
   Color them = ~us;
@@ -1232,6 +1251,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
 
   sideToMove = ~sideToMove;
 
+  if (   counting_rule()
+      && (  ((!st->countingLimit || captured) && count<ALL_PIECES>(sideToMove) == 1)
+          || (!st->countingLimit && !count<PAWN>())))
+  {
+      st->countingLimit = 2 * counting_limit();
+      st->countingPly = st->countingLimit && count<ALL_PIECES>(sideToMove) == 1 ? 2 * count<ALL_PIECES>() : 0;
+  }
+
   // Update king attacks used for fast check detection
   set_check_info(st);
 
@@ -1581,6 +1608,16 @@ bool Position::is_optional_game_end(Value& result, int ply) const {
       }
   }
 
+  // counting rules
+  if (   counting_rule()
+      && st->countingLimit
+      && st->countingPly >= st->countingLimit
+      && (!checkers() || MoveList<LEGAL>(*this).size()))
+  {
+      result = VALUE_DRAW;
+      return true;
+  }
+
   return false;
 }
 
@@ -1739,6 +1776,57 @@ bool Position::has_game_cycle(int ply) const {
 }
 
 
+/// Position::counting_limit() returns the counting limit in full moves.
+
+int Position::counting_limit() const {
+
+  assert(counting_rule());
+
+  // No counting yet
+  if (count<PAWN>() && count<ALL_PIECES>(sideToMove) > 1)
+      return 0;
+
+  switch (counting_rule())
+  {
+  case MAKRUK_COUNTING:
+      // Board's honor rule
+      if (count<ALL_PIECES>(sideToMove) > 1)
+          return 64;
+
+      // Pieces' honor rule
+      if (count<ROOK>(~sideToMove) > 1)
+          return 8;
+      if (count<ROOK>(~sideToMove) == 1)
+          return 16;
+      if (count<KHON>(~sideToMove) > 1)
+          return 22;
+      if (count<KNIGHT>(~sideToMove) > 1)
+          return 32;
+      if (count<KHON>(~sideToMove) == 1)
+          return 44;
+
+      return 64;
+
+  case ASEAN_COUNTING:
+      if (count<ALL_PIECES>(sideToMove) > 1)
+          return 0;
+      if (count<ROOK>(~sideToMove))
+          return 16;
+      if (count<KHON>(~sideToMove) && count<MET>(~sideToMove))
+          return 44;
+      if (count<KNIGHT>(~sideToMove) && count<MET>(~sideToMove))
+          return 64;
+
+      return 0;
+
+  default:
+      assert(false);
+      return 0;
+  }
+
+}
+
+
 /// Position::flip() flips position with the white and black sides reversed. This
 /// is only useful for debugging e.g. for finding evaluation symmetry bugs.
 
index d05ffc0..df16fcf 100644 (file)
@@ -45,6 +45,8 @@ struct StateInfo {
   int    castlingRights;
   int    rule50;
   int    pliesFromNull;
+  int    countingPly;
+  int    countingLimit;
   CheckCount checksGiven[COLOR_NB];
   Score  psq;
   Square epSquare;
@@ -138,6 +140,7 @@ public:
   CheckCount max_check_count() const;
   int connect_n() const;
   CheckCount checks_given(Color c) const;
+  CountingRule counting_rule() const;
 
   // Variant-specific properties
   int count_in_hand(Color c, PieceType pt) const;
@@ -223,6 +226,7 @@ public:
   bool is_immediate_game_end(Value& result, int ply = 0) const;
   bool has_game_cycle(int ply) const;
   bool has_repeated() const;
+  int counting_limit() const;
   int rule50_count() const;
   Score psq_score() const;
   Value non_pawn_material(Color c) const;
@@ -565,6 +569,11 @@ inline CheckCount Position::checks_given(Color c) const {
   return st->checksGiven[c];
 }
 
+inline CountingRule Position::counting_rule() const {
+  assert(var != nullptr);
+  return var->countingRule;
+}
+
 inline bool Position::is_immediate_game_end() const {
   Value result;
   return is_immediate_game_end(result);
index 2a63e68..29757fc 100644 (file)
@@ -170,6 +170,10 @@ enum CheckCount : int {
   CHECKS_0 = 0, CHECKS_NB = 11
 };
 
+enum CountingRule {
+  NO_COUNTING, MAKRUK_COUNTING, ASEAN_COUNTING
+};
+
 enum Phase {
   PHASE_ENDGAME,
   PHASE_MIDGAME = 128,
index 75d6027..91c520b 100644 (file)
@@ -47,6 +47,7 @@ VariantMap variants; // Global object
         v->doubleStep = false;
         v->castling = false;
         v->nMoveRule = 0;
+        v->countingRule = MAKRUK_COUNTING;
         return v;
     }
     Variant* asean_variant() {
@@ -59,6 +60,7 @@ VariantMap variants; // Global object
         v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET};
         v->doubleStep = false;
         v->castling = false;
+        v->countingRule = ASEAN_COUNTING;
         return v;
     }
     Variant* aiwok_variant() {
@@ -238,6 +240,7 @@ VariantMap variants; // Global object
         v->promotionRank = RANK_1; // no regular promotions
         v->sittuyinPromotion = true;
         v->immobilityIllegal = false;
+        v->countingRule = ASEAN_COUNTING;
         return v;
     }
     Variant* minishogi_variant_base() {
index 9fa1c0f..e285731 100644 (file)
@@ -88,6 +88,7 @@ struct Variant {
   bool flagMove = false;
   CheckCount maxCheckCount = CheckCount(0);
   int connectN = 0;
+  CountingRule countingRule = NO_COUNTING;
 
   void add_piece(PieceType pt, char c) {
       pieceToChar[make_piece(WHITE, pt)] = toupper(c);