Add EnPassant variant draft
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 22 Feb 2020 17:59:12 +0000 (18:59 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 22 Feb 2020 17:59:12 +0000 (18:59 +0100)
12 files changed:
client/src/components/Board.vue
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Antimatter/en.pug
client/src/translations/rules/Antimatter/es.pug
client/src/translations/rules/Antimatter/fr.pug
client/src/translations/rules/Enpassant/en.pug [new file with mode: 0644]
client/src/translations/rules/Enpassant/es.pug [new file with mode: 0644]
client/src/translations/rules/Enpassant/fr.pug [new file with mode: 0644]
client/src/variants/Enpassant.js [new file with mode: 0644]
server/db/populate.sql

index bd2612d..6268002 100644 (file)
@@ -343,7 +343,7 @@ export default {
       // Next condition: classList.contains(piece) fails because of marks
       while (landing.tagName == "IMG") landing = landing.parentNode;
       if (this.start.id == landing.id)
-        //one or multi clicks on same piece
+        // One or multi clicks on same piece
         return;
       // OK: process move attempt, landing is a square node
       let endSquare = getSquareFromId(landing.id);
index df3b1d2..f44b82f 100644 (file)
@@ -130,6 +130,7 @@ export const translations = {
   "Board upside down": "Board upside down",
   "Both sides of the mirror": "Both sides of the mirror",
   "Capture all of a kind": "Capture all of a kind",
+  "Capture en passant": "Capture en passant",
   "Captures reborn": "Captures reborn",
   "Change colors": "Change colors",
   "Dangerous collisions": "Dangerous collisions",
index 5588cbb..ea576c1 100644 (file)
@@ -130,6 +130,7 @@ export const translations = {
   "Board upside down": "Tablero al revés",
   "Both sides of the mirror": "Ambos lados del espejo",
   "Capture all of a kind": "Capturar todo del mismo tipo",
+  "Capture en passant": "Capturar en passant",
   "Captures reborn": "Las capturas renacen",
   "Change colors": "Cambiar colores",
   "Dangerous collisions": "Colisiones peligrosas",
index ed302ba..0f09a09 100644 (file)
@@ -130,6 +130,7 @@ export const translations = {
   "Board upside down": "Échiquier à l'envers",
   "Both sides of the mirror": "Les deux côté du miroir",
   "Capture all of a kind": "Capturez tout d'un même type",
+  "Capture en passant": "Capturer en passant",
   "Captures reborn": "Les captures renaissent",
   "Change colors": "Changer les couleurs",
   "Dangerous collisions": "Collisions dangeureuses",
index 3c8e333..b50aac1 100644 (file)
@@ -15,8 +15,6 @@ figure.diagram-container
     | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR:
   figcaption After 1.b3 c5 2.Bb2 Nc6
 
-p This detail excepted, the orthodox chess rules apply.
-
 h3 Source
 
 p
index 60cf1ec..b6adc80 100644 (file)
@@ -15,8 +15,6 @@ figure.diagram-container
     | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR:
   figcaption Después de 1.b3 c5 2.Bb2 Nc6
 
-p Excepto por este detalle, se aplican las reglas del ajedrez ortodoxo.
-
 h3 Fuente
 
 p
index 1e2dbee..7b1cdfa 100644 (file)
@@ -15,8 +15,6 @@ figure.diagram-container
     | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR:
   figcaption After 1.b3 c5 2.Bb2 Nc6
 
-p Ce détail excepté, les règles des échecs orthodoxes s'appliquent.
-
 h3 Source
 
 p
diff --git a/client/src/translations/rules/Enpassant/en.pug b/client/src/translations/rules/Enpassant/en.pug
new file mode 100644 (file)
index 0000000..7ec2cc2
--- /dev/null
@@ -0,0 +1,36 @@
+p.boxed
+  | All pieces can be captured en passant, by any piece.
+
+p More precisely:
+ul
+  li.
+    A piece making a multistep move can be captured by an opponent's piece
+    guarding a square on its way; this capture is only possible right after
+    the move (same condition as for en passant pawns captures).
+  li.
+    Even if the capturer gets captured en passant on next turn,
+    the initial captured piece does not return to the board.
+
+p This is a generalisation of the pawn en passant capture, which is still possible.
+
+h3 Special moves
+
+ul
+  li.
+    Knights become knightriders, which may make multi knight-steps in the same direction.
+    For example in the standard initial position, the knightrider on g1
+    can go to e5 or capture d7 in addition to the knight moves (see diagram below).
+  li The king can capture a piece en passant by making a knight move.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqk1nr/ppppppbp/6p1/8/8/2NP5/PPP1PPPP/R1BQKBNR h3,f3,e5,d7:
+  figcaption.
+    Possible knightrider moves after 1.d3 g6 2.Nc3 Bg7.
+    If 3.Nxd7, 3...Bxe5 e.p. is possible.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/difftaking.dir/enpassant.html") En Passant chess
+  | &nbsp;on chessvariants.com.
diff --git a/client/src/translations/rules/Enpassant/es.pug b/client/src/translations/rules/Enpassant/es.pug
new file mode 100644 (file)
index 0000000..1c50159
--- /dev/null
@@ -0,0 +1,39 @@
+p.boxed
+  | Todas las piezas se pueden capturar en passant, por cualquier pieza.
+
+p Más específicamente:
+ul
+  li.
+    Se puede capturar una pieza que mueve varias casillas
+    por una pieza opuesta que controla un cuadrado en su camino;
+    esta captura solo es posible inmediatamente después del movimiento
+    (misma condición que para capturar peones en passant).
+  li.
+    Incluso si el capturador se encuentra capturado en el siguiente movimiento,
+    la pieza inicialmente capturada no vuelve al tablero.
+
+p Es una generalización de la captura en passant de los peones, que sigue siendo posible.
+
+h3 Movimientos especiales
+
+ul
+  li.
+    Los caballos se convierten en caballeros, capaces de realizar varias
+    saltos en la misma dirección.
+    Por ejemplo en la posición inicial, el caballero en g1 puede ir a e5
+    o tomar d7 además de los movimientos de caballo.
+  li El rey puede capturar una pieza en passant haciendo un movimiento de caballo.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqk1nr/ppppppbp/6p1/8/8/2NP5/PPP1PPPP/R1BQKBNR h3,f3,e5,d7:
+  figcaption.
+    Posibles jugadas de caballero después de 1.d3 g6 2.Nc3 Bg7.
+    Si 3.Nxd7, 3...Bxe5 e.p. es posible.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/enpassant.html") variante En Passant
+  | &nbsp;en chessvariants.com.
diff --git a/client/src/translations/rules/Enpassant/fr.pug b/client/src/translations/rules/Enpassant/fr.pug
new file mode 100644 (file)
index 0000000..6dd1e14
--- /dev/null
@@ -0,0 +1,39 @@
+p.boxed
+  | Toutes les pièces peuvent être capturées en passant, par n'importe quelle pièce.
+
+p Plus précisément :
+ul
+  li.
+    Une pièce effectuant un déplacement de plusieurs cases peut être capturée
+    par une pièce adverse contrôlant une case sur son chemin ;
+    cette capture n'est possible qu'immédiatement après le coup
+    (même condition que pour les captures de pions en passant).
+  li.
+    Même si le capturant se retrouve capturé au coup suivant, la pièce
+    initialement capturée ne revient pas sur l'échiquier.
+
+p C'est une généralisation de la prise en passant des pions, qui reste possible.
+
+h3 Coups spéciaux
+
+ul
+  li.
+    Les cavaliers deviennent des chevaliers, pouvant effectuer plusieurs
+    déplacements de cavalier dans la même direction.
+    Par exemple dans la position initiale, le chevalier en g1 peut aller en e5
+    ou prendre d7 en plus des coups de cavalier.
+  li Le roi peut capturer une pièce en passant en effectuant un coup de cavalier.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqk1nr/ppppppbp/6p1/8/8/2NP5/PPP1PPPP/R1BQKBNR h3,f3,e5,d7:
+  figcaption.
+    Possibles coups de chevalier après 1.d3 g6 2.Nc3 Bg7.
+    Si 3.Nxd7, 3...Bxe5 e.p. est possible.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/enpassant.html") variante En Passant
+  | &nbsp;sur chessvariants.com.
diff --git a/client/src/variants/Enpassant.js b/client/src/variants/Enpassant.js
new file mode 100644 (file)
index 0000000..50f9738
--- /dev/null
@@ -0,0 +1,223 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export const VariantRules = class EnpassantRules extends ChessRules {
+
+  static IsGoodEnpassant(enpassant) {
+    if (enpassant != "-") {
+      const squares = enpassant.split(",");
+      for (let sq of squares) {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep)) return false;
+      }
+    }
+    return true;
+  }
+
+  getEpSquare(moveOrSquare) {
+    if (!moveOrSquare) return undefined;
+    if (typeof moveOrSquare === "string") {
+      const square = moveOrSquare;
+      if (square == "-") return undefined;
+      let res = [];
+      square.split(",").forEach(sq => {
+        res.push(V.SquareToCoords(sq));
+      });
+      return res;
+    }
+    // Argument is a move: all intermediate squares are en-passant candidates,
+    // except if the moving piece is a king.
+    const move = moveOrSquare;
+    const piece = move.appear[0].p;
+    if (piece == V.KING ||
+      (
+        Math.abs(move.end.x-move.start.x) <= 1 &&
+        Math.abs(move.end.y-move.start.y) <= 1
+      )
+    ) {
+      return undefined;
+    }
+    const delta = [move.end.x-move.start.x, move.end.y-move.start.y];
+    let step = undefined;
+    if (piece == V.KNIGHT) {
+      const divisor = Math.min(Math.abs(delta[0]), Math.abs(delta[1]));
+      step = [delta[0]/divisor || 0, delta[1]/divisor || 0];
+    } else {
+      step = [delta[0]/Math.abs(delta[0]) || 0, delta[1]/Math.abs(delta[1]) || 0];
+    }
+    let res = [];
+    for (
+      let [x,y] = [move.start.x+step[0],move.start.y+step[1]];
+      x != move.end.x || y != move.end.y;
+      x += step[0], y += step[1]
+    ) {
+      res.push({x:x, y:y});
+    }
+    // Add final square to know which piece is taken en passant:
+    res.push(move.end);
+    return res;
+  }
+
+  getEnpassantFen() {
+    const L = this.epSquares.length;
+    if (!this.epSquares[L - 1]) return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L - 1].forEach(sq => {
+      res += V.CoordsToSquare(sq) + ",";
+    });
+    return res.slice(0, -1); //remove last comma
+  }
+
+  // TODO: this getPotentialPawnMovesFrom() is mostly duplicated:
+  // it could be split in "capture", "promotion", "enpassant"...
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const shiftX = color == "w" ? -1 : 1;
+    const firstRank = color == "w" ? sizeX - 1 : 0;
+    const startRank = color == "w" ? sizeX - 2 : 1;
+    const lastRank = color == "w" ? 0 : sizeX - 1;
+    const pawnColor = this.getColor(x, y); //can be different for checkered
+
+    // NOTE: next condition is generally true (no pawn on last rank)
+    if (x + shiftX >= 0 && x + shiftX < sizeX) {
+      const finalPieces =
+        x + shiftX == lastRank
+          ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
+          : [V.PAWN];
+      // One square forward
+      if (this.board[x + shiftX][y] == V.EMPTY) {
+        for (let piece of finalPieces) {
+          moves.push(
+            this.getBasicMove([x, y], [x + shiftX, y], {
+              c: pawnColor,
+              p: piece
+            })
+          );
+        }
+        // Next condition because pawns on 1st rank can generally jump
+        if (
+          [startRank, firstRank].includes(x) &&
+          this.board[x + 2 * shiftX][y] == V.EMPTY
+        ) {
+          // Two squares jump
+          moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
+        }
+      }
+      // Captures
+      for (let shiftY of [-1, 1]) {
+        if (
+          y + shiftY >= 0 &&
+          y + shiftY < sizeY &&
+          this.board[x + shiftX][y + shiftY] != V.EMPTY &&
+          this.canTake([x, y], [x + shiftX, y + shiftY])
+        ) {
+          for (let piece of finalPieces) {
+            moves.push(
+              this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
+                c: pawnColor,
+                p: piece
+              })
+            );
+          }
+        }
+      }
+    }
+
+    // En passant
+    const Lep = this.epSquares.length;
+    const squares = this.epSquares[Lep - 1];
+    if (!!squares) {
+      const S = squares.length;
+      const taken = squares[S-1];
+      const pipoV = new PiPo({
+        x: taken.x,
+        y: taken.y,
+        p: this.getPiece(taken.x, taken.y),
+        c: this.getColor(taken.x, taken.y)
+      });
+      [...Array(S-1).keys()].forEach(i => {
+        const sq = squares[i];
+        if (sq.x == x + shiftX && Math.abs(sq.y - y) == 1) {
+          let enpassantMove = this.getBasicMove([x, y], [sq.x, sq.y]);
+          enpassantMove.vanish.push(pipoV);
+          moves.push(enpassantMove);
+        }
+      });
+    }
+
+    return moves;
+  }
+
+  // Remove the "onestep" condition: knight promote to knightrider:
+
+  getPotentialKnightMoves(sq) {
+    return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
+  }
+
+  isAttackedByKnight(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.KNIGHT,
+      V.steps[V.KNIGHT]
+    );
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x,y]);
+    // Add en-passant captures from this square:
+    const L = this.epSquares.length;
+    if (!this.epSquares[L - 1]) return moves;
+    const squares = this.epSquares[L - 1];
+    const S = squares.length;
+    // Object describing the removed opponent's piece:
+    const pipoV = new PiPo({
+      x: squares[S-1].x,
+      y: squares[S-1].y,
+      c: V.GetOppCol(this.turn),
+      p: this.getPiece(squares[S-1].x, squares[S-1].y)
+    });
+    // Check if existing non-capturing moves could also capture en passant
+    moves.forEach(m => {
+      if (
+        m.appear[0].p != V.PAWN && //special pawn case is handled elsewhere
+        m.vanish.length <= 1 &&
+        [...Array(S-1).keys()].some(i => {
+          return m.end.x == squares[i].x && m.end.y == squares[i].y;
+        })
+      ) {
+        m.vanish.push(pipoV);
+      }
+    });
+    // Special case of the king knight's movement:
+    if (this.getPiece(x, y) == V.KING) {
+      V.steps[V.KNIGHT].forEach(step => {
+        const endX = x + step[0];
+        const endY = y + step[1];
+        if (
+          V.OnBoard(endX, endY) &&
+          [...Array(S-1).keys()].some(i => {
+            return endX == squares[i].x && endY == squares[i].y;
+          })
+        ) {
+          let enpassantMove = this.getBasicMove([x, y], [endX, endY]);
+          enpassantMove.vanish.push(pipoV);
+          moves.push(enpassantMove);
+        }
+      });
+    }
+    return moves;
+  }
+
+  static get VALUES() {
+    return {
+      p: 1,
+      r: 5,
+      n: 4,
+      b: 3,
+      q: 9,
+      k: 1000
+    };
+  }
+};
index 3fea093..a9386d6 100644 (file)
@@ -12,6 +12,7 @@ insert or ignore into Variants (name,description) values
   ('Chess960', 'Standard rules'),
   ('Crazyhouse', 'Captures reborn'),
   ('Dark', 'In the shadow'),
+  ('Enpassant', 'Capture en passant'),
   ('Extinction', 'Capture all of a kind'),
   ('Grand', 'Big board'),
   ('Losers', 'Lose all pieces'),