Add Knightmate2: two kings, as in Spartan Chess
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 19 Mar 2021 09:30:53 +0000 (10:30 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 19 Mar 2021 09:30:53 +0000 (10:30 +0100)
18 files changed:
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Clorange/en.pug
client/src/translations/rules/Clorange/es.pug
client/src/translations/rules/Clorange/fr.pug
client/src/translations/rules/Knightmate1/en.pug [moved from client/src/translations/rules/Knightmate/en.pug with 100% similarity]
client/src/translations/rules/Knightmate1/es.pug [moved from client/src/translations/rules/Knightmate/es.pug with 100% similarity]
client/src/translations/rules/Knightmate1/fr.pug [moved from client/src/translations/rules/Knightmate/fr.pug with 96% similarity]
client/src/translations/rules/Knightmate2/en.pug [new file with mode: 0644]
client/src/translations/rules/Knightmate2/es.pug [new file with mode: 0644]
client/src/translations/rules/Knightmate2/fr.pug [new file with mode: 0644]
client/src/translations/variants/en.pug
client/src/translations/variants/es.pug
client/src/translations/variants/fr.pug
client/src/variants/Knightmate1.js [moved from client/src/variants/Knightmate.js with 92% similarity]
client/src/variants/Knightmate2.js [new file with mode: 0644]
server/db/populate.sql

index 5a5461d..e3f361c 100644 (file)
@@ -259,7 +259,8 @@ export const translations = {
   "Mandatory captures": "Mandatory captures",
   "Mate any piece (v1)": "Mate any piece (v1)",
   "Mate any piece (v2)": "Mate any piece (v2)",
   "Mandatory captures": "Mandatory captures",
   "Mate any piece (v1)": "Mate any piece (v1)",
   "Mate any piece (v2)": "Mate any piece (v2)",
-  "Mate the knight": "Mate the knight",
+  "Mate the knight (v1)": "Mate the knight (v1)",
+  "Mate the knight (v2)": "Mate the knight (v2)",
   "Meet the Mammoth": "Meet the Mammoth",
   "Middle battle": "Middle battle",
   "Mind control (v1)": "Mind control (v1)",
   "Meet the Mammoth": "Meet the Mammoth",
   "Middle battle": "Middle battle",
   "Mind control (v1)": "Mind control (v1)",
index f868d26..3869b5d 100644 (file)
@@ -259,7 +259,8 @@ export const translations = {
   "Mandatory captures": "Capturas obligatorias",
   "Mate any piece (v1)": "Matar cualquier pieza (v1)",
   "Mate any piece (v2)": "Matar cualquier pieza (v2)",
   "Mandatory captures": "Capturas obligatorias",
   "Mate any piece (v1)": "Matar cualquier pieza (v1)",
   "Mate any piece (v2)": "Matar cualquier pieza (v2)",
-  "Mate the knight": "Matar el caballo",
+  "Mate the knight (v1)": "Matar el caballo (v1)",
+  "Mate the knight (v2)": "Matar el caballo (v2)",
   "Meet the Mammoth": "Conoce al Mamut",
   "Middle battle": "Batalla media",
   "Mind control (v1)": "Control telepático(v1)",
   "Meet the Mammoth": "Conoce al Mamut",
   "Middle battle": "Batalla media",
   "Mind control (v1)": "Control telepático(v1)",
index b0a9216..e75273a 100644 (file)
@@ -259,7 +259,8 @@ export const translations = {
   "Mandatory captures": "Captures obligatoires",
   "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
   "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)",
   "Mandatory captures": "Captures obligatoires",
   "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
   "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)",
-  "Mate the knight": "Matez le cavalier",
+  "Mate the knight (v1)": "Matez le cavalier (v1)",
+  "Mate the knight (v2)": "Matez le cavalier (v2)",
   "Meet the Mammoth": "Rencontrez le Mammouth",
   "Middle battle": "Bataille du milieu",
   "Mind control (v1)": "Contrôle télépathique (v1)",
   "Meet the Mammoth": "Rencontrez le Mammouth",
   "Middle battle": "Bataille du milieu",
   "Mind control (v1)": "Contrôle télépathique (v1)",
index 26679f5..695b676 100644 (file)
@@ -23,7 +23,6 @@ figure.diagram-container
 h3 Source
 
 p
 h3 Source
 
 p
-  | Slightly simplified from 
   a(href="https://www.chessvariants.com/other.dir/clockworkorange.html")
     | Clockwork Orange Chess
   | &nbsp;on chessvariants.com.
   a(href="https://www.chessvariants.com/other.dir/clockworkorange.html")
     | Clockwork Orange Chess
   | &nbsp;on chessvariants.com.
index a5a8c83..ab73e35 100644 (file)
@@ -26,7 +26,6 @@ figure.diagram-container
 h3 Fuente
 
 p
 h3 Fuente
 
 p
-  | Ligeramente simplificado desde 
   a(href="https://www.chessvariants.com/other.dir/clockworkorange.html")
     | Clockwork Orange Chess
   | &nbsp;en chessvariants.com.
   a(href="https://www.chessvariants.com/other.dir/clockworkorange.html")
     | Clockwork Orange Chess
   | &nbsp;en chessvariants.com.
index 1e8a406..44888fa 100644 (file)
@@ -26,7 +26,6 @@ figure.diagram-container
 h3 Source
 
 p
 h3 Source
 
 p
-  | Légèrement simplifié depuis 
   a(href="https://www.chessvariants.com/other.dir/clockworkorange.html")
     | Clockwork Orange Chess
   | &nbsp;sur chessvariants.com.
   a(href="https://www.chessvariants.com/other.dir/clockworkorange.html")
     | Clockwork Orange Chess
   | &nbsp;sur chessvariants.com.
@@ -1,6 +1,6 @@
 p.boxed
   | Le roi se déplace comme un cavalier, et les cavaliers comme des rois.
 p.boxed
   | Le roi se déplace comme un cavalier, et les cavaliers comme des rois.
-  | l'objectif est encore de mater le roi.
+  | L'objectif est encore de mater le roi.
 
 p.
   Les "cavaliers se déplaçant comme des rois" sont alors assez logiquement
 
 p.
   Les "cavaliers se déplaçant comme des rois" sont alors assez logiquement
diff --git a/client/src/translations/rules/Knightmate2/en.pug b/client/src/translations/rules/Knightmate2/en.pug
new file mode 100644 (file)
index 0000000..dd759e5
--- /dev/null
@@ -0,0 +1,19 @@
+p.boxed
+  | Kings move like knights, and knights move like kings.
+
+p
+  a(href="/#/variants/Knightmate1") Knightmate1
+  | , with two kings (moving like knights) and without castling.
+
+p.
+  As long as a side has two kings, they are considered like non-royal pieces
+  and one can be captured. Then, the remaining king is royal.
+
+figure.diagram-container
+  .diagram
+    | fen:r6r/1pp1qpp1/p1pcbk1p/2b1p1K1/4P3/2KPBP2/PPPQC1PP/R6R:
+  figcaption g5 king is mated, but the game is not over.
+
+p.
+  Checkmating both kings at the same time also counts as a win:
+  if both are under attacks, you must remove at least one attack.
diff --git a/client/src/translations/rules/Knightmate2/es.pug b/client/src/translations/rules/Knightmate2/es.pug
new file mode 100644 (file)
index 0000000..bf0157f
--- /dev/null
@@ -0,0 +1,20 @@
+p.boxed
+  | Los reyes se mueven como caballos y los caballos como reyes.
+
+p
+  a(href="/#/variants/Knightmate1") Knightmate1
+  | , con dos reyes (muevense como caballos) y sin enroque.
+
+p.
+  Siempre que un lado tenga sus dos reyes, se consideran piezas normales:
+  se puede capturar uno de los dos. Entonces el rey restante
+  tiene estatus real.
+
+figure.diagram-container
+  .diagram
+    | fen:r6r/1pp1qpp1/p1pcbk1p/2b1p1K1/4P3/2KPBP2/PPPQC1PP/R6R:
+  figcaption El rey g5 está atrapado, pero el juego continúa.
+
+p.
+  Matar a ambos reyes al mismo tiempo también gana:
+  si ambos son atacados, debes reprimir al menos un ataque.
diff --git a/client/src/translations/rules/Knightmate2/fr.pug b/client/src/translations/rules/Knightmate2/fr.pug
new file mode 100644 (file)
index 0000000..efff223
--- /dev/null
@@ -0,0 +1,20 @@
+p.boxed
+  | Les rois se déplacent comme des cavaliers, et les cavaliers comme des rois.
+
+p
+  a(href="/#/variants/Knightmate1") Knightmate1
+  | , avec deux rois (aux déplacements cavaliers) et sans roque.
+
+p.
+  Tant qu'un camp a ses deux rois, ils sont considérés comme des pièces
+  normales : l'un des deux peut être capturé. Ensuite, le roi restant
+  a un statut royal.
+
+figure.diagram-container
+  .diagram
+    | fen:r6r/1pp1qpp1/p1pcbk1p/2b1p1K1/4P3/2KPBP2/PPPQC1PP/R6R:
+  figcaption Le roi g5 est maté, mais la partie continue.
+
+p.
+  Mater les deux rois en même temps gagne également :
+  si les deux sont attaqués, vous devez supprimez au moins une attaque.
index e5e92f8..15a8aa5 100644 (file)
@@ -90,6 +90,7 @@ p Standard pieces versus a team of different pieces.
     "Empire",
     "Horde",
     "Orda",
     "Empire",
     "Horde",
     "Orda",
+    "Shinobi",
     "Spartan",
     "Synochess"
   ]
     "Spartan",
     "Synochess"
   ]
@@ -249,7 +250,8 @@ p.
 -
   var varlist = [
     "Balaklava",
 -
   var varlist = [
     "Balaklava",
-    "Knightmate",
+    "Knightmate1",
+    "Knightmate2",
     "Knightrelay1",
     "Knightrelay2"
   ]
     "Knightrelay1",
     "Knightrelay2"
   ]
@@ -313,7 +315,7 @@ ul
 
 h3 Repositioning
 
 
 h3 Repositioning
 
-p Pieces can be drop on the board, either immediately or later in the game.
+p Pieces can be dropped on the board, either immediately or later in the game.
 -
   var varlist = [
     "Clorange",
 -
   var varlist = [
     "Clorange",
@@ -321,7 +323,6 @@ p Pieces can be drop on the board, either immediately or later in the game.
     "Madhouse",
     "Rampage",
     "Recycle",
     "Madhouse",
     "Rampage",
     "Recycle",
-    "Shinobi",
     "Shogun",
     "Teleport"
   ]
     "Shogun",
     "Teleport"
   ]
index a09ce9d..cc85500 100644 (file)
@@ -94,6 +94,7 @@ p Piezas estándar contra un equipo de diferentes piezas.
     "Empire",
     "Horde",
     "Orda",
     "Empire",
     "Horde",
     "Orda",
+    "Shinobi",
     "Spartan",
     "Synochess"
   ]
     "Spartan",
     "Synochess"
   ]
