Support placement chess (close #10)
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 4 Nov 2018 11:32:22 +0000 (12:32 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 4 Nov 2018 11:32:22 +0000 (12:32 +0100)
In order to support placement chess, new options are added:
- mandatory piece drops
- limiting drops to certain squares
- castling with dropped pieces
- bishop drops on opposite colors

Furthermore, support slash as separator for pieces in hand in FENs
of drop games for compatibility with FENs from pychess and lichess.

src/movegen.cpp
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
tests/perft.sh

index 6bbdb66..aa1ec14 100644 (file)
@@ -291,8 +291,8 @@ namespace {
         moveList = generate_moves<Checks>(pos, moveList, Us, pt, target);
     // generate drops
     if (pos.piece_drops() && Type != CAPTURES && pos.count_in_hand(Us, ALL_PIECES))
-        for (PieceType pt = PAWN; pt < KING; ++pt)
-            moveList = generate_drops<Us, Checks>(pos, moveList, pt, target & ~pos.pieces(~Us));
+        for (PieceType pt = PAWN; pt <= KING; ++pt)
+            moveList = generate_drops<Us, Checks>(pos, moveList, pt, target & ~pos.pieces(~Us) & pos.drop_region(Us));
 
     if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count<KING>(Us))
     {
index 7c992f7..e54a9f1 100644 (file)
@@ -298,7 +298,11 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
       }
 
       else if (token == '/')
+      {
           sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST;
+          if (!is_ok(sq))
+              break;
+      }
 
       else if ((idx = piece_to_char().find(token)) != string::npos)
       {
@@ -602,7 +606,7 @@ const string Position::fen() const {
   {
       ss << '[';
       for (Color c = WHITE; c <= BLACK; ++c)
-          for (PieceType pt = PieceType(KING - 1); pt >= PAWN; --pt)
+          for (PieceType pt = KING; pt >= PAWN; --pt)
               ss << std::string(pieceCountInHand[c][pt], piece_to_char()[make_piece(c, pt)]);
       ss << ']';
   }
@@ -724,14 +728,44 @@ bool Position::legal(Move m) const {
       }
   }
 
+  // illegal non-drop moves
+  if (must_drop() && type_of(m) != DROP && count_in_hand(us, ALL_PIECES))
+  {
+      if (checkers())
+      {
+          for (const auto& mevasion : MoveList<EVASIONS>(*this))
+              if (type_of(mevasion) == DROP && legal(mevasion))
+                  return false;
+      }
+      else
+      {
+          for (const auto& mquiet : MoveList<QUIETS>(*this))
+              if (type_of(mquiet) == DROP && legal(mquiet))
+                  return false;
+      }
+  }
+
+  // illegal drop move
+  if (drop_opposite_colored_bishop() && type_of(m) == DROP)
+  {
+      if (type_of(moved_piece(m)) != BISHOP)
+      {
+          Bitboard remaining = drop_region(us) & ~pieces() & ~SquareBB[to];
+          // Are enough squares available to drop bishops on opposite colors?
+          if (  (!( DarkSquares & pieces(us, BISHOP)) && ( DarkSquares & remaining))
+              + (!(~DarkSquares & pieces(us, BISHOP)) && (~DarkSquares & remaining)) < count_in_hand(us, BISHOP))
+              return false;
+      }
+      else
+          // Drop resulting in same-colored bishops
+          if ((DarkSquares & to ? DarkSquares : ~DarkSquares) & pieces(us, BISHOP))
+              return false;
+  }
+
   // no legal moves from target square
   if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(moves_bb(us, type_of(moved_piece(m)), to, 0) & board_bb()))
       return false;
 
-  // illegal drops
-  if (piece_drops() && type_of(m) == DROP)
-      return pieceCountInHand[us][type_of(moved_piece(m))] && empty(to_sq(m));
-
   // game end
   if (is_variant_end())
       return false;
@@ -1041,6 +1075,24 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1];
       if (type_of(pc) != PAWN)
           st->nonPawnMaterial[us] += PieceValue[MG][pc];
+      // Set castling rights for dropped king or rook
+      if (castling_dropped_piece() && relative_rank(us, to, max_rank()) == RANK_1)
+      {
+          if (type_of(pc) == KING && file_of(to) == FILE_E)
+          {
+              Bitboard castling_rooks =  pieces(us, ROOK)
+                                       & rank_bb(relative_rank(us, RANK_1, max_rank()))
+                                       & (file_bb(FILE_A) | file_bb(max_file()));
+              while (castling_rooks)
+                  set_castling_right(us, pop_lsb(&castling_rooks));
+          }
+          else if (type_of(pc) == ROOK)
+          {
+              if (   (file_of(to) == FILE_A || file_of(to) == max_file())
+                  && piece_on(make_square(FILE_E, relative_rank(us, RANK_1, max_rank()))) == make_piece(us, KING))
+                  set_castling_right(us, to);
+          }
+      }
   }
   else if (type_of(m) != CASTLING)
       move_piece(pc, from, to);
