Support kyoto shogi
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 23 Dec 2018 13:45:46 +0000 (14:45 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 23 Dec 2018 16:05:37 +0000 (17:05 +0100)
https://en.wikipedia.org/wiki/Kyoto_shogi

Added features:
- Piece demotion
- Dropping pieces in promoted state

Repeating unchanged bench here to fix CI.
bench: 5141418

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

index 296105b..98fc09d 100644 (file)
@@ -97,10 +97,20 @@ namespace {
                     b &= ~file_bb(f);
         if (pt == ROOK && pos.sittuyin_rook_drop())
             b &= rank_bb(relative_rank(Us, RANK_1, pos.max_rank()));
+
+        // Add to move list
+        if (pos.drop_promoted() && pos.promoted_piece_type(pt))
+        {
+            Bitboard b2 = b;
+            if (Checks)
+                b2 &= pos.check_squares(pos.promoted_piece_type(pt));
+            while (b2)
+                *moveList++ = make_drop(pop_lsb(&b2), pt, pos.promoted_piece_type(pt));
+        }
         if (Checks)
             b &= pos.check_squares(pt);
         while (b)
-            *moveList++ = make_drop(pop_lsb(&b), pt);
+            *moveList++ = make_drop(pop_lsb(&b), pt, pt);
     }
 
     return moveList;
@@ -287,24 +297,29 @@ namespace {
 
         Bitboard b1 = (  (pos.attacks_from(us, pt, from) & pos.pieces())
                        | (pos.moves_from(us, pt, from) & ~pos.pieces())) & target;
-        PieceType pt_promotion = pos.promoted_piece_type(pt);
-        Bitboard b2 = pt_promotion ? b1 : 0;
+        Bitboard b2 = pos.promoted_piece_type(pt) ? b1 : 0;
+        Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : 0;
 
         if (Checks)
         {
             b1 &= pos.check_squares(pt);
-            if (pt_promotion)
-                b2 &= pos.check_squares(pt_promotion);
+            if (b2)
+                b2 &= pos.check_squares(pos.promoted_piece_type(pt));
+            if (b3)
+                b3 &= pos.check_squares(type_of(pos.unpromoted_piece_on(from)));
         }
 
         // Restrict target squares considering promotion zone
-        if (pt_promotion)
+        if (b2 | b3)
         {
             Bitboard promotion_zone = promotion_zone_bb(us, pos.promotion_rank(), pos.max_rank());
             if (pos.mandatory_piece_promotion())
                 b1 &= promotion_zone & from ? 0 : ~promotion_zone;
             if (!(promotion_zone & from))
+            {
                 b2 &= promotion_zone;
+                b3 &= promotion_zone;
+            }
         }
 
         while (b1)
@@ -313,6 +328,10 @@ namespace {
         // Shogi-style piece promotions
         while (b2)
             *moveList++ = make<PIECE_PROMOTION>(from, pop_lsb(&b2));
+
+        // Piece demotions
+        while (b3)
+            *moveList++ = make<PIECE_DEMOTION>(from, pop_lsb(&b3));
     }
 
     return moveList;
index bed440e..e5828ee 100644 (file)
@@ -813,6 +813,12 @@ bool Position::pseudo_legal(const Move m) const {
   if (type_of(m) != NORMAL)
       return MoveList<LEGAL>(*this).contains(m);
 
+  // Handle the case where a mandatory piece promotion/demotion is not taken
+  if (    mandatory_piece_promotion()
+      && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE)
+      && (promotion_zone_bb(us, promotion_rank(), max_rank()) & (SquareBB[from] | to)))
+      return false;
+
   // Is not a promotion, so promotion piece must be empty
   if (promotion_type(m) != NO_PIECE_TYPE)
       return false;
@@ -888,7 +894,7 @@ bool Position::gives_check(Move m) const {
       return false;
 
   // Is there a direct check?
-  if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && (st->checkSquares[type_of(moved_piece(m))] & to))
+  if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && (st->checkSquares[type_of(moved_piece(m))] & to))
       return true;
 
   // Is there a discovered check?
@@ -909,6 +915,9 @@ bool Position::gives_check(Move m) const {
   case PIECE_PROMOTION:
       return attacks_bb(sideToMove, promoted_piece_type(type_of(moved_piece(m))), to, pieces() ^ from) & square<KING>(~sideToMove);
 
+  case PIECE_DEMOTION:
+      return attacks_bb(sideToMove, type_of(unpromoted_piece_on(from)), to, pieces() ^ from) & square<KING>(~sideToMove);
+
   // En passant capture with check? We have already handled the case
   // of direct checks and ordinary discovered check, so the only case we
   // need to handle is the unusual case of a discovered check through
@@ -1051,9 +1060,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
 
   // Update hash key
   if (type_of(m) == DROP)
+  {
+      Piece pc_hand = make_piece(us, in_hand_piece_type(m));
       k ^=  Zobrist::psq[pc][to]
-          ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)] - 1]
-          ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)]];
+          ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1]
+          ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]];
+  }
   else
       k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
 
