From 10cceb25109739fa39b9b968be2707dee1d25a07 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 14 Apr 2021 17:06:29 +0200
Subject: [PATCH] Merge Atomic1 & 2

---
 client/src/base_rules.js                      |   3 +-
 client/src/translations/en.js                 |   6 +-
 client/src/translations/es.js                 |   4 +-
 client/src/translations/fr.js                 |   6 +-
 .../rules/{Atomic1 => Atomic}/en.pug          |  25 ++++-
 .../rules/{Atomic1 => Atomic}/es.pug          |  26 ++++-
 .../rules/{Atomic1 => Atomic}/fr.pug          |  26 ++++-
 client/src/translations/rules/Atomic2/en.pug  |  22 ----
 client/src/translations/rules/Atomic2/es.pug  |  23 ----
 client/src/translations/rules/Atomic2/fr.pug  |  23 ----
 client/src/variants/{Atomic1.js => Atomic.js} | 105 +++++++++++++++++-
 client/src/variants/Atomic2.js                |  62 -----------
 client/src/variants/Cwda.js                   |   3 +-
 server/db/populate.sql                        |   7 +-
 14 files changed, 174 insertions(+), 167 deletions(-)
 rename client/src/translations/rules/{Atomic1 => Atomic}/en.pug (62%)
 rename client/src/translations/rules/{Atomic1 => Atomic}/es.pug (58%)
 rename client/src/translations/rules/{Atomic1 => Atomic}/fr.pug (59%)
 delete mode 100644 client/src/translations/rules/Atomic2/en.pug
 delete mode 100644 client/src/translations/rules/Atomic2/es.pug
 delete mode 100644 client/src/translations/rules/Atomic2/fr.pug
 rename client/src/variants/{Atomic1.js => Atomic.js} (66%)
 delete mode 100644 client/src/variants/Atomic2.js

diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index 7c1c8232..0876473d 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -135,16 +135,15 @@ export const ChessRules = class ChessRules {
     return V.CanFlip;
   }
 
+  // NOTE: these will disappear once each variant has its dedicated SVG board.
   // For (generally old) variants without checkered board
   static get Monochrome() {
     return false;
   }
-
   // Some games are drawn unusually (bottom right corner is black)
   static get DarkBottomRight() {
     return false;
   }
