Draft 2 new variants. Still 4 to add in this series
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 25 Feb 2020 23:24:14 +0000 (00:24 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 25 Feb 2020 23:24:14 +0000 (00:24 +0100)
26 files changed:
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Arena/en.pug [new file with mode: 0644]
client/src/translations/rules/Arena/es.pug [new file with mode: 0644]
client/src/translations/rules/Arena/fr.pug [new file with mode: 0644]
client/src/translations/rules/Chaturanga/en.pug [new file with mode: 0644]
client/src/translations/rules/Chaturanga/es.pug [new file with mode: 0644]
client/src/translations/rules/Chaturanga/fr.pug [new file with mode: 0644]
client/src/translations/rules/Check3/en.pug [new file with mode: 0644]
client/src/translations/rules/Check3/es.pug [new file with mode: 0644]
client/src/translations/rules/Check3/fr.pug [new file with mode: 0644]
client/src/translations/rules/Knightrelay/en.pug [new file with mode: 0644]
client/src/translations/rules/Knightrelay/es.pug [new file with mode: 0644]
client/src/translations/rules/Knightrelay/fr.pug [new file with mode: 0644]
client/src/translations/rules/Rifle/en.pug [new file with mode: 0644]
client/src/translations/rules/Rifle/es.pug [new file with mode: 0644]
client/src/translations/rules/Rifle/fr.pug [new file with mode: 0644]
client/src/translations/rules/Wormhole/en.pug [new file with mode: 0644]
client/src/translations/rules/Wormhole/es.pug [new file with mode: 0644]
client/src/translations/rules/Wormhole/fr.pug [new file with mode: 0644]
client/src/variants/Arena.js [new file with mode: 0644]
client/src/variants/Chaturanga.js [new file with mode: 0644]
client/src/variants/Royalrace.js
client/src/variants/Wormhole.js [new file with mode: 0644]
server/db/populate.sql

index 56b5bab..3c7cd7e 100644 (file)
@@ -126,6 +126,7 @@ export const translations = {
   "Your message": "Your message",
 
   // Variants boxes:
   "Your message": "Your message",
 
   // Variants boxes:
+  "Ancient rules": "Ancient rules",
   "Attract opposite king": "Attract opposite king",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
   "Big board": "Big board",
   "Attract opposite king": "Attract opposite king",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
   "Big board": "Big board",
@@ -136,15 +137,17 @@ export const translations = {
   "Captures reborn": "Captures reborn",
   "Change colors": "Change colors",
   "Dangerous collisions": "Dangerous collisions",
   "Captures reborn": "Captures reborn",
   "Change colors": "Change colors",
   "Dangerous collisions": "Dangerous collisions",
-  "Exchange pieces positions": "Exchange pieces positions",
   "Exotic captures": "Exotic captures",
   "Explosive captures": "Explosive captures",
   "In the shadow": "In the shadow",
   "Exotic captures": "Exotic captures",
   "Explosive captures": "Explosive captures",
   "In the shadow": "In the shadow",
+  "Give three checks": "Give three checks",
   "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",
   "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",
+  "Middle battle": "Middle battle",
+  "Move like a knight": "Move like a knight",
   "Move twice": "Move twice",
   "Neverending rows": "Neverending rows",
   "Pawns move diagonally": "Pawns move diagonally",
   "Move twice": "Move twice",
   "Neverending rows": "Neverending rows",
   "Pawns move diagonally": "Pawns move diagonally",
@@ -152,6 +155,8 @@ export const translations = {
   "Reverse captures": "Reverse captures",
   "Run forward": "Run forward",
   "Shared pieces": "Shared pieces",
   "Reverse captures": "Reverse captures",
   "Run forward": "Run forward",
   "Shared pieces": "Shared pieces",
+  "Shoot pieces": "Shoot pieces",
+  "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
   "Unidentified pieces": "Unidentified pieces"
 };
   "Standard rules": "Standard rules",
   "Unidentified pieces": "Unidentified pieces"
 };
index 48eb21e..1024f78 100644 (file)
@@ -126,6 +126,7 @@ export const translations = {
   "Your message": "Tu mensaje",
 
   // Variants boxes:
   "Your message": "Tu mensaje",
 
   // Variants boxes:
+  "Ancient rules": "Viejas reglas",
   "Attract opposite king": "Atraer al rey contrario",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
   "Big board": "Gran tablero",
   "Attract opposite king": "Atraer al rey contrario",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
   "Big board": "Gran tablero",
@@ -136,15 +137,17 @@ export const translations = {
   "Captures reborn": "Las capturas renacen",
   "Change colors": "Cambiar colores",
   "Dangerous collisions": "Colisiones peligrosas",
   "Captures reborn": "Las capturas renacen",
   "Change colors": "Cambiar colores",
   "Dangerous collisions": "Colisiones peligrosas",
-  "Exchange pieces positions": "Intercambiar las posiciones de las piezas",
   "Exotic captures": "Capturas exóticas",
   "Explosive captures": "Capturas explosivas",
   "In the shadow": "En la sombra",
   "Exotic captures": "Capturas exóticas",
   "Explosive captures": "Capturas explosivas",
   "In the shadow": "En la sombra",
+  "Give three checks": "Dar tres jaques",
   "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",
   "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",
+  "Middle battle": "Batalla media",
+  "Move like a knight": "Moverse como un caballo",
   "Move twice": "Mover dos veces",
   "Neverending rows": "Filas interminables",
   "Pawns move diagonally": "Peones se mueven en diagonal",
   "Move twice": "Mover dos veces",
   "Neverending rows": "Filas interminables",
   "Pawns move diagonally": "Peones se mueven en diagonal",
@@ -152,6 +155,8 @@ export const translations = {
   "Reverse captures": "Capturas invertidas",
   "Run forward": "Correr hacia adelante",
   "Shared pieces": "Piezas compartidas",
   "Reverse captures": "Capturas invertidas",
   "Run forward": "Correr hacia adelante",
   "Shared pieces": "Piezas compartidas",
+  "Shoot pieces": "Tirar de las piezas",
+  "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
   "Unidentified pieces": "Piezas no identificadas"
 };
   "Standard rules": "Reglas estandar",
   "Unidentified pieces": "Piezas no identificadas"
 };
index 1e73e1e..592e9a0 100644 (file)
@@ -126,6 +126,7 @@ export const translations = {
   "Your message": "Votre message",
 
   // Variants boxes:
   "Your message": "Votre message",
 
   // Variants boxes:
+  "Ancient rules": "Règles anciennes",
   "Attract opposite king": "Attirer le roi adverse",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
   "Big board": "Grand échiquier",
   "Attract opposite king": "Attirer le roi adverse",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
   "Big board": "Grand échiquier",
@@ -136,15 +137,17 @@ export const translations = {
   "Captures reborn": "Les captures renaissent",
   "Change colors": "Changer les couleurs",
   "Dangerous collisions": "Collisions dangeureuses",
   "Captures reborn": "Les captures renaissent",
   "Change colors": "Changer les couleurs",
   "Dangerous collisions": "Collisions dangeureuses",
-  "Exchange pieces positions": "Échangez les positions des pièces",
   "Exotic captures": "Captures exotiques",
   "Explosive captures": "Captures explosives",
   "In the shadow": "Dans l'ombre",
   "Exotic captures": "Captures exotiques",
   "Explosive captures": "Captures explosives",
   "In the shadow": "Dans l'ombre",
+  "Give three checks": "Donnez trois échecs",
   "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",
   "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",
+  "Middle battle": "Bataille du milieu",
+  "Move like a knight": "Bougez comme un cavalier",
   "Move twice": "Jouer deux coups",
   "Neverending rows": "Rangées sans fin",
   "Pawns move diagonally": "Les pions vont en diagonale",
   "Move twice": "Jouer deux coups",
   "Neverending rows": "Rangées sans fin",
   "Pawns move diagonally": "Les pions vont en diagonale",
@@ -152,6 +155,8 @@ export const translations = {
   "Reverse captures": "Captures inversées",
   "Run forward": "Courir vers l'avant",
   "Shared pieces": "Pièces partagées",
   "Reverse captures": "Captures inversées",
   "Run forward": "Courir vers l'avant",
   "Shared pieces": "Pièces partagées",
+  "Shoot pieces": "Tirez sur les pièces",
+  "Squares disappear": "Les cases disparaissent",
   "Standard rules": "Règles usuelles",
   "Unidentified pieces": "Pièces non identifiées"
 };
   "Standard rules": "Règles usuelles",
   "Unidentified pieces": "Pièces non identifiées"
 };
diff --git a/client/src/translations/rules/Arena/en.pug b/client/src/translations/rules/Arena/en.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Arena/es.pug b/client/src/translations/rules/Arena/es.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Arena/fr.pug b/client/src/translations/rules/Arena/fr.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Chaturanga/en.pug b/client/src/translations/rules/Chaturanga/en.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Chaturanga/es.pug b/client/src/translations/rules/Chaturanga/es.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Chaturanga/fr.pug b/client/src/translations/rules/Chaturanga/fr.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Check3/en.pug b/client/src/translations/rules/Check3/en.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Check3/es.pug b/client/src/translations/rules/Check3/es.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Check3/fr.pug b/client/src/translations/rules/Check3/fr.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Knightrelay/en.pug b/client/src/translations/rules/Knightrelay/en.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Knightrelay/es.pug b/client/src/translations/rules/Knightrelay/es.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Knightrelay/fr.pug b/client/src/translations/rules/Knightrelay/fr.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Rifle/en.pug b/client/src/translations/rules/Rifle/en.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Rifle/es.pug b/client/src/translations/rules/Rifle/es.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Rifle/fr.pug b/client/src/translations/rules/Rifle/fr.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Wormhole/en.pug b/client/src/translations/rules/Wormhole/en.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Wormhole/es.pug b/client/src/translations/rules/Wormhole/es.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/translations/rules/Wormhole/fr.pug b/client/src/translations/rules/Wormhole/fr.pug
new file mode 100644 (file)
index 0000000..4f56997
--- /dev/null
@@ -0,0 +1 @@
+p TODO
diff --git a/client/src/variants/Arena.js b/client/src/variants/Arena.js
new file mode 100644 (file)
index 0000000..b92be1e
--- /dev/null
@@ -0,0 +1,152 @@
+import { ChessRules } from "@/base_rules";
+
+export const VariantRules = class ArenaRules extends ChessRules {
+  static get hasFlags() {
+    return false;
+  }
+
+  static GenRandInitFen() {
+    return ChessRules.GenRandInitFen().replace("w 1111 -", "w -");
+  }
+
+  static InArena(x) {
+    return Math.abs(3.5 - x) <= 1.5;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const moves = super.getPotentialMovesFrom([x, y]);
+    // Eliminate moves which neither enter the arena or capture something
+    return moves.filter(m => {
+      const startInArena = V.InArena(m.start.x);
+      const endInArena = V.InArena(m.end.x);
+      return (
+        (startInArena && endInArena && m.vanish.length == 2) ||
+        (!startInArena && endInArena)
+      );
+    });
+
+    return moves;
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const shiftX = color == "w" ? -1 : 1;
+    const startRank = color == "w" ? sizeX - 2 : 1;
+
+    if (this.board[x + shiftX][y] == V.EMPTY) {
+      // One square forward
+      moves.push(this.getBasicMove([x, y], [x + shiftX, y]));
+      // Next condition because pawns on 1st rank can generally jump
+      if (
+        x == startRank &&
+        this.board[x + 2 * shiftX][y] == V.EMPTY
+      ) {
+        // Two squares jump
+        moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
+      }
+    }
+    // Captures
+    for (let shiftY of [-1, 1]) {
+      if (
+        y + shiftY >= 0 &&
+        y + shiftY < sizeY &&
+        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]));
+      }
+    }
+
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    if (
+      !!epSquare &&
+      epSquare.x == x + shiftX &&
+      Math.abs(epSquare.y - y) == 1
+    ) {
+      let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare.y,
+        p: "p",
+        c: this.getColor(x, epSquare.y)
+      });
+      moves.push(enpassantMove);
+    }
+
+    return moves;
+  }
+
+  getPotentialQueenMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+    ).filter(m => {
+      // Filter out moves longer than 3 squares
+      return Math.max(
+        Math.abs(m.end.x - m.start.x),
+        Math.abs(m.end.y - m.start.y)) <= 3;
+    });
+  }
+
+  getPotentialKingMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+    ).filter(m => {
+      // Filter out moves longer than 3 squares
+      return Math.max(
+        Math.abs(m.end.x - m.start.x),
+        Math.abs(m.end.y - m.start.y)) <= 3;
+    });
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  filterValid(moves) {
+    // No check conditions
+    return moves;
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    if (!this.atLeastOneMove())
+      // I cannot move anymore
+      return color == "w" ? "0-1" : "1-0";
+    // Win if the opponent has no more pieces left (in the Arena),
+    // (and/)or if he lost both his dukes.
+    let someUnitRemain = false;
+    let atLeastOneDuke = false;
+    let somethingInArena = false;
+    outerLoop: for (let i=0; i<V.size.x; i++) {
+      for (let j=0; j<V.size.y; j++) {
+        if (this.getColor(i,j) == color) {
+          someUnitRemain = true;
+          if (this.movesCount >= 2 && V.InArena(i)) {
+            somethingInArena = true;
+            if (atLeastOneDuke)
+              break outerLoop;
+          }
+          if ([V.QUEEN,V.KING].includes(this.getPiece(i,j))) {
+            atLeastOneDuke = true;
+            if (this.movesCount < 2 || somethingInArena)
+              break outerLoop;
+          }
+        }
+      }
+    }
+    if (
+      !someUnitRemain ||
+      !atLeastOneDuke ||
+      (this.movesCount >= 2 && !somethingInArena)
+    ) {
+      return color == "w" ? "0-1" : "1-0";
+    }
+    return "*";
+  }
+};
diff --git a/client/src/variants/Chaturanga.js b/client/src/variants/Chaturanga.js
new file mode 100644 (file)
index 0000000..a1b6228
--- /dev/null
@@ -0,0 +1,121 @@
+import { ChessRules } from "@/base_rules";
+
+export const VariantRules = class ChaturangaRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get ElephantSteps() {
+    return [
+      [-2, -2],
+      [-2, 2],
+      [2, -2],
+      [2, 2]
+    ];
+  }
+
+  static GenRandInitFen() {
+    return ChessRules.GenRandInitFen().replace("w 1111 -", "w");
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const shiftX = color == "w" ? -1 : 1;
+    const startRank = color == "w" ? sizeX - 2 : 1;
+    const lastRank = color == "w" ? 0 : sizeX - 1;
+    // Promotion in minister (queen) only:
+    const finalPiece = x + shiftX == lastRank ? V.QUEEN : V.PAWN;
+
+    if (this.board[x + shiftX][y] == V.EMPTY) {
+      // One square forward
+      moves.push(
+        this.getBasicMove([x, y], [x + shiftX, y], {
+          c: color,
+          p: finalPiece
+        })
+      );
+    }
+    // Captures
+    for (let shiftY of [-1, 1]) {
+      if (
+        y + shiftY >= 0 &&
+        y + shiftY < sizeY &&
+        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], {
+            c: color,
+            p: finalPiece
+          })
+        );
+      }
+    }
+
+    return moves;
+  }
+
+  getPotentialBishopMoves(sq) {
+    let moves = this.getSlideNJumpMoves(sq, V.ElephantSteps, "oneStep");
+    // Complete with "repositioning moves": like a queen, without capture
+    let repositioningMoves = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.BISHOP],
+      "oneStep"
+    ).filter(m => m.vanish.length == 1);
+    return moves.concat(repositioningMoves);
+  }
+
+  getPotentialQueenMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.BISHOP],
+      "oneStep"
+    );
+  }
+
+  getPotentialKingMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  isAttackedByBishop(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.BISHOP,
+      V.ElephantSteps,
+      "oneStep"
+    );
+  }
+
+  isAttackedByQueen(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.QUEEN,
+      V.steps[V.BISHOP],
+      "oneStep"
+    );
+  }
+
+  static get VALUES() {
+    return {
+      p: 1,
+      r: 5,
+      n: 3,
+      b: 2.5,
+      q: 2,
+      k: 1000
+    };
+  }
+};
index e3ed224..6f64d24 100644 (file)
@@ -178,7 +178,10 @@ export const VariantRules = class RoyalraceRules extends ChessRules {
     if (this.kingPos[color][0] == 0)
       // The opposing edge is reached!
       return color == "w" ? "1-0" : "0-1";
     if (this.kingPos[color][0] == 0)
       // The opposing edge is reached!
       return color == "w" ? "1-0" : "0-1";
-    return "*";
+    if (this.atLeastOneMove())
+      return "*";
+    // Stalemate (will probably never happen)
+    return "1/2";
   }
 
   static get SEARCH_DEPTH() {
   }
 
   static get SEARCH_DEPTH() {
diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js
new file mode 100644 (file)
index 0000000..964c5e4
--- /dev/null
@@ -0,0 +1,333 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt } from "@/utils/alea";
+
+// TODO:
+
+export const VariantRules = class HiddenRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  // Analyse in Hidden mode makes no sense
+  static get CanAnalyze() {
+    return false;
+  }
+
+  // Moves are revealed only when game ends
+  static get ShowMoves() {
+    return "none";
+  }
+
+  static get HIDDEN_DECODE() {
+    return {
+      s: "p",
+      t: "q",
+      u: "r",
+      c: "b",
+      o: "n",
+      l: "k"
+    };
+  }
+  static get HIDDEN_CODE() {
+    return {
+      p: "s",
+      q: "t",
+      r: "u",
+      b: "c",
+      n: "o",
+      k: "l"
+    };
+  }
+
+  // Turn a hidden piece or revealed piece into revealed piece:
+  static Decode(p) {
+    if (Object.keys(V.HIDDEN_DECODE).includes(p))
+      return V.HIDDEN_DECODE[p];
+    return p;
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat(Object.values(V.HIDDEN_CODE));
+  }
+
+  // Pieces can be hidden :)
+  getPiece(i, j) {
+    const piece = this.board[i][j].charAt(1);
+    if (Object.keys(V.HIDDEN_DECODE).includes(piece))
+      return V.HIDDEN_DECODE[piece];
+    return piece;
+  }
+
+  // Scan board for kings positions (no castling)
+  scanKingsRooks(fen) {
+    this.kingPos = { w: [-1, -1], b: [-1, -1] };
+    const fenRows = V.ParseFen(fen).position.split("/");
+    for (let i = 0; i < fenRows.length; i++) {
+      let k = 0; //column index on board
+      for (let j = 0; j < fenRows[i].length; j++) {
+        switch (fenRows[i].charAt(j)) {
+          case "k":
+          case "l":
+            this.kingPos["b"] = [i, k];
+            break;
+          case "K":
+          case "L":
+            this.kingPos["w"] = [i, k];
+            break;
+          default: {
+            const num = parseInt(fenRows[i].charAt(j));
+            if (!isNaN(num)) k += num - 1;
+          }
+        }
+        k++;
+      }
+    }
+  }
+
+  getPpath(b, color, score) {
+    if (Object.keys(V.HIDDEN_DECODE).includes(b[1])) {
+      // Supposed to be hidden.
+      if (score == "*" && (!color || color != b[0]))
+        return "Hidden/" + b[0] + "p";
+      // Else: condition OK to show the piece
+      return b[0] + V.HIDDEN_DECODE[b[1]];
+    }
+    // The piece is already not supposed to be hidden:
+    return b;
+  }
+
+  getBasicMove([sx, sy], [ex, ey], tr) {
+    if (
+      tr &&
+      Object.keys(V.HIDDEN_DECODE).includes(this.board[sx][sy].charAt(1))
+    ) {
+      // The transformed piece is a priori hidden
+      tr.p = V.HIDDEN_CODE[tr.p];
+    }
+    let mv = new Move({
+      appear: [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: tr ? tr.c : this.getColor(sx, sy),
+          p: tr ? tr.p : this.board[sx][sy].charAt(1)
+        })
+      ],
+      vanish: [
+        new PiPo({
+          x: sx,
+          y: sy,
+          c: this.getColor(sx, sy),
+          p: this.board[sx][sy].charAt(1)
+        })
+      ]
+    });
+
+    // The opponent piece disappears if we take it
+    if (this.board[ex][ey] != V.EMPTY) {
+      mv.vanish.push(
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: this.getColor(ex, ey),
+          p: this.board[ex][ey].charAt(1)
+        })
+      );
+      // Pieces are revealed when they capture
+      mv.appear[0].p = V.Decode(mv.appear[0].p);
+    }
+
+    return mv;
+  }
+
+  // What are the king moves from square x,y ?
+  getPotentialKingMoves(sq) {
+    // No castling:
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  static GenRandInitFen() {
+    let pieces = { w: new Array(8), b: new Array(8) };
+    // Shuffle pieces + pawns on two first ranks
+    for (let c of ["w", "b"]) {
+      let positions = ArrayFun.range(16);
+
+      // Get random squares for bishops
+      let randIndex = 2 * randInt(8);
+      const bishop1Pos = positions[randIndex];
+      // The second bishop must be on a square of different color
+      let randIndex_tmp = 2 * randInt(8) + 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);
+
+      // Get random squares for knights
+      randIndex = randInt(14);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(13);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random squares for rooks
+      randIndex = randInt(12);
+      const rook1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(11);
+      const rook2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for queen
+      randIndex = randInt(10);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for king
+      randIndex = randInt(9);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Pawns position are all remaining slots:
+      for (let p of positions)
+        pieces[c][p] = "s";
+
+      // Finally put the shuffled pieces in the board array
+      pieces[c][rook1Pos] = "u";
+      pieces[c][knight1Pos] = "o";
+      pieces[c][bishop1Pos] = "c";
+      pieces[c][queenPos] = "t";
+      pieces[c][kingPos] = "l";
+      pieces[c][bishop2Pos] = "c";
+      pieces[c][knight2Pos] = "o";
+      pieces[c][rook2Pos] = "u";
+    }
+    let upFen = pieces["b"].join("");
+    upFen = upFen.substr(0,8) + "/" + upFen.substr(8).split("").reverse().join("");
+    let downFen = pieces["b"].join("").toUpperCase();
+    downFen = downFen.substr(0,8) + "/" + downFen.substr(8).split("").reverse().join("");
+    return upFen + "/8/8/8/8/" + downFen + " w 0";
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  updateVariables(move) {
+    super.updateVariables(move);
+    if (
+      move.vanish.length >= 2 &&
+      [V.KING,V.HIDDEN_CODE[V.KING]].includes(move.vanish[1].p)
+    ) {
+      // We took opponent king
+      this.kingPos[this.turn] = [-1, -1];
+    }
+  }
+
+  unupdateVariables(move) {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if (this.kingPos[oppCol][0] < 0)
+      // Last move took opponent's king:
+      this.kingPos[oppCol] = [move.vanish[1].x, move.vanish[1].y];
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0)
+      // King disappeared
+      return color == "w" ? "0-1" : "1-0";
+    // Assume that stalemate is impossible:
+    return "*";
+  }
+
+  getComputerMove() {
+    const color = this.turn;
+    let moves = this.getAllValidMoves();
+    for (let move of moves) {
+      move.eval = 0; //a priori...
+
+      // Can I take something ? If yes, do it with some probability
+      if (move.vanish.length == 2 && move.vanish[1].c != color) {
+        // OK this isn't a castling move
+        const myPieceVal = V.VALUES[move.appear[0].p];
+        const hisPieceVal = Object.keys(V.HIDDEN_DECODE).includes(move.vanish[1].p)
+          ? undefined
+          : V.VALUES[move.vanish[1].p];
+        if (!hisPieceVal) {
+          // Opponent's piece is unknown: do not take too much risk
+          move.eval = -myPieceVal + 1.5; //so that pawns always take
+        }
+        // Favor captures
+        else if (myPieceVal <= hisPieceVal)
+          move.eval = hisPieceVal - myPieceVal + 1;
+        else {
+          // Taking a pawn with minor piece,
+          // or minor piece or pawn with a rook,
+          // or anything but a queen with a queen,
+          // or anything with a king.
+          move.eval = hisPieceVal - myPieceVal;
+        }
+      } else {
+        // If no capture, favor small step moves,
+        // but sometimes move the knight anyway
+        const penalty = V.Decode(move.vanish[0].p) != V.KNIGHT
+          ? Math.abs(move.end.x - move.start.x) + Math.abs(move.end.y - move.start.y)
+          : (Math.random() < 0.5 ? 3 : 1);
+        move.eval -= penalty / (V.size.x + V.size.y - 1);
+      }
+
+      // TODO: also favor movements toward the center?
+    }
+
+    moves.sort((a, b) => b.eval - a.eval);
+    let candidates = [0];
+    for (let j = 1; j < moves.length && moves[j].eval == moves[0].eval; j++)
+      candidates.push(j);
+    return moves[candidates[randInt(candidates.length)]];
+  }
+
+  getNotation(move) {
+    // Translate final square
+    const finalSquare = V.CoordsToSquare(move.end);
+
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN) {
+      // Pawn move
+      let notation = "";
+      if (move.vanish.length > move.appear.length) {
+        // Capture
+        const startColumn = V.CoordToColumn(move.start.y);
+        notation = startColumn + "x" + finalSquare;
+      }
+      else notation = finalSquare;
+      if (move.appear.length > 0 && !["p","s"].includes(move.appear[0].p)) {
+        // Promotion
+        const appearPiece = V.Decode(move.appear[0].p);
+        notation += "=" + appearPiece.toUpperCase();
+      }
+      return notation;
+    }
+    // Piece movement
+    return (
+      piece.toUpperCase() +
+      (move.vanish.length > move.appear.length ? "x" : "") +
+      finalSquare
+    );
+  }
+};
index 84a34d5..7043996 100644 (file)
@@ -5,11 +5,14 @@ insert or ignore into Variants (name,description) values
   ('Allmate', 'Mate any piece'),
   ('Antiking', 'Keep antiking in check'),
   ('Antimatter', 'Dangerous collisions'),
   ('Allmate', 'Mate any piece'),
   ('Antiking', 'Keep antiking in check'),
   ('Antimatter', 'Dangerous collisions'),