@@ -1075,7 +1087,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   // Move the piece. The tricky Chess960 castling is handled earlier
   if (type_of(m) == DROP)
   {
-      drop_piece(pc, to);
+      drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to);
       st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1];
       if (type_of(pc) != PAWN)
           st->nonPawnMaterial[us] += PieceValue[MG][pc];
@@ -1168,6 +1180,26 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       // Update material
       st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc];
   }
+  else if (type_of(m) == PIECE_DEMOTION)
+  {
+      Piece demotion = unpromoted_piece_on(from);
+
+      remove_piece(pc, to);
+      put_piece(demotion, to);
+      promotedPieces ^= from;
+      unpromotedBoard[from] = NO_PIECE;
+
+      // Update hash keys
+      k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[demotion][to];
+      st->materialKey ^=  Zobrist::psq[demotion][pieceCount[demotion]-1]
+                        ^ Zobrist::psq[pc][pieceCount[pc]];
+
+      // Update incremental score
+      st->psq += PSQT::psq[demotion][to] - PSQT::psq[pc][to];
+
+      // Update material
+      st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc];
+  }
 
   // Update incremental scores
   st->psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
@@ -1187,12 +1219,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   // Update information about promoted pieces
   if (type_of(m) != DROP && is_promoted(from))
       promotedPieces = (promotedPieces - from) | to;
+  else if (type_of(m) == DROP && in_hand_piece_type(m) != dropped_piece_type(m))
+      promotedPieces = promotedPieces | to;
 
   if (type_of(m) != DROP && unpromoted_piece_on(from))
   {
       unpromotedBoard[to] = unpromotedBoard[from];
       unpromotedBoard[from] = NO_PIECE;
   }
+  else if (type_of(m) == DROP && in_hand_piece_type(m) != dropped_piece_type(m))
+      unpromotedBoard[to] = make_piece(us, in_hand_piece_type(m));
 
   sideToMove = ~sideToMove;
 
@@ -1240,6 +1276,14 @@ void Position::undo_move(Move m) {
       unpromotedBoard[to] = NO_PIECE;
       promotedPieces -= to;
   }
+  else if (type_of(m) == PIECE_DEMOTION)
+  {
+      remove_piece(pc, to);
+      unpromotedBoard[from] = pc;
+      pc = make_piece(us, promoted_piece_type(type_of(pc)));
+      put_piece(pc, to);
+      promotedPieces |= from;
+  }
 
   if (type_of(m) == CASTLING)
   {
@@ -1249,14 +1293,19 @@ void Position::undo_move(Move m) {
   else
   {
       if (type_of(m) == DROP)
-          undrop_piece(pc, to); // Remove the dropped piece
+          undrop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); // Remove the dropped piece
       else
           move_piece(pc, to, from); // Put the piece back at the source square
       if (captures_to_hand() && !drop_loop() && is_promoted(to))
-          promotedPieces = (promotedPieces - to) | from;
+      {
+          promotedPieces = (promotedPieces - to);
+          if (type_of(m) != DROP)
+              promotedPieces |= from;
+      }
       if (unpromoted_piece_on(to))
       {
-          unpromotedBoard[from] = unpromotedBoard[to];
+          if (type_of(m) != DROP)
+              unpromotedBoard[from] = unpromotedBoard[to];
           unpromotedBoard[to] = NO_PIECE;
       }
 
@@ -1382,8 +1431,11 @@ Key Position::key_after(Move m) const {
       }
   }
   if (type_of(m) == DROP)
-      return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)]]
-            ^ Zobrist::inHand[pc][pieceCountInHand[color_of(pc)][type_of(pc)] - 1];
+  {
+      Piece pc_hand = make_piece(sideToMove, in_hand_piece_type(m));
+      return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]]
+            ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1];
+  }
 
   return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
 }