index ea06a38..7ecd822 100644 (file)
@@ -103,15 +103,19 @@ public:
   bool double_step_enabled() const;
   bool first_rank_double_steps() const;
   bool castling_enabled() const;
+  bool castling_dropped_piece() const;
   File castling_kingside_file() const;
   File castling_queenside_file() const;
   bool checking_permitted() const;
   bool must_capture() const;
+  bool must_drop() const;
   bool piece_drops() const;
   bool drop_loop() const;
   bool captures_to_hand() const;
   bool first_rank_drops() const;
   bool drop_on_top() const;
+  Bitboard drop_region(Color c) const;
+  bool drop_opposite_colored_bishop() const;
   bool immobility_illegal() const;
   // winning conditions
   Value stalemate_value(int ply = 0) const;
@@ -329,6 +333,11 @@ inline bool Position::castling_enabled() const {
   return var->castling;
 }
 
+inline bool Position::castling_dropped_piece() const {
+  assert(var != nullptr);
+  return var->castlingDroppedPiece;
+}
+
 inline File Position::castling_kingside_file() const {
   assert(var != nullptr);
   return var->castlingKingsideFile;
@@ -349,6 +358,11 @@ inline bool Position::must_capture() const {
   return var->mustCapture;
 }
 
+inline bool Position::must_drop() const {
+  assert(var != nullptr);
+  return var->mustDrop;
+}
+
 inline bool Position::piece_drops() const {
   assert(var != nullptr);
   return var->pieceDrops;
@@ -374,6 +388,16 @@ inline bool Position::drop_on_top() const {
   return var->dropOnTop;
 }
 
+inline Bitboard Position::drop_region(Color c) const {
+  assert(var != nullptr);
+  return c == WHITE ? var->whiteDropRegion : var->blackDropRegion;
+}
+
+inline bool Position::drop_opposite_colored_bishop() const {
+  assert(var != nullptr);
+  return var->dropOppositeColoredBishop;
+}
+
 inline bool Position::immobility_illegal() const {
   assert(var != nullptr);
   return var->immobilityIllegal;
index 88cdf4d..aeb0432 100644 (file)
@@ -210,6 +210,18 @@ VariantMap variants; // Global object
         v->capturesToHand = false;
         return v;
     }
+    Variant* placement_variant() {
+        Variant* v = chess_variant();
+        v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1";
+        v->mustDrop = true;
+        v->pieceDrops = true;
+        v->capturesToHand = false;
+        v->whiteDropRegion = Rank1BB;
+        v->blackDropRegion = Rank8BB;
+        v->dropOppositeColoredBishop = true;
+        v->castlingDroppedPiece = true;
+        return v;
+    }
     Variant* euroshogi_variant() {
         Variant* v = fairy_variant_base();
         v->reset_pieces();
@@ -528,6 +540,7 @@ void VariantMap::init() {
     add("loop", loop_variant());
     add("chessgi", chessgi_variant());
     add("pocketknight", pocketknight_variant());
+    add("placement", placement_variant());
     add("euroshogi", euroshogi_variant());
     add("judkinshogi", judkinsshogi_variant());
     add("minishogi", minishogi_variant());
index eda559c..f197460 100644 (file)
@@ -46,15 +46,20 @@ struct Variant {
   bool doubleStep = true;
   bool firstRankDoubleSteps = false;
   bool castling = true;
+  bool castlingDroppedPiece = false;
   File castlingKingsideFile = FILE_G;
   File castlingQueensideFile = FILE_C;
   bool checking = true;
   bool mustCapture = false;
+  bool mustDrop = false;
   bool pieceDrops = false;
   bool dropLoop = false;
   bool capturesToHand = false;
   bool firstRankDrops = false;
   bool dropOnTop = false;
+  Bitboard whiteDropRegion = AllSquares;
+  Bitboard blackDropRegion = AllSquares;
+  bool dropOppositeColoredBishop = false;
   bool immobilityIllegal = false;
   // game end
   Value stalemateValue = VALUE_DRAW;
index 981f2cc..e84f28c 100755 (executable)
@@ -47,6 +47,7 @@ expect perft.exp ai-wok startpos 5 13275068 > /dev/null
 expect perft.exp euroshogi startpos 5 9451149 > /dev/null
 expect perft.exp minishogi startpos 5 533203 > /dev/null
 expect perft.exp horde startpos 6 5396554 > /dev/null
+expect perft.exp placement startpos 4 1597696 > /dev/null
 
 rm perft.exp