From 35ff9d1b79c050a7b8304bd725221aaee57f7209 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 2 May 2020 14:27:48 +0200
Subject: [PATCH] Add Rampage Chess

---
 client/src/translations/en.js                |  1 +
 client/src/translations/es.js                |  1 +
 client/src/translations/fr.js                |  1 +
 client/src/translations/rules/Rampage/en.pug | 29 +++++++
 client/src/translations/rules/Rampage/es.pug | 30 +++++++
 client/src/translations/rules/Rampage/fr.pug | 30 +++++++
 client/src/variants/Rampage.js               | 83 ++++++++++++++++++--
 server/db/populate.sql                       |  1 +
 8 files changed, 170 insertions(+), 6 deletions(-)
 create mode 100644 client/src/translations/rules/Rampage/en.pug
 create mode 100644 client/src/translations/rules/Rampage/es.pug
 create mode 100644 client/src/translations/rules/Rampage/fr.pug

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index a72cf612..8b7daa72 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -210,6 +210,7 @@ export const translations = {
   "Mongolian Horde": "Mongolian Horde",
   "Move like a knight (v1)": "Move like a knight (v1)",
   "Move like a knight (v2)": "Move like a knight (v2)",
+  "Move under cover": "Move under cover",
   "Neverending rows": "Neverending rows",
   "No-check mode": "No-check mode",
   "Non-conformism and utopia": "Non-conformism and utopia",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 10e5422b..4b5c2759 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -210,6 +210,7 @@ export const translations = {
   "Mongolian Horde": "Horda mongol",
   "Move like a knight (v1)": "Moverse como un caballo (v1)",
   "Move like a knight (v2)": "Moverse como un caballo (v2)",
+  "Move under cover": "Ir bajo cubierta",
   "Neverending rows": "Filas interminables",
   "No-check mode": "Modo sin jaque",
   "Non-conformism and utopia": "No-conformismo y utopía",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index c4de1448..8684b286 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -210,6 +210,7 @@ export const translations = {
   "Mongolian Horde": "Horde mongole",
   "Move like a knight (v1)": "Bouger comme un cavalier (v1)",
   "Move like a knight (v2)": "Bouger comme un cavalier (v2)",
+  "Move under cover": "Avancez à couvert",
   "Neverending rows": "Rangées sans fin",
   "No-check mode": "Mode sans échec",
   "Non-conformism and utopia": "Non-conformisme et utopie",
diff --git a/client/src/translations/rules/Rampage/en.pug b/client/src/translations/rules/Rampage/en.pug
new file mode 100644
index 00000000..a7ddca75
--- /dev/null
+++ b/client/src/translations/rules/Rampage/en.pug
@@ -0,0 +1,29 @@
+p.boxed
+  | Units may move to any square that is guarded by more friendly units
+  | than enemy units.
+
+p.
+  In addition to their usual abilities, pieces may jump to controlled squares,
+  that is to say squares attacked by more friendly pieces than enemy pieces.
+  The king in check may only move as in orthodox chess, but "rampage" moves
+  of other pieces are allowed to cover the check.
+
+p.
+  Attacks on the same row, file or diagonal are cumulative. So,
+  on the next diagram the marked square (e5) is guarded twice by each side:
+  "rampage" moves to that square are impossible.
+
+figure.diagram-container
+  .diagram
+    | fen:k7/p7/2n1r3/8/8/8/4R2P/4Q2K e5:
+  figcaption No repositioning moves to e5.
+
+h3 Source
+
+p
+  | This variant is 
+  a(href="https://www.chessvariants.com/play/erf/RampageC.html")
+    | mentioned here
+  | .
+
+p Inventor: Bruce R. Trose (1976)
diff --git a/client/src/translations/rules/Rampage/es.pug b/client/src/translations/rules/Rampage/es.pug
new file mode 100644
index 00000000..4fedc77f
--- /dev/null
+++ b/client/src/translations/rules/Rampage/es.pug
@@ -0,0 +1,30 @@
+p.boxed
+  | Las piezas pueden moverse en cualquier espacio defendido por más de
+  | piezas amigas que piezas enemigas.
+
+p.
+  Además de sus capacidades habituales, las piezas pueden saltar a
+  espacios controlados, es decir, espacios atacados por más piezas del
+  mismo lado que las piezas opuestas.
+  El rey en jaque solo puede hacer movimientos normales, pero las jugadas
+  "rampage" de las otras piezas pueden contrarrestar el jaque.
+
+p.
+  Los ataques a la misma columna, fila o diagonal son acumulativos. Entonces
+  en el siguiente diagrama, la casilla marcada (e5) es defendida dos veces por
+  cada lado: los movimientos "rampage" hacia esta casilla son imposibles.
+
+figure.diagram-container
+  .diagram
+    | fen:k7/p7/2n1r3/8/8/8/4R2P/4Q2K e5:
+  figcaption No movimientos de reposicionamiento a e5.
+
+h3 Fuente
+
+p
+  | Esta variante es 
+  a(href="https://www.chessvariants.com/play/erf/RampageC.html")
+    | mencionada aquí
+  | .
+
+p Inventor: Bruce R. Trose (1976)
diff --git a/client/src/translations/rules/Rampage/fr.pug b/client/src/translations/rules/Rampage/fr.pug
new file mode 100644
index 00000000..d0a57f5f
--- /dev/null
+++ b/client/src/translations/rules/Rampage/fr.pug
@@ -0,0 +1,30 @@
+p.boxed
+  | Les pièces peuvent se déplacer sur toute case défendue par plus de
+  | pièces amies que de pièces ennemies.
+
+p.
+  En plus de leurs capacités habituelles, les pièces peuvent sauter vers des
+  cases controlées, c'est-à-dire des cases attaquées par plus de pièces du
+  même camp que de pièces adverses.
+  Le roi en échec ne peut effectuer que des coups normaux, mais les coups
+  "rampage" des autres pièces sont autorisés pour parer l'échec.
+
+p.
+  Les attaques sur la même colonne, rangée ou diagonale se cumulent. Ainsi,
+  sur le diagramme suivant la case marquée (e5) est défendue deux fois par
+  chaque camp : les coups "rampage" vers cette case sont impossibles.
+
+figure.diagram-container
+  .diagram
+    | fen:k7/p7/2n1r3/8/8/8/4R2P/4Q2K e5:
+  figcaption Pas de coups de repositionnement vers e5.
+
+h3 Source
+
+p
+  | Cette variante est 
+  a(href="https://www.chessvariants.com/play/erf/RampageC.html")
+    | mentionnée ici
+  | .
+
+p Inventeur : Bruce R. Trose (1976)
diff --git a/client/src/variants/Rampage.js b/client/src/variants/Rampage.js
index 31d4f359..2fe96db6 100644
--- a/client/src/variants/Rampage.js
+++ b/client/src/variants/Rampage.js
@@ -1,10 +1,81 @@
 import { ChessRules } from "@/base_rules";
 
-// Plan : garder intact isAttacked,
-// ajouter "guarded" qui somme les attaques blanches et soustrait les attaques noires sur une case
-// (regarder toutes directions, OK)
-// --> boulot à faire sur chaque case vide, après chaque getPotentialMoves()
-// --> sauf si le roi est en échec (avant de jouer).
-
 export class RampageRules extends ChessRules {
+  // Sum white pieces attacking a square, and remove black pieces count.
+  sumAttacks([x, y]) {
+    const getSign = (color) => {
+      return (color == 'w' ? 1 : -1);
+    };
+    let res = 0;
+    // Knights:
+    V.steps[V.KNIGHT].forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KNIGHT)
+        res += getSign(this.getColor(i, j));
+    });
+    // Kings:
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KING)
+        res += getSign(this.getColor(i, j));
+    });
+    // Pawns:
+    for (let c of ['w', 'b']) {
+      for (let shift of [-1, 1]) {
+        const sign = getSign(c);
+        const [i, j] = [x + sign, y + shift];
+        if (
+          V.OnBoard(i, j) &&
+          this.getPiece(i, j) == V.PAWN &&
+          this.getColor(i, j) == c
+        ) {
+          res += sign;
+        }
+      }
+    }
+    // Other pieces (sliders):
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      let [i, j] = [x + s[0], y + s[1]];
+      let compatible = [V.QUEEN];
+      compatible.push(s[0] == 0 || s[1] == 0 ? V.ROOK : V.BISHOP);
+      let firstCol = undefined;
+      while (V.OnBoard(i, j)) {
+        if (this.board[i][j] != V.EMPTY) {
+          if (!(compatible.includes(this.getPiece(i, j)))) break;
+          const colIJ = this.getColor(i, j);
+          if (!firstCol) firstCol = colIJ;
+          if (colIJ == firstCol) res += getSign(colIJ);
+          else break;
+        }
+        i += s[0];
+        j += s[1];
+      }
+    });
+    return res;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    const color = this.turn;
+    if (this.getPiece(x, y) == V.KING && this.underCheck(color))
+      // The king under check can only move as usual
+      return moves;
+    // Remember current final squares to not add moves twice:
+    const destinations = {};
+    moves.forEach(m => destinations[m.end.x + "_" + m.end.y] = true);
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (this.board[i][j] == V.EMPTY && !destinations[i + "_" + j]) {
+          const sa = this.sumAttacks([i, j]);
+          if ((color == 'w' && sa > 0) || (color == 'b' && sa < 0))
+            moves.push(this.getBasicMove([x, y], [i, j]));
+        }
+      }
+    }
+    return moves;
+  }
+
+  static get SEARCH_DEPTH() {
+    return 1;
+  }
 };
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 49419674..97f78bc4 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -70,6 +70,7 @@ insert or ignore into Variants (name, description) values
   ('Parachute', 'Landing on the board'),
   ('Perfect', 'Powerful pieces'),
   ('Racingkings', 'Kings cross the 8x8 board'),
+  ('Rampage', 'Move under cover'),
   ('Rifle', 'Shoot pieces'),
   ('Recycle', 'Reuse pieces'),
   ('Rococo', 'Capture on the edge'),
-- 
2.44.0