From 14c35dc66973e66f9d9a680abb0a35db93ee2bcb Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Fri, 20 Mar 2020 22:08:16 +0100
Subject: [PATCH] Add Twokings variant

---
 client/src/translations/en.js                 |  1 +
 client/src/translations/es.js                 |  1 +
 client/src/translations/fr.js                 |  1 +
 client/src/translations/rules/Coregal/es.pug  |  4 +-
 client/src/translations/rules/Twokings/en.pug | 32 +++++++
 client/src/translations/rules/Twokings/es.pug | 34 ++++++++
 client/src/translations/rules/Twokings/fr.pug | 34 ++++++++
 client/src/variants/Coregal.js                | 17 ++--
 client/src/variants/Twokings.js               | 83 +++++++++++++++++++
 server/db/populate.sql                        |  1 +
 10 files changed, 199 insertions(+), 9 deletions(-)
 create mode 100644 client/src/translations/rules/Twokings/en.pug
 create mode 100644 client/src/translations/rules/Twokings/es.pug
 create mode 100644 client/src/translations/rules/Twokings/fr.pug
 create mode 100644 client/src/variants/Twokings.js

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index ded2b8a9..af674cc4 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -197,6 +197,7 @@ export const translations = {
   "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
   "Transform an essay": "Transform an essay",
+  "Two kings": "Two kings",
   "Two royal pieces": "Two royal pieces",
   "Unidentified pieces": "Unidentified pieces"
 };
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index c065cf0e..183bd6f3 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -197,6 +197,7 @@ export const translations = {
   "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
   "Transform an essay": "Transformar un ensayo",
+  "Two kings": "Dos reyes",
   "Two royal pieces": "Dos piezas reales",
   "Unidentified pieces": "Piezas no identificadas"
 };
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 120571b7..b317bb41 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -197,6 +197,7 @@ export const translations = {
   "Squares disappear": "Les cases disparaissent",
   "Standard rules": "Règles usuelles",
   "Transform an essay": "Transformer un essai",
+  "Two kings": "Deux rois",
   "Two royal pieces": "Deux pièces royales",
   "Unidentified pieces": "Pièces non identifiées"
 };
diff --git a/client/src/translations/rules/Coregal/es.pug b/client/src/translations/rules/Coregal/es.pug
index 489775b9..f7484e8a 100644
--- a/client/src/translations/rules/Coregal/es.pug
+++ b/client/src/translations/rules/Coregal/es.pug
@@ -15,7 +15,7 @@ p.
   jaque las dos piezas reales al mismo tiempo, como en el siguiente diagrama.
 
 figure.diagram-container
-  .diagrama
+  .diagram
     | fen:4Q3/4K3/8/8/3N4/5k2/2q5/8:
   figcaption Las blancas ganan porque el rey y la dama negra están en jaque.
 
@@ -32,7 +32,7 @@ p.
   un gran enroque negro con la dama:
 
 figure.diagram-container
-  .diagrama
+  .diagram
     | fen:r4rq1/ppppppkp/6p1/8/8/8/PPPPPPPP/1QR2RK1:
   figcaption Después de dos pequeñas rocas blancas y un gran enroque negro.
 
