From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 5 May 2020 21:35:47 +0000 (+0200)
Subject: Add Freecapture + advance on Koopa Chess
X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/images/scripts/doc/pieces/cn.svg?a=commitdiff_plain;h=e88d69a8735e334b681cdad380af53e2ff574d1f;p=vchess.git

Add Freecapture + advance on Koopa Chess
---

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 59d409b9..0d1f0b94 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -171,6 +171,7 @@ export const translations = {
   "Both sides of the mirror": "Both sides of the mirror",
   "Burmese chess": "Burmese chess",
   "Capture all of a kind": "Capture all of a kind",
+  "Capture both colors": "Capture both colors",
   "Capture en passant": "Capture en passant",
   "Capture on the edge": "Capture on the edge",
   "Capture powers": "Capture powers",
@@ -243,6 +244,7 @@ export const translations = {
   "Shoot pieces": "Shoot pieces",
   "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
+  "Stun & kick pieces": "Stun & kick pieces",
   "Thai Chess": "Thai Chess",
   "The colorbound clobberers": "The colorbound clobberers",
   "The end of the world": "The end of the world",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index e702b325..d7fef9b1 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -171,6 +171,7 @@ export const translations = {
   "Both sides of the mirror": "Ambos lados del espejo",
   "Burmese chess": "Ajedrez birmano",
   "Capture all of a kind": "Capturar todo del mismo tipo",
+  "Capture both colors": "Captura ambos colores",
   "Capture en passant": "Capturar en passant",
   "Capture on the edge": "Capturar en el borde",
   "Capture powers": "Capturar los poderes",
@@ -243,6 +244,7 @@ export const translations = {
   "Shoot pieces": "Tirar de las piezas",
   "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
+  "Stun & kick pieces": "Aturdir & patear piezas",
   "Thai Chess": "Ajedrez tailandés",
   "The colorbound clobberers": "Los batidores unicolor",
   "The end of the world": "El fin del mundo",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 980742a3..d66ba0b4 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -171,6 +171,7 @@ export const translations = {
   "Both sides of the mirror": "Les deux côté du miroir",
   "Burmese chess": "Échecs birmans",
   "Capture all of a kind": "Capturez tout d'un même type",
+  "Capture both colors": "Capturer les deux couleurs",
   "Capture en passant": "Capturer en passant",
   "Capture on the edge": "Capturer sur le bord",
   "Capture powers": "Capturer les pouvoirs",
@@ -243,6 +244,7 @@ export const translations = {
   "Shoot pieces": "Tirez sur les pièces",
   "Squares disappear": "Les cases disparaissent",
   "Standard rules": "Règles usuelles",
+  "Stun & kick pieces": "Étourdissez & frappez les pièces",
   "Thai Chess": "Échecs thai",
   "The colorbound clobberers": "Les tabasseurs unicolores",
   "The end of the world": "La fin du monde",
diff --git a/client/src/translations/rules/Freecapture/en.pug b/client/src/translations/rules/Freecapture/en.pug
new file mode 100644
index 00000000..ad38fb0a
--- /dev/null
+++ b/client/src/translations/rules/Freecapture/en.pug
@@ -0,0 +1,24 @@
+p.boxed.
+  Pieces can capture both colors.
+
+p.
+  This can be useful to open lines for attack, and it also eliminate
+  back rank mate threats for example.
+  On the diagram 1.Qh5 is possible because 1...Ra1+ 2.Kxh2.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r5k1/5ppp/8/6N1/8/8/6PP/3Q3K:
+  .diagram.diag22
+    | fen:r5k1/5ppp/8/6NQ/8/8/6PP/7K:
+  figcaption Before and after 1.Qh5
+
+h3 Source
+
+p
+  |This variant is mentioned in 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
+
+p Inventor: L. Tabi (1971)
diff --git a/client/src/translations/rules/Freecapture/es.pug b/client/src/translations/rules/Freecapture/es.pug
new file mode 100644
index 00000000..a236b2f1
--- /dev/null
+++ b/client/src/translations/rules/Freecapture/es.pug
@@ -0,0 +1,24 @@
+p.boxed.
+  Las piezas pueden capturar ambos colores.
+
+p.
+  Esto puede ser útil para abrir líneas de ataque, por ejemplo, y que
+  también elimina las amenazas de mate del corredor, por ejemplo.
+  En el diagrama 1.Qh5 es posible porque 1...Ra1+ 2.Kxh2.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r5k1/5ppp/8/6N1/8/8/6PP/3Q3K:
+  .diagram.diag22
+    | fen:r5k1/5ppp/8/6NQ/8/8/6PP/7K:
+  figcaption Antes y después 1.Qh5
+
+h3 Fuente
+
+p
+  | Esta variante se menciona en 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
+
+p Inventor: L. Tabi (1971)
diff --git a/client/src/translations/rules/Freecapture/fr.pug b/client/src/translations/rules/Freecapture/fr.pug
new file mode 100644
index 00000000..1d8622dd
--- /dev/null
+++ b/client/src/translations/rules/Freecapture/fr.pug
@@ -0,0 +1,24 @@
+p.boxed.
+  Les pièces peuvent capturer les deux couleurs.
+
+p.
+  Cela peut être utile pour ouvrir des lignes d'attaque par exemple, et ça
+  élimine aussi les menaces de mat du couloir par exemple.
+  Sur le diagramme 1.Qh5 est possible car 1...Ra1+ 2.Kxh2.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r5k1/5ppp/8/6N1/8/8/6PP/3Q3K:
+  .diagram.diag22
+    | fen:r5k1/5ppp/8/6NQ/8/8/6PP/7K:
+  figcaption Before and after 1.Qh5
+
+h3 Source
+
+p
+  | Cette variante est mentionnée dans 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
+
+p Inventeur : L. Tabi (1971)
diff --git a/client/src/variants/Freecapture.js b/client/src/variants/Freecapture.js
new file mode 100644
index 00000000..19098b6d
--- /dev/null
+++ b/client/src/variants/Freecapture.js
@@ -0,0 +1,12 @@
+import { ChessRules } from "@/base_rules";
+
+export class FreecaptureRules extends ChessRules {
+  canTake() {
+    // Can capture both colors:
+    return true;
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+};
diff --git a/client/src/variants/Koopa.js b/client/src/variants/Koopa.js
new file mode 100644
index 00000000..9ca83848
--- /dev/null
+++ b/client/src/variants/Koopa.js
@@ -0,0 +1,139 @@
+import { ChessRulesi, PiPo } from "@/base_rules";
+
+export class KoopaRules extends ChessRules {
+  static get HasEnpassant() {
+    return false;
+  }
+
+  // Between stun time and stun + 1 move
+  static get STUNNED_1() {
+    return ['s', 'u', 'o', 'c', 't', 'l'];
+  }
+
+  // Between stun + 1 move and stun + 2 moves
+  static get STUNNED_2() {
+    return ['v', 'x', 'a', 'd', 'w', 'm'];
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat(V.STUNNED_1).concat(V.STUNNED_2);
+  }
+
+  getNormalizedStep(step) {
+    const [deltaX, deltaY] = [Math.abs(step[0]), Math.abs(step[1])];
+    if (deltaX == 0 || deltaY == 0 || deltaX == deltaY)
+      return [step[0] / deltaX || 0, step[1] / deltaY || 0];
+    // Knight:
+    const divisor = Math.min(deltaX, deltaY)
+    return [step[0] / divisor, step[1] / divisor];
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    // Complete moves: stuns & kicks
+    const stun = V.STUNNED_1.concat(V.STUNNED_2);
+    moves.forEach(m => {
+      if (m.vanish.length == 2 && m.appear.length == 1) {
+        const step =
+          this.getNormalizedStep([m.end.x - m.start.x, m.end.y - m.start.y]);
+        // "Capture" something: is target stunned?
+        if (stun.includes(m.vanish[1].p)) {
+          // Kick it: continue movement in the same direction,
+          // destroying all on its path.
+          let [i, j] = [m.end.x + step[0], m.end.y + step[1]];
+          while (V.OnBoard(i, j)) {
+            if (this.board[i][j] != V.EMPTY) {
+              m.vanish.push(
+                new PiPo({
+                  x: i,
+                  y: j,
+                  c: this.getColor(i, j),
+                  p: this.getPiece(i, j)
+                })
+              );
+            }
+            i += step[0];
+            j += step[1];
+          }
+        }
+        else {
+          // The piece is now stunned
+          m.appear.push(m.vanish.pop());
+          const pIdx = ChessRules.PIECES.findIndex(p => p == m.appear[1].p);
+          m.appear[1].p = V.STUNNED_1[pIdx];
+          // And the capturer continue in the same direction until an empty
+          // square or the edge of the board, maybe stunning other pieces.
+          let [i, j] = [m.end.x + step[0], m.end.y + step[1]];
+          while (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) {
+            const colIJ = this.getColor(i, j);
+            const pieceIJ = this.getPiece(i, j);
+            m.vanish.push(
+              new PiPo({
+                x: i,
+                y: j,
+                c: colIJ,
+                p: pieceIJ
+              })
+            );
+            const pIdx = ChessRules.PIECES.findIndex(p => p == pieceIJ);
+            m.appear.push(
+              new PiPo({
+                x: i,
+                y: j,
+                c: colIJ,
+                p: V.STUNNED_1[pIdx]
+              })
+            );
+            i += step[0];
+            j += step[1];
+          }
+          if (V.OnBoard(i, j)) {
+            m.appear[0].x = i;
+            m.appear[0].y = j;
+            // Is it a pawn on last rank?
+          }
+          else {
+            // The piece is out
+            m.appear.shift();
+          }
+        }
+      }
+    });
+    return moves;
+  }
+
+  static GenRandInitFen(randomness) {
+    // No en-passant:
+    return ChessRules.GenRandInitFen(randomness).slice(0, -2);
+  }
+
+  filterValid(moves) {
+    // Forbid kicking own king out
+    const color = this.turn;
+    return moves.filter(m => {
+      return m.vanish.every(v => v.c != color || !(['l','m'].includes(v.p)));
+    });
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    if (this.kingPos['w'][0] < 0) return "0-1";
+    if (this.kingPos['b'][0] < 0) return "1-0";
+    if (!this.atLeastOneMove()) return "1/2";
+    return "*";
+  }
+
+  postPlay(move) {
+    // TODO: toutes les pièces "stunned" by me (turn) avancent d'un niveau
+    // --> alter board
+    move.wasStunned = array of stunned stage 2 pieces (just back to normal then)
+  }
+
+  postUndo(move) {
+    if (wasStunned
+      STUNNED_2
+  }
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index a4b2b9a5..29595571 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -47,6 +47,7 @@ insert or ignore into Variants (name, description) values
   ('Enpassant', 'Capture en passant'),
   ('Extinction', 'Capture all of a kind'),
   ('Football', 'Score a goal'),
+  ('Freecapture', 'Capture both colors'),
   ('Grand', 'Big board'),
   ('Grasshopper', 'Long jumps over pieces'),
   ('Gridolina', 'Jump the borders'),
@@ -57,6 +58,7 @@ insert or ignore into Variants (name, description) values
   ('Knightmate', 'Mate the knight'),
   ('Knightrelay1', 'Move like a knight (v1)'),
   ('Knightrelay2', 'Move like a knight (v2)'),
+  ('Koopa', 'Stun & kick pieces'),
   ('Koth', 'King of the Hill'),
   ('Losers', 'Get strong at self-mate'),
   ('Madrasi', 'Paralyzed pieces'),