From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 22 Feb 2020 10:23:15 +0000 (+0100)
Subject: Fix en-passant for Wildebeest, draft Benedict variant
X-Git-Url: https://git.auder.net/doc/html/app_dev.php/img/%7B%7B%20pkg.url%20%7D%7D?a=commitdiff_plain;h=9bd6786b863c31c3ccd0057b87cf454c90886056;p=vchess.git

Fix en-passant for Wildebeest, draft Benedict variant
---

diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index 350e0de6..656f7019 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -435,7 +435,7 @@ export const ChessRules = class ChessRules {
     if (V.HasEnpassant) {
       const epSq =
         parsedFen.enpassant != "-"
-          ? V.SquareToCoords(parsedFen.enpassant)
+          ? this.getEpSquare(parsedFen.enpassant)
           : undefined;
       this.epSquares = [epSq];
     }
@@ -722,10 +722,11 @@ export const ChessRules = class ChessRules {
     const oppCol = V.GetOppCol(c);
     let moves = [];
     let i = 0;
+    // King, then rook:
     const finalSquares = [
       [2, 3],
       [V.size.y - 2, V.size.y - 3]
-    ]; //king, then rook
+    ];
     castlingCheck: for (
       let castleSide = 0;
       castleSide < 2;
@@ -1022,9 +1023,9 @@ export const ChessRules = class ChessRules {
 
   play(move) {
     // DEBUG:
-    //    if (!this.states) this.states = [];
-    //    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
-    //    this.states.push(stateFen);
+//    if (!this.states) this.states = [];
+//    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+//    this.states.push(stateFen);
 
     if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
     if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move));
@@ -1043,9 +1044,9 @@ export const ChessRules = class ChessRules {
     this.unupdateVariables(move);
 
     // DEBUG:
-    //    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
-    //    if (stateFen != this.states[this.states.length-1]) debugger;
-    //    this.states.pop();
+//    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+//    if (stateFen != this.states[this.states.length-1]) debugger;
+//    this.states.pop();
   }
 
   ///////////////
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 2d7ccce5..64ff2b02 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -131,6 +131,7 @@ export const translations = {
   "Both sides of the mirror": "Both sides of the mirror",
   "Capture all of a kind": "Capture all of a kind",
   "Captures reborn": "Captures reborn",
+  "Change colors": "Change colors",
   "Exchange pieces positions": "Exchange pieces positions",
   "Exotic captures": "Exotic captures",
   "Explosive captures": "Explosive captures",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index f004b123..760e644c 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -131,6 +131,7 @@ export const translations = {
   "Both sides of the mirror": "Ambos lados del espejo",
   "Capture all of a kind": "Capturar todo del mismo tipo",
   "Captures reborn": "Las capturas renacen",
+  "Change colors": "Cambiar colores",
   "Exchange pieces positions": "Intercambiar las posiciones de las piezas",
   "Exotic captures": "Capturas exóticas",
   "Explosive captures": "Capturas explosivas",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index de8b1dae..b6906745 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -131,6 +131,7 @@ export const translations = {
   "Both sides of the mirror": "Les deux côté du miroir",
   "Capture all of a kind": "Capturez tout d'un même type",
   "Captures reborn": "Les captures renaissent",
+  "Change colors": "Changer les couleurs",
   "Exchange pieces positions": "Échangez les positions des pièces",
   "Exotic captures": "Captures exotiques",
   "Explosive captures": "Captures explosives",
diff --git a/client/src/translations/rules/Benedict/en.pug b/client/src/translations/rules/Benedict/en.pug
new file mode 100644
index 00000000..805dbf4c
--- /dev/null
+++ b/client/src/translations/rules/Benedict/en.pug
@@ -0,0 +1,30 @@
+p.boxed
+  | Attacked pieces change color after each turn. Goal is to change king's color.
+
+p.
+  More precisely, only pieces attacked by the moving unit are flipped.
+  They change side, until the opponent in turn can attack them.
+  There are no captures: only color changes.
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/ppPpPppp/2n5/3N4/8/8/PPPPPPPP/R1BQKBNR:
+  figcaption After 1.Nc3 Nc6 2.Nd5: pawns on c7 and e7 change color.
+
+h3 End of the game
+
+p.
+  The game ends when a king changes color.
+  There can be no stalemate since all pieces remain on the board.
+
+p.
+  In the diagram position, 2...g6?? for example would allow 3.Nf6 which
+  attacks the king and therefore ends the game.
+  However black can defend with 2...Nb4 or 2...Nf6 which flips the d5 knight,
+  or even 2...f6.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/difftaking.dir/benedict.html") Benedict chess
+  | &nbsp;on chessvariants.com.
diff --git a/client/src/translations/rules/Benedict/es.pug b/client/src/translations/rules/Benedict/es.pug
new file mode 100644
index 00000000..30a7b743
--- /dev/null
+++ b/client/src/translations/rules/Benedict/es.pug
@@ -0,0 +1,27 @@
+p.boxed
+  | Las piezas atacadas cambian de color después de cada turno.
+  | El objetivo es cambiar el color del rey.
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/ppPpPppp/2n5/3N4/8/8/PPPPPPPP/R1BQKBNR:
+  figcaption Después de 1.Nc3 Nc6 2.Nd5: los peones en c7 y e7 cambian de color.
+
+h3 Fin de la partida
+
+p.
+  El juego termina cuando un rey cambia de color.
+  No puede hay empate ya que todas las piezas permanecen en el tablero.
+
+p.
+  En la posición del diagrama, 2...g6?? por ejemplo permitiría 3.Nf6 que
+  atacar al rey y ganar (1-0).
+  Sin embargo, las negras pueden defender con 2...Nb4 o 2...Nf6 que hacen
+  cambiar de lado el caballo d5, o incluso 2...f6.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/benedict.html") variante Benedict
+  | &nbsp;en chessvariants.com.
diff --git a/client/src/translations/rules/Benedict/fr.pug b/client/src/translations/rules/Benedict/fr.pug
new file mode 100644
index 00000000..6625912b
--- /dev/null
+++ b/client/src/translations/rules/Benedict/fr.pug
@@ -0,0 +1,27 @@
+p.boxed
+  | Les pièces attaquées changent de couleur après chaque tour.
+  | Le but est de changer la couleur du roi.
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/ppPpPppp/2n5/3N4/8/8/PPPPPPPP/R1BQKBNR:
+  figcaption Après 1.Nc3 Nc6 2.Nd5 : les pions en c7 et e7 changent de couleur.
+
+h3 Fin de la partie
+
+p.
+  La partie s'achève quand un roi change de couleur.
+  Il ne peut pas y avoir de pat puisque toutes les pièces restent sur l'échiquier.
+
+p.
+  Dans la position du diagrame, 2...g6?? par exemple permettrait 3.Nf6 qui
+  attaque le roi et gagne (1-0).
+  Cependant les noirs peuvent défendre avec 2...Nb4 ou 2...Nf6 qui font
+  changer de camp le cavalier d5, ou même 2...f6.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/benedict.html") variante Benedict
+  | &nbsp;sur chessvariants.com.
diff --git a/client/src/translations/rules/Extinction/en.pug b/client/src/translations/rules/Extinction/en.pug
index 55ad4201..5ca6a7cb 100644
--- a/client/src/translations/rules/Extinction/en.pug
+++ b/client/src/translations/rules/Extinction/en.pug
@@ -24,5 +24,5 @@ figure.diagram-container
 h3 Source
 
 p
-  a(href="https://www.chessvariants.com/winning.dir/extinction.html") Extinction chess 
-  | on chessvariants.com.
+  a(href="https://www.chessvariants.com/winning.dir/extinction.html") Extinction chess
+  | &nbsp;on chessvariants.com.
diff --git a/client/src/variants/Benedict.js b/client/src/variants/Benedict.js
new file mode 100644
index 00000000..e3e55eb6
--- /dev/null
+++ b/client/src/variants/Benedict.js
@@ -0,0 +1,186 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export const VariantRules = class BenedictRules extends ChessRules {
+  static get HasEnpassant() {
+    return false;
+  }
+
+  // TODO(?): some duplicated code in 2 next functions
+  getSlideNJumpMoves([x, y], steps, oneStep) {
+    let moves = [];
+    outerLoop: for (let loop = 0; loop < steps.length; loop++) {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        moves.push(this.getBasicMove([x, y], [i, j]));
+        if (oneStep) continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      // No capture check: handled elsewhere (next method)
+    }
+    return moves;
+  }
+
+  // Find possible captures from a square
+  // follow steps from x,y until something is met.
+  findCaptures([x, y]) {
+    const color = this.getColor(x, y);
+    const piece = this.getPiece(x, y);
+    let squares = [];
+    const steps =
+      piece != V.PAWN
+        ? [V.QUEEN,V.KING].includes(piece)
+          ? V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+          : V.steps[piece]
+        : color == "w"
+          ? [
+            [-1, -1],
+            [-1, 1]
+          ]
+          : [
+            [1, -1],
+            [1, 1]
+          ];
+    const oneStep = [V.KNIGHT,V.PAWN,V.KING].includes(piece);
+    outerLoop: for (let loop = 0; loop < steps.length; loop++) {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        if (oneStep) continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      if (
+        V.OnBoard(i, j) &&
+        this.getColor(i, j) == V.GetOppCol(color)
+      ) {
+        // eat!
+        squares.push([i, j]);
+      }
+    }
+    return squares;
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    const color = this.getColor(x, y);
+    let moves = [];
+    const sizeY = V.size.y;
+    const shift = color == "w" ? -1 : 1;
+    const startRank = color == "w" ? sizeY - 2 : 1;
+    const firstRank = color == "w" ? sizeY - 1 : 0;
+    const lastRank = color == "w" ? 0 : sizeY - 1;
+
+    if (x + shift != lastRank) {
+      // Normal moves
+      if (this.board[x + shift][y] == V.EMPTY) {
+        moves.push(this.getBasicMove([x, y], [x + shift, y]));
+        if (
+          [startRank, firstRank].includes(x) &&
+          this.board[x + 2 * shift][y] == V.EMPTY
+        ) {
+          // Two squares jump
+          moves.push(this.getBasicMove([x, y], [x + 2 * shift, y]));
+        }
+      }
+    }
+    else {
+      // Promotion
+      let promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
+      promotionPieces.forEach(p => {
+        // Normal move
+        if (this.board[x + shift][y] == V.EMPTY)
+          moves.push(
+            this.getBasicMove([x, y], [x + shift, y], { c: color, p: p })
+          );
+      });
+    }
+
+    // No en passant here
+
+    return moves;
+  }
+
+  getPotentialRookMoves(sq) {
+    return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
+  }
+
+  getPotentialKnightMoves(sq) {
+    return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
+  }
+
+  getPotentialBishopMoves(sq) {
+    return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
+  }
+
+  getPotentialQueenMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+    );
+  }
+
+  getPotentialKingMoves(sq) {
+    // Initialize with normal (non-capturing) moves
+    let noCaptures = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+    return noCaptures.concat(this.getCastleMoves(sq));
+  }
+
+  // TODO: appear/vanish description of a move is too verbose for Benedict.
+  // => Would need a new "flipped" array, to be passed in Game.vue...
+  getPotentialMovesFrom([x, y]) {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    // Get all moves from x,y without captures:
+    let moves = super.getPotentialMovesFrom([x, y]);
+    // Add flips:
+    moves.forEach(m => {
+      V.PlayOnBoard(this.board, m);
+      const flipped = this.findCaptures([m.end.x, m.end.y]);
+      V.UndoOnBoard(this.board, m);
+      flipped.forEach(sq => {
+        const piece = this.getPiece(sq[0],sq[1]);
+        const pipoA = new PiPo({
+          x:sq[0],
+          y:sq[1],
+          c:color,
+          p:piece
+        });
+        const pipoV = new PiPo({
+          x:sq[0],
+          y:sq[1],
+          c:oppCol,
+          p:piece
+        });
+        m.appear.push(pipoA);
+        m.vanish.push(pipoV);
+      });
+    });
+    return moves;
+  }
+
+  // Moves cannot flip our king's color, so all are valid
+  filterValid(moves) {
+    return moves;
+  }
+
+  // No notion of check here:
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    // Did a king change color?
+    const kp = this.kingPos[color];
+    if (this.getColor(kp[0], kp[1]) != color)
+      return color == "w" ? "0-1" : "1-0";
+    return "*";
+  }
+};
diff --git a/client/src/variants/Zen.js b/client/src/variants/Zen.js
index f5bff8ff..a6067f9c 100644
--- a/client/src/variants/Zen.js
+++ b/client/src/variants/Zen.js
@@ -28,7 +28,7 @@ export const VariantRules = class ZenRules extends ChessRules {
   // if met piece is opponent and same movement (asA): eat it!
   findCaptures_aux([x, y], asA) {
     const color = this.getColor(x, y);
-    var moves = [];
+    let moves = [];
     const steps =
       asA != V.PAWN
         ? asA == V.QUEEN
@@ -43,7 +43,7 @@ export const VariantRules = class ZenRules extends ChessRules {
             [1, -1],
             [1, 1]
           ];
-    const oneStep = asA == V.KNIGHT || asA == V.PAWN; //we don't capture king
+    const oneStep = [V.KNIGHT,V.PAWN].includes(asA); //we don't capture king
     const lastRank = color == "w" ? 0 : V.size.x - 1;
     const promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
     outerLoop: for (let loop = 0; loop < steps.length; loop++) {
@@ -105,12 +105,13 @@ export const VariantRules = class ZenRules extends ChessRules {
           [startRank, firstRank].includes(x) &&
           this.board[x + 2 * shift][y] == V.EMPTY
         ) {
-          //two squares jump
+          // Two squares jump
           moves.push(this.getBasicMove([x, y], [x + 2 * shift, y]));
         }
       }
-    } //promotion
+    }
     else {
+      // Promotion
       let promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
       promotionPieces.forEach(p => {
         // Normal move
diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue
index 00202e64..015df7b3 100644
--- a/client/src/views/Rules.vue
+++ b/client/src/views/Rules.vue
@@ -24,6 +24,8 @@ main
           @click="gotoAnalyze()"
         )
           | {{ st.tr["Analyse"] }}
+  .row
+    .col-sm-12.col-md-8.col-md-offset-2.col-lg-6.col-lg-offset-3
       div(
         v-show="display=='rules'"
         v-html="content"
@@ -69,7 +71,7 @@ export default {
   },
   computed: {
     showAnalyzeBtn: function() {
-      return (this.display=='rules' && (!window.V || V.CanAnalyse));
+      return (this.display=='rules' && (!window.V || V.CanAnalyze));
     },
     content: function() {
       if (!this.gameInfo.vname) return ""; //variant not set yet
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 96f9e07e..34d0f44e 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -5,6 +5,7 @@ insert or ignore into Variants (name,description) values
   ('Antiking', 'Keep antiking in check'),
   ('Atomic', 'Explosive captures'),
   ('Baroque', 'Exotic captures'),
+  ('Benedict', 'Change colors'),
   ('Berolina', 'Pawns move diagonally'),
   ('Checkered', 'Shared pieces'),
   ('Chess960', 'Standard rules'),