index 350b9c9..c9163fb 100644 (file)
@@ -100,6 +100,7 @@ public:
   bool sittuyin_promotion() const;
   PieceType promoted_piece_type(PieceType pt) const;
   bool mandatory_piece_promotion() const;
+  bool piece_demotion() const;
   bool endgame_eval() const;
   bool double_step_enabled() const;
   bool first_rank_double_steps() const;
@@ -118,6 +119,7 @@ public:
   Bitboard drop_region(Color c) const;
   bool sittuyin_rook_drop() const;
   bool drop_opposite_colored_bishop() const;
+  bool drop_promoted() const;
   bool shogi_doubled_pawn() const;
   bool immobility_illegal() const;
   // winning conditions
@@ -191,6 +193,7 @@ public:
   // Piece specific
   bool pawn_passed(Color c, Square s) const;
   bool opposite_bishops() const;
+  bool is_promoted(Square s) const;
 
   // Doing and undoing moves
   void do_move(Move m, StateInfo& newSt);
@@ -259,11 +262,10 @@ private:
   bool chess960;
   int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB];
   Bitboard promotedPieces;
-  bool is_promoted(Square s) const;
   void add_to_hand(Color c, PieceType pt);
   void remove_from_hand(Color c, PieceType pt);
-  void drop_piece(Piece pc, Square s);
-  void undrop_piece(Piece pc, Square s);
+  void drop_piece(Piece pc_hand, Piece pc_drop, Square s);
+  void undrop_piece(Piece pc_hand, Piece pc_drop, Square s);
 };
 
 extern std::ostream& operator<<(std::ostream& os, const Position& pos);
@@ -323,6 +325,11 @@ inline bool Position::mandatory_piece_promotion() const {
   return var->mandatoryPiecePromotion;
 }
 
+inline bool Position::piece_demotion() const {
+  assert(var != nullptr);
+  return var->pieceDemotion;
+}
+
 inline bool Position::endgame_eval() const {
   assert(var != nullptr);
   return var->endgameEval;
@@ -413,6 +420,11 @@ inline bool Position::drop_opposite_colored_bishop() const {
   return var->dropOppositeColoredBishop;
 }
 
+inline bool Position::drop_promoted() const {
+  assert(var != nullptr);
+  return var->dropPromoted;
+}
+
 inline bool Position::shogi_doubled_pawn() const {
   assert(var != nullptr);
   return var->shogiDoubledPawn;
@@ -757,6 +769,10 @@ inline bool Position::opposite_bishops() const {
         && opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
 }
 
+inline bool Position::is_promoted(Square s) const {
+  return promotedPieces & s;
+}
+
 inline bool Position::is_chess960() const {
   return chess960;
 }
@@ -840,21 +856,17 @@ inline void Position::remove_from_hand(Color c, PieceType pt) {
   pieceCountInHand[c][ALL_PIECES]--;
 }
 
-inline bool Position::is_promoted(Square s) const {
-  return promotedPieces & s;
-}
-
-inline void Position::drop_piece(Piece pc, Square s) {
-  assert(pieceCountInHand[color_of(pc)][type_of(pc)]);
-  put_piece(pc, s);
-  remove_from_hand(color_of(pc), type_of(pc));
+inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s) {
+  assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]);
+  put_piece(pc_drop, s);
+  remove_from_hand(color_of(pc_hand), type_of(pc_hand));
 }
 
