Support Othello/Reversi
authorFabian Fichter <ianfab@users.noreply.github.com>
Fri, 3 Jul 2020 10:58:13 +0000 (12:58 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Fri, 3 Jul 2020 10:58:13 +0000 (12:58 +0200)
src/movegen.cpp
src/parser.cpp
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
src/variants.ini
src/xboard.cpp

index 129d596..a271a57 100644 (file)
@@ -342,7 +342,7 @@ namespace {
             moveList = make_move_and_gating<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
 
         // Passing move by king
-        if (pos.king_pass())
+        if (pos.pass())
             *moveList++ = make<SPECIAL>(ksq, ksq);
 
         if (Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO)))
@@ -354,6 +354,9 @@ namespace {
                 moveList = make_move_and_gating<CASTLING>(pos, moveList, Us, ksq, pos.castling_rook_square(OOO));
         }
     }
+    // Workaround for passing: Execute a non-move with any piece
+    else if (pos.pass() && !pos.count<KING>(Us) && pos.pieces(Us))
+        *moveList++ = make<SPECIAL>(lsb(pos.pieces(Us)), lsb(pos.pieces(Us)));
 
     // Castling with non-king piece
     if (!pos.count<KING>(Us) && Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO)))
@@ -468,7 +471,7 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
   Bitboard sliders = pos.checkers();
 
   // Passing move by king in bikjang
-  if (pos.bikjang() && pos.king_pass())
+  if (pos.bikjang() && pos.pass())
       *moveList++ = make<SPECIAL>(ksq, ksq);
 
   // Consider all evasion moves for special pieces
index d89f25c..dca83f8 100644 (file)
@@ -252,6 +252,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("firstRankPawnDrops", v->firstRankPawnDrops);
     parse_attribute("promotionZonePawnDrops", v->promotionZonePawnDrops);
     parse_attribute("dropOnTop", v->dropOnTop);
+    parse_attribute("enclosingDrop", v->enclosingDrop);
+    parse_attribute("enclosingDropStart", v->enclosingDropStart);
     parse_attribute("whiteDropRegion", v->whiteDropRegion);
     parse_attribute("blackDropRegion", v->blackDropRegion);
     parse_attribute("sittuyinRookDrop", v->sittuyinRookDrop);
@@ -263,11 +265,12 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("seirawanGating", v->seirawanGating);
     parse_attribute("cambodianMoves", v->cambodianMoves);
     parse_attribute("diagonalLines", v->diagonalLines);
-    parse_attribute("kingPass", v->kingPass);
-    parse_attribute("kingPassOnStalemate", v->kingPassOnStalemate);
+    parse_attribute("pass", v->pass);
+    parse_attribute("passOnStalemate", v->passOnStalemate);
     parse_attribute("makpongRule", v->makpongRule);
     parse_attribute("flyingGeneral", v->flyingGeneral);
     parse_attribute("soldierPromotionRank", v->soldierPromotionRank);
+    parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces);
     // game end
     parse_attribute("nMoveRule", v->nMoveRule);
     parse_attribute("nFoldRule", v->nFoldRule);
@@ -275,6 +278,7 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("nFoldValueAbsolute", v->nFoldValueAbsolute);
     parse_attribute("perpetualCheckIllegal", v->perpetualCheckIllegal);
     parse_attribute("stalemateValue", v->stalemateValue);
+    parse_attribute("stalematePieceCount", v->stalematePieceCount);
     parse_attribute("checkmateValue", v->checkmateValue);
     parse_attribute("shogiPawnDropMateIllegal", v->shogiPawnDropMateIllegal);
     parse_attribute("shatarMateRule", v->shatarMateRule);
index d6a2daf..db17d95 100644 (file)
@@ -893,7 +893,7 @@ bool Position::legal(Move m) const {
       return false;
 
   // Illegal king passing move
-  if (king_pass_on_stalemate() && is_pass(m) && !checkers())
+  if (pass_on_stalemate() && is_pass(m) && !checkers())
   {
       for (const auto& move : MoveList<NON_EVASIONS>(*this))
           if (!is_pass(move) && legal(move))
@@ -1213,7 +1213,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to);
   if (to == from)
   {
-      assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && king_pass()));
+      assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass()));
       captured = NO_PIECE;
   }
   st->capturedpromoted = is_promoted(to);
@@ -1339,6 +1339,35 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
                   set_castling_right(us, to);
           }
       }
+      // Flip enclosed pieces
+      if (flip_enclosed_pieces())
+      {
+          st->flippedPieces = 0;
+          // Find end of rows to be flipped
+          Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us);
+          while(b)
+              st->flippedPieces |= between_bb(to, pop_lsb(&b));
+          // Flip pieces
+          Bitboard to_flip = st->flippedPieces;
+          while(to_flip)
+          {
+              Square s = pop_lsb(&to_flip);
+              Piece flipped = piece_on(s);
+              Piece resulting = ~flipped;
+
+              // remove opponent's piece
+              remove_piece(s);
+              k ^= Zobrist::psq[flipped][s];
+              st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]];
+              st->nonPawnMaterial[them] -= PieceValue[MG][flipped];
+
+              // add our piece
+              put_piece(resulting, s);
+              k ^= Zobrist::psq[resulting][s];
+              st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1];
+              st->nonPawnMaterial[us] += PieceValue[MG][resulting];
+          }
+      }
   }
   else if (type_of(m) != CASTLING)
       move_piece(from, to);
