From 459edd581fa23f511e224f07554944bbe53a2d70 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 12 Jan 2021 09:58:32 +0100
Subject: [PATCH] Add Joker Chess

---
 TODO                                       |  4 -
 client/public/images/pieces/Joker/bj.svg   |  1 +
 client/public/images/pieces/Joker/wj.svg   |  1 +
 client/src/translations/en.js              |  1 +
 client/src/translations/es.js              |  1 +
 client/src/translations/fr.js              |  1 +
 client/src/translations/rules/Joker/en.pug | 23 +++++-
 client/src/translations/rules/Joker/es.pug | 24 +++++-
 client/src/translations/rules/Joker/fr.pug | 26 ++++++-
 client/src/translations/variants/en.pug    |  1 +
 client/src/translations/variants/es.pug    |  1 +
 client/src/translations/variants/fr.pug    |  1 +
 client/src/variants/Joker.js               | 87 ++++++++++++++++++++++
 server/db/populate.sql                     |  1 +
 14 files changed, 166 insertions(+), 7 deletions(-)
 create mode 120000 client/public/images/pieces/Joker/bj.svg
 create mode 120000 client/public/images/pieces/Joker/wj.svg
 create mode 100644 client/src/variants/Joker.js

diff --git a/TODO b/TODO
index 734ab154..0bd4ab0d 100644
--- a/TODO
+++ b/TODO
@@ -3,10 +3,6 @@ Embedded rules language not updated when language is set (in Analyse, Game and P
 If new live game starts in background, "new game" notify OK but not first move.
 
 NEW VARIANTS:
-https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/
---> replaced by "Joker" chess --> start on bishop of our color (instead),
-    moves like a Bishop + Dabbabah, can swap with a piece *on same color* (as a move) at anytime. hmm
-    or rather: start as in Antiking + Mesmer, in front of. moves like an amazon. swap with any color. better!
 http://history.chess.free.fr/rollerball.htm
   https://www.chessvariants.com/40.dir/rollerball/index.html
 
diff --git a/client/public/images/pieces/Joker/bj.svg b/client/public/images/pieces/Joker/bj.svg
new file mode 120000
index 00000000..000f539e
--- /dev/null
+++ b/client/public/images/pieces/Joker/bj.svg
@@ -0,0 +1 @@
+../Maxima/bg.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Joker/wj.svg b/client/public/images/pieces/Joker/wj.svg
new file mode 120000
index 00000000..436f84d6
--- /dev/null
+++ b/client/public/images/pieces/Joker/wj.svg
@@ -0,0 +1 @@
+../Maxima/wg.svg
\ No newline at end of file
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 1839c669..bfd3f2ea 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -275,6 +275,7 @@ export const translations = {
   "Queen versus pawns": "Queen versus pawns",
   "Reach the last rank (v1)": "Reach the last rank (v1)",
   "Reach the last rank (v2)": "Reach the last rank (v2)",
+  "Replace pieces": "Replace pieces",
   "Reposition pieces": "Reposition pieces",
   "Reuse pieces": "Reuse pieces",
   "Reverse captures": "Reverse captures",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 322661c8..03be9bbf 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -275,6 +275,7 @@ export const translations = {
   "Queen versus pawns": "Dama contra peones",
   "Reach the last rank (v1)": "Llegar a la última fila (v1)",
   "Reach the last rank (v2)": "Llegar a la última fila (v2)",
+  "Replace pieces": "Reemplazar piezas",
   "Reposition pieces": "Reposicionar las piezas",
   "Reuse pieces": "Reutilizar piezas",
   "Reverse captures": "Capturas invertidas",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 94a36189..22f333af 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -275,6 +275,7 @@ export const translations = {
   "Queen versus pawns": "Dame contre pions",
   "Reach the last rank (v1)": "Atteignez la dernière rangée (v1)",
   "Reach the last rank (v2)": "Atteignez la dernière rangée (v2)",
+  "Replace pieces": "Remplacer les pièces",
   "Reposition pieces": "Replacer les pièces",
   "Reuse pieces": "Réutiliser les pièces",
   "Reverse captures": "Captures inversées",
diff --git a/client/src/translations/rules/Joker/en.pug b/client/src/translations/rules/Joker/en.pug
index 21203baa..1f102384 100644
--- a/client/src/translations/rules/Joker/en.pug
+++ b/client/src/translations/rules/Joker/en.pug
@@ -1 +1,22 @@
-p.boxed TODO
+p.boxed
+  | A new piece can swap with any of your other pieces.
+
+p.
+  A new piece appears, starting in front of the pawns.
+  It moves like an Amazon: Queen + Knight, but doesn't capture.
+  Instead, it can swap its position with any piece.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/3j4/8/8/3J4/PPPPPPPP/RNBQKBNR:
+  figcaption Deterministic initial position.
+
+p Pawns can promote into a Joker if you have none on the board.
+
+h3 Source
+
+p
+  | This variant is inspired by 
+  a(href="https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/")
+    | Totem Chess
+  | , which seemed a bit too complicated.
diff --git a/client/src/translations/rules/Joker/es.pug b/client/src/translations/rules/Joker/es.pug
index 21203baa..7aaaf9fb 100644
--- a/client/src/translations/rules/Joker/es.pug
+++ b/client/src/translations/rules/Joker/es.pug
@@ -1 +1,23 @@
-p.boxed TODO
+p.boxed
+  | Se puede cambiar una nueva pieza con cualquiera de sus otras piezas.
+
+p.
+  Aparece una nueva pieza, comenzando por delante de los peones.
+  Se mueve como una Amazona: Dama + Caballo, pero no captura.
+  En cambio, puede cambiar su posición con cualquier pieza.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/3j4/8/8/3J4/PPPPPPPP/RNBQKBNR:
+  figcaption Posición inicial determinista.
+
+p.
+  Los peones pueden promocionarse a Joker si no le queda nada en el tablero.
+
+h3 Fuente
+
+p
+  | Esta variante está inspirada en 
+  a(href="https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/")
+    | Totem Chess
+  | , que me pareció un poco complicado.
diff --git a/client/src/translations/rules/Joker/fr.pug b/client/src/translations/rules/Joker/fr.pug
index 21203baa..853d7040 100644
--- a/client/src/translations/rules/Joker/fr.pug
+++ b/client/src/translations/rules/Joker/fr.pug
@@ -1 +1,25 @@
-p.boxed TODO
+p.boxed
+  | Une nouvelle pièce peut s'échanger avec
+  | n'importe laquelle de vos autres pièces.
+
+p.
+  Une nouvelle pièce fait son apparition, démarrant devant les pions.
+  Elle se déplace comme une Amazone : Dame + Cavalier, mais ne capture pas.
+  Au lieu de ça, elle peut échanger sa position avec n'importe quelle pièce.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/3j4/8/8/3J4/PPPPPPPP/RNBQKBNR:
+  figcaption Position initiale déterministe.
+
+p.
+  Les pions peuvent se promouvoir en Joker
+  s'il ne vous en reste plus sur l'échiquier.
+
+h3 Source
+
+p
+  | Cette variante est inspirée de 
+  a(href="https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/")
+    | Totem Chess
+  | , qui m'a paru un peu trop compliquée.
diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug
index 7d788bdf..6e7d42ad 100644
--- a/client/src/translations/variants/en.pug
+++ b/client/src/translations/variants/en.pug
@@ -408,6 +408,7 @@ p.
     "Hamilton",
     "Hypnotic",
     "Isardam",
+    "Joker",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug
index 9ba6c2d9..2b767403 100644
--- a/client/src/translations/variants/es.pug
+++ b/client/src/translations/variants/es.pug
@@ -419,6 +419,7 @@ p.
     "Hamilton",
     "Hypnotic",
     "Isardam",
+    "Joker",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug
index 82a38424..431a725b 100644
--- a/client/src/translations/variants/fr.pug
+++ b/client/src/translations/variants/fr.pug
@@ -418,6 +418,7 @@ p.
     "Hamilton",
     "Hypnotic",
     "Isardam",
+    "Joker",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
diff --git a/client/src/variants/Joker.js b/client/src/variants/Joker.js
new file mode 100644
index 00000000..137249c4
--- /dev/null
+++ b/client/src/variants/Joker.js
@@ -0,0 +1,87 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { Antiking2Rules } from "@/variants/Antiking2";
+
+export class JokerRules extends ChessRules {
+
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      { promotions: ChessRules.PawnSpecs.promotions.concat([V.JOKER]) }
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    const antikingFen = Antiking2Rules.GenRandInitFen(randomness);
+    return antikingFen.replace('a', 'J').replace('A', 'j');
+  }
+
+  static get JOKER() {
+    return 'j';
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.JOKER]);
+  }
+
+  getPpath(b) {
+    return (b.charAt(1) == 'j' ? "Joker/" : "") + b;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const piece = this.getPiece(x, y);
+    if (piece == V.JOKER) return this.getPotentialJokerMoves([x, y]);
+    let moves = super.getPotentialMovesFrom([x, y]);
+    if (piece == V.PAWN) {
+      const c = this.turn;
+      const alreadyOneJoker = this.board.some(row =>
+        row.some(cell => cell == c + 'j'));
+      if (alreadyOneJoker) moves = moves.filter(m => m.appear[0].p != V.JOKER)
+    }
+    return moves;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    if (this.getPiece(x1, y1) == V.JOKER) return false;
+    return super.canTake([x1, y1], [x2, y2]);
+  }
+
+  getPotentialJokerMoves([x, y]) {
+    const moving =
+      super.getSlideNJumpMoves([x, y], V.steps[V.KNIGHT], "oneStep")
+      .concat(super.getSlideNJumpMoves([x, y],
+        V.steps[V.ROOK].concat(V.steps[V.BISHOP])));
+    let swapping = [];
+    const c = this.turn;
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        // Following test is OK because only one Joker on board at a time
+        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == c) {
+          const p = this.getPiece(i, j);
+          swapping.push(
+            new Move({
+              vanish: [
+                new PiPo({ x: x, y: y, c: c, p: V.JOKER }),
+                new PiPo({ x: i, y: j, c: c, p: p })
+              ],
+              appear: [
+                new PiPo({ x: i, y: j, c: c, p: V.JOKER }),
+                new PiPo({ x: x, y: y, c: c, p: p })
+              ]
+            })
+          );
+        }
+      }
+    }
+    return moving.concat(swapping);
+  }
+
+  static get VALUES() {
+    return Object.assign({ j: 2 }, ChessRules.VALUES);
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index b6d0bf78..043c8a8f 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -74,6 +74,7 @@ insert or ignore into Variants (name, description) values
   ('Interweave', 'Interweaved colorbound teams'),
   ('Isardam', 'No paralyzed pieces'),
   ('Janggi', 'Korean Chess'),
+  ('Joker', 'Replace pieces'),
   ('Kinglet', 'Protect your pawns'),
   ('Kingsmaker', 'Promote into kings'),
   ('Knightmate', 'Mate the knight'),
-- 
2.44.0