From 0ba6420d3515e278b34c29e5afa1e58f6e08e9eb Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 25 Feb 2020 11:59:15 +0100
Subject: [PATCH] Draft Royalrace variant

---
 client/src/base_rules.js                      |   4 +-
 client/src/translations/en.js                 |   1 +
 client/src/translations/es.js                 |   1 +
 client/src/translations/fr.js                 |   1 +
 .../src/translations/rules/Royalrace/en.pug   |  12 +
 .../src/translations/rules/Royalrace/es.pug   |   1 +
 .../src/translations/rules/Royalrace/fr.pug   |   1 +
 client/src/variants/Circular.js               |   4 +-
 client/src/variants/Royalrace.js              | 211 ++++++++++++++++++
 server/db/populate.sql                        |   1 +
 10 files changed, 233 insertions(+), 4 deletions(-)
 create mode 100644 client/src/translations/rules/Royalrace/en.pug
 create mode 100644 client/src/translations/rules/Royalrace/es.pug
 create mode 100644 client/src/translations/rules/Royalrace/fr.pug
 create mode 100644 client/src/variants/Royalrace.js

diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index d5849f3b..4f1478e1 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -467,7 +467,7 @@ export const ChessRules = class ChessRules {
     return { x: 8, y: 8 };
   }
 
-  // Color of thing on suqare (i,j). 'undefined' if square is empty
+  // Color of thing on square (i,j). 'undefined' if square is empty
   getColor(i, j) {
     return this.board[i][j].charAt(0);
   }
