Support Sittuyin (Burmese chess)
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 4 Nov 2018 16:50:10 +0000 (17:50 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 4 Nov 2018 16:50:10 +0000 (17:50 +0100)
- Rook drops are limited to back ranks.
- Promotion is allowed on main diagonals within opponent's half
  or when only one pawn is left. The player must not have a general.
- Promoting pawns stay on same square or move like a general.
- Pawn promotions may not capture or give check.

src/bitboard.h
src/endgame.cpp
src/movegen.cpp
src/pawns.cpp
src/position.cpp
src/position.h
src/types.h
src/variant.cpp
src/variant.h
tests/perft.sh

index 35ee1cb..a6aa395 100644 (file)
@@ -272,6 +272,10 @@ inline Bitboard between_bb(Square s1, Square s2) {
 /// in front of the given one, from the point of view of the given color. For instance,
 /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
 
+inline Bitboard forward_ranks_bb(Color c, Rank r) {
+  return ForwardRanksBB[c][r];
+}
+
 inline Bitboard forward_ranks_bb(Color c, Square s) {
   return ForwardRanksBB[c][rank_of(s)];
 }
index 6cf3aee..fad1d65 100644 (file)
@@ -186,10 +186,20 @@ Value Endgame<KPK>::operator()(const Position& pos) const {
 
   Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
 
-  if (!Bitbases::probe(wksq, psq, bksq, us))
-      return VALUE_DRAW;
+  Value result;
+  if (   pos.promotion_rank() == RANK_8
+      && pos.promotion_piece_types().find(QUEEN) != pos.promotion_piece_types().end())
+  {
+      if (!Bitbases::probe(wksq, psq, bksq, us))
+          return VALUE_DRAW;
 
-  Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq));
+      result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq));
+  }
+  else
+  {
+      // Non-standard promotion, evaluation unclear
+      result = PawnValueEg + Value(rank_of(psq));
+  }
 
   return strongSide == pos.side_to_move() ? result : -result;
 }
@@ -791,5 +801,9 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
 
   // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
   // it's probably at least a draw even with the pawn.
-  return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
+  if (   pos.promotion_rank() == RANK_8
+      && pos.promotion_piece_types().find(QUEEN) != pos.promotion_piece_types().end())
+      return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
+  else
+      return SCALE_FACTOR_NONE;
 }
