From 9618937e4a56277f35cbd2dd22078c61bb3f47d4 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 4 Mar 2020 01:30:31 +0100
Subject: [PATCH] Add Allmate v1 and v2 (old buggish but funny rules)

---
 TODO                                          |   2 +
 client/src/translations/en.js                 |   3 +-
 client/src/translations/es.js                 |   3 +-
 client/src/translations/fr.js                 |   3 +-
 .../rules/{Allmate => Allmate1}/en.pug        |   2 +-
 .../rules/{Allmate => Allmate1}/es.pug        |   0
 .../rules/{Allmate => Allmate1}/fr.pug        |   0
 client/src/translations/rules/Allmate2/en.pug |  21 ++
 client/src/translations/rules/Allmate2/es.pug |  42 +++
 client/src/translations/rules/Allmate2/fr.pug |  21 ++
 .../src/variants/{Allmate.js => Allmate1.js}  |   2 +-
 client/src/variants/Allmate2.js               | 305 ++++++++++++++++++
 server/db/populate.sql                        |   3 +-
 13 files changed, 401 insertions(+), 6 deletions(-)
 rename client/src/translations/rules/{Allmate => Allmate1}/en.pug (94%)
 rename client/src/translations/rules/{Allmate => Allmate1}/es.pug (100%)
 rename client/src/translations/rules/{Allmate => Allmate1}/fr.pug (100%)
 create mode 100644 client/src/translations/rules/Allmate2/en.pug
 create mode 100644 client/src/translations/rules/Allmate2/es.pug
 create mode 100644 client/src/translations/rules/Allmate2/fr.pug
 rename client/src/variants/{Allmate.js => Allmate1.js} (99%)
 create mode 100644 client/src/variants/Allmate2.js

diff --git a/TODO b/TODO
index d37e708b..aed4016e 100644
--- a/TODO
+++ b/TODO
@@ -20,3 +20,5 @@ Both were asked a challenge probably, and both challenges added as different one
 # New variants
 8-pieces https://www.youtube.com/watch?v=XZ8K02Da7Ps&list=PLRyjH8DPuzTBiym6lA0r84P8N0HnTtZyN&index=6&t=0s
 https://www.chessvariants.com/rules/8-piece-chess