@@ -543,7 +543,7 @@ export const ChessRules = class ChessRules {
   ////////////////////
   // MOVES GENERATION
 
-  // All possible moves from selected square (assumption: color is OK)
+  // All possible moves from selected square
   getPotentialMovesFrom([x, y]) {
     switch (this.getPiece(x, y)) {
       case V.PAWN:
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 963380a7..56b5bab3 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -141,6 +141,7 @@ export const translations = {
   "Explosive captures": "Explosive captures",
   "In the shadow": "In the shadow",
   "Keep antiking in check": "Keep antiking in check",
+  "King crosses the board": "King crosses the board",
   "Laws of attraction": "Laws of attraction",
   "Lose all pieces": "Lose all pieces",
   "Mate any piece": "Mate any piece",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 8b71fb4c..48eb21ec 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -141,6 +141,7 @@ export const translations = {
   "Explosive captures": "Capturas explosivas",
   "In the shadow": "En la sombra",
   "Keep antiking in check": "Mantener el antirey en jaque",
+  "King crosses the board": "El rey cruza el tablero",
   "Laws of attraction": "Las leyes de las atracciones",
   "Lose all pieces": "Perder todas las piezas",
   "Mate any piece": "Matar cualquier pieza",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 6d0f82d6..1e73e1e4 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -141,6 +141,7 @@ export const translations = {
   "Explosive captures": "Captures explosives",
   "In the shadow": "Dans l'ombre",
   "Keep antiking in check": "Gardez l'antiroi en échec",
+  "King crosses the board": "Le roi traverse l'échiquier",
   "Laws of attraction": "Les lois de l'attraction",
   "Lose all pieces": "Perdez toutes les pièces",
   "Mate any piece": "Mater n'importe quelle pièce",
diff --git a/client/src/translations/rules/Royalrace/en.pug b/client/src/translations/rules/Royalrace/en.pug
new file mode 100644
index 00000000..e9c796ad
--- /dev/null
+++ b/client/src/translations/rules/Royalrace/en.pug
@@ -0,0 +1,12 @@
+p.boxed
+  | Bring your king to the other side of the board.
+  | Giving check is not allowed.
+
+p TODO
+
+h3 Source
+
+p
+  | Strongly inspired by the Racing Kings variant which is playable for example
+  a(href="https://lichess.org/variant/racingKings") on lichess
+  | .
diff --git a/client/src/translations/rules/Royalrace/es.pug b/client/src/translations/rules/Royalrace/es.pug
new file mode 100644
index 00000000..4f56997b
--- /dev/null
+++ b/client/src/translations/rules/Royalrace/es.pug
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Royalrace/fr.pug b/client/src/translations/rules/Royalrace/fr.pug
new file mode 100644
index 00000000..4f56997b
--- /dev/null
+++ b/client/src/translations/rules/Royalrace/fr.pug
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/variants/Circular.js b/client/src/variants/Circular.js
index 4db5a52a..9ec597bd 100644
--- a/client/src/variants/Circular.js
+++ b/client/src/variants/Circular.js
@@ -170,9 +170,9 @@ export const VariantRules = class CircularRules extends ChessRules {
   }
 
   isAttackedByPawn([x, y], colors) {
+    const pawnShift = 1;
+    const attackerRow = V.ComputeX(x + pawnShift);
     for (let c of colors) {
-      let pawnShift = 1;
-      const attackerRow = V.ComputeX(x + pawnShift);
       for (let i of [-1, 1]) {
         if (
           y + i >= 0 &&
diff --git a/client/src/variants/Royalrace.js b/client/src/variants/Royalrace.js
new file mode 100644
index 00000000..1ae531d5
--- /dev/null
+++ b/client/src/variants/Royalrace.js
@@ -0,0 +1,211 @@
+import { ChessRules } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt, shuffle } from "@/utils/alea";
+
+export const VariantRules = class RoyalraceRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get CanFlip() {
+    return false;
+  }
+
+  static get size() {
+    return { x: 11, y: 11 };
+  }
+
+  static GenRandInitFen() {
+    let pieces = { w: new Array(10), b: new Array(10) };
+    // Shuffle pieces on first and second rank
+    for (let c of ["w", "b"]) {
+      // Reserve 4 and 5 which are pawns positions
+      let positions = ArrayFun.range(10).filter(i => i != 4 && i != 5);
+
+      // Get random squares for bishops
+      let randIndex = 2 * randInt(4);
+      const bishop1Pos = positions[randIndex];
+      // The second bishop must be on a square of different color
+      let randIndex_tmp = 2 * randInt(4) + 1;
+      const bishop2Pos = positions[randIndex_tmp];
+      // Remove chosen squares
+      positions.splice(Math.max(randIndex, randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex, randIndex_tmp), 1);
+
+      // Place the king at random on (remaining squares of) first row
+      let maxIndex = 4;
+      if (positions[maxIndex-1] >= 4)
+        maxIndex--;
+      if (positions[maxIndex-1] >= 4)
+        maxIndex--;
+      randIndex = randInt(maxIndex);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random squares for knights
+      randIndex = randInt(5);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(4);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random squares for rooks
+      randIndex = randInt(3);
+      const rook1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(2);
+      const rook2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Queen position is now determined,
+      // because pawns are not placed at random
+      const queenPos = positions[0];
+
+      // Finally put the shuffled pieces in the board array
+      pieces[c][rook1Pos] = "r";
+      pieces[c][knight1Pos] = "n";
+      pieces[c][bishop1Pos] = "b";
+      pieces[c][queenPos] = "q";
+      pieces[c][kingPos] = "k";
+      pieces[c][bishop2Pos] = "b";
+      pieces[c][knight2Pos] = "n";
+      pieces[c][rook2Pos] = "r";
+      pieces[c][4] = "p";
+      pieces[c][5] = "p";
+    }
+    const whiteFen = pieces["w"].join("").toUpperCase();
+    const blackFen = pieces["b"].join("");
+    return (
+      "11/11/11/11/11/11/11/11/11/" +
+      whiteFen.substr(5).split("").reverse().join("") +
+      "1" +
+      blackFen.substr(5).split("").reverse().join("") +
+      "/" +
+      whiteFen.substr(0,5) + "1" + blackFen.substr(0,5) +
+      " w 0"
+    );
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    // Normal moves (as a rook)
+    let moves =
+      this.getSlideNJumpMoves([x, y], V.steps[V.ROOK]).filter(m => {
+        // Remove captures. Alt: redefine canTake
+        return m.vanish.length == 1;
+      });
+
+    // Captures
+    const shiftX = -1;
+    for (let shiftY of [-1, 1]) {
+      if (
+        V.OnBoard(x + shiftX, y + shiftY) &&
+        this.board[x + shiftX][y + shiftY] != V.EMPTY &&
+        this.canTake([x, y], [x + shiftX, y + shiftY])
+      ) {
+        moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
+      }
+    }
+
+    return moves;
+  }
+
+  getPotentialKnightMoves(sq) {
+    // Knight becomes knightrider:
+    return this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT]);
+  }
+
+  // What are the king moves from square x,y ?
+  getPotentialKingMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  filterValid(moves) {
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    return moves.filter(m => {
+      this.play(m);
+      // Giving check is forbidden as well:
+      const res = !this.underCheck(color) && !this.underCheck(oppCol);
+      this.undo(m);
+      return res;
+    });
+  }
+
+  isAttackedByPawn([x, y], colors) {
+    const pawnShift = 1;
+    if (x + pawnShift < V.size.x) {
+      for (let c of colors) {
+        for (let i of [-1, 1]) {
+          if (
+            y + i >= 0 &&
+            y + i < V.size.y &&
+            this.getPiece(x + pawnShift, y + i) == V.PAWN &&
+            this.getColor(x + pawnShift, y + i) == c
+          ) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByKnight(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.KNIGHT,
+      V.steps[V.KNIGHT]
+    );
+  }
+
+  getCurrentScore() {
+    // Turn has changed:
+    const color = V.GetOppCol(this.turn);
+    if (this.kingPos[color][0] == 0)
+      // The opposing edge is reached!
+      return color == "w" ? "1-0" : "0-1";
+    return "*";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+  static get VALUES() {
+    return {
+      p: 2,
+      r: 5,
+      n: 3,
+      b: 3,
+      q: 9,
+      k: 1000
+    };
+  }
+
+  evalPosition() {
+    // Count material:
+    let evaluation = super.evalPosition();
+    // Ponder with king position:
+    return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0];
+  }
+
+  getNotation(move) {
+    // Since pawns are much more mobile, treat them as other pieces:
+    return (
+      move.vanish[0].p.toUpperCase() +
+      (move.vanish.length > move.appear.length ? "x" : "") +
+      V.CoordsToSquare(move.end)
+    );
+  }
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index e6291462..84a34d5d 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -22,6 +22,7 @@ insert or ignore into Variants (name,description) values
   ('Losers', 'Lose all pieces'),
   ('Magnetic', 'Laws of attraction'),
   ('Marseille', 'Move twice'),
+  ('Royalrace', 'King crosses the board'),
   ('Recycle', 'Reuse pieces'),
   ('Suction', 'Attract opposite king'),
   ('Upsidedown', 'Board upside down'),
-- 
2.44.0