-inline void Position::undrop_piece(Piece pc, Square s) {
-  remove_piece(pc, s);
+inline void Position::undrop_piece(Piece pc_hand, Piece pc_drop, Square s) {
+  remove_piece(pc_drop, s);
   board[s] = NO_PIECE;
-  add_to_hand(color_of(pc), type_of(pc));
-  assert(pieceCountInHand[color_of(pc)][type_of(pc)]);
+  add_to_hand(color_of(pc_hand), type_of(pc_hand));
+  assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]);
 }
 
 #endif // #ifndef POSITION_H_INCLUDED
index fd7a2d0..2a63e68 100644 (file)
@@ -137,8 +137,11 @@ enum MoveType : int {
   PROMOTION          = 3 << (2 * SQUARE_BITS),
   DROP               = 4 << (2 * SQUARE_BITS),
   PIECE_PROMOTION    = 5 << (2 * SQUARE_BITS),
+  PIECE_DEMOTION     = 6 << (2 * SQUARE_BITS),
 };
 
+constexpr int MOVE_TYPE_BITS = 4;
+
 enum Color {
   WHITE, BLACK, COLOR_NB = 2
 };
@@ -228,7 +231,7 @@ enum Value : int {
   MidgameLimit  = 15258, EndgameLimit  = 3915
 };
 
-const int PIECE_TYPE_BITS = 5; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS)
+constexpr int PIECE_TYPE_BITS = 5; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS)
 
 enum PieceType {
   NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN,
@@ -244,6 +247,8 @@ static_assert(KING < PIECE_TYPE_NB, "KING exceeds PIECE_TYPE_NB.");
 static_assert(PIECE_TYPE_BITS <= 6, "PIECE_TYPE uses more than 6 bit");
 static_assert(!(PIECE_TYPE_NB & (PIECE_TYPE_NB - 1)), "PIECE_TYPE_NB is not a power of 2");
 
+static_assert(2 * SQUARE_BITS + MOVE_TYPE_BITS + 2 * PIECE_TYPE_BITS <= 32, "Move encoding uses more than 32 bits");
+
 enum Piece {
   NO_PIECE,
   PIECE_NB = 2 * PIECE_TYPE_NB
@@ -551,7 +556,7 @@ inline int from_to(Move m) {
 }
 
 inline PieceType promotion_type(Move m) {
-  return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + 4)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE;
+  return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE;
 }
 
 inline Move make_move(Square from, Square to) {
@@ -560,15 +565,19 @@ inline Move make_move(Square from, Square to) {
 
 template<MoveType T>
 inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) {
-  return Move((pt << (2 * SQUARE_BITS + 4)) + T + (from << SQUARE_BITS) + to);
+  return Move((pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to);
 }
 
-constexpr Move make_drop(Square to, PieceType pt) {
-  return Move((pt << (2 * SQUARE_BITS + 4)) + DROP + to);
+constexpr Move make_drop(Square to, PieceType pt_in_hand, PieceType pt_dropped) {
+  return Move((pt_in_hand << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt_dropped << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + DROP + to);
 }
 
 constexpr PieceType dropped_piece_type(Move m) {
-  return PieceType((m >> (2 * SQUARE_BITS + 4)) & (PIECE_TYPE_NB - 1));
+  return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1));
+}
+
+constexpr PieceType in_hand_piece_type(Move m) {
+  return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & (PIECE_TYPE_NB - 1));
 }
 
 inline bool is_ok(Move m) {
index e1a9238..97a9639 100644 (file)
@@ -289,6 +289,17 @@ std::string UCI::square(const Position& pos, Square s) {
 #endif
 }
 
+/// UCI::dropped_piece() generates a piece label string from a Move.
+
+string UCI::dropped_piece(const Position& pos, Move m) {
+  assert(type_of(m) == DROP);
+  if (dropped_piece_type(m) == pos.promoted_piece_type(in_hand_piece_type(m)))
+      // Dropping as promoted piece
+      return std::string{'+', pos.piece_to_char()[in_hand_piece_type(m)]};
+  else
+      return std::string{pos.piece_to_char()[dropped_piece_type(m)]};
+}
+
 
 /// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q).
 /// The only special case is castling, where we print in the e1g1 notation in
@@ -309,15 +320,15 @@ string UCI::move(const Position& pos, Move m) {
   if (type_of(m) == CASTLING && !pos.is_chess960())
       to = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), rank_of(from));
 
-  string move = (type_of(m) == DROP ? std::string{pos.piece_to_char()[type_of(pos.moved_piece(m))],
-                                                  Options["Protocol"] == "usi" ? '*' : '@'}
+  string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (Options["Protocol"] == "usi" ? '*' : '@')
                                     : UCI::square(pos, from)) + UCI::square(pos, to);
 
   if (type_of(m) == PROMOTION)
       move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))];
