Support Knightmate chess
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 5 Jan 2020 21:02:38 +0000 (22:02 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 5 Jan 2020 21:47:37 +0000 (22:47 +0100)
https://www.chessvariants.com/diffobjective.dir/knightmate.html

No functional change for other variants.

Closes #50.

Readme.md
src/evaluate.cpp
src/movegen.cpp
src/parser.cpp
src/parser.h
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
src/variants.ini
tests/perft.sh

index 1a233bd..a16d9af 100644 (file)
--- a/Readme.md
+++ b/Readme.md
@@ -25,7 +25,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca
 - [Capablanca](https://en.wikipedia.org/wiki/Capablanca_Chess), [Janus](https://en.wikipedia.org/wiki/Janus_Chess), [Modern](https://en.wikipedia.org/wiki/Modern_Chess_(chess_variant)), [Chancellor](https://en.wikipedia.org/wiki/Chancellor_Chess), [Embassy](https://en.wikipedia.org/wiki/Embassy_Chess), [Gothic](https://www.chessvariants.com/large.dir/gothicchess.html), [Capablanca random chess](https://en.wikipedia.org/wiki/Capablanca_Random_Chess)
 - [Grand](https://en.wikipedia.org/wiki/Grand_Chess), [Shako](https://www.chessvariants.com/large.dir/shako.html), [Centaur](https://www.chessvariants.com/large.dir/contest/royalcourt.html)
 - [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess)
-- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse
+- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Bughouse](https://en.wikipedia.org/wiki/Bughouse_chess), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse
 - [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse
 - [Amazon](https://en.wikipedia.org/wiki/Amazon_(chess)), [Chigorin](https://en.wikipedia.org/wiki/Chigorin_Chess), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess)
 - [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html), New Zealand
@@ -35,6 +35,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca
 - [Three-check](https://en.wikipedia.org/wiki/Three-check_chess), Five-check
 - [Los Alamos](https://en.wikipedia.org/wiki/Los_Alamos_chess)
 - [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess)
+- [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html)
 
 ### Shogi variants
 - [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi)
index 3cf43af..ef375a9 100644 (file)
@@ -564,7 +564,7 @@ namespace {
                  +   4 * (kingFlankAttack - kingFlankDefense)
                  +   3 * kingFlankAttack * kingFlankAttack / 8
                  +       mg_value(mobility[Them] - mobility[Us])
-                 - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand() || pos.xiangqi_general()) / (1 + pos.check_counting())
+                 - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand() || pos.king_type() == WAZIR) / (1 + pos.check_counting())
                  - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
                  -  35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING])
                  -   6 * mg_value(score) / 8
@@ -581,7 +581,7 @@ namespace {
     // Penalty if king flank is under attack, potentially moving toward the king
     score -= FlankAttacks * kingFlankAttack * (1 + 5 * pos.captures_to_hand() + pos.check_counting());
 
-    if (pos.check_counting() || pos.xiangqi_general())
+    if (pos.check_counting() || pos.king_type() == WAZIR)
         score += make_score(0, mg_value(score) / 2);
 
     // For drop games, king danger is independent of game phase
index 119c205..422a91c 100644 (file)
@@ -329,9 +329,8 @@ namespace {
     if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count<KING>(Us))
     {
         Square ksq = pos.square<KING>(Us);
-        Bitboard b = pos.attacks_from<KING>(ksq, Us) & target;
-        if (pos.xiangqi_general())
-            b &= PseudoAttacks[Us][WAZIR][ksq];
+        Bitboard b = (  (pos.attacks_from(Us, KING, ksq) & pos.pieces())
+                      | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & target;
         while (b)
             moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
 
@@ -433,12 +432,8 @@ ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
 
      Bitboard b = pos.moves_from(us, pt, from) & ~pos.pieces();
 
-     if (pt == KING)
-     {
+     if (pt == KING && pos.king_type() == KING)
          b &= ~PseudoAttacks[~us][QUEEN][pos.square<KING>(~us)];
-         if (pos.xiangqi_general())
-             b &= PseudoAttacks[us][WAZIR][from];
-     }
 
      while (b)
          moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(&b));
@@ -465,9 +460,8 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   if (sliders & (pos.pieces(CANNON, BANNER) | pos.pieces(HORSE, ELEPHANT)))
   {
       Bitboard target = pos.board_bb() & ~pos.pieces(us);
-      Bitboard b = pos.attacks_from<KING>(ksq, us) & target;
-      if (pos.xiangqi_general())
-          b &= PseudoAttacks[us][WAZIR][ksq];
+      Bitboard b = (  (pos.attacks_from(us, KING, ksq) & pos.pieces())
+                    | (pos.moves_from(us, KING, ksq) & ~pos.pieces())) & target;
       while (b)
           moveList = make_move_and_gating<NORMAL>(pos, moveList, us, ksq, pop_lsb(&b));
       return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList, target)
@@ -484,9 +478,8 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   }
 
   // Generate evasions for king, capture and non capture moves
-  Bitboard b = pos.attacks_from<KING>(ksq, us) & ~pos.pieces(us) & ~sliderAttacks;
-  if (pos.xiangqi_general())
-      b &= PseudoAttacks[us][WAZIR][ksq];
+  Bitboard b = (  (pos.attacks_from(us, KING, ksq) & pos.pieces())
+                | (pos.moves_from(us, KING, ksq) & ~pos.pieces())) & ~pos.pieces(us) & ~sliderAttacks;
   while (b)
       moveList = make_move_and_gating<NORMAL>(pos, moveList, us, ksq, pop_lsb(&b));
 
index 3165970..52a66ca 100644 (file)
@@ -91,6 +91,18 @@ template <class T> void VariantParser::parse_attribute(const std::string& key, T
         set(it->second, target);
 }
 
+void VariantParser::parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar) {
+    const auto& it = config.find(key);
+    if (it != config.end())
+    {
+        char token;
+        size_t idx;
+        std::stringstream ss(it->second);
+        if (ss >> token && (idx = pieceToChar.find(toupper(token))) != std::string::npos)
+            target = PieceType(idx);
+    }
+}
+
 Variant* VariantParser::parse() {
     Variant* v = new Variant();
     v->reset_pieces();
@@ -158,16 +170,8 @@ Variant* VariantParser::parse(Variant* v) {
     parse_attribute("castlingKingsideFile", v->castlingKingsideFile);
     parse_attribute("castlingQueensideFile", v->castlingQueensideFile);
     parse_attribute("castlingRank", v->castlingRank);
-    // castling rook piece type
-    const auto& it_castling_rook_piece = config.find("castlingRookPiece");
-    if (it_castling_rook_piece != config.end())
-    {
-        char token;
-        size_t idx;
-        std::stringstream ss(it_castling_rook_piece->second);
-        if (ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos)
-            v->castlingRookPiece = PieceType(idx);
-    }
+    parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar);
+    parse_attribute("kingType", v->kingType, v->pieceToChar);
     parse_attribute("checking", v->checking);
     parse_attribute("mustCapture", v->mustCapture);
     parse_attribute("mustDrop", v->mustDrop);
@@ -187,7 +191,6 @@ Variant* VariantParser::parse(Variant* v) {
     parse_attribute("seirawanGating", v->seirawanGating);
     parse_attribute("cambodianMoves", v->cambodianMoves);
     parse_attribute("flyingGeneral", v->flyingGeneral);
-    parse_attribute("xiangqiGeneral", v->xiangqiGeneral);
     parse_attribute("xiangqiSoldier", v->xiangqiSoldier);
     // game end
     parse_attribute("nMoveRule", v->nMoveRule);
@@ -212,16 +215,7 @@ Variant* VariantParser::parse(Variant* v) {
         while (ss >> token && ((idx = v->pieceToChar.find(toupper(token))) != std::string::npos || token == '*'))
             v->extinctionPieceTypes.insert(PieceType(token == '*' ? 0 : idx));
     }
-    // flag piece type
-    const auto& it_flag_pt = config.find("flagPiece");
-    if (it_flag_pt != config.end())
-    {
-        char token;
-        size_t idx;
-        std::stringstream ss(it_flag_pt->second);
-        if (ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos)
-            v->flagPiece = PieceType(idx);
-    }
+    parse_attribute("flagPiece", v->flagPiece, v->pieceToChar);
     parse_attribute("whiteFlag", v->whiteFlag);
     parse_attribute("blackFlag", v->blackFlag);
     parse_attribute("flagMove", v->flagMove);
index e634bda..310a81e 100644 (file)
@@ -42,6 +42,7 @@ public:
 private:
     Config config;
     template <class T> void parse_attribute(const std::string& key, T& target);
+    void parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar);
 };
 
 #endif // #ifndef PARSER_H_INCLUDED
\ No newline at end of file
index 02f849e..105b5cf 100644 (file)
@@ -773,19 +773,20 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const {
   for (PieceType pt : piece_types())
       if (board_bb(c, pt) & s)
       {
+          PieceType move_pt = pt == KING ? king_type() : pt;
           // Consider asymmetrical move of horse
-          if (pt == HORSE || pt == BANNER)
+          if (move_pt == HORSE || move_pt == BANNER)
           {
-              Bitboard horses = PseudoAttacks[~c][pt][s] & pieces(c, pt);
+              Bitboard horses = PseudoAttacks[~c][move_pt][s] & pieces(c, pt);
               while (horses)
               {
                   Square s2 = pop_lsb(&horses);
-                  if (attacks_bb(c, pt, s2, occupied) & s)
+                  if (attacks_bb(c, move_pt, s2, occupied) & s)
                       b |= s2;
               }
           }
           else
-              b |= attacks_bb(~c, pt, s, occupied) & pieces(c, pt);
+              b |= attacks_bb(~c, move_pt, s, occupied) & pieces(c, pt);
       }
 
   // Consider special move of neang in cambodian chess
@@ -796,9 +797,6 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const {
           b |= pieces(c, FERS) & gates(c) & fers_sq;
   }
 
-  if (xiangqi_general())
-      b ^= b & pieces(KING) & ~PseudoAttacks[~c][WAZIR][s];
-
   if (unpromoted_soldier(c, s))
       b ^= b & pieces(SOLDIER) & ~PseudoAttacks[~c][SHOGI_PAWN][s];
 
@@ -972,10 +970,6 @@ bool Position::pseudo_legal(const Move m) const {
   if (type_of(m) != NORMAL || is_gating(m))
       return MoveList<LEGAL>(*this).contains(m);
 
-  // Xiangqi general
-  if (xiangqi_general() && type_of(pc) == KING && !(PseudoAttacks[us][WAZIR][from] & to))
-      return false;
-
   // Xiangqi soldier
   if (type_of(pc) == SOLDIER && unpromoted_soldier(us, from) && file_of(from) != file_of(to))
       return false;
index 33723c6..3ea59f3 100644 (file)
@@ -119,6 +119,7 @@ public:
   File castling_queenside_file() const;
   Rank castling_rank(Color c) const;
   PieceType castling_rook_piece() const;
+  PieceType king_type() const;
   bool checking_permitted() const;
   bool must_capture() const;
   bool must_drop() const;
@@ -137,7 +138,6 @@ public:
   bool gating() const;
   bool seirawan_gating() const;
   bool cambodian_moves() const;
-  bool xiangqi_general() const;
   bool unpromoted_soldier(Color c, Square s) const;
   // winning conditions
   int n_move_rule() const;
@@ -437,6 +437,11 @@ inline PieceType Position::castling_rook_piece() const {
   return var->castlingRookPiece;
 }
 
+inline PieceType Position::king_type() const {
+  assert(var != nullptr);
+  return var->kingType;
+}
+
 inline bool Position::checking_permitted() const {
   assert(var != nullptr);
   return var->checking;
@@ -547,11 +552,6 @@ inline bool Position::cambodian_moves() const {
   return var->cambodianMoves;
 }
 
-inline bool Position::xiangqi_general() const {
-  assert(var != nullptr);
-  return var->xiangqiGeneral;
-}
-
 inline bool Position::unpromoted_soldier(Color c, Square s) const {
   assert(var != nullptr);
   return var->xiangqiSoldier && relative_rank(c, s, var->maxRank) <= RANK_5;
@@ -787,11 +787,11 @@ inline Bitboard Position::attacks_from(Square s, Color c) const {
 }
 
 inline Bitboard Position::attacks_from(Color c, PieceType pt, Square s) const {
-  return attacks_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt);
+  return attacks_bb(c, pt == KING ? king_type() : pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt);
 }
 
 inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const {
-  return moves_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt);
+  return moves_bb(c, pt == KING ? king_type() : pt, s, byTypeBB[ALL_PIECES]) & board_bb(c, pt);
 }
 
 inline Bitboard Position::attackers_to(Square s) const {
index a2d3fc9..708cbcb 100644 (file)
@@ -174,6 +174,15 @@ namespace {
         v->checking = false;
         return v;
     }
+    Variant* knightmate_variant() {
+        Variant* v = fairy_variant_base();
+        v->add_piece(COMMONER, 'm');
+        v->remove_piece(KNIGHT);
+        v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1";
+        v->kingType = KNIGHT;
+        v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP};
+        return v;
+    }
     Variant* losers_variant() {
         Variant* v = fairy_variant_base();
         v->checkmateValue = VALUE_MATE;
@@ -569,6 +578,7 @@ namespace {
                                               SQ_C7, SQ_D7, SQ_E7);
         v->mobilityRegion[WHITE][KING] = white_castle;
         v->mobilityRegion[BLACK][KING] = black_castle;
+        v->kingType = WAZIR;
         v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
@@ -576,7 +586,6 @@ namespace {
         //v->nFoldValue = VALUE_MATE;
         v->perpetualCheckIllegal = true;
         v->flyingGeneral = true;
-        v->xiangqiGeneral = true;
         return v;
     }
 #ifdef LARGEBOARDS
@@ -815,6 +824,7 @@ void VariantMap::init() {
     add("newzealand", newzealand_variant());
     add("kingofthehill", kingofthehill_variant());
     add("racingkings", racingkings_variant());
+    add("knightmate", knightmate_variant());
     add("losers", losers_variant());
     add("giveaway", giveaway_variant());
     add("antichess", antichess_variant());
index 49bbf76..e7f09fd 100644 (file)
@@ -63,6 +63,7 @@ struct Variant {
   File castlingQueensideFile = FILE_C;
   Rank castlingRank = RANK_1;
   PieceType castlingRookPiece = ROOK;
+  PieceType kingType = KING;
   bool checking = true;
   bool mustCapture = false;
   bool mustDrop = false;
@@ -82,7 +83,6 @@ struct Variant {
   bool seirawanGating = false;
   bool cambodianMoves = false;
   bool flyingGeneral = false;
-  bool xiangqiGeneral = false;
   bool xiangqiSoldier = false;
   // game end
   int nMoveRule = 50;
index bd27721..5f55b45 100644 (file)
 # castlingQueensideFile: destination file of king after queenside castling [File] (default: c)
 # castlingRank: relative rank of castling [Rank] (default: 1)
 # castlingRookPiece: piece type that participates in castling [PieceType] (default: r)
+# kingType: piece type defining moves of king/royal piece (default: k)
 # checking: allow checks [bool] (default: true)
 # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false)
 # mustDrop: drops are mandatory (e.g., for Sittuyin setup phase) [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)
 # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false)
-# xiangqiGeneral: restrict king to wazir-like movements [bool] (default: false)
 # xiangqiSoldier: restrict soldier to shogi pawn movements on first five ranks [bool] (default: false)
 # nMoveRule: move count for 50/n-move rule [int] (default: 50)
 # nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3)
index c8efc11..8e7eb9a 100755 (executable)
@@ -45,6 +45,7 @@ if [[ $1 == "" || $1 == "variant" ]]; then
   expect perft.exp loop startpos 5 4888832 > /dev/null
   expect perft.exp chessgi startpos 5 4889167 > /dev/null
   expect perft.exp racingkings startpos 5 9472927 > /dev/null
+  expect perft.exp knightmate startpos 5 3249033 > /dev/null
   expect perft.exp losers startpos 5 2723795 > /dev/null
   expect perft.exp antichess startpos 5 2732672 > /dev/null
   expect perft.exp giveaway startpos 5 2732672 > /dev/null