Start Enpassant variant main
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 18 Jun 2026 08:49:23 +0000 (10:49 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 18 Jun 2026 08:49:23 +0000 (10:49 +0200)
js/base_rules.js
js/variants.js
variants/Checkered/class.js
variants/Empire/class.js
variants/Enpassant/class.js
variants/Enpassant/rules.html [new file with mode: 0644]
variants/Enpassant/style.css [new file with mode: 0644]
variants/Refusal/class.js

index 749b65c..3a960aa 100644 (file)
@@ -329,9 +329,9 @@ export default class ChessRules {
 
   // Enpassant part of the FEN string
   getEnpassantFen() {
-    if (!this.epSquare)
+    if (!this.epSquare_s)
       return "-";
-    return C.CoordsToSquare(this.epSquare);
+    return this.epSquare_s.map(C.CoordsToSquare).join(',');
   }
 
   static get NewGameReserves() {
@@ -424,7 +424,7 @@ export default class ChessRules {
     if (this.hasFlags)
       this.setFlags(fenParsed.flags);
     if (this.hasEnpassant)
-      this.epSquare = this.getEpSquare(fenParsed.enpassant);
+      this.epSquare_s = this.readEpSquare_s(fenParsed.enpassant);
     if (this.hasReserve && !this.isDiagram)
       this.initReserves(fenParsed.reserve);
     if (this.options["crazyhouse"])
@@ -1103,27 +1103,34 @@ export default class ChessRules {
         }
       }
     }
-    if (this.epSquare)
+    if (this.epSquare_s)
       this.enlightEnpassant();
   }
 
   // Include square of the en-passant capturing square:
   enlightEnpassant() {
+    this.epSquare_s.forEach(sq => {
+      if (this.findEpAttack(sq, this.playerColor))
+        this.enlightened[sq.x][sq.y] = true;
+    })
+  }
+
+  findEpAttack(sq, color) {
     // NOTE: shortcut, pawn has only one attack type, doesn't depend on square
     // TODO: (0, 0) is wrong, would need to place an attacker here...
     const steps = this.pieceDef('p', this.playerColor, 0, 0).attack[0].steps;
     for (let step of steps) {
-      const x = this.epSquare.x - step[0], //NOTE: epSquare.x not on edge
-            y = this.getY(this.epSquare.y - step[1]);
+      const x = sq.x - step[0], //NOTE: (ep) sq.x not on edge
+            y = this.getY(sq.y - step[1]);
       if (
         this.onBoard(x, y) &&
-        this.getColor(x, y) == this.playerColor &&
+        this.getColor(x, y) == color &&
         this.getPieceType(x, y) == "p"
       ) {
-        this.enlightened[x][this.epSquare.y] = true;
-        break;
+        return true;
       }
     }
+    return false;
   }
 
   // Apply diff this.enlightened --> oldEnlightened on board
@@ -1560,8 +1567,8 @@ export default class ChessRules {
       return [];
     const piece = this.getPieceType(x, y);
     let moves = this.getPotentialMovesOf(piece, [x, y]);
-    if (piece == "p" && this.hasEnpassant && this.epSquare)
-      Array.prototype.push.apply(moves, this.getEnpassantCaptures([x, y]));
+    if (this.hasEnpassant && !!this.epSquare_s) {
+      moves = [...moves, this.getEnpassantCaptures(piece, [x, y])];
     if (this.isKing(0, 0, piece) && this.hasCastle)
       Array.prototype.push.apply(moves, this.getCastleMoves([x, y]));
     if (!noPP)
@@ -2005,21 +2012,25 @@ export default class ChessRules {
     return mv;
   }
 
-  // En-passant square, if any
-  getEpSquare(moveOrSquare) {
-    if (typeof moveOrSquare === "string") {
-      const square = moveOrSquare;
-      if (square == "-")
-        return undefined;
-      return C.SquareToCoords(square);
-    }
-    // Argument is a move:
-    const move = moveOrSquare;
+  // En-passant square from FEN string, if any
+  readEpSquare_s(fenSquare_s) {
+    if (fenSquare_s == "-")
+      return undefined;
+    let res = fenSquare_s.split(',').map(C.SquareToCoords);
+    if (res.length == 1) //most common case
+      return res[0];
+    return res;
+  }
+
+  // Extract potential en-passant square from just played move
+  setEpSquare_s(move) {
+    this.epSquare = undefined;
     const s = move.start,
           e = move.end;
+    const gap = Math.abs(e.x - s.x);
     if (
       s.y == e.y &&
-      Math.abs(s.x - e.x) == 2 &&
+      gap >= 2 &&
       // Next conditions for variants like Atomic or Rifle, Recycle...
       (
         move.appear.length > 0 &&
@@ -2031,16 +2042,18 @@ export default class ChessRules {
         this.getPieceType(0, 0, move.vanish[0].p) == 'p'
       )
     ) {
-      return {
+      const step = (e.x - s.x) / gap;
+      this.epSquare = {
         x: (s.x + e.x) / 2,
         y: s.y
       };
     }
-    return undefined; //default
   }
 
   // Special case of en-passant captures: treated separately
-  getEnpassantCaptures([x, y]) {
+  getEnpassantCaptures(piece, [x, y]) {
+    if (piece != 'p')
+      return [];
     const color = this.getColor(x, y);
     const shiftX = (color == 'w' ? -1 : 1);
     const oppCols = this.getOppCols(color);
@@ -2382,7 +2395,7 @@ export default class ChessRules {
   play(move) {
     this.prePlay(move);
     if (this.hasEnpassant)
-      this.epSquare = this.getEpSquare(move);
+      this.setEpSquare_s(move);
     this.playOnBoard(move);
     this.postPlay(move);
   }
index f135626..7315dac 100644 (file)
@@ -51,9 +51,9 @@ const variants = [
   {name: 'Eightpieces', desc: 'Each piece is unique', disp: '8 Pieces'},
   {name: 'Emergo', desc: 'Stacking Checkers variant'},
   {name: 'Empire', desc: 'Empire versus Kingdom'},
-//  {name: 'Enpassant', desc: 'Capture en passant', disp: 'En-passant'},
-//  {name: 'Evolution', desc: 'Faster development'},
-//  {name: 'Extinction', desc: 'Capture all of a kind'},
+  {name: 'Enpassant', desc: 'Capture en passant', disp: 'En-passant'},
+  {name: 'Evolution', desc: 'Faster development'},
+  {name: 'Extinction', desc: 'Capture all of a kind'},
 //  {name: 'Fanorona', desc: 'Malagasy Draughts'},
 //  {name: 'Football', desc: 'Score a goal'},
 //  {name: 'Forward', desc: 'Moving forward'},
index 793ed7e..3e6237d 100644 (file)
@@ -191,16 +191,15 @@ export default class CheckeredRules extends ChessRules {
     }
   }
 
-  getEpSquare(moveOrSquare) {
-    // At stage 2, all pawns can be captured en-passant
+  setEpSquare_s(move) {
     if (
+      // At stage 2, all pawns can be captured en-passant
       this.stage == 2 ||
-      typeof moveOrSquare !== "object" ||
-      (moveOrSquare.appear.length > 0 && moveOrSquare.appear[0].c != 'c')
-    )
-      return super.getEpSquare(moveOrSquare);
-    // Checkered or switch move: no en-passant
-    return undefined;
+      // Checkered or switch move ==> no en-passant:
+      (move.appear.length > 0 && move.appear[0].c != 'c')
+    ) {
+      super.setEpSquare_s(move);
+    }
   }
 
   getCmove(move) {
index 64b2156..6dd3dc3 100644 (file)
@@ -21,7 +21,8 @@ export default class EmpireRules extends ChessRules {
       'K': 'K'
     };
 
-    const bf = super.genRandInitBaseFen();
+    let bf = super.genRandInitBaseFen();
+    bf.o.flags = "88" + bf.o.flags.substr(2);
     return {
       fen: bf.fen.substr(0, 24) + "PPPSSPPP/8/" +
            bf.fen.substr(35, 8).split('').map(p => piecesMap[p]).join('') +
index d6eaef5..dd2dfd6 100644 (file)
@@ -1,33 +1,32 @@
-import { ChessRules, PiPo, Move } from "@/js/base_rules";
+import ChessRules from "/js/base_rules.js";
 
-export class EnpassantRules extends ChessRules {
+export default class EnpassantRules extends ChessRules {
 
-  static IsGoodEnpassant(enpassant) {
-    if (enpassant != "-") {
-      const squares = enpassant.split(",");
-      if (squares.length > 2) return false;
-      for (let sq of squares) {
-        const ep = V.SquareToCoords(sq);
-        if (isNaN(ep.x) || !V.OnBoard(ep)) return false;
-      }
-    }
-    return true;
+  static get Options() {
+    return {
+      select: C.Options.select,
+      input: C.Options.input,
+      styles: C.Options.styles.filter(s => s != "zen")
+    };
   }
 
-  getPpath(b) {
-    return (b[1] == V.KNIGHT ? "Enpassant/" : "") + b;
+  pieceDef(piece, color, x, y) {
+    let res = super.pieceDef(piece, color, x, y);
+    if (piece == 'n')
+      res.both[0].range = 8; //"infinite"
+    return res;
   }
 
-  getEpSquare(moveOrSquare) {
-    if (!moveOrSquare) return undefined;
-    if (typeof moveOrSquare === "string") {
-      const square = moveOrSquare;
-      if (square == "-") return undefined;
+  // TODO: comma separated (convention ?! yes..)
+  readEpSquare_s(squares) {
+      if (squares == "-")
+        return undefined;
       // Expand init + dest squares into a full path:
-      const init = V.SquareToCoords(square.substr(0, 2));
+      const init = C.SquareToCoords(square.substr(0, 2));
       let newPath = [init];
-      if (square.length == 2) return newPath;
-      const dest = V.SquareToCoords(square.substr(2));
+      if (square.length == 2)
+        return newPath;
+      const dest = C.SquareToCoords(square.substr(2));
       const delta = ['x', 'y'].map(i => Math.abs(dest[i] - init[i]));
       // Check if it's a knight(rider) movement:
       let step = [0, 0];
@@ -36,7 +35,8 @@ export class EnpassantRules extends ChessRules {
         const minShift = Math.min(delta[0], delta[1]);
         step[0] = (dest.x - init.x) / minShift;
         step[1] = (dest.y - init.y) / minShift;
-      } else {
+      }
+      else {
         // "Sliders"
         step = ['x', 'y'].map((i, idx) => {
           return (dest[i] - init[i]) / delta[idx] || 0
@@ -51,7 +51,9 @@ export class EnpassantRules extends ChessRules {
       }
       newPath.push(dest);
       return newPath;
-    }
+  }
+
+  setEpSquare_s(move) {
     // Argument is a move: all intermediate squares are en-passant candidates,
     // except if the moving piece is a king.
     const move = moveOrSquare;
@@ -168,11 +170,6 @@ export class EnpassantRules extends ChessRules {
     return moves;
   }
 
-  // Remove the "onestep" condition: knight promote to knightrider:
-  getPotentialKnightMoves(sq) {
-    return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
-  }
-
   filterValid(moves) {
     const filteredMoves = super.filterValid(moves);
     // If at least one full move made, everything is allowed:
@@ -182,28 +179,4 @@ export class EnpassantRules extends ChessRules {
     return filteredMoves.filter(m => m.vanish.length == 1);
   }
 
-  isAttackedByKnight(sq, color) {
-    return this.isAttackedBySlideNJump(
-      sq,
-      color,
-      V.KNIGHT,
-      V.steps[V.KNIGHT]
-    );
-  }
-
-  static get SEARCH_DEPTH() {
-    return 2;
-  }
-
-  static get VALUES() {
-    return {
-      p: 1,
-      r: 5,
-      n: 4,
-      b: 3,
-      q: 9,
-      k: 1000
-    };
-  }
-
 };
diff --git a/variants/Enpassant/rules.html b/variants/Enpassant/rules.html
new file mode 100644 (file)
index 0000000..e3ca56e
--- /dev/null
@@ -0,0 +1,17 @@
+<p>
+  All pieces can be captured "en passant" when they make more than one step.
+  So, for more fun, knights can make multi-steps: they turn into knightriders.
+</p>
+
+<p>
+  For example from initial position,
+  1.e4 d5 2.Qh5 Bxg4 would take the queen en passant.
+</p>
+
+<p class="author">Andy Kurnia (1998).</p>
+
+<p>
+  See also 
+  <a href="https://www.chessvariants.com/difftaking.dir/enpassant.html">En Passant chess</a>
+  on chessvariants.com.
+</p>
diff --git a/variants/Enpassant/style.css b/variants/Enpassant/style.css
new file mode 100644 (file)
index 0000000..95e35b2
--- /dev/null
@@ -0,0 +1 @@
+@import url("/css/base_pieces.css");
index e57a360..0edf0da 100644 (file)
@@ -83,10 +83,9 @@ export default class RefusalRules extends ChessRules {
     return super.getPotentialMovesFrom([x, y]);
   }
 
-  getEpSquare(move) {
+  setEpSquare_s(move) {
     if (!move.refusal)
-      return super.getEpSquare(move);
-    return null;
+      super.setEpSquare_s(move);
   }
 
   filterValid(moves) {