diff --git a/client/src/translations/rules/Twokings/en.pug b/client/src/translations/rules/Twokings/en.pug
new file mode 100644
index 00000000..03037777
--- /dev/null
+++ b/client/src/translations/rules/Twokings/en.pug
@@ -0,0 +1,32 @@
+p.boxed
+  | Each side has two kings. Mate any of them.
+
+p.
+  Both kings can be checked and mated. This adds a new option to win a game,
+  by attaking both kings at the same time, as illustrated below.
+
+figure.diagram-container
+  .diagram
+    | fen:6KK/8/2Q5/8/8/5k2/2k5/8:
+  figcaption Both black kings are in check: white wins.
+
+h3 Special moves
+
+p.
+  A pawn can promote into a king, which can be mated too.
+  Probably not useful in a real game, but interesting for problems.
+
+p.
+  You can castle with any king and rook, under the same conditions
+  as orthodox castling. However, castling long is possible only with one king.
+
+p.
+  Note: to castle in a game you need to select
+  the king or queen first, and then move it to a rook.
+
+h3 Source
+
+p
+  a(href="https://greenchess.net/rules.php?v=dual") Dual Chess
+  | &nbsp;on greenchess.net. Probably invented by the webmaster Uray M. János,
+  | inspired by Coregal chess from chessvariants.com.
diff --git a/client/src/translations/rules/Twokings/es.pug b/client/src/translations/rules/Twokings/es.pug
new file mode 100644
index 00000000..fe3f0712
--- /dev/null
+++ b/client/src/translations/rules/Twokings/es.pug
@@ -0,0 +1,34 @@
+p.boxed
+  | Cada campo tiene dos reyes. Mate a cualquiera.
+
+p.
+  Los dos reyes pueden estar en jaque (mate). Esto agrega un nuevo
+  opción de ganar una partida, como se muestra a continuación.
+
+figure.diagram-container
+  .diagram
+    | fen:6KK/8/2Q5/8/8/5k2/2k5/8:
+  figcaption Los dos reyes negros están en jaque: las blancas ganan.
+
+h3 Movimientos especiales
+
+p.
+  Un peón puede ser promovido a rey, que también puede estar en jaque.
+  Sin duda no es muy útil en partida, pero sí interesante por los problemas.
+
+p.
+  Puedes rocar con cualquier rey y torre, bajo las mismas
+  condiciones que al ajedrez ortodoxo. Sin embargo,
+  el enroque grande no es posible que con uno de los dos reyes.
+
+p.
+  Nota: para enrocar en una partida debes seleccionar el rey o la dama
+  primero, luego mueva la pieza a una torre.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://greenchess.net/rules.php?v=dual") dariante Dual
+  | &nbsp;en greenchess.net. Probablemente inventado por el webmaster
+  | Uray M. János, inspirado por la variante Coregal en chessvariants.com.
diff --git a/client/src/translations/rules/Twokings/fr.pug b/client/src/translations/rules/Twokings/fr.pug
new file mode 100644
index 00000000..be6c14dc
--- /dev/null
+++ b/client/src/translations/rules/Twokings/fr.pug
@@ -0,0 +1,34 @@
+p.boxed
+  | Chaque camp a deux rois. Matez n'importe lequel.
+
+p.
+  Les deux rois peuvent être mis en échec et matés. Cela ajoute une nouvelle
+  option pour gagner une partie, comme illustré ci-dessous.
+
+figure.diagram-container
+  .diagram
+    | fen:6KK/8/2Q5/8/8/5k2/2k5/8:
+  figcaption Les deux rois noirs sont en échec : les blancs gagnent.
+
+h3 Coups spéciaux
+
+p.
+  Un pion peut être promu en roi, qui pourra être maté également.
+  Sans doute peu utile en partie, mais intéressant pour les problèmes.
+
+p.
+  Vous pouvez roquer avec n'importe quel roi et tour, sous les mêmes
+  conditions qu'aux échecs orthodoxes. Cependant, le grand roque n'est possible
+  qu'avec un des deux rois.
+
+p.
+  Note : pour roquer dans une partie il faut sélectionner le roi ou la dame
+  d'abord, puis déplacer la pièce sur une tour.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://greenchess.net/rules.php?v=dual") variante Dual
+  | &nbsp;sur greenchess.net. Probablement inventée par le webmaster
+  | Uray M. János, inspiré par la variante Coregal sur chessvariants.com.
diff --git a/client/src/variants/Coregal.js b/client/src/variants/Coregal.js
index 704ce4c5..e0729ac3 100644
--- a/client/src/variants/Coregal.js
+++ b/client/src/variants/Coregal.js
@@ -268,18 +268,21 @@ export class CoregalRules extends ChessRules {
     return false;
   }
 
