Support S-chess
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 4 Aug 2019 16:35:47 +0000 (18:35 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Mon, 5 Aug 2019 18:41:07 +0000 (20:41 +0200)
https://en.wikipedia.org/wiki/Seirawan_chess

No functional change for other variants.

src/evaluate.cpp
src/movegen.cpp
src/position.cpp
src/position.h
src/types.h
src/uci.cpp
src/variant.cpp
src/variant.h
tests/perft.sh

index c469a85..60d02a3 100644 (file)
@@ -1053,7 +1053,7 @@ namespace {
             score += pieces<WHITE>(pt) - pieces<BLACK>(pt);
 
     // Evaluate pieces in hand once attack tables are complete
-    if (pos.piece_drops())
+    if (pos.piece_drops() || pos.gating())
         for (PieceType pt = PAWN; pt < KING; ++pt)
             score += hand<WHITE>(pt) - hand<BLACK>(pt);
 
index 6cb35f2..3263266 100644 (file)
 
 namespace {
 
+  template<MoveType T>
+  ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) {
+
+    *moveList++ = make<T>(from, to);
+
+    // Gating moves
+    if (pos.gating() && (pos.gates(us) & square_bb(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) & square_bb(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);
+
+    return moveList;
+  }
+
   template<Color c, GenType Type, Direction D>
   ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) {
 
@@ -273,7 +291,7 @@ namespace {
         }
 
         while (b1)
-            *moveList++ = make_move(from, pop_lsb(&b1));
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(&b1));
 
         // Shogi-style piece promotions
         while (b2)
@@ -308,15 +326,15 @@ namespace {
         Square ksq = pos.square<KING>(Us);
         Bitboard b = pos.attacks_from<KING>(Us, ksq) & target;
         while (b)
-            *moveList++ = make_move(ksq, pop_lsb(&b));
+            moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
 
         if (Type != CAPTURES && pos.can_castle(CastlingRight(OO | OOO)))
         {
             if (!pos.castling_impeded(OO) && pos.can_castle(OO))
-                *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO));
+                moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, ksq, pos.castling_rook_square(OO));
 
             if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
-                *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OOO));
+                moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, ksq, pos.castling_rook_square(OOO));
         }
     }
 
@@ -325,10 +343,10 @@ namespace {
     {
         Square from = make_square(FILE_E, relative_rank(Us, pos.castling_rank(), pos.max_rank()));
         if (!pos.castling_impeded(OO) && pos.can_castle(OO))
-            *moveList++ = make<CASTLING>(from, pos.castling_rook_square(OO));
+            moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(OO));
 
         if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
-            *moveList++ = make<CASTLING>(from, pos.castling_rook_square(OOO));
+            moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, from, pos.castling_rook_square(OOO));
     }
 
     return moveList;
@@ -390,7 +408,7 @@ ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
          b &= ~PseudoAttacks[~us][QUEEN][pos.square<KING>(~us)];
 
      while (b)
-         *moveList++ = make_move(from, pop_lsb(&b));
+         moveList = make_move_and_gating<NORMAL>(pos, moveList, us, from, pop_lsb(&b));
   }
 
   return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList, ~pos.pieces())
@@ -422,7 +440,7 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   // Generate evasions for king, capture and non capture moves
   Bitboard b = pos.attacks_from<KING>(us, ksq) & ~pos.pieces(us) & ~sliderAttacks;
   while (b)
-      *moveList++ = make_move(ksq, pop_lsb(&b));
+      moveList = make_move_and_gating<NORMAL>(pos, moveList, us, ksq, pop_lsb(&b));
 
   if (more_than_one(pos.checkers()))
       return moveList; // Double check, only a king move can save the day
index 931e346..a9449cc 100644 (file)
@@ -372,9 +372,31 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
           else
               continue;
 
+          // Set gates (and skip castling rights)
+          if (gating())
+          {
+              st->gatesBB[c] |= rsq;
+              if (token == 'K' || token == 'Q')
+                  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))
+                  continue;
+          }
+
           set_castling_right(c, rsq);
       }
 
+      // Set castling rights for 960 gating variants
+      if (gating())
+          for (Color c : {WHITE, BLACK})
+              if ((gates(c) & pieces(KING)) && !castling_rights(c) && count_in_hand(c, ALL_PIECES))
+              {
+                  Bitboard castling_rooks = gates(c) & pieces(ROOK);
+                  while (castling_rooks)
+                      set_castling_right(c, pop_lsb(&castling_rooks));
+              }
+
       // counting limit
       if (counting_rule() && isdigit(ss.peek()))
           ss >> st->countingLimit;
@@ -527,7 +549,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())
+          if (piece_drops() || gating())
               si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]];
       }
 
@@ -598,7 +620,7 @@ const string Position::fen() const {
   }
 
   // pieces in hand