+Eightpieces : non-symmetric
+Sym8pieces : symmetric (960)
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 635e44ac..e7fcb24d 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -154,7 +154,8 @@ export const translations = {
   "Laws of attraction": "Laws of attraction",
   "Long jumps over pieces": "Long jumps over pieces",
   "Lose all pieces": "Lose all pieces",
-  "Mate any piece": "Mate any piece",
+  "Mate any piece (v1)": "Mate any piece (v1)",
+  "Mate any piece (v2)": "Mate any piece (v2)",
   "Mate the knight": "Mate the knight",
   "Middle battle": "Middle battle",
   "Move twice": "Move twice",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 89f2d31d..70e00e70 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -154,7 +154,8 @@ export const translations = {
   "Laws of attraction": "Las leyes de las atracciones",
   "Long jumps over pieces": "Saltos largos sobre las piezas",
   "Lose all pieces": "Perder todas las piezas",
-  "Mate any piece": "Matar cualquier pieza",
+  "Mate any piece (v1)": "Matar cualquier pieza (v1)",
+  "Mate any piece (v2)": "Matar cualquier pieza (v2)",
   "Mate the knight": "Matar el caballo",
   "Middle battle": "Batalla media",
   "Move twice": "Mover dos veces",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index cc3a7b56..3fa73d08 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -154,7 +154,8 @@ export const translations = {
   "Laws of attraction": "Les lois de l'attraction",
   "Long jumps over pieces": "Sauts longs par dessus les pièces",
   "Lose all pieces": "Perdez toutes les pièces",
-  "Mate any piece": "Matez n'importe quelle pièce",
+  "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
+  "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)",
   "Mate the knight": "Matez le cavalier",
   "Middle battle": "Bataille du milieu",
   "Move twice": "Jouer deux coups",
diff --git a/client/src/translations/rules/Allmate/en.pug b/client/src/translations/rules/Allmate1/en.pug
similarity index 94%
rename from client/src/translations/rules/Allmate/en.pug
rename to client/src/translations/rules/Allmate1/en.pug
index ad61e691..24dfe9d1 100644
--- a/client/src/translations/rules/Allmate/en.pug
+++ b/client/src/translations/rules/Allmate1/en.pug
@@ -1,5 +1,5 @@
 p.boxed
-  | If a piece is attacked and cannot move out of attack, it is captured.
+  | If a piece is checkmated (as if it was a king), it is captured.
 
 p.
   If a piece is attacked and no orthodox move can prevent its (normal) capture,
diff --git a/client/src/translations/rules/Allmate/es.pug b/client/src/translations/rules/Allmate1/es.pug
similarity index 100%
rename from client/src/translations/rules/Allmate/es.pug
rename to client/src/translations/rules/Allmate1/es.pug
diff --git a/client/src/translations/rules/Allmate/fr.pug b/client/src/translations/rules/Allmate1/fr.pug
similarity index 100%
rename from client/src/translations/rules/Allmate/fr.pug
rename to client/src/translations/rules/Allmate1/fr.pug
diff --git a/client/src/translations/rules/Allmate2/en.pug b/client/src/translations/rules/Allmate2/en.pug
new file mode 100644
index 00000000..34c44ec8
--- /dev/null
+++ b/client/src/translations/rules/Allmate2/en.pug
@@ -0,0 +1,21 @@
+p.boxed
+  | An attacked piece is taken if the capture cannot
+  | be prevented with non-capturing moves.
+
+p.
+  This is exactly the Allmate1 variant, with a weaker mating condition:
+  capturing moves to escape from checkmate are not considered.
+  (Mate-)Capturing is thus easier: on the next diagram, 1.Qe6 captures
+  the d7, e7 and f7 pawns.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:4k3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3:
+  figcaption Before 1.Qe6
+  .diagram.diag22
+    | fen:4k3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3:
+  figcaption After
+
+h3 Source
+
+p See Allmate1 variant.
diff --git a/client/src/translations/rules/Allmate2/es.pug b/client/src/translations/rules/Allmate2/es.pug
new file mode 100644
index 00000000..9eca21ad
--- /dev/null
+++ b/client/src/translations/rules/Allmate2/es.pug
@@ -0,0 +1,42 @@
+p.boxed
+  | Si una pieza es atacada y no puede escapar del ataque, se captura.
+
+p.
+  Si se ataca una pieza y ningún movimiento ortodoxo puede evitar su
+  capturado (en el sentido habitual), se considera matado y se elimina del juego.
+  Si tal movimiento ortodoxo existe, no necesita ser jugado
+  (incluso podría ser ilegal porque las capturas normales están prohibidas):
+  la pieza no se captura.
+  Por lo tanto, los reyes y otras piezas pueden permanecer en jaque.
+
+p.
+  Las piezas no pueden capturar en el sentido habitual, solo a través de
+  "mate-captura" descrito anteriormente.
+
+p El juego termina cuando un rey es "mate-capturado".
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/p1pppppp/8/8/1P6/2N5/P1PPPPPP/RNBQKBNR:
+  figcaption Después de 1.b4 b5 2.Nc3X el peón b5 es "mate-capturado"
+
+p.
+  Nota sobre la notación de las jugadas: ya que las capturas no se realizan
+  en la casilla de llegada, se agrega una marca "X" al movimiento para indicar la captura.
+  Para evitar las notaciones demasiado largas, la lista potencial de casillas
+  de captura no está escrita.
+
+h3 Nota "suicidio"
+
+p.
+  En la implementación actual, todas las piezas excepto los reyes pueden
+  "suicidarse", es decir, ir o quedarse en una casilla donde son mate.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/allmate.html") variante Allmate
+  | &nbsp;en chessvariants.com.
+
+p Inventor: Dr. Chris Taylor (1979)
diff --git a/client/src/translations/rules/Allmate2/fr.pug b/client/src/translations/rules/Allmate2/fr.pug
new file mode 100644
index 00000000..b6a3295c
--- /dev/null
+++ b/client/src/translations/rules/Allmate2/fr.pug
@@ -0,0 +1,21 @@
+p.boxed
+  | Une pièce attaquée est capturée si la prise ne peut être
+  | empêchée par des coups non capturants.
+
+p.
+  C'est exactement la variante Allmate1, avec une condition de mat plus faible :
+  les coups capturants pour échapper à un mat ne sont pas considérés.
+  Les (mat-)captures sont donc plus faciles : sur le diagramme suivant,
+  1.Qe6 prend les pions d7, e7 et f7.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:4k3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3:
+  figcaption Avant 1.Qe6
+  .diagram.diag22
+    | fen:4k3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3:
+  figcaption Après
+
+h3 Source
+
+p Voir la variante Allmate1.
diff --git a/client/src/variants/Allmate.js b/client/src/variants/Allmate1.js
similarity index 99%
rename from client/src/variants/Allmate.js
rename to client/src/variants/Allmate1.js
index 728de25f..bf8f1be9 100644
--- a/client/src/variants/Allmate.js
+++ b/client/src/variants/Allmate1.js
@@ -1,6 +1,6 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
-export const VariantRules = class AllmateRules extends ChessRules {
+export const VariantRules = class Allmate1Rules extends ChessRules {
   static get HasEnpassant() {
     return false;
   }
diff --git a/client/src/variants/Allmate2.js b/client/src/variants/Allmate2.js
new file mode 100644
index 00000000..34a0f8b0
--- /dev/null
+++ b/client/src/variants/Allmate2.js
@@ -0,0 +1,305 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export const VariantRules = class Allmate2Rules extends ChessRules {
+  static get HasEnpassant() {
+    return false;
+  }
+
+  getCheckSquares() {
+    // No notion of check
+    return [];
+  }
+
+  static GenRandInitFen() {
+    return ChessRules.GenRandInitFen().replace(/ -$/, "");
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    // Remove standard captures (without removing castling):
+    moves = moves.filter(m => {
+      return m.vanish.length == 1 || m.appear.length == 2;
+    });
+
+    // Augment moves with "mate-captures":
+    // TODO: this is coded in a highly inefficient way...
+    const color = this.turn;
+    const oppCol = V.GetOppCol(this.turn);
+    moves.forEach(m => {
+      this.play(m);
+
+      // 1) What is attacked?
+      let attacked = {};
+      for (let i=0; i<V.size.x; i++) {
+        for (let j=0; j<V.size.y; j++) {
+          if (this.getColor(i,j) == oppCol && this.isAttacked([i,j], [color]))
+            attacked[i+"_"+j] = [i,j];
+        }
+      }
+
+      // 2) Among attacked pieces, which cannot escape capture?
+      // --> without (normal-)capturing: difference with Allmate variant
+      // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
+      outerLoop: for (let i=0; i<V.size.x; i++) {
+        for (let j=0; j<V.size.y; j++) {
+          if (this.getColor(i,j) == oppCol) {
+            let oppMoves = [];
+            switch (this.getPiece(i, j)) {
+              case V.PAWN:
+                oppMoves = this.getPotentialPawnMoves([i, j]);
+                break;
+              case V.ROOK:
+                oppMoves = this.getPotentialRookMoves([i, j]);
+                break;
+              case V.KNIGHT:
+                oppMoves = this.getPotentialKnightMoves([i, j]);
+                break;
+              case V.BISHOP:
+                oppMoves = this.getPotentialBishopMoves([i, j]);
+                break;
+              case V.QUEEN:
+                oppMoves = this.getPotentialQueenMoves([i, j]);
+                break;
+              case V.KING:
+                oppMoves = this.getPotentialKingMoves([i, j]);
+                break;
+            }
+            for (let om of oppMoves) {
+              if (om.vanish.length == 2 && om.appear.length == 1)
+                // Skip captures: forbidden in this mode
+                continue;
+              V.PlayOnBoard(this.board, om);
+              Object.values(attacked).forEach(sq => {
+                const origSq = [sq[0], sq[1]];
+                if (om.start.x == sq[0] && om.start.y == sq[1])
+                  // Piece moved:
+                  sq = [om.appear[0].x, om.appear[0].y];
+                if (!this.isAttacked(sq, [color]))
+                  delete attacked[origSq[0]+"_"+origSq[1]];
+              });
+              V.UndoOnBoard(this.board, om);
+              if (Object.keys(attacked).length == 0)
+                // No need to explore more moves
+                break outerLoop;
+            }
+          }
+        }
+      }
+
+      // 3) Add mate-captures:
+      Object.values(attacked).forEach(sq => {
+        m.vanish.push(new PiPo({
+          x: sq[0],
+          y: sq[1],
+          c: oppCol,
+          p: this.getPiece(sq[0], sq[1])
+        }));
+      });
+
+      this.undo(m);
+    });
+
+    return moves;
+  }
+
+  // No "under check" conditions in castling
+  getCastleMoves([x, y]) {
+    const c = this.getColor(x, y);
+    if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
+      return []; //x isn't first rank, or king has moved (shortcut)
+
+    // Castling ?
+    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]
+    ];
+    castlingCheck: for (
+      let castleSide = 0;
+      castleSide < 2;
+      castleSide++ //large, then small
+    ) {
+      if (!this.castleFlags[c][castleSide]) continue;
+      // If this code is reached, rooks and king are on initial position
+
+      // Nothing on the path of the king ? (and no checks)
+      const finDist = finalSquares[castleSide][0] - y;
+      let step = finDist / Math.max(1, Math.abs(finDist));
+      for (let i = y; i != finalSquares[castleSide][0]; i += step) {
+        if (
+          this.board[x][i] != V.EMPTY &&
+            // NOTE: next check is enough, because of chessboard constraints
+            (this.getColor(x, i) != c ||
+              ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
+        ) {
+          continue castlingCheck;
+        }
+      }
+
+      // Nothing on the path to the rook?
+      step = castleSide == 0 ? -1 : 1;
+      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
+      }
+      const rookPos = this.INIT_COL_ROOK[c][castleSide];
+
+      // Nothing on final squares, except maybe king and castling rook?
+      for (i = 0; i < 2; i++) {
+        if (
+          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
+          this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
+          finalSquares[castleSide][i] != rookPos
+        ) {
+          continue castlingCheck;
+        }
+      }
+
+      // If this code is reached, castle is valid
+      moves.push(
+        new Move({
+          appear: [
+            new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
+            new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
+          ],
+          vanish: [
+            new PiPo({ x: x, y: y, p: V.KING, c: c }),
+            new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
+          ],
+          end:
+            Math.abs(y - rookPos) <= 2
+              ? { x: x, y: rookPos }
+              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
+        })
+      );
+    }
+
+    return moves;
+  }
+
+  // TODO: allow pieces to "commit suicide"? (Currently yes except king)
+  filterValid(moves) {
+    // Remove moves which let the king mate-captured:
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    return moves.filter(m => {
+      let res = true;
+      this.play(m);
+      if (this.underCheck(color)) {
+        res = false;
+        const attacked = this.kingPos[color];
+        // Try to find a move to escape check
+        // TODO: very inefficient method.
+        outerLoop: for (let i=0; i<V.size.x; i++) {
+          for (let j=0; j<V.size.y; j++) {
+            if (this.getColor(i,j) == color) {
+              let emoves = [];
+              // Artficial turn change to "play twice":
+              this.turn = color;
+              switch (this.getPiece(i, j)) {
+                case V.PAWN:
+                  emoves = this.getPotentialPawnMoves([i, j]);
+                  break;
+                case V.ROOK:
+                  emoves = this.getPotentialRookMoves([i, j]);
+                  break;
+                case V.KNIGHT:
+                  emoves = this.getPotentialKnightMoves([i, j]);
+                  break;
+                case V.BISHOP:
+                  emoves = this.getPotentialBishopMoves([i, j]);
+                  break;
+                case V.QUEEN:
+                  emoves = this.getPotentialQueenMoves([i, j]);
+                  break;
+                case V.KING:
+                  emoves = this.getPotentialKingMoves([i, j]);
+                  break;
+              }
+              this.turn = oppCol;
+              for (let em of emoves) {
+                V.PlayOnBoard(this.board, em);
+                let sq = attacked;
+                if (em.start.x == attacked[0] && em.start.y == attacked[1])
+                  // King moved:
+                  sq = [em.appear[0].x, em.appear[0].y];
+                if (!this.isAttacked(sq, [oppCol]))
+                  res = true;
+                V.UndoOnBoard(this.board, em);
+                if (res)
+                  // No need to explore more moves
+                  break outerLoop;
+              }
+            }
+          }
+        }
+      }
+      this.undo(m);
+      return res;
+    });
+  }
+
+  updateVariables(move) {
+    super.updateVariables(move);
+    const color = V.GetOppCol(this.turn);
+    if (move.vanish.length >= 2 && move.appear.length == 1) {
+      move.vanish.forEach(v => {
+        if (v.c == color)
+          return;
+        // Did opponent king disappeared?
+        if (v.p == V.KING)
+          this.kingPos[this.turn] = [-1, -1];
+        // Or maybe a rook?
+        else if (v.p == V.ROOK) {
+          if (v.y < this.INIT_COL_KING[v.c])
+            this.castleFlags[v.c][0] = false;
+          else
+            // v.y > this.INIT_COL_KING[v.c]
+            this.castleFlags[v.c][1] = false;
+        }
+      });
+    }
+  }
+
+  unupdateVariables(move) {
+    super.unupdateVariables(move);
+    const color = this.turn;
+    if (move.vanish.length >= 2 && move.appear.length == 1) {
+      // Did opponent king disappeared?
+      const psq = move.vanish.find(v => v.p == V.KING && v.c != color)
+      if (psq)
+        this.kingPos[psq.c] = [psq.x, psq.y];
+    }
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0)
+      // King disappeared
+      return color == "w" ? "0-1" : "1-0";
+    if (this.atLeastOneMove())
+      return "*";
+    // Kings still there, no moves:
+    return "1/2";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    // Add a capture mark (not describing what is captured...):
+    if (move.vanish.length > 1 && move.appear.length == 1) {
+      if (notation.match(/^[a-h]x/))
+        // Pawn capture: remove initial "b" in bxc4 for example
+        notation = notation.substr(1);
+      notation = notation.replace("x","") + "X";
+    }
+    return notation;
+  }
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 0989f716..b151b596 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -2,7 +2,8 @@
 
 insert or ignore into Variants (name,description) values
   ('Alice', 'Both sides of the mirror'),
-  ('Allmate', 'Mate any piece'),
+  ('Allmate1', 'Mate any piece (v1)'),
+  ('Allmate2', 'Mate any piece (v2)'),
   ('Antiking', 'Keep antiking in check'),
   ('Antimatter', 'Dangerous collisions'),
   ('Arena', 'Middle battle'),
-- 
2.44.0