-
-  if (type_of(m) == PIECE_PROMOTION)
+  else if (type_of(m) == PIECE_PROMOTION)
       move += '+';
+  else if (type_of(m) == PIECE_DEMOTION)
+      move += '-';
 
   return move;
 }
index 408d191..553c350 100644 (file)
--- a/src/uci.h
+++ b/src/uci.h
@@ -73,6 +73,7 @@ void init(OptionsMap&);
 void loop(int argc, char* argv[]);
 std::string value(Value v);
 std::string square(const Position& pos, Square s);
+std::string dropped_piece(const Position& pos, Move m);
 std::string move(const Position& pos, Move m);
 std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
 Move to_move(const Position& pos, std::string& str);
index d90e2e0..5e2eb40 100644 (file)
@@ -268,6 +268,27 @@ VariantMap variants; // Global object
         v->shogiPawnDropMateIllegal = true;
         return v;
     }
+    Variant* kyotoshogi_variant() {
+        Variant* v = minishogi_variant();
+        v->add_piece(LANCE, 'l');
+        v->add_piece(SHOGI_KNIGHT, 'n');
+        v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1";
+        v->promotionRank = RANK_1;
+        v->mandatoryPiecePromotion = true;
+        v->pieceDemotion = true;
+        v->dropPromoted = true;
+        v->promotedPieceType[LANCE]        = GOLD;
+        v->promotedPieceType[SILVER]       = BISHOP;
+        v->promotedPieceType[SHOGI_KNIGHT] = GOLD;
+        v->promotedPieceType[SHOGI_PAWN]   = ROOK;
+        v->promotedPieceType[GOLD]         = NO_PIECE_TYPE;
+        v->promotedPieceType[BISHOP]       = NO_PIECE_TYPE;
+        v->promotedPieceType[ROOK]         = NO_PIECE_TYPE;
+        v->immobilityIllegal = false;
+        v->shogiPawnDropMateIllegal = false;
+        v->shogiDoubledPawn = true;
+        return v;
+    }
     Variant* dobutsu_variant() {
         Variant* v = minishogi_variant();
         v->maxRank = RANK_4;
@@ -534,6 +555,7 @@ void VariantMap::init() {
     add("placement", placement_variant());
     add("sittuyin", sittuyin_variant());
     add("minishogi", minishogi_variant());
+    add("kyotoshogi", kyotoshogi_variant());
     add("dobutsu", dobutsu_variant());
     add("gorogoro", gorogoroshogi_variant());
     add("judkinshogi", judkinsshogi_variant());
index 2c182a3..c2e8d4a 100644 (file)
@@ -44,6 +44,7 @@ struct Variant {
   bool sittuyinPromotion = false;
   PieceType promotedPieceType[PIECE_TYPE_NB] = {};
   bool mandatoryPiecePromotion = false;
+  bool pieceDemotion = false;
   bool endgameEval = false;
   bool doubleStep = true;
   bool firstRankDoubleSteps = false;
@@ -63,6 +64,7 @@ struct Variant {
   Bitboard blackDropRegion = AllSquares;
   bool sittuyinRookDrop = false;
   bool dropOppositeColoredBishop = false;
+  bool dropPromoted = false;
   bool shogiDoubledPawn = true;
   bool immobilityIllegal = false;
   // game end