-  if (piece_drops())
+  if (piece_drops() || gating())
   {
       ss << '[';
       for (Color c : {WHITE, BLACK})
@@ -615,13 +637,23 @@ const string Position::fen() const {
   if (can_castle(WHITE_OOO))
       ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
 
+  if (gating() && gates(WHITE))
+      for (File f = FILE_A; f <= max_file(); ++f)
+          if (gates(WHITE) & file_bb(f))
+              ss << char('A' + f);
+
   if (can_castle(BLACK_OO))
       ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
 
   if (can_castle(BLACK_OOO))
       ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
 
-  if (!can_castle(ANY_CASTLING))
+  if (gating() && gates(BLACK))
+      for (File f = FILE_A; f <= max_file(); ++f)
+          if (gates(BLACK) & file_bb(f))
+              ss << char('a' + f);
+
+  if (!can_castle(ANY_CASTLING) && !(gating() && (gates(WHITE) | gates(BLACK))))
       ss << '-';
 
   // Counting limit or ep-square
@@ -854,7 +886,7 @@ bool Position::pseudo_legal(const Move m) const {
                 || (drop_promoted() && promoted_piece_type(type_of(pc)) == in_hand_piece_type(m)));
 
   // Use a slower but simpler function for uncommon cases
-  if (type_of(m) != NORMAL)
+  if (type_of(m) != NORMAL || is_gating(m))
       return MoveList<LEGAL>(*this).contains(m);
 
   // Handle the case where a mandatory piece promotion/demotion is not taken
@@ -949,6 +981,11 @@ bool Position::gives_check(Move m) const {
       && attackers_to(square<KING>(~sideToMove), (pieces() ^ from) | to, sideToMove))
       return true;
 
+  // Is there a check by gated pieces?
+  if (    is_gating(m)
+      && attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square<KING>(~sideToMove))
+      return true;
+
   switch (type_of(m))
   {
   case NORMAL:
@@ -1241,9 +1278,34 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   if (captures_to_hand() && !captured)
       st->capturedpromoted = false;
 
+  // Add gating piece
+  if (is_gating(m))
+  {
+      Square gate = gating_square(m);
+      Piece gating_piece = make_piece(us, gating_type(m));
+      put_piece(gating_piece, gate);
+      remove_from_hand(gating_piece);
+      st->gatesBB[us] ^= gate;
+      k ^= Zobrist::psq[gating_piece][gate];
+      st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]];
+      st->nonPawnMaterial[us] += PieceValue[MG][gating_piece];
+  }
+
+  // Remove gates
+  if (gating())
+  {
+      if (is_ok(from) && (gates(us) & from))
+          st->gatesBB[us] ^= from;
+      if (type_of(m) == CASTLING)
+          st->gatesBB[us] ^= to;
+      if (gates(them) & to)
+          st->gatesBB[them] ^= to;
+      if (!count_in_hand(us, ALL_PIECES) && !captures_to_hand())
+          st->gatesBB[us] = 0;
+  }
+
   // Update the key with the final value
   st->key = k;
-
   // Calculate checkers bitboard (if move gives check)
   st->checkersBB = givesCheck ? attackers_to(square<KING>(them), us) & pieces(us) : Bitboard(0);
 
@@ -1311,9 +1373,18 @@ void Position::undo_move(Move m) {
   Square to = to_sq(m);
   Piece pc = piece_on(to);
 
-  assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || (type_of(m) == PROMOTION && sittuyin_promotion()));
+  assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) || (type_of(m) == PROMOTION && sittuyin_promotion()));
   assert(type_of(st->capturedPiece) != KING);
 
