Support Cambodian chess (Ouk Chatrang, Kar Ouk) (close #39)
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 13 Oct 2019 21:49:14 +0000 (23:49 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Wed, 16 Oct 2019 17:42:16 +0000 (19:42 +0200)
https://en.wikipedia.org/wiki/Makruk#Cambodian_chess

- Captures by special move of queen/neang are allowed.
- King may only make its leap if the side to move is not in check,
  and if the move does not capture a piece.

No functional change for existing variants.

Readme.md
src/evaluate.cpp
src/movegen.cpp
src/parser.cpp
src/position.cpp
src/position.h
src/types.h
src/variant.cpp
src/variant.h
src/variants.ini

index 915cd97..56e435f 100644 (file)
--- a/Readme.md
+++ b/Readme.md
@@ -15,7 +15,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca
 
 ### Regional and historical games
 - [Shatranj](https://en.wikipedia.org/wiki/Shatranj), [Courier](https://en.wikipedia.org/wiki/Courier_chess)
-- [Makruk](https://en.wikipedia.org/wiki/Makruk), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Ai-Wok
+- [Makruk](https://en.wikipedia.org/wiki/Makruk), [Ouk Chatrang](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [Kar Ouk](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Ai-Wok
 - [Sittuyin](https://en.wikipedia.org/wiki/Sittuyin)
 - [Shatar](https://en.wikipedia.org/wiki/Shatar), [Jeson Mor](https://en.wikipedia.org/wiki/Jeson_Mor)
 - [Shogi](https://en.wikipedia.org/wiki/Shogi)
index bf4ac36..15eb155 100644 (file)
@@ -1068,7 +1068,7 @@ namespace {
             score += pieces<WHITE>(pt) - pieces<BLACK>(pt);
 
     // Evaluate pieces in hand once attack tables are complete
-    if (pos.piece_drops() || pos.gating())
+    if (pos.piece_drops() || pos.seirawan_gating())
         for (PieceType pt = PAWN; pt < KING; ++pt)
             score += hand<WHITE>(pt) - hand<BLACK>(pt);
 
index 55c4d5a..92d92dc 100644 (file)
@@ -31,11 +31,11 @@ namespace {
     *moveList++ = make<T>(from, to);
 
     // Gating moves
-    if (pos.gating() && (pos.gates(us) & from))
+    if (pos.seirawan_gating() && (pos.gates(us) & from))
         for (PieceType pt_gating = PAWN; pt_gating <= KING; ++pt_gating)
             if (pos.count_in_hand(us, pt_gating) && (pos.drop_region(us, pt_gating) & from))
                 *moveList++ = make_gating<T>(from, to, pt_gating, from);
-    if (pos.gating() && T == CASTLING && (pos.gates(us) & to))
+    if (pos.seirawan_gating() && T == CASTLING && (pos.gates(us) & to))
         for (PieceType pt_gating = PAWN; pt_gating <= KING; ++pt_gating)
             if (pos.count_in_hand(us, pt_gating) && (pos.drop_region(us, pt_gating) & to))
                 *moveList++ = make_gating<T>(from, to, pt_gating, to);
@@ -351,6 +351,28 @@ namespace {
             moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(OOO));
     }
 
+    // Special moves
+    if (pos.cambodian_moves() && pos.gates(Us))
+    {
+        if (Type != CAPTURES && Type != EVASIONS && (pos.pieces(Us, KING) & pos.gates(Us)))
+        {
+            Square from = pos.square<KING>(Us);
+            Bitboard b = PseudoAttacks[WHITE][KNIGHT][from] & rank_bb(rank_of(from + (Us == WHITE ? NORTH : SOUTH)))
+                        & target & ~pos.pieces();
+            while (b)
+                moveList = make_move_and_gating<SPECIAL>(pos, moveList, Us, from, pop_lsb(&b));
+        }
+
+        Bitboard b = pos.pieces(Us, FERS) & pos.gates(Us);
+        while (b)
+        {
+            Square from = pop_lsb(&b);
+            Square to = from + 2 * (Us == WHITE ? NORTH : SOUTH);
+            if (is_ok(to) && (target & to))
+                moveList = make_move_and_gating<SPECIAL>(pos, moveList, Us, from, to);
+        }
+    }
+
     return moveList;
   }
 
index 13be2ee..ee7dd26 100644 (file)
@@ -172,6 +172,8 @@ Variant* VariantParser::parse(Variant* v) {
     parse_attribute("shogiDoubledPawn", v->shogiDoubledPawn);
     parse_attribute("immobilityIllegal", v->immobilityIllegal);
     parse_attribute("gating", v->gating);
+    parse_attribute("seirawanGating", v->seirawanGating);
+    parse_attribute("cambodianMoves", v->cambodianMoves);
     // game end
     parse_attribute("nMoveRule", v->nMoveRule);
     parse_attribute("nFoldRule", v->nFoldRule);
index 0462ff3..88c71ea 100644 (file)
@@ -380,7 +380,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
                   st->gatesBB[c] |= count<KING>(c) ? square<KING>(c) : make_square(FILE_E, relative_rank(c, castling_rank(), max_rank()));
               // Do not set castling rights for gates unless there are no pieces in hand,
               // which means that the file is referring to a chess960 castling right.
-              else if (count_in_hand(c, ALL_PIECES) || captures_to_hand())
+              else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand())
                   continue;
           }
 
@@ -391,7 +391,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
       // Set castling rights for 960 gating variants
       if (gating() && castling_enabled())
           for (Color c : {WHITE, BLACK})
-              if ((gates(c) & pieces(KING)) && !castling_rights(c) && (count_in_hand(c, ALL_PIECES) || captures_to_hand()))
+              if ((gates(c) & pieces(KING)) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) || captures_to_hand()))
               {
                   Bitboard castling_rooks = gates(c) & pieces(ROOK);
                   while (castling_rooks)
@@ -417,11 +417,21 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
   // Check counter for nCheck
   ss >> std::skipws >> token >> std::noskipws;
 
-  if (check_counting() && ss.peek() == '+')
+  if (check_counting())
   {
-      st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0));
-      ss >> token >> token;
-      st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0));
+      if (ss.peek() == '+')
+      {
+          st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0));
+          ss >> token >> token;
+          st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0));
+      }
+      else
+      {
+          // If check count is not provided, assume that the next check wins
+          st->checksRemaining[WHITE] = CheckCount(1);
+          st->checksRemaining[BLACK] = CheckCount(1);
+          ss.putback(token);
+      }
   }
   else
       ss.putback(token);