@@ -256,7 +257,8 @@ p.
 -
   var varlist = [
     "Balaklava",
 -
   var varlist = [
     "Balaklava",
-    "Knightmate",
+    "Knightmate1",
+    "Knightmate2",
     "Knightrelay1",
     "Knightrelay2"
   ]
     "Knightrelay1",
     "Knightrelay2"
   ]
@@ -330,7 +332,6 @@ p.
     "Madhouse",
     "Rampage",
     "Recycle",
     "Madhouse",
     "Rampage",
     "Recycle",
-    "Shinobi",
     "Shogun",
     "Teleport"
   ]
     "Shogun",
     "Teleport"
   ]
index 2737859..f23512e 100644 (file)
@@ -93,6 +93,7 @@ p Pièces standard contre une équipe de pièces différentes.
     "Empire",
     "Horde",
     "Orda",
     "Empire",
     "Horde",
     "Orda",
+    "Shinobi",
     "Spartan",
     "Synochess"
   ]
     "Spartan",
     "Synochess"
   ]
@@ -255,7 +256,8 @@ p.
 -
   var varlist = [
     "Balaklava",
 -
   var varlist = [
     "Balaklava",
-    "Knightmate",
+    "Knightmate1",
+    "Knightmate2",
     "Knightrelay1",
     "Knightrelay2"
   ]
     "Knightrelay1",
     "Knightrelay2"
   ]
