Partial support for shogi variants
authorianfab <ianfab@users.noreply.github.com>
Sat, 21 Jul 2018 15:25:57 +0000 (17:25 +0200)
committerianfab <ianfab@users.noreply.github.com>
Sat, 21 Jul 2018 15:39:48 +0000 (17:39 +0200)
Support minishogi and euroshogi with the limitation that the position
must be sent via "position startpos moves ..." and not using an FEN.

Limitations:
- FENs with promoted pieces do not work.
- Mates by pawn drops are not recognized to be losing.

bench: 4604661

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

index 52f42fb..534e4a3 100644 (file)
@@ -167,6 +167,7 @@ void Bitboards::init() {
       { 15, 17 }, // shogi knight
       { -1, 1, 15, 17 }, // euroshogi knight
       { -8, -1, 1, 7, 8, 9 }, // gold
+      { -8, -1, 1, 8 }, // horse
       { -9, -8, -7, -1, 1, 7, 8, 9 }, // commoner
       { -9, -8, -7, -1, 1, 7, 8, 9 } // king
   };
@@ -191,6 +192,7 @@ void Bitboards::init() {
       { 15, 17 }, // shogi knight
       { -1, 1, 15, 17 }, // euroshogi knight
       { -8, -1, 1, 7, 8, 9 }, // gold
+      { -8, -1, 1, 8 }, // horse
       { -9, -8, -7, -1, 1, 7, 8, 9 }, // commoner
       { -9, -8, -7, -1, 1, 7, 8, 9 } // king
   };