@@ -549,7 +559,7 @@ void Position::set_state(StateInfo* si) const {
           for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
               si->materialKey ^= Zobrist::psq[pc][cnt];
 
-          if (piece_drops() || gating())
+          if (piece_drops() || seirawan_gating())
               si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]];
       }
 
@@ -633,7 +643,7 @@ const string Position::fen(bool sfen) const {
   }
 
   // pieces in hand
-  if (piece_drops() || gating())
+  if (piece_drops() || seirawan_gating())
   {
       ss << '[';
       for (Color c : {WHITE, BLACK})
@@ -650,7 +660,7 @@ const string Position::fen(bool sfen) const {
   if (can_castle(WHITE_OOO))
       ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
 
-  if (gating() && gates(WHITE) && (count_in_hand(WHITE, ALL_PIECES) || captures_to_hand()))
+  if (gating() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) || captures_to_hand()))
       for (File f = FILE_A; f <= max_file(); ++f)
           if (gates(WHITE) & file_bb(f))
               ss << char('A' + f);
@@ -661,7 +671,7 @@ const string Position::fen(bool sfen) const {
   if (can_castle(BLACK_OOO))
       ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
 
-  if (gating() && gates(BLACK) && (count_in_hand(BLACK, ALL_PIECES) || captures_to_hand()))
+  if (gating() && gates(BLACK) && (!seirawan_gating() || count_in_hand(BLACK, ALL_PIECES) || captures_to_hand()))
       for (File f = FILE_A; f <= max_file(); ++f)
           if (gates(BLACK) & file_bb(f))
               ss << char('a' + f);
@@ -741,6 +751,15 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const {
   Bitboard b = 0;
   for (PieceType pt : piece_types())
       b |= attacks_bb(~c, pt, s, occupied) & pieces(c, pt);
+
+  // Consider special move of neang in cambodian chess
+  if (cambodian_moves())
+  {
+      Square fers_sq = s + 2 * (c == WHITE ? SOUTH : NORTH);
+      if (is_ok(fers_sq))
+          b |= pieces(c, FERS) & gates(c) & fers_sq;
+  }
+
   return b;
 }
 
@@ -1003,6 +1022,7 @@ bool Position::gives_check(Move m) const {
   {
   case NORMAL:
   case DROP:
+  case SPECIAL:
       return false;
 
   case PROMOTION:
@@ -1313,7 +1333,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
           st->gatesBB[us] ^= to_sq(m);
       if (gates(them) & to)
           st->gatesBB[them] ^= to;
-      if (!count_in_hand(us, ALL_PIECES) && !captures_to_hand())
+      if (seirawan_gating() && !count_in_hand(us, ALL_PIECES) && !captures_to_hand())
           st->gatesBB[us] = 0;
   }
 
index 9852061..5e962ba 100644 (file)
@@ -132,6 +132,8 @@ public:
   bool shogi_doubled_pawn() const;
   bool immobility_illegal() const;
   bool gating() const;
+  bool seirawan_gating() const;
+  bool cambodian_moves() const;
   // winning conditions
   int n_move_rule() const;
   int n_fold_rule() const;
@@ -515,6 +517,16 @@ inline bool Position::gating() const {
   return var->gating;
 }
 
+inline bool Position::seirawan_gating() const {
+  assert(var != nullptr);
+  return var->seirawanGating;
+}
+
+inline bool Position::cambodian_moves() const {
+  assert(var != nullptr);
+  return var->cambodianMoves;
+}
+
 inline int Position::n_move_rule() const {
   assert(var != nullptr);
   return var->nMoveRule;
index e553616..87d10f7 100644 (file)
@@ -244,6 +244,7 @@ enum MoveType : int {
   DROP               = 4 << (2 * SQUARE_BITS),
   PIECE_PROMOTION    = 5 << (2 * SQUARE_BITS),
   PIECE_DEMOTION     = 6 << (2 * SQUARE_BITS),
+  SPECIAL            = 7 << (2 * SQUARE_BITS),
 };
 
 constexpr int MOVE_TYPE_BITS = 4;
index 47f0937..273a2c1 100644 (file)
@@ -62,6 +62,18 @@ namespace {
         v->countingRule = MAKRUK_COUNTING;
         return v;
     }
+    Variant* cambodian_variant() {
+        Variant* v = makruk_variant();
+        v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1";
+        v->gating = true;
+        v->cambodianMoves = true;
+        return v;
+    }
+    Variant* karouk_variant() {
+        Variant* v = cambodian_variant();
+        v->checkCounting = true;
+        return v;
+    }
     Variant* asean_variant() {
         Variant* v = chess_variant();
         v->remove_piece(BISHOP);
@@ -260,6 +272,7 @@ namespace {
         v->add_piece(CHANCELLOR, 'e');
         v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1";
         v->gating = true;
+        v->seirawanGating = true;
         v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
         return v;
     }
@@ -648,6 +661,8 @@ void VariantMap::init() {
     add("standard", chess_variant());
     add("fairy", fairy_variant()); // fairy variant used for endgame code initialization
     add("makruk", makruk_variant());
+    add("cambodian", cambodian_variant());
+    add("karouk", karouk_variant());
     add("asean", asean_variant());
     add("ai-wok", aiwok_variant());
     add("shatranj", shatranj_variant());
index 340f3c7..a574df0 100644 (file)
@@ -75,6 +75,8 @@ struct Variant {
   bool shogiDoubledPawn = true;
   bool immobilityIllegal = false;
   bool gating = false;
+  bool seirawanGating = false;
+  bool cambodianMoves = false;
   // game end
   int nMoveRule = 50;
   int nFoldRule = 3;
index c57abc8..680c84e 100644 (file)
 # dropPromoted: [bool] (default: false)
 # shogiDoubledPawn: allow shogi pawns to be doubled [bool] (default: true)
 # immobilityIllegal: [bool] (default: false)
-# gating: [bool] (default: false)
+# gating: maintain squares on backrank with extra rights in castling field of FEN [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)
 # nMoveRule: [int] (default: 50)
 # nFoldRule: [int] (default: 3)
 # nFoldValue: [Value] (default: draw)