+  // Remove gated piece
+  if (is_gating(m))
+  {
+      Piece gating_piece = make_piece(us, gating_type(m));
+      remove_piece(gating_piece, gating_square(m));
+      add_to_hand(gating_piece);
+      st->gatesBB[us] |= gating_square(m);
+  }
+
   if (type_of(m) == PROMOTION)
   {
       assert(relative_rank(us, to, max_rank()) == promotion_rank() || sittuyin_promotion());
index 6e75dd0..fb396ca 100644 (file)
@@ -49,6 +49,7 @@ struct StateInfo {
   int    countingLimit;
   CheckCount checksGiven[COLOR_NB];
   Square epSquare;
+  Bitboard gatesBB[COLOR_NB];
 
   // Not copied when making a move (will be recomputed anyhow)
   int repetition;
@@ -128,6 +129,7 @@ public:
   bool drop_promoted() const;
   bool shogi_doubled_pawn() const;
   bool immobility_illegal() const;
+  bool gating() const;
   // winning conditions
   int n_move_rule() const;
   int n_fold_rule() const;
@@ -158,6 +160,7 @@ public:
   Piece piece_on(Square s) const;
   Piece unpromoted_piece_on(Square s) const;
   Square ep_square() const;
+  Bitboard gates(Color c) const;
   bool empty(Square s) const;
   int count(Color c, PieceType pt) const;
   template<PieceType Pt> int count(Color c) const;
@@ -494,6 +497,11 @@ inline bool Position::immobility_illegal() const {
   return var->immobilityIllegal;
 }
 
+inline bool Position::gating() const {
+  assert(var != nullptr);
+  return var->gating;
+}
+
 inline int Position::n_move_rule() const {
   assert(var != nullptr);
   return var->nMoveRule;
@@ -684,6 +692,11 @@ inline Square Position::ep_square() const {
   return st->epSquare;
 }
 
+inline Bitboard Position::gates(Color c) const {
+  assert(var != nullptr);
+  return st->gatesBB[c];
+}
+
 inline bool Position::is_on_semiopen_file(Color c, Square s) const {
   return !(pieces(c, PAWN, SHOGI_PAWN) & file_bb(s));
 }
index 22d3075..bf276c6 100644 (file)
@@ -677,6 +677,18 @@ inline PieceType promotion_type(Move m) {
   return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE;
 }
 
+inline PieceType gating_type(Move m) {
+  return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1));
+}
+
+inline Square gating_square(Move m) {
+  return Square((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & SQUARE_BIT_MASK);
+}
+
+inline bool is_gating(Move m) {
+  return gating_type(m) && (type_of(m) == NORMAL || type_of(m) == CASTLING);
+}
+
 constexpr Move make_move(Square from, Square to) {
   return Move((from << SQUARE_BITS) + to);
 }
@@ -690,6 +702,11 @@ 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);
 }
 
+template<MoveType T>
+constexpr Move make_gating(Square from, Square to, PieceType pt, Square gate) {
+  return Move((gate << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to);
+}
+
 constexpr PieceType dropped_piece_type(Move m) {
   return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1));
 }
index 81b2702..0d6f9b9 100644 (file)
@@ -315,7 +315,9 @@ string UCI::move(const Position& pos, Move m) {
   if (m == MOVE_NULL)
       return "0000";
 
-  if (type_of(m) == CASTLING && !pos.is_chess960())
+  if (is_gating(m) && gating_square(m) == to)
+      from = to_sq(m), to = from_sq(m);
+  else 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 ? UCI::dropped_piece(pos, m) + (Options["Protocol"] == "usi" ? '*' : '@')
@@ -327,6 +329,8 @@ string UCI::move(const Position& pos, Move m) {
       move += '+';
   else if (type_of(m) == PIECE_DEMOTION)
       move += '-';
+  else if (is_gating(m))
+      move += pos.piece_to_char()[make_piece(BLACK, gating_type(m))];
 
   return move;
 }
index fd35e6c..443d83e 100644 (file)
@@ -249,6 +249,17 @@ VariantMap variants; // Global object
         v->countingRule = ASEAN_COUNTING;
         return v;
     }
+    Variant* seirawan_variant() {
+        Variant* v = fairy_variant_base();
+        v->add_piece(ARCHBISHOP, 'h');
+        v->add_piece(CHANCELLOR, 'e');
+        v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1";
+        v->gating = true;
+        v->whiteDropRegion = Rank1BB;
+        v->blackDropRegion = Rank8BB;
+        v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT};
+        return v;
+    }
     Variant* minishogi_variant_base() {
         Variant* v = fairy_variant_base();
         v->variantTemplate = "shogi";
@@ -641,6 +652,7 @@ void VariantMap::init() {
     add("pocketknight", pocketknight_variant());
     add("placement", placement_variant());
     add("sittuyin", sittuyin_variant());
+    add("seirawan", seirawan_variant());
     add("minishogi", minishogi_variant());
     add("mini", minishogi_variant());
     add("kyotoshogi", kyotoshogi_variant());
index caa5638..5e72c34 100644 (file)
@@ -72,6 +72,7 @@ struct Variant {
   bool dropPromoted = false;
   bool shogiDoubledPawn = true;
   bool immobilityIllegal = false;
+  bool gating = false;
   // game end
   int nMoveRule = 50;
   int nFoldRule = 3;
index c3f4bf1..e53925a 100755 (executable)
@@ -54,6 +54,7 @@ if [[ $1 == "" || $1 == "variant" ]]; then
   expect perft.exp sittuyin startpos 3 580096 > /dev/null
   expect perft.exp sittuyin "fen 8/8/6R1/s3r3/P5R1/1KP3p1/1F2kr2/8[-] b - - 0 72" 4 657824 > /dev/null
   expect perft.exp sittuyin "fen 2r5/6k1/6p1/3s2P1/3npR2/8/p2N2F1/3K4 w - - 1 50" 4 394031 > /dev/null
+  expect perft.exp seirawan startpos 5 27639803 > /dev/null
 fi
 
 # large-board variants