index aa1ec14..6996f6d 100644 (file)
@@ -96,6 +96,8 @@ namespace {
             for (File f = FILE_A; f <= pos.max_file(); ++f)
                 if (file_bb(f) & pos.pieces(Us, pt))
                     b &= ~file_bb(f);
+        if (pt == ROOK && pos.sittuyin_rook_drop())
+            b &= rank_bb(relative_rank(Us, RANK_1, pos.max_rank()));
         if (Checks)
             b &= pos.check_squares(pt);
         while (b)
@@ -203,6 +205,32 @@ namespace {
             moveList = make_promotions<Us, Type, Up     >(pos, moveList, pop_lsb(&b3));
     }
 
+    // Sittuyin promotions
+    if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS))
+    {
+        Bitboard pawns = pos.pieces(Us, PAWN);
+        // Pawns need to be on diagonals on opponent's half if there is more than one pawn
+        if (pos.count<PAWN>(Us) > 1)
+            pawns &=  (  PseudoAttacks[Us][BISHOP][make_square(FILE_A, relative_rank(Us, RANK_1, pos.max_rank()))]
+                       | PseudoAttacks[Us][BISHOP][make_square(pos.max_file(), relative_rank(Us, RANK_1, pos.max_rank()))])
+                    & forward_ranks_bb(Us, relative_rank(Us, Rank((pos.max_rank() - 1) / 2), pos.max_rank()));
+        while (pawns)
+        {
+            Square from = pop_lsb(&pawns);
+            for (PieceType pt : pos.promotion_piece_types())
+            {
+                if (pos.count(Us, pt))
+                    continue;
+                Bitboard b = (pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from;
+                if (Type == EVASIONS)
+                    b &= target;
+
+                while (b)
+                    *moveList++ = make<PROMOTION>(from, pop_lsb(&b), pt);
+            }
+        }
+    }
+
     // Standard and en-passant captures
     if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
     {
index 2ce62e8..bea3e55 100644 (file)
@@ -102,7 +102,7 @@ namespace {
         opposed    = theirPawns & forward_file_bb(Us, s);
         stoppers   = theirPawns & passed_pawn_mask(Us, s);
         lever      = theirPawns & PseudoAttacks[Us][PAWN][s];
-        leverPush  = theirPawns & PseudoAttacks[Us][PAWN][s + Up];
+        leverPush  = relative_rank(Them, s, pos.max_rank()) > RANK_1 ? theirPawns & PseudoAttacks[Us][PAWN][s + Up] : 0;
         doubled    = relative_rank(Us, s, pos.max_rank()) > RANK_1 ? ourPawns & (s - Up) : 0;
         neighbours = ourPawns   & adjacent_files_bb(f);
         phalanx    = neighbours & rank_bb(s);
@@ -110,7 +110,8 @@ namespace {
 
         // A pawn is backward when it is behind all pawns of the same color
         // on the adjacent files and cannot be safely advanced.
-        backward =  !(ourPawns & pawn_attack_span(Them, s + Up))
+        backward =   relative_rank(Them, s, pos.max_rank()) > RANK_1
+                  && !(ourPawns & pawn_attack_span(Them, s + Up))
                   && (stoppers & (leverPush | (s + Up)));
 
         // Passed pawns will be properly scored in evaluation because we need
@@ -123,7 +124,8 @@ namespace {
             && popcount(phalanx)   >= popcount(leverPush))
             e->passedPawns[Us] |= s;
 
-        else if (   stoppers == SquareBB[s + Up]
+        else if (   relative_rank(Them, s, pos.max_rank()) > RANK_1
+                 && stoppers == SquareBB[s + Up]
                  && relative_rank(Us, s, pos.max_rank()) >= RANK_5)
         {
             b = shift<Up>(supported) & ~theirPawns;
index e54a9f1..2400b54 100644 (file)
@@ -703,15 +703,15 @@ bool Position::legal(Move m) const {
   assert(color_of(moved_piece(m)) == us);
   assert(!count<KING>(us) || piece_on(square<KING>(us)) == make_piece(us, KING));
 
-  // illegal moves to squares outside of board
+  // Illegal moves to squares outside of board
   if (!(board_bb() & to))
       return false;
 
-  // illegal checks
-  if (!checking_permitted() && gives_check(m))
+  // Illegal checks
+  if ((!checking_permitted() || (sittuyin_promotion() && type_of(m) == PROMOTION)) && gives_check(m))
       return false;
 
-  // illegal quiet moves
+  // Illegal quiet moves
   if (must_capture() && !capture(m))
   {
       if (checkers())
@@ -728,7 +728,7 @@ bool Position::legal(Move m) const {
       }
   }
 
-  // illegal non-drop moves
+  // Illegal non-drop moves
   if (must_drop() && type_of(m) != DROP && count_in_hand(us, ALL_PIECES))
   {
       if (checkers())
@@ -745,7 +745,7 @@ bool Position::legal(Move m) const {
       }
   }
 
-  // illegal drop move
+  // Illegal drop move
   if (drop_opposite_colored_bishop() && type_of(m) == DROP)
   {
       if (type_of(moved_piece(m)) != BISHOP)
@@ -762,11 +762,11 @@ bool Position::legal(Move m) const {
               return false;
   }
 
-  // no legal moves from target square
+  // 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;
 
-  // game end
+  // Game end
   if (is_variant_end())
       return false;
 
@@ -969,6 +969,11 @@ 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);
+  if (to == from)
+  {
+      assert(type_of(m) == PROMOTION && sittuyin_promotion());
+      captured = NO_PIECE;
+  }
   Piece unpromotedCaptured = unpromoted_piece_on(to);
 
   assert(color_of(pc) == us);
@@ -1113,7 +1118,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
       {
           Piece promotion = make_piece(us, promotion_type(m));
 
-          assert(relative_rank(us, to, max_rank()) == promotion_rank());
+          assert(relative_rank(us, to, max_rank()) == promotion_rank() || sittuyin_promotion());
           assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING);
 
           remove_piece(pc, to);
@@ -1213,12 +1218,12 @@ 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);
+  assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || (type_of(m) == PROMOTION && sittuyin_promotion()));
   assert(type_of(st->capturedPiece) != KING);
 
   if (type_of(m) == PROMOTION)
   {
-      assert(relative_rank(us, to, max_rank()) == promotion_rank());
+      assert(relative_rank(us, to, max_rank()) == promotion_rank() || sittuyin_promotion());
       assert(type_of(pc) == promotion_type(m));
       assert(type_of(pc) >= KNIGHT && type_of(pc) < KING);
 
index 7ecd822..d362e00 100644 (file)
@@ -97,6 +97,7 @@ public:
   const std::string piece_to_char() const;
   Rank promotion_rank() const;
   const std::set<PieceType, std::greater<PieceType> >& promotion_piece_types() const;
+  bool sittuyin_promotion() const;
   PieceType promoted_piece_type(PieceType pt) const;
   bool mandatory_piece_promotion() const;
   bool endgame_eval() const;
@@ -115,6 +116,7 @@ public:
   bool first_rank_drops() const;
   bool drop_on_top() const;
   Bitboard drop_region(Color c) const;
+  bool sittuyin_rook_drop() const;
   bool drop_opposite_colored_bishop() const;
   bool immobility_illegal() const;
   // winning conditions
@@ -303,6 +305,11 @@ inline const std::set<PieceType, std::greater<PieceType> >& Position::promotion_
   return var->promotionPieceTypes;
 }
 
+inline bool Position::sittuyin_promotion() const {
+  assert(var != nullptr);
+  return var->sittuyinPromotion;
+}
+
 inline PieceType Position::promoted_piece_type(PieceType pt) const {
   assert(var != nullptr);
   return var->promotedPieceType[pt];
@@ -393,6 +400,11 @@ inline Bitboard Position::drop_region(Color c) const {
   return c == WHITE ? var->whiteDropRegion : var->blackDropRegion;
 }
 
+inline bool Position::sittuyin_rook_drop() const {
+  assert(var != nullptr);
+  return var->sittuyinRookDrop;
+}
+
 inline bool Position::drop_opposite_colored_bishop() const {
   assert(var != nullptr);
   return var->dropOppositeColoredBishop;
index f54e913..fd7a2d0 100644 (file)
@@ -572,7 +572,7 @@ constexpr PieceType dropped_piece_type(Move m) {
 }
 
 inline bool is_ok(Move m) {
-  return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
+  return from_sq(m) != to_sq(m) || type_of(m) == PROMOTION; // Catch MOVE_NULL and MOVE_NONE
 }
 
 #endif // #ifndef TYPES_H_INCLUDED
index aeb0432..0328584 100644 (file)
@@ -222,6 +222,22 @@ VariantMap variants; // Global object
         v->castlingDroppedPiece = true;
         return v;
     }
+    Variant* sittuyin_variant() {
+        Variant* v = makruk_variant();
+        v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1";
+        v->remove_piece(MET);
+        v->add_piece(MET, 'f');
+        v->mustDrop = true;
+        v->pieceDrops = true;
+        v->capturesToHand = false;
+        v->whiteDropRegion = Rank1BB | Rank2BB | Rank3BB;
+        v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB;
+        v->sittuyinRookDrop = true;
+        v->promotionRank = RANK_1; // no regular promotions
+        v->sittuyinPromotion = true;
+        v->immobilityIllegal = false;
+        return v;
+    }
     Variant* euroshogi_variant() {
         Variant* v = fairy_variant_base();
         v->reset_pieces();
@@ -541,6 +557,7 @@ void VariantMap::init() {
     add("chessgi", chessgi_variant());
     add("pocketknight", pocketknight_variant());
     add("placement", placement_variant());
+    add("sittuyin", sittuyin_variant());
     add("euroshogi", euroshogi_variant());
     add("judkinshogi", judkinsshogi_variant());
     add("minishogi", minishogi_variant());
index f197460..2aed7d9 100644 (file)
@@ -40,6 +40,7 @@ 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 };
+  bool sittuyinPromotion = false;
   PieceType promotedPieceType[PIECE_TYPE_NB] = {};
   bool mandatoryPiecePromotion = false;
   bool endgameEval = false;
@@ -59,6 +60,7 @@ struct Variant {
   bool dropOnTop = false;
   Bitboard whiteDropRegion = AllSquares;
   Bitboard blackDropRegion = AllSquares;
+  bool sittuyinRookDrop = false;
   bool dropOppositeColoredBishop = false;
   bool immobilityIllegal = false;
   // game end
index e84f28c..1163dd3 100755 (executable)
@@ -48,6 +48,8 @@ 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
+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
 
 rm perft.exp