-  updateCastleFlags(move, piece) {
+  // "twoKings" arg for the similar Twokings variant.
+  updateCastleFlags(move, piece, twoKings) {
     const c = V.GetOppCol(this.turn);
     const firstRank = (c == "w" ? V.size.x - 1 : 0);
     // Update castling flags if castling pieces moved or were captured
     const oppCol = V.GetOppCol(c);
     const oppFirstRank = V.size.x - 1 - firstRank;
-    if (move.start.x == firstRank && [V.KING, V.QUEEN].includes(piece)) {
-      if (this.castleFlags[c][1] == move.start.y)
-        this.castleFlags[c][1] = 8;
-      else if (this.castleFlags[c][2] == move.start.y)
-        this.castleFlags[c][2] = 8;
-      // Else: the flag is already turned off
+    if (move.start.x == firstRank) {
+      if (piece == V.KING || (!twoKings && piece == V.QUEEN)) {
+        if (this.castleFlags[c][1] == move.start.y)
+          this.castleFlags[c][1] = 8;
+        else if (this.castleFlags[c][2] == move.start.y)
+          this.castleFlags[c][2] = 8;
+        // Else: the flag is already turned off
+      }
     }
     else if (
       move.start.x == firstRank && //our rook moves?
diff --git a/client/src/variants/Twokings.js b/client/src/variants/Twokings.js
new file mode 100644
index 00000000..9e82a245
--- /dev/null
+++ b/client/src/variants/Twokings.js
@@ -0,0 +1,83 @@
+import { ChessRules } from "@/base_rules";
+import { CoregalRules } from "@/variants/Coregal";
+
+export class TwokingsRules extends CoregalRules {
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) }
+    );
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kings = { "w": 0, "b": 0 };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (['K','k'].includes(row[i])) kings[row[i]]++;
+        if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // Two kings (at least) per side should be present:
+    if (Object.values(kings).some(v => v < 2)) return false;
+    return true;
+  }
+
+  // Not scanning king positions. In this variant, scan the board everytime.
+  scanKings(fen) {}
+
+  getCheckSquares(color) {
+    let squares = [];
+    const oppCol = V.GetOppCol(color);
+    for (let i=0; i<V.size.x; i++) {
+      for (let j=0; j<V.size.y; j++) {
+        if (
+          this.getColor(i, j) == color &&
+          this.getPiece(i, j) == V.KING &&
+          this.isAttacked([i, j], oppCol)
+        ) {
+          squares.push([i, j]);
+        }
+      }
+    }
+    return squares;
+  }
+
+  static GenRandInitFen(randomness) {
+    const fen = CoregalRules.GenRandInitFen(randomness);
+    return fen.replace("q", "k").replace("Q", "K");
+  }
+
+  underCheck(color) {
+    const oppCol = V.GetOppCol(color);
+    for (let i=0; i<V.size.x; i++) {
+      for (let j=0; j<V.size.y; j++) {
+        if (
+          this.getColor(i, j) == color &&
+          this.getPiece(i, j) == V.KING &&
+          this.isAttacked([i, j], oppCol)
+        ) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  postPlay(move) {
+    const piece = move.vanish[0].p;
+    super.updateCastleFlags(move, piece, "twoKings");
+  }
+
+  postUndo() {}
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index d6566535..a842d3e2 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -44,6 +44,7 @@ insert or ignore into Variants (name,description) values
   ('Suicide', 'Lose all pieces'),
   ('Suction', 'Attract opposite king'),
   ('Threechecks', 'Give three checks'),
+  ('Twokings', 'Two kings'),
   ('Upsidedown', 'Board upside down'),
   ('Wildebeest', 'Balanced sliders & leapers'),
   ('Wormhole', 'Squares disappear'),
-- 
2.44.0