+  ('Arena', 'Middle battle'),
   ('Atomic', 'Explosive captures'),
   ('Baroque', 'Exotic captures'),
   ('Benedict', 'Change colors'),
   ('Berolina', 'Pawns move diagonally'),
   ('Atomic', 'Explosive captures'),
   ('Baroque', 'Exotic captures'),
   ('Benedict', 'Change colors'),
   ('Berolina', 'Pawns move diagonally'),
+  ('Chaturanga', 'Ancient rules'),
   ('Checkered', 'Shared pieces'),
   ('Checkered', 'Shared pieces'),
+  ('Check3', 'Give three checks'),
   ('Chess960', 'Standard rules'),
   ('Circular', 'Run forward'),
   ('Crazyhouse', 'Captures reborn'),
   ('Chess960', 'Standard rules'),
   ('Circular', 'Run forward'),
   ('Crazyhouse', 'Captures reborn'),
@@ -19,12 +22,15 @@ insert or ignore into Variants (name,description) values
   ('Extinction', 'Capture all of a kind'),
   ('Grand', 'Big board'),
   ('Hidden', 'Unidentified pieces'),
   ('Extinction', 'Capture all of a kind'),
   ('Grand', 'Big board'),
   ('Hidden', 'Unidentified pieces'),
+  ('Knightrelay', 'Move like a knight'),
   ('Losers', 'Lose all pieces'),
   ('Magnetic', 'Laws of attraction'),
   ('Marseille', 'Move twice'),
   ('Losers', 'Lose all pieces'),
   ('Magnetic', 'Laws of attraction'),
   ('Marseille', 'Move twice'),
+  ('Rifle', 'Shoot pieces'),
   ('Royalrace', 'King crosses the board'),
   ('Recycle', 'Reuse pieces'),
   ('Suction', 'Attract opposite king'),
   ('Upsidedown', 'Board upside down'),
   ('Wildebeest', 'Balanced sliders & leapers'),
   ('Royalrace', 'King crosses the board'),
   ('Recycle', 'Reuse pieces'),
   ('Suction', 'Attract opposite king'),
   ('Upsidedown', 'Board upside down'),
   ('Wildebeest', 'Balanced sliders & leapers'),
+  ('Wormhole', 'Squares disappear'),
   ('Zen', 'Reverse captures');
   ('Zen', 'Reverse captures');