From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 18 Apr 2020 02:14:26 +0000 (+0200)
Subject: Add Diamond Chess
X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/images/assets/doc/current/%7B%7B%20pkg.url%20%7D%7D?a=commitdiff_plain;h=59e74176f5e2e828ce0b81c2ead6f8cdb0654f69;p=vchess.git

Add Diamond Chess
---

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 520c4823..e3fb36df 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -217,6 +217,7 @@ export const translations = {
   "Queen disguised as a pawn": "Queen disguised as a pawn",
   "Reuse pieces": "Reuse pieces",
   "Reverse captures": "Reverse captures",
+  "Rotating board": "Rotating board",
   "Run forward": "Run forward",
   "Score a goal": "Score a goal",
   "Seirawan-Harper Chess": "Seirawan-Harper Chess",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index f803df2c..556072bd 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -217,6 +217,7 @@ export const translations = {
   "Queen disguised as a pawn": "Reina disfrazada de peón",
   "Reuse pieces": "Reutilizar piezas",
   "Reverse captures": "Capturas invertidas",
+  "Rotating board": "Tablero giratorio",
   "Run forward": "Correr hacia adelante",
   "Score a goal": "Marcar una meta",
   "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index e73e2e50..eb345b8e 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -217,6 +217,7 @@ export const translations = {
   "Queen disguised as a pawn": "Reine déguisée en pion",
   "Reuse pieces": "Réutiliser les pièces",
   "Reverse captures": "Captures inversées",
+  "Rotating board": "Échiquier tournant",
   "Run forward": "Courir vers l'avant",
   "Score a goal": "Marquer un but",
   "Seirawan-Harper Chess": "Échecs Seirawan-Harper",
diff --git a/client/src/translations/rules/Diamond/en.pug b/client/src/translations/rules/Diamond/en.pug
new file mode 100644
index 00000000..95af3245
--- /dev/null
+++ b/client/src/translations/rules/Diamond/en.pug
@@ -0,0 +1,25 @@
+p.boxed
+  | The board is rotated by 45°, and then the game follow usual rules.
+
+p.
+  White (resp. Black) pawns move one square diagonally north-west: h1 to a8
+  (resp. south-east: a8 to h1).
+  White (resp. Black) pawns capture on the adjacent orthogonal squares either
+  up or to the left (resp. down or to the right).
+  This is natural after rotating the board by 45° clockwise,
+  explaining the name "Diamond Chess".
+
+p All other pieces move like in orthodox chess.
+
+figure.diagram-container
+  .diagram
+    | fen:8/1p6/1B4P1/8/8/3n4/3pP3/8 b6,c6,d2,f7:
+  figcaption Pawn moves.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/rules/diamond-chess") Diamond Chess
+  | &nbsp;on chessvariants.com.
+
+p Inventor: James Alexander Porterfield Rynd (1886)
diff --git a/client/src/translations/rules/Diamond/es.pug b/client/src/translations/rules/Diamond/es.pug
new file mode 100644
index 00000000..672283fa
--- /dev/null
+++ b/client/src/translations/rules/Diamond/es.pug
@@ -0,0 +1,26 @@
+p.boxed
+  | El tablero de ajedrez gira 45°, luego el juego sigue las reglas habituales.
+
+p.
+  Los peones blancos (resp. negros) avanzan un espacio en diagonal al noroeste:
+  h1 a a8 (resp. sureste: a8 a h1).
+  Los peones blancos (resp. Negro) capturan en un cuadrado ortogonalmente
+  adyacente ya sea arriba o a la izquierda (resp. abajo o hacia la derecha).
+  Es natural después de girar el tablero de ajedrez 45° en la dirección
+  cada hora, explicando el nombre "Ajedrez diamante".
+
+p Todas las otras piezas se mueven como en el ajedrez ortodoxo.
+
+figure.diagram-container
+  .diagram
+    | fen:8/1p6/1B4P1/8/8/3n4/3pP3/8 b6,c6,d2,f7:
+  figcaption Juagadas del peón.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/rules/diamond-chess") variante Diamond
+  | &nbsp;en chessvariants.com.
+
+p Inventor: James Alexander Porterfield Rynd (1886)
diff --git a/client/src/translations/rules/Diamond/fr.pug b/client/src/translations/rules/Diamond/fr.pug
new file mode 100644
index 00000000..a4dbb0ff
--- /dev/null
+++ b/client/src/translations/rules/Diamond/fr.pug
@@ -0,0 +1,26 @@
+p.boxed
+  | L'échiquier est tourné de 45°, puis la partie suit les règles habituelles.
+
+p.
+  Les pions blancs (resp. noirs) avancent d'une case en diagonale nord-ouest :
+  h1 vers a8 (resp. sud-est : a8 vers h1).
+  Les pions blancs (resp. noirs) capturent sur une case orthogonalement
+  adjacente soit au dessus soit vers la gauche (resp. en dessous ou vers la
+  droite). C'est naturel après avoir tourné l'échiquier de 45° dans le sens
+  horaire, expliquant le nom "Échecs Diamant".
+
+p Toutes les autres pièces se déplacent comme aux échecs orthodoxes.
+
+figure.diagram-container
+  .diagram
+    | fen:8/1p6/1B4P1/8/8/3n4/3pP3/8 b6,c6,d2,f7:
+  figcaption Coups de pion.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/rules/diamond-chess") variante Diamond
+  | &nbsp;sur chessvariants.com.
+
+p Inventeur : James Alexander Porterfield Rynd (1886)
diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js
index 8473b13e..a7f7afa7 100644
--- a/client/src/variants/Berolina.js
+++ b/client/src/variants/Berolina.js
@@ -129,15 +129,11 @@ export class BerolinaRules extends ChessRules {
 
   isAttackedByPawn([x, y], color) {
     let pawnShift = (color == "w" ? 1 : -1);
-    if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
-      if (
-        this.getPiece(x + pawnShift, y) == V.PAWN &&
-        this.getColor(x + pawnShift, y) == color
-      ) {
-        return true;
-      }
-    }
-    return false;
+    return (
+      x + pawnShift >= 0 && x + pawnShift < V.size.x &&
+      this.getPiece(x + pawnShift, y) == V.PAWN &&
+      this.getColor(x + pawnShift, y) == color
+    );
   }
 
   static get SEARCH_DEPTH() {
diff --git a/client/src/variants/Diamond.js b/client/src/variants/Diamond.js
new file mode 100644
index 00000000..2a3869ed
--- /dev/null
+++ b/client/src/variants/Diamond.js
@@ -0,0 +1,139 @@
+import { ChessRules } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { shuffle } from "@/utils/alea";
+
+export class DiamondRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "krbp4/rqnp4/nbpp4/pppp4/4PPPP/4PPBN/4PNQR/4PBRK w 0";
+    let pieces = { w: new Array(8), b: new Array(8) };
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+      // Get random squares for every piece, totally freely
+      let positions = shuffle(ArrayFun.range(8));
+      const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
+      const rem2 = positions[0] % 2;
+      if (rem2 == positions[1] % 2) {
+        // Fix bishops (on different colors)
+        for (let i=2; i<8; i++) {
+          if (positions[i] % 2 != rem2)
+            [positions[1], positions[i]] = [positions[i], positions[1]];
+        }
+      }
+      for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i];
+    }
+    return (
+      pieces["b"].slice(0, 3).join("") + "p4/" +
+      pieces["b"].slice(3, 6).join("") + "p4/" +
+      pieces["b"].slice(6, 8).join("") + "pp4/" +
+      "pppp4/4PPPP/" +
+      "4PP" + pieces["w"].slice(6, 8).reverse().join("").toUpperCase() + "/" +
+      "4P" + pieces["w"].slice(3, 6).reverse().join("").toUpperCase() + "/" +
+      "4P" + pieces["w"].slice(0, 3).reverse().join("").toUpperCase() +
+      " w 0"
+    );
+  }
+
+  // Special pawns movements
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const shift = (color == "w" ? -1 : 1);
+    const lastRank = (color == "w" ? 0 : 7);
+
+    // One square forward (diagonally along h1-a8)
+    if (this.board[x + shift][y + shift] == V.EMPTY) {
+      const finalPieces =
+        [x + shift, y + shift].includes(lastRank)
+          ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
+          : [V.PAWN];
+      for (let piece of finalPieces) {
+        moves.push(
+          this.getBasicMove(
+            [x, y], [x + shift, y + shift], { c: color, p: piece })
+        );
+      }
+    }
+    // Capture
+    for (let pShift of [[0, shift], [shift, 0]]) {
+      if (
+        this.board[x + pShift[0]][y + pShift[1]] != V.EMPTY &&
+        this.canTake([x, y], [x + pShift[0], y + pShift[1]])
+      ) {
+        const finalPieces =
+          [x + pShift[0], y + pShift[1]].includes(lastRank)
+            ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
+            : [V.PAWN];
+        for (let piece of finalPieces) {
+          moves.push(
+            this.getBasicMove(
+              [x, y],
+              [x + pShift[0], y + pShift[1]],
+              {
+                c: color,
+                p: piece
+              }
+            )
+          );
+        }
+      }
+    }
+
+    return moves;
+  }
+
+  isAttackedByPawn([x, y], color) {
+    let pawnShift = (color == "w" ? 1 : -1);
+    return (
+      (
+        x + pawnShift >= 0 && x + pawnShift < V.size.x &&
+        this.getPiece(x + pawnShift, y) == V.PAWN &&
+        this.getColor(x + pawnShift, y) == color
+      )
+      ||
+      (
+        y + pawnShift >= 0 && y + pawnShift < V.size.y &&
+        this.getPiece(x, y + pawnShift) == V.PAWN &&
+        this.getColor(x, y + pawnShift) == color
+      )
+    );
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+  getNotation(move) {
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN) {
+      // Pawn move
+      const finalSquare = V.CoordsToSquare(move.end);
+      let notation = "";
+      if (move.vanish.length == 2)
+        // Capture
+        notation = "Px" + finalSquare;
+      else {
+        // No capture: indicate the initial square for potential ambiguity
+        const startSquare = V.CoordsToSquare(move.start);
+        notation = startSquare + finalSquare;
+      }
+      if (move.appear[0].p != V.PAWN)
+        // Promotion
+        notation += "=" + move.appear[0].p.toUpperCase();
+      return notation;
+    }
+    return super.getNotation(move); //all other pieces are orthodox
+  }
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 48f97c03..21f7c7a8 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -32,6 +32,7 @@ insert or ignore into Variants (name, description) values
   ('Coregal', 'Two royal pieces'),
   ('Crazyhouse', 'Captures reborn'),
   ('Cylinder', 'Neverending rows'),
+  ('Diamond', 'Rotating board'),
   ('Doublearmy', '64 pieces on the board'),
   ('Doublemove1', 'Double moves (v1)'),
   ('Doublemove2', 'Double moves (v2)'),