@@ -1498,7 +1527,7 @@ void Position::undo_move(Move m) {
 
   assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m)
          || (type_of(m) == PROMOTION && sittuyin_promotion())
-         || (is_pass(m) && king_pass()));
+         || (is_pass(m) && pass()));
   assert(type_of(st->capturedPiece) != KING);
 
   // Remove gated piece
@@ -1543,7 +1572,21 @@ void Position::undo_move(Move m) {
   else
   {
       if (type_of(m) == DROP)
+      {
+          if (flip_enclosed_pieces())
+          {
+              // Flip pieces
+              Bitboard to_flip = st->flippedPieces;
+              while(to_flip)
+              {
+                  Square s = pop_lsb(&to_flip);
+                  Piece resulting = ~piece_on(s);
+                  remove_piece(s);
+                  put_piece(resulting, s);
+              }
+          }
           undrop_piece(make_piece(us, in_hand_piece_type(m)), to); // Remove the dropped piece
+      }
       else
           move_piece(to, from); // Put the piece back at the source square
 
index a0ca7cb..4446b08 100644 (file)
@@ -60,6 +60,7 @@ struct StateInfo {
   Bitboard   blockersForKing[COLOR_NB];
   Bitboard   pinners[COLOR_NB];
   Bitboard   checkSquares[PIECE_TYPE_NB];
+  Bitboard   flippedPieces;
   bool       capturedpromoted;
   bool       shak;
   bool       bikjang;
@@ -132,6 +133,7 @@ public:
   bool captures_to_hand() const;
   bool first_rank_pawn_drops() const;
   bool drop_on_top() const;
+  bool enclosing_drop() const;
   Bitboard drop_region(Color c) const;
   Bitboard drop_region(Color c, PieceType pt) const;
   bool sittuyin_rook_drop() const;
@@ -143,10 +145,11 @@ public:
   bool seirawan_gating() const;
   bool cambodian_moves() const;
   Bitboard diagonal_lines() const;
-  bool king_pass() const;
-  bool king_pass_on_stalemate() const;
+  bool pass() const;
+  bool pass_on_stalemate() const;
   Bitboard promoted_soldiers(Color c) const;
   bool makpong() const;
+  bool flip_enclosed_pieces() const;
   // winning conditions
   int n_move_rule() const;
   int n_fold_rule() const;
@@ -508,6 +511,11 @@ inline bool Position::drop_on_top() const {
   return var->dropOnTop;
 }
 
+inline bool Position::enclosing_drop() const {
+  assert(var != nullptr);
+  return var->enclosingDrop;
+}
+
 inline Bitboard Position::drop_region(Color c) const {
   assert(var != nullptr);
   return c == WHITE ? var->whiteDropRegion : var->blackDropRegion;
@@ -536,6 +544,29 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const {
   if (pt == ROOK && sittuyin_rook_drop())
       b &= rank_bb(relative_rank(c, RANK_1, max_rank()));
 
+  // Filter out squares where the drop does not enclose at least one opponent's piece
+  if (enclosing_drop())
+  {
+      // Reversi start
+      if (var->enclosingDropStart & ~pieces())
+          b &= var->enclosingDropStart;
+      else
+      {
+          Bitboard theirs = pieces(~c);
+          b &=  shift<NORTH     >(theirs) | shift<SOUTH     >(theirs)
+              | shift<NORTH_EAST>(theirs) | shift<SOUTH_WEST>(theirs)
+              | shift<EAST      >(theirs) | shift<WEST      >(theirs)
+              | shift<SOUTH_EAST>(theirs) | shift<NORTH_WEST>(theirs);
+          Bitboard b2 = b;
+          while (b2)
+          {
+              Square s = pop_lsb(&b2);
+              if (!(attacks_bb(c, QUEEN, s, board_bb() & ~pieces(~c)) & ~PseudoAttacks[c][KING][s] & pieces(c)))
+                  b ^= s;
+          }
+      }
+  }
+
   return b;
 }
 
@@ -584,14 +615,14 @@ inline Bitboard Position::diagonal_lines() const {
   return var->diagonalLines;
 }
 
-inline bool Position::king_pass() const {
+inline bool Position::pass() const {
   assert(var != nullptr);
-  return var->kingPass || var->kingPassOnStalemate;
+  return var->pass || var->passOnStalemate;
 }
 
-inline bool Position::king_pass_on_stalemate() const {
+inline bool Position::pass_on_stalemate() const {
   assert(var != nullptr);
-  return var->kingPassOnStalemate;
+  return var->passOnStalemate;
 }
 
 inline Bitboard Position::promoted_soldiers(Color c) const {
@@ -614,6 +645,11 @@ inline int Position::n_fold_rule() const {
   return var->nFoldRule;
 }
 
+inline bool Position::flip_enclosed_pieces() const {
+  assert(var != nullptr);
+  return var->flipEnclosedPieces;
+}
+
 inline Value Position::stalemate_value(int ply) const {
   assert(var != nullptr);
   if (var->stalematePieceCount)
index 7aa1535..77365f5 100644 (file)
@@ -876,7 +876,7 @@ namespace {
         v->materialCounting = JANGGI_MATERIAL;
         v->diagonalLines = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3,
                                          SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10);
-        v->kingPass = true;
+        v->pass = true;
         v->nFoldValue = VALUE_DRAW;
         v->perpetualCheckIllegal = true;
         return v;
index 4551498..27065dc 100644 (file)
@@ -76,6 +76,8 @@ struct Variant {
   bool firstRankPawnDrops = false;
   bool promotionZonePawnDrops = false;
   bool dropOnTop = false;
+  bool enclosingDrop = false;
+  Bitboard enclosingDropStart = 0;
   Bitboard whiteDropRegion = AllSquares;
   Bitboard blackDropRegion = AllSquares;
   bool sittuyinRookDrop = false;
@@ -87,11 +89,12 @@ struct Variant {
   bool seirawanGating = false;
   bool cambodianMoves = false;
   Bitboard diagonalLines = 0;
-  bool kingPass = false;
-  bool kingPassOnStalemate = false;
+  bool pass = false;
+  bool passOnStalemate = false;
   bool makpongRule = false;
   bool flyingGeneral = false;
   Rank soldierPromotionRank = RANK_1;
+  bool flipEnclosedPieces = false;
   // game end
   int nMoveRule = 50;
   int nFoldRule = 3;
index 6a22b38..1dcfc0d 100644 (file)
 # firstRankPawnDrops: allow pawn drops to first rank [bool] (default: false)
 # promotionZonePawnDrops: allow pawn drops in promotion zone  [bool] (default: false)
 # dropOnTop: piece drops need to be on top of pieces on board (e.g., for connect4) [bool] (default: false)
+# enclosingDrop: require piece drop to enclose opponent's pieces (e.g., for othello) [bool] (default: false)
+# enclosingDropStart: drop region for starting phase disregarding enclosingDrop (e.g., for reversi) [Bitboard]
 # whiteDropRegion: restrict region for piece drops of all white pieces [Bitboard]
 # blackDropRegion: restrict region for piece drops of all black pieces [Bitboard]
 # sittuyinRookDrop: restrict region of rook drops to first rank [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)
 # diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard]
-# kingPass: allow passing by king [bool] (default: false)
-# kingPassOnStalemate: allow passing by king in case of stalemate [bool] (default: false)
+# pass: allow passing [bool] (default: false)
+# passOnStalemate: allow passing in case of stalemate [bool] (default: false)
 # makpongRule: the king may not move away from check [bool] (default: false)
 # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false)
 # soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [bool] (default: 1)
+# flipEnclosedPieces: change color of pieces that are enclosed by a drop (e.g., for othello) [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)
 # nFoldValue: result in case of 3/n-fold repetition [Value] (default: draw)
 # nFoldValueAbsolute: result in case of 3/n-fold repetition is from white's point of view [bool] (default: false)
 # perpetualCheckIllegal: prohibit perpetual checks [bool] (default: false)
 # stalemateValue: result in case of stalemate [Value] (default: draw)
+# stalematePieceCount: count material in case of stalemate [bool] (default: false)
 # checkmateValue: result in case of checkmate [Value] (default: loss)
 # shogiPawnDropMateIllegal: prohibit checkmate via shogi pawn drops [bool] (default: false)
 # shatarMateRule: enable shatar mating rules [bool] (default: false)
@@ -300,6 +304,26 @@ immobilityIllegal = false
 connectN = 4
 nMoveRule = 0
 
+[reversi]
+immobile = p
+startFen = 8/8/8/8/8/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1
+pieceDrops = true
+promotionPieceTypes = -
+doubleStep = false
+castling = false
+stalemateValue = loss
+stalematePieceCount = true
+materialCounting = unweighted
+enclosingDrop = true
+enclosingDropStart = d4 e4 d5 e5
+immobilityIllegal = false
+flipEnclosedPieces = true
+passOnStalemate = false
+
+[othello:reversi]
+startFen = 8/8/8/3pP3/3Pp3/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1
+passOnStalemate = true
+
 [grandhouse:grand]
 startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1
 pieceDrops = true
index e8adfb7..e199d56 100644 (file)
@@ -238,7 +238,7 @@ void StateMachine::process_command(Position& pos, std::string token, std::istrin
       std::getline(is >> std::ws, fen);
       // Check if setboard actually indicates a passing move
       // to avoid unnecessarily clearing the move history
-      if (pos.king_pass())
+      if (pos.pass())
       {
           StateInfo st;
           Position p;