Support Semi-Torpedo chess (#209)
authorFabian Fichter <ianfab@users.noreply.github.com>
Thu, 19 Nov 2020 18:32:49 +0000 (19:32 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sat, 21 Nov 2020 14:00:55 +0000 (15:00 +0100)
src/bitboard.h
src/evaluate.cpp
src/movegen.cpp
src/parser.cpp
src/position.cpp
src/position.h
src/variant.cpp
src/variant.h
src/variants.ini
tests/perft.sh

index 2a0542f..21a77e6 100644 (file)
@@ -321,11 +321,11 @@ constexpr Bitboard forward_ranks_bb(Color c, Rank r) {
 }
 
 
-/// promotion_zone_bb() returns a bitboard representing the squares on all the ranks
+/// zone_bb() returns a bitboard representing the squares on all the ranks
 /// 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.
+/// For instance, zone_bb(BLACK, RANK_7) will return the 16 squares on ranks 1 and 2.
 
-inline Bitboard promotion_zone_bb(Color c, Rank r, Rank maxRank) {
+inline Bitboard zone_bb(Color c, Rank r, Rank maxRank) {
   return forward_ranks_bb(c, relative_rank(c, r, maxRank)) | rank_bb(relative_rank(c, r, maxRank));
 }
 
index f62fcc1..0efda47 100644 (file)
@@ -493,7 +493,7 @@ namespace {
         // Piece promotion bonus
         if (pos.promoted_piece_type(Pt) != NO_PIECE_TYPE)
         {
-            Bitboard zone = promotion_zone_bb(Us, pos.promotion_rank(), pos.max_rank());
+            Bitboard zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank());
             if (zone & (b | s))
                 score += make_score(PieceValue[MG][pos.promoted_piece_type(Pt)] - PieceValue[MG][Pt],
                                     PieceValue[EG][pos.promoted_piece_type(Pt)] - PieceValue[EG][Pt]) / (zone & s && b ? 6 : 12);
index 7d44606..3971fc5 100644 (file)
@@ -107,12 +107,11 @@ namespace {
     const Square ksq = pos.count<KING>(Them) ? pos.square<KING>(Them) : SQ_NONE;
 
     Bitboard TRank8BB = pos.mandatory_pawn_promotion() ? rank_bb(relative_rank(Us, pos.promotion_rank(), pos.max_rank()))
-                                                       : promotion_zone_bb(Us, pos.promotion_rank(), pos.max_rank());
+                                                       : zone_bb(Us, pos.promotion_rank(), pos.max_rank());
     Bitboard TRank7BB = shift<Down>(TRank8BB);
     // Define squares a pawn can pass during a double step
-    Bitboard  TRank3BB = rank_bb(relative_rank(Us, Rank(pos.double_step_rank() + 1), pos.max_rank()));
-    if (pos.first_rank_double_steps())
-        TRank3BB |= rank_bb(relative_rank(Us, RANK_2, pos.max_rank()));
+    Bitboard  TRank3BB =  forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_min(), pos.max_rank()))
+                        & ~shift<Up>(forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_max(), pos.max_rank())));
 
     Bitboard emptySquares;
 
@@ -242,7 +241,7 @@ namespace {
 
         if (pos.ep_square() != SQ_NONE)
         {
-            assert(rank_of(pos.ep_square()) == relative_rank(Them, Rank(pos.double_step_rank() + 1), pos.max_rank()));
+            assert(relative_rank(Them, rank_of(pos.ep_square()), pos.max_rank()) <= Rank(pos.double_step_rank_max() + 1));
 
             // An en passant capture can be an evasion only if the checking piece
             // is the double pushed pawn and so is in the target. Otherwise this
@@ -294,7 +293,7 @@ namespace {
         // Restrict target squares considering promotion zone
         if (b2 | b3)
         {
-            Bitboard promotion_zone = promotion_zone_bb(Us, pos.promotion_rank(), pos.max_rank());
+            Bitboard promotion_zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank());
             if (pos.mandatory_piece_promotion())
                 b1 &= (promotion_zone & from ? Bitboard(0) : ~promotion_zone) | (pos.piece_promotion_on_capture() ? ~pos.pieces() : Bitboard(0));
             // Exclude quiet promotions/demotions
index 99e0106..9da29fd 100644 (file)
@@ -240,7 +240,8 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("endgameEval", v->endgameEval);
     parse_attribute("doubleStep", v->doubleStep);
     parse_attribute("doubleStepRank", v->doubleStepRank);
-    parse_attribute("firstRankDoubleSteps", v->firstRankDoubleSteps);
+    parse_attribute("doubleStepRankMin", v->doubleStepRankMin);
+    parse_attribute("enPassantRegion", v->enPassantRegion);
     parse_attribute("castling", v->castling);
     parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece);
     parse_attribute("castlingKingsideFile", v->castlingKingsideFile);
index c5bd02d..a39fc60 100644 (file)
@@ -1016,7 +1016,7 @@ bool Position::pseudo_legal(const Move m) const {
   // 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))
+      && (zone_bb(us, promotion_rank(), max_rank()) & (SquareBB[from] | to))
       && (!piece_promotion_on_capture() || capture(m)))
       return false;
 
@@ -1044,8 +1044,8 @@ bool Position::pseudo_legal(const Move m) const {
       if (   !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
           && !((from + pawn_push(us) == to) && empty(to))       // Not a single push
           && !(   (from + 2 * pawn_push(us) == to)              // Not a double push
-               && (rank_of(from) == relative_rank(us, double_step_rank(), max_rank())
-                   || (first_rank_double_steps() && rank_of(from) == relative_rank(us, RANK_1, max_rank())))
+               && (   relative_rank(us, from, max_rank()) <= double_step_rank_max()
+                   && relative_rank(us, from, max_rank()) >= double_step_rank_min())
                && empty(to)
                && empty(to - pawn_push(us))
                && double_step_enabled()))
@@ -1271,7 +1271,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
 
               assert(pc == make_piece(us, PAWN));
               assert(to == st->epSquare);
-              assert(relative_rank(~us, to, max_rank()) == Rank(double_step_rank() + 1));
+              assert((var->enPassantRegion & to)
+                      && relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1)
+                      && relative_rank(~us, to, max_rank()) > double_step_rank_min());
               assert(piece_on(to) == NO_PIECE);
               assert(piece_on(capsq) == make_piece(them, PAWN));
           }
@@ -1432,7 +1434,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
   {
       // Set en-passant square if the moved pawn can be captured
       if (   std::abs(int(to) - int(from)) == 2 * NORTH
-          && relative_rank(us, rank_of(from), max_rank()) == double_step_rank()
+          && (var->enPassantRegion & (to - pawn_push(us)))
           && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
       {
           st->epSquare = to - pawn_push(us);
@@ -1683,7 +1685,7 @@ void Position::undo_move(Move m) {
 
               assert(type_of(pc) == PAWN);
               assert(to == st->previous->epSquare);
-              assert(relative_rank(~us, to, max_rank()) == Rank(double_step_rank() + 1));
+              assert(relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1));
               assert(piece_on(capsq) == NO_PIECE);
               assert(st->capturedPiece == make_piece(~us, PAWN));
           }
@@ -2320,7 +2322,7 @@ bool Position::pos_is_ok() const {
       || (count<KING>(WHITE) && piece_on(square<KING>(WHITE)) != make_piece(WHITE, KING))
       || (count<KING>(BLACK) && piece_on(square<KING>(BLACK)) != make_piece(BLACK, KING))
       || (   ep_square() != SQ_NONE
-          && relative_rank(~sideToMove, ep_square(), max_rank()) != Rank(double_step_rank() + 1)))
+          && relative_rank(~sideToMove, ep_square(), max_rank()) > Rank(double_step_rank_max() + 1)))
       assert(0 && "pos_is_ok: Default");
 
   if (Fast)
index 49598a1..6e4dc48 100644 (file)
@@ -122,8 +122,8 @@ public:
   bool piece_demotion() const;
   bool endgame_eval() const;
   bool double_step_enabled() const;
-  Rank double_step_rank() const;
-  bool first_rank_double_steps() const;
+  Rank double_step_rank_max() const;
+  Rank double_step_rank_min() const;
   bool castling_enabled() const;
   bool castling_dropped_piece() const;
   File castling_kingside_file() const;
@@ -432,14 +432,14 @@ inline bool Position::double_step_enabled() const {
   return var->doubleStep;
 }
 
-inline Rank Position::double_step_rank() const {
+inline Rank Position::double_step_rank_max() const {
   assert(var != nullptr);
   return var->doubleStepRank;
 }
 
-inline bool Position::first_rank_double_steps() const {
+inline Rank Position::double_step_rank_min() const {
   assert(var != nullptr);
-  return var->firstRankDoubleSteps;
+  return var->doubleStepRankMin;
 }
 
 inline bool Position::castling_enabled() const {
@@ -542,7 +542,7 @@ inline Bitboard Position::drop_region(Color c, PieceType pt) const {
   if (pt == PAWN)
   {
       if (!var->promotionZonePawnDrops)
-          b &= ~promotion_zone_bb(c, promotion_rank(), max_rank());
+          b &= ~zone_bb(c, promotion_rank(), max_rank());
       if (!first_rank_pawn_drops())
           b &= ~rank_bb(relative_rank(c, RANK_1, max_rank()));
   }
@@ -655,7 +655,7 @@ inline bool Position::pass_on_stalemate() const {
 
 inline Bitboard Position::promoted_soldiers(Color c) const {
   assert(var != nullptr);
-  return pieces(c, SOLDIER) & promotion_zone_bb(c, var->soldierPromotionRank, max_rank());
+  return pieces(c, SOLDIER) & zone_bb(c, var->soldierPromotionRank, max_rank());
 }
 
 inline bool Position::makpong() const {
@@ -1005,8 +1005,9 @@ inline bool Position::pawn_passed(Color c, Square s) const {
 }
 
 inline bool Position::advanced_pawn_push(Move m) const {
-  return   type_of(moved_piece(m)) == PAWN
-        && relative_rank(sideToMove, to_sq(m), max_rank()) > (max_rank() + 1) / 2;
+  return  (   type_of(moved_piece(m)) == PAWN
+           && relative_rank(sideToMove, to_sq(m), max_rank()) > (max_rank() + 1) / 2)
+        || type_of(m) == ENPASSANT;
 }
 
 inline int Position::pawns_on_same_color_squares(Color c, Square s) const {
index 6932d3e..3aed47c 100644 (file)
@@ -278,10 +278,13 @@ namespace {
         v->extinctionPieceCount = 2;
         return v;
     }
+    // Horde chess
+    // https://en.wikipedia.org/wiki/Dunsany%27s_chess#Horde_chess
     Variant* horde_variant() {
         Variant* v = fairy_variant_base();
         v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1";
-        v->firstRankDoubleSteps = true;
+        v->doubleStepRankMin = RANK_1;
+        v->enPassantRegion = Rank3BB | Rank6BB; // exclude en passant on second rank
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = {ALL_PIECES};
         return v;
@@ -608,7 +611,6 @@ namespace {
         v->add_piece(BREAKTHROUGH_PIECE, 'p');
         v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1";
         v->promotionPieceTypes = {};
-        v->firstRankDoubleSteps = false;
         v->doubleStep = false;
         v->castling = false;
         v->stalemateValue = -VALUE_MATE;
@@ -821,6 +823,7 @@ namespace {
         v->mandatoryPawnPromotion = false;
         v->immobilityIllegal = true;
         v->doubleStepRank = RANK_3;
+        v->doubleStepRankMin = RANK_3;
         v->castling = false;
         return v;
     }
@@ -838,6 +841,7 @@ namespace {
         v->castlingQueensideFile = FILE_D;
         v->castlingRank = RANK_2;
         v->doubleStepRank = RANK_3;
+        v->doubleStepRankMin = RANK_3;
         return v;
     }
     Variant* clobber10_variant() {
index 515a44f..078063b 100644 (file)
@@ -57,7 +57,8 @@ struct Variant {
   bool endgameEval = false;
   bool doubleStep = true;
   Rank doubleStepRank = RANK_2;
-  bool firstRankDoubleSteps = false;
+  Rank doubleStepRankMin = RANK_2;
+  Bitboard enPassantRegion = AllSquares;
   bool castling = true;
   bool castlingDroppedPiece = false;
   File castlingKingsideFile = FILE_G;
index 2292130..5fd8c11 100644 (file)
 # endgameEval: enable special endgame evaluation (for very chess-like variants only) [bool] (default: false)
 # doubleStep: enable pawn double step [bool] (default: true)
 # doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2)
-# firstRankDoubleSteps: enable pawn double steps from first rank [bool] (default: false)
+# doubleStepRankMin: earlist relative rank from where pawn double steps are allowed [Rank] (default: 2)
+# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard]
 # castling: enable castling [bool] (default: true)
 # castlingDroppedPiece: enable castling with dropped rooks/kings [bool] (default: false)
 # castlingKingsideFile: destination file of king after kingside castling [File] (default: g)
@@ -294,6 +295,9 @@ startFen = 1nn1k1n1/4p3/8/8/8/8/PPPPPPPP/4K3 w - - 0 1
 [weak:chess]
 startFen = nnnnknnn/pppppppp/2p2p2/1pppppp1/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1
 
+[semitorpedo:chess]
+doubleStepRank = 3
+
 # This variant is similar to Capablanca Chess, but with two archbishops and no chancellor piece.
 [gemini:janus]
 startFen = rnbaqkabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBAQKABNR w KQkq - 0 1
index faf594f..261e565 100755 (executable)
@@ -54,7 +54,8 @@ if [[ $1 == "" || $1 == "variant" ]]; then
   expect perft.exp euroshogi startpos 5 9451149 > /dev/null
   expect perft.exp minishogi startpos 5 533203 > /dev/null
   expect perft.exp kyotoshogi startpos 5 225903 > /dev/null
-  expect perft.exp horde startpos 6 5396554 > /dev/null
+  expect perft.exp horde startpos 5 265223 > /dev/null
+  expect perft.exp horde "fen 4k3/7r/8/P7/2p1n2P/3p2P1/1P3P2/PPP1PPP1 w - - 0 1" 4 128809 > /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 652686 > /dev/null