From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 28 Mar 2020 19:49:02 +0000 (+0100)
Subject: Add Horde variant + fix typos
X-Git-Url: https://git.auder.net/doc/html/app_dev.php/img/mini-custom.min.css?a=commitdiff_plain;h=f9385686d9434c607cdaa55e41a0425269db0815;p=vchess.git

Add Horde variant + fix typos
---

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index ef503530..04900207 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -156,6 +156,7 @@ export const translations = {
 
   // Variants boxes:
   "64 pieces on the board": "64 pieces on the board",
+  "A pawns cloud": "A pawns cloud",
   "Ancient rules": "Ancient rules",
   "Attract opposite king": "Attract opposite king",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index c5c32af3..32282e25 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -156,6 +156,7 @@ export const translations = {
 
   // Variants boxes:
   "64 pieces on the board": "64 piezas en el tablero",
+  "A pawns cloud": "Une nube de peones",
   "Ancient rules": "Viejas reglas",
   "Attract opposite king": "Atraer al rey contrario",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index e2c5f236..0e516844 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -156,6 +156,7 @@ export const translations = {
 
   // Variants boxes:
   "64 pieces on the board": "64 pièces sur l'échiquier",
+  "A pawns cloud": "Une nuée de pions",
   "Ancient rules": "Règles anciennes",
   "Attract opposite king": "Attirer le roi adverse",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
diff --git a/client/src/translations/rules/Horde/en.pug b/client/src/translations/rules/Horde/en.pug
new file mode 100644
index 00000000..66538b1b
--- /dev/null
+++ b/client/src/translations/rules/Horde/en.pug
@@ -0,0 +1,35 @@
+p.boxed
+  | A horde of 36 pawns is fighting to checkmate the king.
+  | Black goal is to eliminate all white pieces.
+
+p.
+  The initial configuration shows 36 white pawns, filling the four first ranks
+  and half of the fifth:
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP:
+  figcaption Deterministic starting position.
+
+p.
+  From white perspective, the material is unusual but the goal is the same as
+  in orthodox chess. Since there is no white king, black wins by capturing all
+  white pieces.
+
+p.
+  There is no castling, but en-passant captures may be executed after each two
+  squares pawn move, either from the first or second rank.
+
+h3 Source
+
+p
+  | This variant is inspired by 
+  a(href="https://www.chessvariants.com/unequal.dir/dunsany.html")
+    | Dunsany's Chess
+  | , invented by Lord Dunsany in 1942. The additional white pawns are here to
+  | balance the game. See also 
+  a(href="https://lichess.org/variant/horde") Horde on lichess.org
+  | &nbsp; and 
+  a(href="https://docs.google.com/document/d/136BCRPzm1QH_OBK3qjKwlmK3MIji7ZmLZPMYgDpmOCU/edit")
+    | this document
+  | &nbsp;about the strategy to adopt for both sides.
diff --git a/client/src/translations/rules/Horde/es.pug b/client/src/translations/rules/Horde/es.pug
new file mode 100644
index 00000000..15571fe5
--- /dev/null
+++ b/client/src/translations/rules/Horde/es.pug
@@ -0,0 +1,36 @@
+p.boxed
+  | Una horda de 36 peones lucha para dar jaque mate al rey contrario.
+  | El objetivo de las negras es eliminar todas las piezas blancas.
+
+p.
+  36 peones blancos están presentes en la configuración inicial,
+  llenando las primeras cuatro filas y la mitad de la quinta:
+
+figure.diagram-container
+  .diagrama
+    | fen:rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPPP:
+  figcaption Posición de inicio determinista.
+
+p.
+  Desde un punto de vista blanco, el material es inusual pero el objetivo sigue
+  siendo lo mismo que el ajedrez ortodoxo. Como no hay un rey blanco, las
+  negras gana capturando todas las piezas blancas.
+
+p.
+  El enroque no es posible, pero las capturas al pasar pueden ser
+  ejecutado después de cualquier movimiento de peón por dos espacios, desde el
+  primera o segunda fila.
+
+h3 Fuente
+
+p
+  | Esta variante está inspirada por el 
+  a(href="https://www.chessvariants.com/unequal.dir/dunsany.html")
+    | Ajedrez de Dunsany
+  | , inventado por Lord Dunsany en 1942. Peones blancos adicionales
+  | sirven para equilibrar el juego. Ver también 
+  a(href="https://lichess.org/variant/horde") Horda en lichess.org
+  | &nbsp; y
+  a(href="https://docs.google.com/document/d/136BCRPzm1QH_OBK3qjKwlmK3MIji7ZmLZPMYgDpmOCU/edit")
+    | este documento
+  | &nbsp; sobre la estrategia a adoptar para cada campamento.
diff --git a/client/src/translations/rules/Horde/fr.pug b/client/src/translations/rules/Horde/fr.pug
new file mode 100644
index 00000000..92809794
--- /dev/null
+++ b/client/src/translations/rules/Horde/fr.pug
@@ -0,0 +1,36 @@
+p.boxed
+  | Une horde de 36 pions combat pour mater le roi adverse.
+  | L'objectif des noirs est d'éliminer toutes les pièces blanches.
+
+p.
+  36 pions blancs sont présents dans la configuration initiale,
+  remplissant les quatre premières rangées et la moitié de la cinquième :
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP:
+  figcaption Position de départ déterministe.
+
+p.
+  Du point de vue des blancs, le matériel est inhabituel mais l'objectif reste
+  le même qu'aux échecs orthodoxes. Puisqu'il n'y a pas de roi blanc, les noirs
+  gagnent en capturant toutes les pièces blanches.
+
+p.
+  Le roque n'est pas possible, mais les prises en passant peuvent être
+  exécutées après n'importe quel déplacement de pion de deux cases, depuis la
+  première ou seconde rangée.
+
+h3 Source
+
+p
+  | Cette variante est inspirée par les 
+  a(href="https://www.chessvariants.com/unequal.dir/dunsany.html")
+    | Échecs de Dunsany
+  | , inventés par Lord Dunsany en 1942. Les pions blancs supplémentaires
+  | servent à équilibrer le jeu. Voir aussi la 
+  a(href="https://lichess.org/variant/horde") Horde sur lichess.org
+  | &nbsp; et 
+  a(href="https://docs.google.com/document/d/136BCRPzm1QH_OBK3qjKwlmK3MIji7ZmLZPMYgDpmOCU/edit")
+    | ce document
+  | &nbsp;sur la stratégie à adopter pour chaque camp.
diff --git a/client/src/variants/Dynamo.js b/client/src/variants/Dynamo.js
index c600b8ae..9f4cf3ff 100644
--- a/client/src/variants/Dynamo.js
+++ b/client/src/variants/Dynamo.js
@@ -73,7 +73,7 @@ export class DynamoRules extends ChessRules {
     if (
       this.subTurn == 2 &&
       square.x == this.firstMove.end.x &&
-      square.y == this.firstMove.end.y)
+      square.y == this.firstMove.end.y
     ) {
       return {
         appear: [],
@@ -186,32 +186,29 @@ export class DynamoRules extends ChessRules {
     if (this.getColor(x, y) != color)
       // The only moves possible with enemy pieces are pulls and pushes:
       return this.getPactions([x, y], color);
-    else {
-      // Playing my pieces: either on their own, or pushed by another
-      // If subTurn == 2 then we should have a first move,
-      // TODO = use it to allow some type of action
-      if (this.subTurn == 2) {
-        return (
-          this.moveOnSubturn1.isAnAction
-            ? super.getPotentialMovesFrom([x, y])
-            : this.getPactions([x, y], color, TODO_arg)
-        );
-      } else {
-        // Both options are possible at subTurn1: normal move, or push
-        moves =
-          super.getPotentialMovesFrom([x, y])
-          .concat(this.getPactions([x, y], color, "push");
-          // TODO: discard moves that let the king underCheck, and no second
-          // move can counter check. Example: pinned queen pushes pinned pawn.
-          .filter(m => {
-            this.play(m);
-            const res = this.filterMoves(this.getPotentialMoves(/* TODO: args? */)).length > 0;
-            this.undo(m);
-            return res;
-          });
-      }
+    // Playing my pieces: either on their own, or pushed by another
+    // If subTurn == 2 then we should have a first move,
+    // TODO = use it to allow some type of action
+    if (this.subTurn == 2) {
+      return (
+        this.moveOnSubturn1.isAnAction
+          ? super.getPotentialMovesFrom([x, y])
+          : this.getPactions([x, y], color, TODO_arg)
+      );
     }
-    return moves;
+    // Both options are possible at subTurn1: normal move, or push
+    return (
+      super.getPotentialMovesFrom([x, y])
+      .concat(this.getPactions([x, y], color, "push"))
+      // TODO: discard moves that let the king underCheck, and no second
+      // move can counter check. Example: pinned queen pushes pinned pawn.
+      .filter(m => {
+        this.play(m);
+        const res = this.filterMoves(this.getPotentialMoves(/* TODO: args? */)).length > 0;
+        this.undo(m);
+        return res;
+      })
+    );
   }
 
   // TODO: track rooks locations, should be a field in FEN, in castleflags?
diff --git a/client/src/variants/Horde.js b/client/src/variants/Horde.js
new file mode 100644
index 00000000..e61803b3
--- /dev/null
+++ b/client/src/variants/Horde.js
@@ -0,0 +1,69 @@
+import { ChessRules } from "@/base_rules";
+
+export class HordeRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static IsGoodPosition() {
+    // At least one white unit, and exactly one black king:
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let things = { "k": 0, "w": false };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (row[i] == 'k') things['k']++;
+        if (V.PIECES.includes(row[i].toLowerCase())) {
+          const rowCharCode = row[i].charCodeAt(0);
+          if (rowCharCode >= 65 && rowCharCode <= 90) {
+            // No white king:
+            if (row[i] == 'K') return false;
+            if (!things['w']) things['w'] = true;
+          }
+          sumElts++;
+        } else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    if (things[''] != 1 || !things['w']) return false;
+    return true;
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 2) randomness--;
+    const fen = ChessRules.GenRandInitFen(randomness);
+    return (
+      // 27 first chars are 3 rows + 3 slashes
+      fen.substr(0, 27)
+      // En passant available, but no castle:
+      .concat("1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w 0 -")
+    );
+  }
+
+  getCurrentScore() {
+    if (this.turn == 'w') {
+      // Do I have any unit remaining? If not, I lost.
+      // If yes and no available move, draw.
+      let somethingRemains = false;
+      outerLoop: for (let i=0; i<8; i++) {
+        for (let j=0; j<8; j++) {
+          if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == 'w') {
+            somethingRemains = true;
+            break outerLoop;
+          }
+        }
+      }
+      if (!somethingRemains) return "0-1";
+      if (this.atLeastOneMove()) return "*";
+      return "1/2";
+    }
+    // From black side, just run usual checks:
+    return super.getCurrentScore();
+  }
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 5046b54b..147868d9 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -36,6 +36,7 @@ insert or ignore into Variants (name, description) values
   ('Extinction', 'Capture all of a kind'),
   ('Grand', 'Big board'),
   ('Grasshopper', 'Long jumps over pieces'),
+  ('Horde', 'A pawns cloud'),
   ('Knightmate', 'Mate the knight'),
   ('Knightrelay1', 'Move like a knight (v1)'),
   ('Knightrelay2', 'Move like a knight (v2)'),