@@ -215,6 +217,7 @@ void Bitboards::init() {
     {}, // shogi knight
     {}, // euroshogi knight
     {}, // gold
+    { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // horse
     {}, // commoner
     {} // king
   };
@@ -239,6 +242,7 @@ void Bitboards::init() {
     {}, // shogi knight
     {}, // euroshogi knight
     {}, // gold
+    { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // horse
     {}, // commoner
     {} // king
   };
@@ -263,6 +267,7 @@ void Bitboards::init() {
     0, // shogi knight
     0, // euroshogi knight
     0, // gold
+    7, // horse
     0, // commoner
     0  // king
   };
@@ -287,6 +292,7 @@ void Bitboards::init() {
     0, // shogi knight
     0, // euroshogi knight
     0, // gold
+    7, // horse
     0, // commoner
     0  // king
   };
index 40a4d8f..4abfd80 100644 (file)
@@ -230,8 +230,8 @@ inline Bitboard forward_ranks_bb(Color c, Square s) {
 /// in front of and on the given relative rank, from the point of view of the given color.
 /// For instance, promotion_zone_bb(BLACK, RANK_7) will return the 16 squares on ranks 1 and 2.
 
-inline Bitboard promotion_zone_bb(Color c, Rank r) {
-  return ForwardRanksBB[c][relative_rank(c, r)] | rank_bb(relative_rank(c, r));
+inline Bitboard promotion_zone_bb(Color c, Rank r, Rank maxRank) {
+  return ForwardRanksBB[c][relative_rank(c, r, maxRank)] | rank_bb(relative_rank(c, r, maxRank));
 }
 
 
index 4425bd7..2a84d87 100644 (file)
@@ -88,10 +88,14 @@ namespace {
     {
         if (pt == PAWN)
         {
-            b &= ~promotion_zone_bb(Us, pos.promotion_rank());
+            b &= ~promotion_zone_bb(Us, pos.promotion_rank(), pos.max_rank());
             if (!pos.first_rank_drops())
                 b &= ~rank_bb(relative_rank(Us, RANK_1, pos.max_rank()));
         }
+        if (pt == SHOGI_PAWN)
+            for (File f = FILE_A; f <= pos.max_file(); ++f)
+                if (file_bb(f) & pos.pieces(Us, pt))
+                    b &= ~file_bb(f);
         if (Checks)
             b &= pos.check_squares(pt);
         while (b)
@@ -257,7 +261,16 @@ namespace {
             b &= pos.check_squares(pt);
 
         while (b)
-            *moveList++ = make_move(from, pop_lsb(&b));
+        {
+            Square s = pop_lsb(&b);
+            bool piece_promotion =   pos.promoted_piece_type(pt) != NO_PIECE_TYPE
+                                  && (promotion_zone_bb(us, pos.promotion_rank(), pos.max_rank()) & (SquareBB[from] | s));
+            if (!piece_promotion || !pos.mandatory_piece_promotion())
+                *moveList++ = make_move(from, s);
+            // Shogi-style piece promotions
+            if (piece_promotion)
+                *moveList++ = make<PIECE_PROMOTION>(from, s);
+        }
     }
 
     return moveList;
index 618c82a..bb54593 100644 (file)
@@ -664,7 +664,7 @@ bool Position::legal(Move m) const {
 
   // illegal drops
   if (piece_drops() && type_of(m) == DROP)
-      return pieceCountInHand[us][type_of(moved_piece(m))] && empty(to_sq(m));
+      return pieceCountInHand[us][type_of(moved_piece(m))] && empty(to_sq(m)) && moves_bb(us, type_of(moved_piece(m)), to, 0);
 
   // game end
   if (is_variant_end())
@@ -788,7 +788,7 @@ bool Position::gives_check(Move m) const {
       return false;
 
   // Is there a direct check?
-  if (st->checkSquares[type_of(moved_piece(m))] & to)
+  if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && (st->checkSquares[type_of(moved_piece(m))] & to))
       return true;
 
   // Is there a discovered check?
@@ -807,6 +807,9 @@ bool Position::gives_check(Move m) const {
   case PROMOTION:
       return attacks_bb(sideToMove, promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
 
+  case PIECE_PROMOTION:
+      return attacks_bb(sideToMove, promoted_piece_type(type_of(moved_piece(m))), 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
@@ -866,6 +869,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   Square to = to_sq(m);
   Piece pc = moved_piece(m);
   Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to);
+  Piece unpromotedCaptured = unpromoted_piece_on(to);
 
   assert(color_of(pc) == us);
   assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
@@ -922,13 +926,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       if (captures_to_hand())
       {
           st->capturedpromoted = is_promoted(to);
-          Piece pieceToHand = is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured;
+          Piece pieceToHand =  !is_promoted(to)   ? ~captured
+                             : unpromotedCaptured ? ~unpromotedCaptured
+                                                  : make_piece(~color_of(captured), PAWN);
           add_to_hand(color_of(pieceToHand), type_of(pieceToHand));
           st->psq += PSQT::psq[pieceToHand][SQ_NONE];
           k ^=  Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1]
               ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]];
           promotedPieces -= to;
       }
+      unpromotedBoard[to] = NO_PIECE;
 
       // Update material hash key and prefetch access to materialTable
       k ^= Zobrist::psq[captured][capsq];
@@ -1020,12 +1027,33 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       // Reset rule 50 draw counter
       st->rule50 = 0;
   }
+  else if (type_of(m) == PIECE_PROMOTION)
+  {
+      Piece promotion = make_piece(us, promoted_piece_type(type_of(pc)));
+
+      remove_piece(pc, to);
+      put_piece(promotion, to);
+      promotedPieces |= to;
+      unpromotedBoard[to] = pc;
+
+      // Update hash keys
+      k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
+      st->materialKey ^=  Zobrist::psq[promotion][pieceCount[promotion]-1]
+                        ^ Zobrist::psq[pc][pieceCount[pc]];
+
+      // Update incremental score
+      st->psq += PSQT::psq[promotion][to] - PSQT::psq[pc][to];
+
+      // Update material
+      st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc];
+  }
 
   // Update incremental scores
   st->psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
 
   // Set capture piece
   st->capturedPiece = captured;
+  st->unpromotedCapturedPiece = captured ? unpromotedCaptured : NO_PIECE;
   if (captures_to_hand() && !captured)
       st->capturedpromoted = false;
 
@@ -1035,9 +1063,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   // Calculate checkers bitboard (if move gives check)
   st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
 
+  // Update information about promoted pieces
   if (type_of(m) != DROP && is_promoted(from))
       promotedPieces = (promotedPieces - from) | to;
 
+  if (type_of(m) != DROP && unpromoted_piece_on(from))
+  {
+      unpromotedBoard[to] = unpromotedBoard[from];
+      unpromotedBoard[from] = NO_PIECE;
+  }
+
   sideToMove = ~sideToMove;
 
   // Update king attacks used for fast check detection
@@ -1076,6 +1111,14 @@ void Position::undo_move(Move m) {
       if (captures_to_hand() && !drop_loop())
           promotedPieces -= to;
   }
+  else if (type_of(m) == PIECE_PROMOTION)
+  {
+      remove_piece(pc, to);
+      pc = unpromoted_piece_on(to);
+      put_piece(pc, to);
+      unpromotedBoard[to] = NO_PIECE;
+      promotedPieces -= to;
+  }
 
   if (type_of(m) == CASTLING)
   {
@@ -1090,6 +1133,11 @@ void Position::undo_move(Move m) {
           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;
+      if (unpromoted_piece_on(to))
+      {
+          unpromotedBoard[from] = unpromotedBoard[to];
+          unpromotedBoard[to] = NO_PIECE;
+      }
 
       if (st->capturedPiece)
       {
@@ -1110,10 +1158,14 @@ void Position::undo_move(Move m) {
           if (captures_to_hand())
           {
               remove_from_hand(~color_of(st->capturedPiece),
-                               !drop_loop() && st->capturedpromoted ? PAWN : type_of(st->capturedPiece));
+                               !drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? type_of(st->unpromotedCapturedPiece)
+                                                                                                   : PAWN)
+                                                                    : type_of(st->capturedPiece));
               if (!drop_loop() && st->capturedpromoted)
                   promotedPieces |= to;
           }
+          if (st->unpromotedCapturedPiece)
+              unpromotedBoard[to] = st->unpromotedCapturedPiece;
       }
   }
 
index 9adf7cd..bf53546 100644 (file)
@@ -53,6 +53,7 @@ struct StateInfo {
   Key        key;
   Bitboard   checkersBB;
   Piece      capturedPiece;
+  Piece      unpromotedCapturedPiece;
   StateInfo* previous;
   Bitboard   blockersForKing[COLOR_NB];
   Bitboard   pinners[COLOR_NB];
@@ -94,6 +95,8 @@ public:
   const std::string piece_to_char() const;
   Rank promotion_rank() const;
   const std::set<PieceType, std::greater<PieceType> >& promotion_piece_types() const;
+  PieceType promoted_piece_type(PieceType pt) const;
+  bool mandatory_piece_promotion() const;
   bool endgame_eval() const;
   bool double_step_enabled() const;
   bool castling_enabled() const;
@@ -128,6 +131,7 @@ public:
   Bitboard pieces(Color c, PieceType pt) const;
   Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const;
   Piece piece_on(Square s) const;
+  Piece unpromoted_piece_on(Square s) const;
   Square ep_square() const;
   bool empty(Square s) const;
   int count(Color c, PieceType pt) const;
@@ -218,6 +222,7 @@ private:
 
   // Data members
   Piece board[SQUARE_NB];
+  Piece unpromotedBoard[SQUARE_NB];
   Bitboard byTypeBB[PIECE_TYPE_NB];
   Bitboard byColorBB[COLOR_NB];
   int pieceCount[PIECE_NB];
@@ -280,6 +285,16 @@ inline const std::set<PieceType, std::greater<PieceType> >& Position::promotion_
   return var->promotionPieceTypes;
 }
 
+inline PieceType Position::promoted_piece_type(PieceType pt) const {
+  assert(var != nullptr);
+  return var->promotedPieceType[pt];
+}
+
+inline bool Position::mandatory_piece_promotion() const {
+  assert(var != nullptr);
+  return var->mandatoryPiecePromotion;
+}
+
 inline bool Position::endgame_eval() const {
   assert(var != nullptr);
   return var->endgameEval;
@@ -449,6 +464,10 @@ inline Piece Position::piece_on(Square s) const {
   return board[s];
 }
 
+inline Piece Position::unpromoted_piece_on(Square s) const {
+  return unpromotedBoard[s];
+}
+
 inline Piece Position::moved_piece(Move m) const {
   if (type_of(m) == DROP)
       return make_piece(sideToMove, dropped_piece_type(m));
index ae83b84..04c6919 100644 (file)
@@ -26,12 +26,12 @@ Value PieceValue[PHASE_NB][PIECE_NB] = {
   { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg,
     FersValueMg, AlfilValueMg, SilverValueMg, AiwokValueMg, BersValueMg, ChancellorValueMg,
     AmazonValueMg, KnibisValueMg, BiskniValueMg,
-    ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, EuroShogiKnightValueMg, GoldValueMg,
+    ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, EuroShogiKnightValueMg, GoldValueMg, HorseValueMg,
     CommonerValueMg },
   { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg,
     FersValueEg, AlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ChancellorValueEg,
     AmazonValueEg, KnibisValueMg, BiskniValueMg,
-    ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, EuroShogiKnightValueEg, GoldValueEg,
+    ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, EuroShogiKnightValueEg, GoldValueEg, HorseValueEg,
     CommonerValueEg }
 };
 
index 93868ea..f127211 100644 (file)
@@ -129,6 +129,7 @@ enum MoveType {
   PROMOTION_LEFT     = 4 << 12,
   PROMOTION_RIGHT    = 5 << 12,
   DROP               = 6 << 12,
+  PIECE_PROMOTION    = 7 << 12,
 };
 
 enum Color {
@@ -199,7 +200,7 @@ enum Value : int {
   AlfilValueMg           = 300,   AlfilValueEg           = 300,
   SilverValueMg          = 600,   SilverValueEg          = 600,
   AiwokValueMg           = 2500,  AiwokValueEg           = 2500,
-  BersValueMg            = 2500,  BersValueEg            = 2500,
+  BersValueMg            = 2000,  BersValueEg            = 2000,
   ChancellorValueMg      = 2500,  ChancellorValueEg      = 2500,
   AmazonValueMg          = 3000,  AmazonValueEg          = 3000,
   KnibisValueMg          = 800,   KnibisValueEg          = 800,
@@ -209,6 +210,7 @@ enum Value : int {
   ShogiKnightValueMg     = 300,   ShogiKnightValueEg     = 300,
   EuroShogiKnightValueMg = 400,   EuroShogiKnightValueEg = 400,
   GoldValueMg            = 600,   GoldValueEg            = 600,
+  HorseValueMg           = 1500,  HorseValueEg           = 1500,
   CommonerValueMg        = 600,   CommonerValueEg        = 600,
 
   MidgameLimit  = 15258, EndgameLimit  = 3915
@@ -219,7 +221,7 @@ const int PIECE_TYPE_BITS = 5; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS)
 enum PieceType {
   NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK,
   QUEEN, FERS, MET = FERS, ALFIL, SILVER, KHON = SILVER, AIWOK, BERS, DRAGON = BERS, CHANCELLOR,
-  AMAZON, KNIBIS, BISKNI, SHOGI_PAWN, LANCE, SHOGI_KNIGHT, EUROSHOGI_KNIGHT, GOLD, COMMONER, KING,
+  AMAZON, KNIBIS, BISKNI, SHOGI_PAWN, LANCE, SHOGI_KNIGHT, EUROSHOGI_KNIGHT, GOLD, HORSE, COMMONER, KING,
   ALL_PIECES = 0,
 
   PIECE_TYPE_NB = 1 << PIECE_TYPE_BITS
index 1692705..e5c5f20 100644 (file)
@@ -294,6 +294,9 @@ string UCI::move(const Position& pos, Move m) {
   if (type_of(m) == PROMOTION)
       move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))];
 
+  if (type_of(m) == PIECE_PROMOTION)
+      move += '+';
+
   return move;
 }
 
index f1ade24..87be444 100644 (file)
@@ -230,8 +230,10 @@ void VariantMap::init() {
         v->add_piece(EUROSHOGI_KNIGHT, 'n');
         v->add_piece(GOLD, 'g');
         v->add_piece(BISHOP, 'b');
+        v->add_piece(HORSE, 'h');
         v->add_piece(ROOK, 'r');
         v->add_piece(KING, 'k');
+        v->add_piece(DRAGON, 'd');
         v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1";
         v->pieceDrops = true;
         v->capturesToHand = true;
@@ -239,7 +241,12 @@ void VariantMap::init() {
         v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
-        // TODO: piece promotions, illegal pawn drops
+        v->promotedPieceType[SHOGI_PAWN]       = GOLD;
+        v->promotedPieceType[EUROSHOGI_KNIGHT] = GOLD;
+        v->promotedPieceType[SILVER]           = GOLD;
+        v->promotedPieceType[BISHOP]           = HORSE;
+        v->promotedPieceType[ROOK]             = DRAGON;
+        v->mandatoryPiecePromotion = true;
         return v;
     } ();
     const Variant* minishogi = [&]{
@@ -251,7 +258,9 @@ void VariantMap::init() {
         v->add_piece(SILVER, 's');
         v->add_piece(GOLD, 'g');
         v->add_piece(BISHOP, 'b');
+        v->add_piece(HORSE, 'h');
         v->add_piece(ROOK, 'r');
+        v->add_piece(DRAGON, 'd');
         v->add_piece(KING, 'k');
         v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1";
         v->pieceDrops = true;
@@ -260,7 +269,10 @@ void VariantMap::init() {
         v->promotionPieceTypes = {};
         v->doubleStep = false;
         v->castling = false;
-        // TODO: piece promotions, illegal pawn drops
+        v->promotedPieceType[SHOGI_PAWN] = GOLD;
+        v->promotedPieceType[SILVER]     = GOLD;
+        v->promotedPieceType[BISHOP]     = HORSE;
+        v->promotedPieceType[ROOK]       = DRAGON;
         return v;
     } ();
     const Variant* losalamos = [&]{
index 8af0c54..4f751d0 100644 (file)
@@ -42,6 +42,8 @@ struct Variant {
   std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
   Rank promotionRank = RANK_8;
   std::set<PieceType, std::greater<PieceType> > promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT };
+  PieceType promotedPieceType[PIECE_TYPE_NB] = {};
+  bool mandatoryPiecePromotion = false;
   bool endgameEval = false;
   bool doubleStep = true;
   bool castling = true;