-
   // Some variants require lines drawing
   static get Lines() {
     if (V.Monochrome) {
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index cdcb7eaf..81614cf3 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -16,7 +16,8 @@ export const translations = {
   "Asymmetric random": "Asymmetric random",
   "Authentication successful!": "Authentication successful!",
   "Back to list": "Back to list",
-  "Black": "Black",
+  Balanced: "Balanced",
+  Black: "Black",
   "Black to move": "Black to move",
   "Black surrender": "Black surrender",
   "Black win": "Black win",
@@ -234,8 +235,7 @@ export const translations = {
   "Enter the disco": "Enter the disco",
   "Exchange pieces' positions": "Exchange pieces' positions",
   "Exotic captures": "Exotic captures",
-  "Explosive captures (v1)": "Explosive captures (v1)",
-  "Explosive captures (v2)": "Explosive captures (v2)",
+  "Explosive captures": "Explosive captures",
   "Extra bishops and knights": "Extra bishops and knights",
   "Faster development": "Faster development",
   "First capture wins": "First capture wins",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 0e8af8cb..4fc5470c 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -16,6 +16,7 @@ export const translations = {
   "Asymmetric random": "Aleatorio asimétrico",
   "Authentication successful!": "¡Autenticación exitosa!",
   "Back to list": "Volver a la lista",
+  Balanced: "Equilibrado",
   Black: "Negras",
   "Black to move": "Juegan las negras",
   "Black surrender": "Las negras abandonan",
@@ -234,8 +235,7 @@ export const translations = {
   "Enter the disco": "Entrar en la discoteca",
   "Exchange pieces' positions": "Intercambiar posiciones de piezas",
   "Exotic captures": "Capturas exóticas",
-  "Explosive captures (v1)": "Capturas explosivas (v1)",
-  "Explosive captures (v2)": "Capturas explosivas (v2)",
+  "Explosive captures": "Capturas explosivas",
   "Extra bishops and knights": "Alfiles y caballos adicionales",
   "Faster development": "Desarrollo acelerado",
   "First capture wins": "La primera captura gana",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index bc8b4080..1ee78eeb 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -16,7 +16,8 @@ export const translations = {
   "Asymmetric random": "Aléatoire asymétrique",
   "Authentication successful!": "Authentification réussie !",
   "Back to list": "Retour à la liste",
-  "Black": "Noirs",
+  Balanced: "Équilibré",
+  Black: "Noirs",
   "Black to move": "Trait aux noirs",
   "Black surrender": "Les noirs abandonnent",
   "Black win": "Les noirs gagnent",
@@ -234,8 +235,7 @@ export const translations = {
   "Enter the disco": "Entrez dans la boîte",
   "Exchange pieces' positions": "Échangez les positions des pièces",
   "Exotic captures": "Captures exotiques",
-  "Explosive captures (v1)": "Captures explosives (v1)",
-  "Explosive captures (v2)": "Captures explosives (v2)",
+  "Explosive captures": "Captures explosives",
   "Extra bishops and knights": "Fous et cavaliers supplémentaires",
   "Faster development": "Développement accéléré",
   "First capture wins": "La première capture gagne",
diff --git a/client/src/translations/rules/Atomic1/en.pug b/client/src/translations/rules/Atomic/en.pug
similarity index 62%
rename from client/src/translations/rules/Atomic1/en.pug
rename to client/src/translations/rules/Atomic/en.pug
index 9a856e4e..63a8e886 100644
--- a/client/src/translations/rules/Atomic1/en.pug
+++ b/client/src/translations/rules/Atomic/en.pug
@@ -29,11 +29,26 @@ ol
 p Explosions have priority: a checkmate followed by a king explosion loses.
 
 figure.diagram-container
-  .diagram
-    | fen:r3kbnr/pp3ppp/3p4/4p3/8/8/PPPPPPPP/R1BQKBNR:
-  figcaption.
-    After the moves 1.Nc3 d6?? 2.Nd5 e5 (forced to avoid king explosion)
-    3.Nxc7
+  .diagram.diag12
+    | fen:3rkbnr/pb1pp2p/npp5/q4ppQ/P1B1P3/NP5N/1BPP1PPP/R3K2R:
+  .diagram.diag22
+    | fen:3rkbnr/pb1pp2p/npp5/5ppQ/P1B1P3/NP5N/1BP2PPP/R6R:
+  figcaption 1.Qh5+ (left) 1...Qxd2# (right), black wins.
+
+h3 Balancing
+
+p.
+  White has a big advantage in this variant. In order to balance the odds,
+  you can select "Balanced" to let white choose any pawn (any color) to
+  remove before the game starts.
+  The game then continues normally, black playing first.
+
+p
+  | This was suggested (relayed?) and analyzed recently (2020) by a strong
+  | Atomic player (Gannet on Discord). See 
+  a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
+    | the messages
+  | &nbsp;on Discord vchess server.
 
 h3 More information
 
diff --git a/client/src/translations/rules/Atomic1/es.pug b/client/src/translations/rules/Atomic/es.pug
similarity index 58%
rename from client/src/translations/rules/Atomic1/es.pug
rename to client/src/translations/rules/Atomic/es.pug
index bece0367..57b98d90 100644
--- a/client/src/translations/rules/Atomic1/es.pug
+++ b/client/src/translations/rules/Atomic/es.pug
@@ -29,11 +29,27 @@ ol
 p Las explosiones tienen prioridad: un mate seguida de una explosión pierde.
 
 figure.diagram-container
-  .diagram
-    | fen:r3kbnr/pp3ppp/3p4/4p3/8/8/PPPPPPPP/R1BQKBNR:
-  figcaption.
-    Después de las jugadas 1.Nc3 d6?? 2.Nd5 e5 (forzado, para evitar la
-    explosión del rey) 3.Nxc7
+  .diagram.diag12
+    | fen:3rkbnr/pb1pp2p/npp5/q4ppQ/P1B1P3/NP5N/1BPP1PPP/R3K2R:
+  .diagram.diag22
+    | fen:3rkbnr/pb1pp2p/npp5/5ppQ/P1B1P3/NP5N/1BP2PPP/R6R:
+  figcaption 1.Qh5+ (izquierda) 1...Qxd2# (derecha), las negras ganan.
+
+h4 Equilibrio
+
+p.
+  Las blancas tienen una gran ventaja en esta variante. Para reequilibrar
+  probabilidades, puede seleccionar "Equilibrado" para que las blancas
+  elige un peón (de cualquier color) para eliminar
+  antes de que comience el juego. Luego, la partida continúa
+  como de costumbre, con las negras jugando primero.
+
+p
+  | Esto ha sido sugerido (¿retransmitido?) y analizado recientemente (2020)
+  | por un fuerte jugador de ajedrez atómico (Gannet en Discord). Ver 
+  a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
+    | los mensajes
+  | en el servidor Discord de vchess.
 
 h3 Más información
 
diff --git a/client/src/translations/rules/Atomic1/fr.pug b/client/src/translations/rules/Atomic/fr.pug
similarity index 59%
rename from client/src/translations/rules/Atomic1/fr.pug
rename to client/src/translations/rules/Atomic/fr.pug
index e33f9be1..0b1849cd 100644
--- a/client/src/translations/rules/Atomic1/fr.pug
+++ b/client/src/translations/rules/Atomic/fr.pug
@@ -30,11 +30,27 @@ ol
 p Les explosions ont la priorité : un mat suivi d'une explosion perd.
 
 figure.diagram-container
-  .diagram
-    | fen:r3kbnr/pp3ppp/3p4/4p3/8/8/PPPPPPPP/R1BQKBNR:
-  figcaption.
-    Après les coups 1.Nc3 d6?? 2.Nd5 e5 (forcé pour éviter l'explosion du roi)
-    3.Nxc7
+  .diagram.diag12
+    | fen:3rkbnr/pb1pp2p/npp5/q4ppQ/P1B1P3/NP5N/1BPP1PPP/R3K2R:
+  .diagram.diag22
+    | fen:3rkbnr/pb1pp2p/npp5/5ppQ/P1B1P3/NP5N/1BP2PPP/R6R:
+  figcaption 1.Qh5+ (gauche) 1...Qxd2# (droite), les noirs gagnent.
+
+h4 Équilibrage
+
+p.
+  Les blancs ont un gros avantage dans cette variante. Afin de rééquilibrer
+  les chances, vous pouvez sélectionner "Équilibré" pour laisser les blancs
+  choisir un pion (de n'importe quelle couleur) à supprimer
+  avant que la partie commence.
+  Le jeu continue ensuite normalement, les noirs jouant en premier.
+
+p
+  | Ceci a été suggéré (relayé ?) et analysé récemment (2020) par un fort
+  | joueur d'échecs atomiques (Gannet sur Discord). Voir 
+  a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
+    | les messages
+  | &nbsp;sur le serveur Discord de vchess.
 
 h3 Plus d'information
 
diff --git a/client/src/translations/rules/Atomic2/en.pug b/client/src/translations/rules/Atomic2/en.pug
deleted file mode 100644
index 9311018e..00000000
--- a/client/src/translations/rules/Atomic2/en.pug
+++ /dev/null
@@ -1,22 +0,0 @@
-p.boxed.
-  Atomic1, but white's first move is a pawn removal.
-
-p.
-  At the very first move of the game, white must remove a pawn,
-  from either side.
-  Black then replies, and the game continues normally.
-
-figure.diagram-container
-  .diagram
-    | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPP1PPP/RNBQKBNR:
-  figcaption After 1.e2X, pawn removal at e2.
-
-h3 More information
-
-p
-  | This variant aims at balancing the big white's advantage in Atomic1.
-  | It was suggested (relayed?) and analyzed recently (2020) by a strong
-  | Atomic player (Gannet on Discord). See 
-  a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
-    | the messages
-  | &nbsp;on Discord vchess server.
diff --git a/client/src/translations/rules/Atomic2/es.pug b/client/src/translations/rules/Atomic2/es.pug
deleted file mode 100644
index 56cdaff2..00000000
--- a/client/src/translations/rules/Atomic2/es.pug
+++ /dev/null
@@ -1,23 +0,0 @@
-p.boxed.
-  Atomic1, pero el primer movimiento de las blancas es quitar un peón.
-
-p.
-  En el primer movimiento del juego, las blancas deben elegir un peón para
-  borrar, blanco o negro.
-  Entonces las negras responden, luego el juego continúa normalmente.
-
-figure.diagram-container
-  .diagram
-    | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPP1PPP/RNBQKBNR:
-  figcaption Después de 1.e2X, remoción de peones en e2.
-
-h3 Más información
-
-p
-  | Esta variante tiene como objetivo reequilibrar la gran ventaja de las
-  | blancas en Atomic1. Ha sido sugerido (¿retransmitido?) y analizado
-  | recientemente (2020) por un fuerte jugador de ajedrez atómico
-  | (Gannet en Discord). Ver 
-  a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
-    | los mensajes
-  | &nbsp;en el servidor Discord de vchess.
diff --git a/client/src/translations/rules/Atomic2/fr.pug b/client/src/translations/rules/Atomic2/fr.pug
deleted file mode 100644
index d072c7b1..00000000
--- a/client/src/translations/rules/Atomic2/fr.pug
+++ /dev/null
@@ -1,23 +0,0 @@
-p.boxed.
-  Atomic1, mais le premier coup des blancs consiste à supprimer un pion.
-
-p.
-  Au tout premier coup de la partie, les blancs doivent choisir un pion à
-  supprimer, blanc ou noir.
-  Les noirs répondent alors, puis la partie continue normalement.
-
-figure.diagram-container
-  .diagram
-    | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPP1PPP/RNBQKBNR:
-  figcaption.
-    Après 1.e2X, suppression de pion en e2.
-
-h3 Plus d'information
-
-p
-  | Cette variante vise à rééquilibrer le gros avantage blanc en Atomic1.
-  | Elle a été suggérée (relayée ?) et analysée récemment (2020) par un fort
-  | joueur d'échecs atomiques (Gannet sur Discord). Voir 
-  a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
-    | les messages
-  | &nbsp;sur le serveur Discord de vchess.
diff --git a/client/src/variants/Atomic1.js b/client/src/variants/Atomic.js
similarity index 66%
rename from client/src/variants/Atomic1.js
rename to client/src/variants/Atomic.js
index a5198d69..831fb2f2 100644
--- a/client/src/variants/Atomic1.js
+++ b/client/src/variants/Atomic.js
@@ -1,10 +1,98 @@
-import { ChessRules, PiPo } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
 
-export class Atomic1Rules extends ChessRules {
+export class AtomicRules extends ChessRules {
+
+  static get Options() {
+    return {
+      check: [
+        {
+          label: "Balanced",
+          defaut: false,
+          variable: "balanced"
+        }
+      ],
+      select: ChessRules.Options.select
+    };
+  }
+
+  static AbbreviateOptions(opts) {
+    return opts["balanced"] ? 'B' : '';
+  }
+
+  static GenRandInitFen(options) {
+    return ChessRules.GenRandInitFen(options) + (options.balanced ? " B" : "");
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.balanced = !!V.ParseFen(fen).balanced;
+  }
+
+  static ParseFen(fen) {
+    return Object.assign(
+      { balanced: fen.split(" ")[5] },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const balanced = V.ParseFen(fen).balanced;
+    return (!balanced || balanced == 'B');
+  }
+
+  getFen() {
+    return super.getFen() + (this.balanced ? " B" : "");
+  }
+
+  hoverHighlight([x, y]) {
+    return this.balanced && this.movesCount == 0 && [1, 6].includes(x);
+  }
+
+  canIplay(side, [x, y]) {
+    if (this.balanced && this.movesCount == 0)
+      return (this.turn == side && this.getPiece(x, y) == V.PAWN);
+    return super.canIplay(side, [x, y]);
+  }
+
+  doClick(square) {
+    if (!this.balanced || this.movesCount >= 1) return null;
+    const [x, y] = [square[0], square[1]];
+    if (![1, 6].includes(x)) return null;
+    return new Move({
+      appear: [],
+      vanish: [
+        new PiPo({
+          x: x,
+          y: y,
+          c: this.getColor(x, y),
+          p: V.PAWN
+        })
+      ],
+      start: { x: x, y: y },
+      end: { x: x, y: y }
+    });
+  }
 
   getPotentialMovesFrom([x, y]) {
-    let moves = super.getPotentialMovesFrom([x, y]);
+    if (this.balanced && this.movesCount == 0) {
+      if ([1, 6].includes(x)) {
+        const c = this.getColor(x, y);
+        return [
+          new Move({
+            appear: [],
+            vanish: [
+              new PiPo({ x: x, y: y, p: V.PAWN, c: c })
+            ],
+            start: { x: x, y: y },
+            end: { x: x, y: y }
+          })
+        ];
+      }
+      return [];
+    }
 
+    let moves = super.getPotentialMovesFrom([x, y]);
     if (this.getPiece(x, y) == V.PAWN) {
       // Promotions by captures can be reduced to only one deterministic
       // move (because of the explosion).
@@ -15,7 +103,6 @@ export class Atomic1Rules extends ChessRules {
         );
       });
     }
-
     // Handle explosions
     moves.forEach(m => {
       // NOTE: if vanish.length==2 and appear.length==2, this is castle
@@ -53,7 +140,6 @@ export class Atomic1Rules extends ChessRules {
         m.appear.pop(); //Nothin appears in this case
       }
     });
-
     return moves;
   }
 
@@ -120,7 +206,7 @@ export class Atomic1Rules extends ChessRules {
     super.postUndo(move);
     const c = this.turn;
     const oppCol = V.GetOppCol(c);
-    // NOTE: condition on movesCount for Atomic2
+    // NOTE: condition on movesCount for balanced setting
     if (
       this.movesCount >= 1 &&
       [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)
@@ -168,4 +254,11 @@ export class Atomic1Rules extends ChessRules {
     return color == "w" ? "0-1" : "1-0"; //checkmate
   }
 
+  getNotation(move) {
+    if (move.appear.length == 0 && move.vanish.length == 1)
+      // First move in game (balanced == true)
+      return V.CoordsToSquare(move.start) + "X";
+    return super.getNotation(move);
+  }
+
 };
diff --git a/client/src/variants/Atomic2.js b/client/src/variants/Atomic2.js
deleted file mode 100644
index 54ffb17a..00000000
--- a/client/src/variants/Atomic2.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { ChessRules, Move, PiPo } from "@/base_rules";
-import { Atomic1Rules } from "@/variants/Atomic1";
-
-export class Atomic2Rules extends Atomic1Rules {
-
-  getPotentialMovesFrom([x, y]) {
-    if (this.movesCount == 0) {
-      if ([1, 6].includes(x)) {
-        const c = this.getColor(x, y);
-        return [
-          new Move({
-            appear: [],
-            vanish: [
-              new PiPo({ x: x, y: y, p: V.PAWN, c: c })
-            ],
-            start: { x: x, y: y },
-            end: { x: x, y: y }
-          })
-        ];
-      }
-      return [];
-    }
-    return super.getPotentialMovesFrom([x, y]);
-  }
-
-  hoverHighlight([x, y]) {
-    return this.movesCount == 0 && [1, 6].includes(x);
-  }
-
-  canIplay(side, [x, y]) {
-    if (this.movesCount == 0)
-      return (this.turn == side && this.getPiece(x, y) == V.PAWN);
-    return super.canIplay(side, [x, y]);
-  }
-
-  doClick(square) {
-    if (this.movesCount >= 1) return null;
-    const [x, y] = [square[0], square[1]];
-    if (![1, 6].includes(x)) return null;
-    return new Move({
-      appear: [],
-      vanish: [
-        new PiPo({
-          x: x,
-          y: y,
-          c: this.getColor(x, y),
-          p: V.PAWN
-        })
-      ],
-      start: { x: x, y: y },
-      end: { x: x, y: y }
-    });
-  }
-
-  getNotation(move) {
-    if (move.appear.length == 0 && move.vanish.length == 1)
-      // First move in game
-      return V.CoordsToSquare(move.start) + "X";
-    return super.getNotation(move);
-  }
-
-};
diff --git a/client/src/variants/Cwda.js b/client/src/variants/Cwda.js
index a07672a1..2865d1fd 100644
--- a/client/src/variants/Cwda.js
+++ b/client/src/variants/Cwda.js
@@ -112,8 +112,7 @@ export class CwdaRules extends ChessRules {
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const armies = V.ParseFen(fen).armies;
-    if (!armies || !armies.match(/^[CNRF]{2,2}$/)) return false;
-    return true;
+    return (!!armies && armies.match(/^[CNRF]{2,2}$/));
   }
 
   getFen() {
diff --git a/server/db/populate.sql b/server/db/populate.sql
index d4f6b0c5..c496d59e 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -24,10 +24,9 @@ insert or ignore into Variants (name, description, groupe, display) values
   ('Antimatter', 'Dangerous collisions', 18, 'Antimatter'),
   ('Arena', 'Middle battle', 1, 'Arena'),
   ('Atarigo', 'First capture wins', 28, 'Atari-Go'),
-  ('Atomic1', 'Explosive captures (v1)', 18, 'Atomic 1'),
-  ('Atomic2', 'Explosive captures (v2)', 18, 'Atomic 2'),
-  ('Avalam1', 'Build towers (v1)', 28, 'Avalam 1'),
-  ('Avalam2', 'Build towers (v2)', 28, 'Avalam 2'),
+  ('Atomic', 'Explosive captures', 18, 'Atomic Chess'),
+  ('Avalam1', 'Build towers (v1)', 28, 'Avalam1'),
+  ('Avalam2', 'Build towers (v2)', 28, 'Avalam2'),
   ('Avalanche', 'Pawnfalls', 24, 'Avalanche'),
   ('Ball', 'Score a goal', 6, 'Ball'),
   ('Balaklava', 'Meet the Mammoth', 15, 'Balaklava'),
-- 
2.44.0