@@ -329,7 +331,6 @@ p.
     "Madhouse",
     "Rampage",
     "Recycle",
     "Madhouse",
     "Rampage",
     "Recycle",
-    "Shinobi",
     "Shogun",
     "Teleport"
   ]
     "Shogun",
     "Teleport"
   ]
similarity index 92%
rename from client/src/variants/Knightmate.js
rename to client/src/variants/Knightmate1.js
index bc886c1..80e7005 100644 (file)
@@ -1,8 +1,6 @@
 import { ChessRules } from "@/base_rules";
 import { ChessRules } from "@/base_rules";
-import { ArrayFun } from "@/utils/array";
-import { randInt } from "@/utils/alea";
 
 
-export class KnightmateRules extends ChessRules {
+export class Knightmate1Rules extends ChessRules {
 
   static get COMMONER() {
     return "c";
 
   static get COMMONER() {
     return "c";
diff --git a/client/src/variants/Knightmate2.js b/client/src/variants/Knightmate2.js
new file mode 100644 (file)
index 0000000..c90fb0c
--- /dev/null
@@ -0,0 +1,194 @@
+import { ChessRules } from "@/base_rules";
+
+export class Knightmate2Rules extends ChessRules {
+
+  static get HasFlags() {
+    return false;
+  }
+
+  static get COMMONER() {
+    return "c";
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.COMMONER]);
+  }
+
+  getPpath(b) {
+    return ([V.KING, V.COMMONER].includes(b[1]) ? "Knightmate/" : "") + b;
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kings = { "k": 0, "K": 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], 10);
+          if (isNaN(num) || num <= 0) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // 1 or 2 kings should be on board.
+    if (Object.values(kings).some(k => ![1, 2].includes(k))) return false;
+    return true;
+  }
+
+  scanKings() {}
+
+  static GenRandInitFen(randomness) {
+    return (
+      ChessRules.GenRandInitFen(randomness)
+      .replace(/k/g, 'c').replace(/K/g, 'C')
+      .replace(/n/g, 'k').replace(/N/g, 'K')
+    );
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    switch (this.getPiece(x, y)) {
+      case V.COMMONER:
+        return this.getPotentialCommonerMoves([x, y]);
+      default:
+        return super.getPotentialMovesFrom([x, y]);
+    }
+  }
+
+  getPotentialCommonerMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  getPotentialKingMoves(sq) {
+    return super.getPotentialKnightMoves(sq);
+  }
+
+  isAttacked(sq, color) {
+    return (
+      this.isAttackedByCommoner(sq, color) ||
+      this.isAttackedByPawn(sq, color) ||
+      this.isAttackedByRook(sq, color) ||
+      this.isAttackedByBishop(sq, color) ||
+      this.isAttackedByQueen(sq, color) ||
+      this.isAttackedByKing(sq, color)
+    );
+  }
+
+  isAttackedByKing(sq, color) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      color,
+      V.KING,
+      V.steps[V.KNIGHT],
+      "oneStep"
+    );
+  }
+
+  isAttackedByCommoner(sq, color) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      color,
+      V.COMMONER,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  postPlay() {}
+  postUndo() {}
+
+  // NOTE: 4 next functions (almost) copy-paste from Spartan Chess
+  getKingsPos(color) {
+    let kings = [];
+    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) == color &&
+          this.getPiece(i, j) == V.KING
+        ) {
+          kings.push({ x: i, y: j });
+        }
+      }
+    }
+    return kings;
+  }
+
+  getCheckSquares() {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const kings = this.getKingsPos(color);
+    let res = [];
+    for (let i of [0, 1]) {
+      if (
+        kings.length >= i+1 &&
+        super.isAttacked([kings[i].x, kings[i].y], oppCol)
+      ) {
+        res.push([kings[i].x, kings[i].y]);
+      }
+    }
+    return res;
+  }
+
+  filterValid(moves) {
+    if (moves.length == 0) return [];
+    const color = moves[0].vanish[0].c;
+    const oppCol = V.GetOppCol(color);
+    // Check if both kings under attack.
+    // If yes, moves must remove at least one attack.
+    const kings = this.getKingsPos(color);
+    return moves.filter(m => {
+      this.play(m);
+      let attacks = 0;
+      for (let k of kings) {
+        const curKingPos =
+          this.board[k.x][k.y] == V.EMPTY
+            ? [m.appear[0].x, m.appear[0].y] //king moved
+            : [k.x, k.y]
+        if (super.isAttacked(curKingPos, oppCol)) attacks++;
+        else break; //no need to check further
+      }
+      this.undo(m);
+      return (
+        (kings.length == 2 && attacks <= 1) ||
+        (kings.length == 1 && attacks == 0)
+      );
+    });
+  }
+
+  getCurrentScore() {
+    if (super.atLeastOneMove()) return "*";
+    // Count kings on board
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const kings = this.getKingsPos(color);
+    if (
+      super.isAttacked([kings[0].x, kings[0].y], oppCol) ||
+      (kings.length == 2 && super.isAttacked([kings[1].x, kings[1].y], oppCol))
+    ) {
+      return (color == 'w' ? "0-1" : "1-0");
+    }
+    return "1/2"; //stalemate
+  }
+
+  static get VALUES() {
+    return {
+      p: 1,
+      r: 5,
+      c: 5, //the commoner is valuable
+      b: 3,
+      q: 9,
+      k: 1000
+    };
+  }
+
+};
index 58af05d..f9722f6 100644 (file)
@@ -92,7 +92,8 @@ insert or ignore into Variants (name, description) values
   ('Karouk', 'Thai Chess (v3)'),
   ('Kinglet', 'Protect your pawns'),
   ('Kingsmaker', 'Promote into kings'),
   ('Karouk', 'Thai Chess (v3)'),
   ('Kinglet', 'Protect your pawns'),
   ('Kingsmaker', 'Promote into kings'),
-  ('Knightmate', 'Mate the knight'),
+  ('Knightmate1', 'Mate the knight (v1)'),
+  ('Knightmate2', 'Mate the knight (v2)'),
   ('Knightpawns', 'Knight versus pawns'),
   ('Knightrelay1', 'Move like a knight (v1)'),
   ('Knightrelay2', 'Move like a knight (v2)'),
   ('Knightpawns', 'Knight versus pawns'),
   ('Knightrelay1', 'Move like a knight (v1)'),
   ('Knightrelay2', 'Move like a knight (v2)'),