Support color specific enPassantRegion (#900)
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 17 Aug 2025 11:47:36 +0000 (13:47 +0200)
committerGitHub <noreply@github.com>
Sun, 17 Aug 2025 11:47:36 +0000 (13:47 +0200)
.github/copilot-instructions.md
src/parser.cpp
src/position.cpp
src/variant.cpp
src/variant.h
src/variants.ini

index 1ac766b..611774a 100644 (file)
@@ -152,7 +152,7 @@ tests/                # Test scripts and data
 - [Chess Variants on Chess.com](https://www.chess.com/variants)
 
 ### Development Best Practices
-* Make sure to only stage and commit changes that are intended to be part of the task.
+* Make sure to only stage and commit changes that were changed as part of the task, do not simply add all changes.
 * Keep changes minimal and focused on the task at hand.
 * After applying changes make sure that all places related to the task have been identified.
 * Stay consistent with the existing code style and conventions.
index 9f2f848..cbd2404 100644 (file)
@@ -426,7 +426,10 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
     parse_attribute("doubleStepRegionBlack", v->doubleStepRegion[BLACK]);
     parse_attribute("tripleStepRegionWhite", v->tripleStepRegion[WHITE]);
     parse_attribute("tripleStepRegionBlack", v->tripleStepRegion[BLACK]);
-    parse_attribute("enPassantRegion", v->enPassantRegion);
+    parse_attribute("enPassantRegion", v->enPassantRegion[WHITE]);
+    parse_attribute("enPassantRegion", v->enPassantRegion[BLACK]);
+    parse_attribute("enPassantRegionWhite", v->enPassantRegion[WHITE]);
+    parse_attribute("enPassantRegionBlack", v->enPassantRegion[BLACK]);
     parse_attribute("enPassantTypes", v->enPassantTypes[WHITE], v->pieceToChar);
     parse_attribute("enPassantTypes", v->enPassantTypes[BLACK], v->pieceToChar);
     parse_attribute("enPassantTypesWhite", v->enPassantTypes[WHITE], v->pieceToChar);
index c55ab3f..2488a71 100644 (file)
@@ -447,7 +447,7 @@ Position& Position::set(const Variant* v, const string& fenStr, bool isChess960,
               // a) side to move have a pawn threatening epSquare
               // b) there is an enemy pawn one or two (for triple steps) squares in front of epSquare
               // c) there is no (non-wall) piece on epSquare or behind epSquare
-              if (   (var->enPassantRegion & epSquare)
+              if (   (var->enPassantRegion[sideToMove] & epSquare)
                   && (   !var->fastAttacks
                       || (var->enPassantTypes[sideToMove] & ~piece_set(PAWN))
                       || (   pawn_attacks_bb(~sideToMove, epSquare) & pieces(sideToMove, PAWN)
@@ -1607,7 +1607,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
           st->captureSquare = capsq;
 
           assert(st->epSquares & to);
-          assert(var->enPassantRegion & to);
+          assert(var->enPassantRegion[us] & to);
           assert(piece_on(to) == NO_PIECE);
       }
 
@@ -1844,7 +1844,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
           && (   std::abs(int(to) - int(from)) == 2 * NORTH
               || std::abs(int(to) - int(from)) == 3 * NORTH))
       {
-          if (   (var->enPassantRegion & (to - pawn_push(us)))
+          if (   (var->enPassantRegion[them] & (to - pawn_push(us)))
               && ((pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN))
               && !(walling() && gating_square(m) == to - pawn_push(us)))
           {
@@ -1852,7 +1852,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
               k ^= Zobrist::enpassant[file_of(to)];
           }
           if (   std::abs(int(to) - int(from)) == 3 * NORTH
-              && (var->enPassantRegion & (to - 2 * pawn_push(us)))
+              && (var->enPassantRegion[them] & (to - 2 * pawn_push(us)))
               && ((pawn_attacks_bb(us, to - 2 * pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN))
               && !(walling() && gating_square(m) == to - 2 * pawn_push(us)))
           {
@@ -1924,7 +1924,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
            && ((PseudoMoves[1][us][type_of(pc)][from] & ~PseudoMoves[0][us][type_of(pc)][from]) & to))
   {
       assert(type_of(pc) != PAWN);
-      st->epSquares = between_bb(from, to) & var->enPassantRegion;
+      st->epSquares = between_bb(from, to) & var->enPassantRegion[them];
       for (Bitboard b = st->epSquares; b; )
           k ^= Zobrist::enpassant[file_of(pop_lsb(b))];
   }
@@ -2229,7 +2229,7 @@ void Position::undo_move(Move m) {
               capsq = st->captureSquare;
 
               assert(st->previous->epSquares & to);
-              assert(var->enPassantRegion & to);
+              assert(var->enPassantRegion[sideToMove] & to);
               assert(piece_on(capsq) == NO_PIECE);
           }
 
@@ -3237,7 +3237,7 @@ bool Position::pos_is_ok() const {
   if (   (sideToMove != WHITE && sideToMove != BLACK)
       || (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_squares() & ~var->enPassantRegion))
+      || (ep_squares() & ~(var->enPassantRegion[WHITE] | var->enPassantRegion[BLACK])))
       assert(0 && "pos_is_ok: Default");
 
   if (Fast)
index c8aa137..ad97b97 100644 (file)
@@ -471,7 +471,8 @@ namespace {
         Variant* v = chess_variant_base()->init();
         v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1";
         v->doubleStepRegion[WHITE] |= Rank1BB;
-        v->enPassantRegion = Rank3BB | Rank6BB; // exclude en passant on second rank
+        v->enPassantRegion[WHITE] = Rank6BB; // exclude en passant on second rank
+        v->enPassantRegion[BLACK] = Rank3BB; // exclude en passant on second rank
         v->extinctionValue = -VALUE_MATE;
         v->extinctionPieceTypes = piece_set(ALL_PIECES);
         return v;
@@ -1073,7 +1074,8 @@ namespace {
         v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECE_1);
         v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | DRAGON | ARCHBISHOP | CUSTOM_PIECE_2 | CUSTOM_PIECE_3;
         v->promotionLimit[COMMONER] = 2;
-        v->enPassantRegion = 0;
+        v->enPassantRegion[WHITE] = 0;
+        v->enPassantRegion[BLACK] = 0;
         v->extinctionPieceCount = 0;
         v->extinctionPseudoRoyal = true;
         v->dupleCheck = true;
index c13305d..0e6d44c 100644 (file)
@@ -71,7 +71,7 @@ struct Variant {
   bool doubleStep = true;
   Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB};
   Bitboard tripleStepRegion[COLOR_NB] = {};
-  Bitboard enPassantRegion = AllSquares;
+  Bitboard enPassantRegion[COLOR_NB] = {AllSquares, AllSquares};
   PieceSet enPassantTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)};
   bool castling = true;
   bool castlingDroppedPiece = false;
index 24dc66d..f2e06ba 100644 (file)
 # tripleStepRegionWhite: region where pawn triple steps are allowed for white [Bitboard] (default: -)
 # tripleStepRegionBlack: region where pawn triple steps are allowed for black [Bitboard] (default: -)
 # enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] (default: AllSquares)
+# enPassantRegionWhite: define region (target squares) where en passant is allowed for white [Bitboard] (default: AllSquares)
+# enPassantRegionBlack: define region (target squares) where en passant is allowed for black [Bitboard] (default: AllSquares)
 # enPassantTypes: define pieces able to capture en passant [PieceSet] (default: p)
 # enPassantTypesWhite: define white pieces able to capture en passant [PieceSet] (default: p)
 # enPassantTypesBlack: define black pieces able to capture en passant [PieceSet] (default: p)