From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sun, 5 Apr 2020 22:47:00 +0000 (+0200)
Subject: Add Checkered1 + fix last move highlights
X-Git-Url: https://git.auder.net/variants/Chakart/%7B%7B%20asset%28%27mixstore/images/scripts/DESCRIPTION?a=commitdiff_plain;h=af34341d92d47d14f396e7f4adb81f2a7e9d9a61;p=vchess.git

Add Checkered1 + fix last move highlights
---

diff --git a/TODO b/TODO
index c3dddb58..a4ffcfd7 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,15 @@
 Chakart :)
 
-Checkered1 & 2 --> avec switch, coup roi sur roi.
+// mode analyse + charger rules dans page modal
+
++ bouton analyse en vert !
+// TODO: rules button in Game Page :: modal just show rules text, easy
+// // TODO: analyse mode in Game Page :: just stay, pass in analyze, easy
+// // --> indicateur isConnected: vérifie aussi que pas en mode analyse ?! non...
+// // Attention si coup reçu pendant mode analyse faut d'abord sortir du mode
+// // (qu'on soit joueur ou spectateur)
+
+Ball --> capture ballon prend à distance ?! bof, si on a ballon et capture ennemi : lui passe la balle ?
 
 Ambiguous chess
 https://www.chessvariants.com/mvopponent.dir/ambiguous-chess.html
@@ -9,6 +18,6 @@ Also in notation, show only dest square for "opponents ambiguous moves".
 Similar to Marseille-chess, white first move only 1 subTurn, do not choose a square.
 Then black has a free first move, then choose a square, and the process goes on.
 
-(Note: could simplify Marseille FEN, just indicate turn)
-
-[Sittuyin (burmese chess) --> probably not]
+Sittuyin (burmese chess)
+https://github.com/gbtami/pychess-variants/tree/master/static/images/pieces/sittuyin
+https://drive.google.com/open?id=1HIPJLJBU2F6R6vxGziU6j8MHoHu_Kmab
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index 167be91f..734af025 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -263,7 +263,8 @@ export const ChessRules = class ChessRules {
   }
 
   // On which squares is color under check ? (for interface)
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     return (
       this.underCheck(color)
         // kingPos must be duplicated, because it may change:
@@ -468,7 +469,7 @@ export const ChessRules = class ChessRules {
       return;
     const fenParsed = V.ParseFen(fen);
     this.board = V.GetBoard(fenParsed.position);
-    this.turn = fenParsed.turn[0]; //[0] to work with MarseilleRules
+    this.turn = fenParsed.turn;
     this.movesCount = parseInt(fenParsed.movesCount);
     this.setOtherVariables(fen);
   }
@@ -703,7 +704,7 @@ export const ChessRules = class ChessRules {
   // Consider all potential promotions:
   addPawnMoves([x1, y1], [x2, y2], moves, promotions) {
     let finalPieces = [V.PAWN];
-    const color = this.turn;
+    const color = this.turn; //this.getColor(x1, y1);
     const lastRank = (color == "w" ? 0 : V.size.x - 1);
     if (x2 == lastRank) {
       // promotions arg: special override for Hiddenqueen variant
@@ -720,7 +721,7 @@ export const ChessRules = class ChessRules {
 
   // What are the pawn moves from square x,y ?
   getPotentialPawnMoves([x, y], promotions) {
-    const color = this.turn;
+    const color = this.turn; //this.getColor(x, y);
     const [sizeX, sizeY] = [V.size.x, V.size.y];
     const pawnShiftX = V.PawnSpecs.directions[color];
     const firstRank = (color == "w" ? sizeX - 1 : 0);
@@ -1246,10 +1247,11 @@ export const ChessRules = class ChessRules {
     return 3;
   }
 
-  getComputerMove() {
+  // 'movesList' arg for some variants to provide a custom list
+  getComputerMove(movesList) {
     const maxeval = V.INFINITY;
     const color = this.turn;
-    let moves1 = this.getAllValidMoves();
+    let moves1 = movesList || this.getAllValidMoves();
 
     if (moves1.length == 0)
       // TODO: this situation should not happen
diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue
index 7156670c..572c80ad 100644
--- a/client/src/components/BaseGame.vue
+++ b/client/src/components/BaseGame.vue
@@ -207,15 +207,21 @@ export default {
       const firstMoveColor = parsedFen.turn;
       this.firstMoveNumber = Math.floor(parsedFen.movesCount / 2) + 1;
       let L = this.moves.length;
-      this.moves.forEach(move => {
+      this.moves.forEach((move,idx) => {
         // Strategy working also for multi-moves:
         if (!Array.isArray(move)) move = [move];
-        move.forEach((m,idx) => {
+        const Lm = move.length;
+        move.forEach((m,idxM) => {
           m.notation = this.vr.getNotation(m);
           m.unambiguous = V.GetUnambiguousNotation(m);
           this.vr.play(m);
-          if (idx < L - 1 && this.vr.getCheckSquares(this.vr.turn).length > 0)
-            m.notation += "+";
+          const checkSquares = this.vr.getCheckSquares();
+          if (checkSquares.length > 0) m.notation += "+";
+          if (idx == L - 1 && idxM == Lm - 1) {
+            this.incheck = checkSquares;
+            const score = this.vr.getCurrentScore();
+            if (["1-0", "0-1"].includes(score)) m.notation += "#";
+          }
         });
       });
       if (firstMoveColor == "b") {
@@ -229,25 +235,13 @@ export default {
         });
         L++;
       }
-      this.positionCursorTo(this.moves.length - 1);
-      this.incheck = this.vr.getCheckSquares(this.vr.turn);
-      const score = this.vr.getCurrentScore();
-      if (L > 0 && this.moves[L - 1].notation != "...") {
-        if (["1-0","0-1"].includes(score)) this.moves[L - 1].notation += "#";
-        else if (this.incheck.length > 0) this.moves[L - 1].notation += "+";
-      }
+      this.positionCursorTo(L - 1);
     },
     positionCursorTo: function(index) {
       this.cursor = index;
-      // Caution: last move in moves array might be a multi-move
-      if (index >= 0) {
-        if (Array.isArray(this.moves[index])) {
-          const L = this.moves[index].length;
-          this.lastMove = this.moves[index][L - 1];
-        } else {
-          this.lastMove = this.moves[index];
-        }
-      } else this.lastMove = null;
+      // Note: last move in moves array might be a multi-move
+      if (index >= 0) this.lastMove = this.moves[index];
+      else this.lastMove = null;
     },
     analyzePosition: function() {
       let newUrl =
@@ -378,10 +372,15 @@ export default {
     // For Analyse mode:
     emitFenIfAnalyze: function() {
       if (this.game.mode == "analyze") {
-        this.$emit(
-          "fenchange",
-          !!this.lastMove ? this.lastMove.fen : this.game.fenStart
-        );
+        let fen = this.game.fenStart;
+        if (!!this.lastMove) {
+          if (Array.isArray(this.lastMove)) {
+            const L = this.lastMove.length;
+            fen = this.lastMove[L-1].fen;
+          }
+          else fen = this.lastMove.fen;
+        }
+        this.$emit("fenchange", fen);
       }
     },
     clickSquare: function(square) {
@@ -408,10 +407,17 @@ export default {
         smove.notation = this.vr.getNotation(smove);
         smove.unambiguous = V.GetUnambiguousNotation(smove);
         this.vr.play(smove);
-        this.lastMove = smove;
+        if (!!this.lastMove) {
+          if (!Array.isArray(this.lastMove))
+            this.lastMove = [this.lastMove, smove];
+          else this.lastMove.push(smove);
+        }
         // Is opponent (or me) in check?
-        this.incheck = this.vr.getCheckSquares(this.vr.turn);
+        this.incheck = this.vr.getCheckSquares();
+        if (this.incheck.length > 0) smove.notation += "+";
         if (!this.inMultimove) {
+          // First sub-move:
+          this.lastMove = smove;
           // Condition is "!navigate" but we mean "!this.autoplay"
           if (!navigate) {
             if (this.cursor < this.moves.length - 1)
@@ -458,8 +464,13 @@ export default {
       const computeScore = () => {
         const score = this.vr.getCurrentScore();
         if (!navigate) {
-          if (["1-0","0-1"].includes(score)) this.lastMove.notation += "#";
-          else if (this.incheck.length > 0) this.lastMove.notation += "+";
+          if (["1-0","0-1"].includes(score)) {
+            if (Array.isArray(this.lastMove)) {
+              const L = this.lastMove.length;
+              this.lastMove[L - 1].notation += "#";
+            }
+            else this.lastMove.notation += "#";
+          }
         }
         if (score != "*" && this.game.mode == "analyze") {
           const message = getScoreMessage(score);
@@ -503,8 +514,8 @@ export default {
           if (!Array.isArray(move)) move = [move];
           for (let i=0; i < move.length; i++) this.vr.play(move[i]);
           if (!light) {
-            this.lastMove = move[move.length-1];
-            this.incheck = this.vr.getCheckSquares(this.vr.turn);
+            this.lastMove = move;
+            this.incheck = this.vr.getCheckSquares();
             this.score = computeScore();
             this.emitFenIfAnalyze();
           }
@@ -548,7 +559,7 @@ export default {
       this.$refs["board"].resetCurrentAttempt();
       if (this.inMultimove) {
         this.cancelCurrentMultimove();
-        this.incheck = this.vr.getCheckSquares(this.vr.turn);
+        this.incheck = this.vr.getCheckSquares();
       } else {
         if (!move) {
           const minCursor =
@@ -563,7 +574,7 @@ export default {
         if (light) this.cursor--;
         else {
           this.positionCursorTo(this.cursor - 1);
-          this.incheck = this.vr.getCheckSquares(this.vr.turn);
+          this.incheck = this.vr.getCheckSquares();
           this.emitFenIfAnalyze();
         }
       }
@@ -584,7 +595,7 @@ export default {
       }
       // NOTE: next line also re-assign cursor, but it's very light
       this.positionCursorTo(index);
-      this.incheck = this.vr.getCheckSquares(this.vr.turn);
+      this.incheck = this.vr.getCheckSquares();
       this.emitFenIfAnalyze();
     },
     gotoBegin: function() {
@@ -597,7 +608,7 @@ export default {
           : 0;
       while (this.cursor >= minCursor) this.undo(null, null, "light");
       this.lastMove = (minCursor == 1 ? this.moves[0] : null);
-      this.incheck = this.vr.getCheckSquares(this.vr.turn);
+      this.incheck = this.vr.getCheckSquares();
       this.emitFenIfAnalyze();
     },
     gotoEnd: function() {
diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue
index 6c26722a..e6a0aafd 100644
--- a/client/src/components/Board.vue
+++ b/client/src/components/Board.vue
@@ -54,7 +54,16 @@ export default {
       incheckSq[sq[0]][sq[1]] = true;
     });
 
-    const lm = this.lastMove;
+    let lm = this.lastMove;
+    // Precompute lastMove highlighting squares
+    const lmHighlights = {};
+    if (!!lm) {
+      if (!Array.isArray(lm)) lm = [lm];
+      lm.forEach(m => {
+        lmHighlights[m.start.x + sizeX * m.start.y] = true;
+        lmHighlights[m.end.x + sizeX * m.end.y] = true;
+      });
+    }
     const showLight = (
       this.settings.highlight &&
       ["all","highlight"].includes(V.ShowMoves)
@@ -74,9 +83,7 @@ export default {
       );
     };
     const inHighlight = (x, y) => {
-      return showLight && !!lm && (
-        (lm.end.x == x && lm.end.y == y) ||
-        (lm.start.x == x && lm.start.y == y));
+      return showLight && !!lmHighlights[x + sizeX * y];
     };
     const inShadow = (x, y) => {
       return (
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 20d8f40d..0aa822f1 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -214,7 +214,8 @@ export const translations = {
   "Run forward": "Run forward",
   "Score a goal": "Score a goal",
   "Seirawan-Harper Chess": "Seirawan-Harper Chess",
-  "Shared pieces": "Shared pieces",
+  "Shared pieces (v1)": "Shared pieces (v1)",
+  "Shared pieces (v2)": "Shared pieces (v2)",
   "Shoot pieces": "Shoot pieces",
   "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index fd37ad0e..4c2bb994 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -214,7 +214,8 @@ export const translations = {
   "Run forward": "Correr hacia adelante",
   "Score a goal": "Marcar una meta",
   "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper",
-  "Shared pieces": "Piezas compartidas",
+  "Shared pieces (v1)": "Piezas compartidas (v1)",
+  "Shared pieces (v2)": "Piezas compartidas (v2)",
   "Shoot pieces": "Tirar de las piezas",
   "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index f2dd3ab5..663d4b8a 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -214,7 +214,8 @@ export const translations = {
   "Run forward": "Courir vers l'avant",
   "Score a goal": "Marquer un but",
   "Seirawan-Harper Chess": "Échecs Seirawan-Harper",
-  "Shared pieces": "Pièces partagées",
+  "Shared pieces (v1)": "Pièces partagées (v1)",
+  "Shared pieces (v2)": "Pièces partagées (v2)",
   "Shoot pieces": "Tirez sur les pièces",
   "Squares disappear": "Les cases disparaissent",
   "Standard rules": "Règles usuelles",
diff --git a/client/src/translations/rules/Checkered/en.pug b/client/src/translations/rules/Checkered1/en.pug
similarity index 94%
rename from client/src/translations/rules/Checkered/en.pug
rename to client/src/translations/rules/Checkered1/en.pug
index ca0af4da..4103caa3 100644
--- a/client/src/translations/rules/Checkered/en.pug
+++ b/client/src/translations/rules/Checkered1/en.pug
@@ -57,8 +57,6 @@ ul
     Checkered pawns cannot capture en passant, because while the pawn was
     "passing" they were of the same color.
 
-p.bold.bigfont If you wanna play, you can stop reading here.
-
 h3 Stalemate or checkmate?
 
 p.
@@ -82,8 +80,6 @@ p.
 
 h2.stageDelimiter Stage 2
 
-p.warn This stage is not (and probably will never be) implemented.
-
 p.
   During the game one of the two players can decide to take control of the
   checkered pieces.
@@ -92,7 +88,12 @@ p.
   tries to eliminate checkered pieces.
   The checkered side wins by checkmating either the white or black king.
 
-h4 Variant of stage 2
+p.
+  To take the checkered pieces, play a move that "takes opponent's king" with
+  your king, and then execute a move. This is possible only if no such move
+  can capture a king.
+
+h4 Variant of stage 2 (unimplemented)
 p.
   An observer could decide to join the game by taking the checkered pieces
   at any moment.
diff --git a/client/src/translations/rules/Checkered/es.pug b/client/src/translations/rules/Checkered1/es.pug
similarity index 94%
rename from client/src/translations/rules/Checkered/es.pug
rename to client/src/translations/rules/Checkered1/es.pug
index 0c66d662..eb1b26f6 100644
--- a/client/src/translations/rules/Checkered/es.pug
+++ b/client/src/translations/rules/Checkered1/es.pug
@@ -56,8 +56,6 @@ ul
     Los peones a cuadros no pueden capturar en passant, ya que mientras
     el peón contrario "pasó" estaban en el mismo campamento.
 
-p.bold.bigfont Para jugar, puedes dejar de leer aquí.
-
 h3 ¿Jaque mate o empate?
 
 p.
@@ -81,8 +79,6 @@ p.
 
 h2.stageDelimiter Fase 2
 
-p.warn Este paso no se implementa (y probablemente nunca se implementará) aquí.
-
 p.
   Durante el juego, uno de los dos jugadores puede decidir tomar
   las piezas a cuadros. Estos luego se vuelven autónomos
@@ -91,7 +87,12 @@ p.
   con el objetivo de eliminar el campo a cuadros.
   Este último puede ganar si jaque mate a uno de los dos reyes.
 
-h4 Variante de la fase 2
+p.
+  Para controlar las piezas astilladas, haz un movimiento que "tome al rey
+  oponente" con tu rey. Esto solo es posible si tal movimiento no puede
+  capturar a un rey.
+
+h4 Variante de la fase 2 (no implementado)
 
 p.
   Un observador puede en cualquier momento decidir unirse a la partida,
diff --git a/client/src/translations/rules/Checkered/fr.pug b/client/src/translations/rules/Checkered1/fr.pug
similarity index 94%
rename from client/src/translations/rules/Checkered/fr.pug
rename to client/src/translations/rules/Checkered1/fr.pug
index 1acf6ae0..380f15ce 100644
--- a/client/src/translations/rules/Checkered/fr.pug
+++ b/client/src/translations/rules/Checkered1/fr.pug
@@ -61,8 +61,6 @@ ul
     Les pions échiquetés ne peuvent capturer en passant, puisque pendant que
     le pion adverse "passait" ils étaient dans le même camp.
 
-p.bold.bigfont Pour jouer, vous pouvez arrêter de lire ici.
-
 h3 Mat ou pat ?
 
 p.
@@ -85,8 +83,6 @@ p.
 
 h2.stageDelimiter Phase 2
 
-p.warn Cette étape n'est pas (et ne sera probablement jamais) implémentée ici.
-
 p.
   Au cours de la partie l'un des deux joueurs peut décider de prendre le
   contrôle des pièces échiquetées. Celles-ci deviennent alors autonomes
@@ -95,7 +91,12 @@ p.
   avec pour but l'élimination du camp échiqueté.
   Ce dernier peut gagner en matant l'un des deux rois.
 
-h4 Variante de la phase 2
+p.
+  Pour contrôler les pièces échiquetées, jouez un coup qui "prend le roi
+  adverse" avec votre roi, puis exécutez un coup. C'est possible seulement
+  si aucun tel coup ne peut capturer un roi.
+
+h4 Variante de la phase 2 (non implémentée)
 p.
   Un observateur peut à tout moment décider de se joindre à la partie
   en contrôlant le camp échiqueté. On se retrouve alors dans un jeu à trois
diff --git a/client/src/translations/rules/Checkered2/en.pug b/client/src/translations/rules/Checkered2/en.pug
new file mode 100644
index 00000000..4c004a35
--- /dev/null
+++ b/client/src/translations/rules/Checkered2/en.pug
@@ -0,0 +1,10 @@
+p.boxed
+  | The capture of an enemy piece produces a new "checkered" piece belonging
+  | to both players.
+
+p
+  | This is the 
+  a(href="/variants/Checkered1") Checkered1 variant
+  | , without the stage 2.
+  | Probably more drawish, but also quite different.
+  | I think both are interesting.
diff --git a/client/src/translations/rules/Checkered2/es.pug b/client/src/translations/rules/Checkered2/es.pug
new file mode 100644
index 00000000..a93f5790
--- /dev/null
+++ b/client/src/translations/rules/Checkered2/es.pug
@@ -0,0 +1,10 @@
+p.boxed
+  | La captura de una pieza enemiga da lugar al nacimiento de una pieza
+  | "a cuadros", que pertenece a ambos campos.
+
+p
+  | Esta la 
+  a(href="/variants/Checkered1") variante Checkered1
+  | , sin fase 2.
+  | Probablemente más canceladoras, pero también bastante diferente.
+  | Creo que ambos tienen interés.
diff --git a/client/src/translations/rules/Checkered2/fr.pug b/client/src/translations/rules/Checkered2/fr.pug
new file mode 100644
index 00000000..5b5de767
--- /dev/null
+++ b/client/src/translations/rules/Checkered2/fr.pug
@@ -0,0 +1,10 @@
+p.boxed
+  | La capture d'une pièce ennemie donne lieu à la naissance d'une pièce
+  | "échiquetée", qui appartient aux deux camps.
+
+p
+  | C'est la 
+  a(href="/variants/Checkered1") variante Checkered1
+  | , sans la phase 2.
+  | Sans doute plus annulant, mais aussi assez différente.
+  | Je pense que les deux ont un intérêt.
diff --git a/client/src/variants/Alice.js b/client/src/variants/Alice.js
index e193bbd2..d6fb0ac6 100644
--- a/client/src/variants/Alice.js
+++ b/client/src/variants/Alice.js
@@ -235,7 +235,8 @@ export class AliceRules extends ChessRules {
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     const pieces = Object.keys(V.ALICE_CODES);
     const kp = this.kingPos[color];
     const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
diff --git a/client/src/variants/Antiking1.js b/client/src/variants/Antiking1.js
index ac28c31c..ab86f69b 100644
--- a/client/src/variants/Antiking1.js
+++ b/client/src/variants/Antiking1.js
@@ -176,7 +176,8 @@ export class Antiking1Rules extends BerolinaRules {
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let res = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
diff --git a/client/src/variants/Antiking2.js b/client/src/variants/Antiking2.js
index 2dad5e49..a7ec2a83 100644
--- a/client/src/variants/Antiking2.js
+++ b/client/src/variants/Antiking2.js
@@ -117,7 +117,8 @@ export class Antiking2Rules extends ChessRules {
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let res = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
diff --git a/client/src/variants/Apocalypse.js b/client/src/variants/Apocalypse.js
index c136ff28..4587be3b 100644
--- a/client/src/variants/Apocalypse.js
+++ b/client/src/variants/Apocalypse.js
@@ -428,7 +428,7 @@ export class ApocalypseRules extends ChessRules {
     else this.whiteMove = move.whiteMove;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
     return [];
   }
 
diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js
index 90089e8b..9675c58a 100644
--- a/client/src/variants/Atomic.js
+++ b/client/src/variants/Atomic.js
@@ -127,7 +127,8 @@ export class AtomicRules extends ChessRules {
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let res = [];
     if (
       this.kingPos[color][0] >= 0 && //king might have exploded
diff --git a/client/src/variants/Ball.js b/client/src/variants/Ball.js
index e3c8e5ec..e4a0b2fa 100644
--- a/client/src/variants/Ball.js
+++ b/client/src/variants/Ball.js
@@ -126,7 +126,7 @@ export class BallRules extends ChessRules {
     );
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
     return [];
   }
 
diff --git a/client/src/variants/Checkered1.js b/client/src/variants/Checkered1.js
new file mode 100644
index 00000000..561f02f1
--- /dev/null
+++ b/client/src/variants/Checkered1.js
@@ -0,0 +1,703 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class Checkered1Rules extends ChessRules {
+  static board2fen(b) {
+    const checkered_codes = {
+      p: "s",
+      q: "t",
+      r: "u",
+      b: "c",
+      n: "o"
+    };
+    if (b[0] == "c") return checkered_codes[b[1]];
+    return ChessRules.board2fen(b);
+  }
+
+  static fen2board(f) {
+    // Tolerate upper-case versions of checkered pieces (why not?)
+    const checkered_pieces = {
+      s: "p",
+      S: "p",
+      t: "q",
+      T: "q",
+      u: "r",
+      U: "r",
+      c: "b",
+      C: "b",
+      o: "n",
+      O: "n"
+    };
+    if (Object.keys(checkered_pieces).includes(f))
+      return "c" + checkered_pieces[f];
+    return ChessRules.fen2board(f);
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat(["s", "t", "u", "c", "o"]);
+  }
+
+  getPpath(b) {
+    return (b[0] == "c" ? "Checkered/" : "") + b;
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    // Local stack of non-capturing checkered moves:
+    this.cmoves = [];
+    const cmove = V.ParseFen(fen).cmove;
+    if (cmove == "-") this.cmoves.push(null);
+    else {
+      this.cmoves.push({
+        start: ChessRules.SquareToCoords(cmove.substr(0, 2)),
+        end: ChessRules.SquareToCoords(cmove.substr(2))
+      });
+    }
+    // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
+    const stageInfo = V.ParseFen(fen).stage;
+    this.stage = parseInt(stageInfo[0]);
+    this.sideCheckered = (this.stage == 2 ? stageInfo[1] : undefined);
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParts = fen.split(" ");
+    if (fenParts.length != 7) return false;
+    if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/))
+      return false;
+    if (!fenParts[6].match(/^[12][wb]?$/)) return false;
+    return true;
+  }
+
+  static IsGoodFlags(flags) {
+    // 4 for castle + 16 for pawns
+    return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/);
+  }
+
+  setFlags(fenflags) {
+    super.setFlags(fenflags); //castleFlags
+    this.pawnFlags = {
+      w: [...Array(8)], //pawns can move 2 squares?
+      b: [...Array(8)]
+    };
+    const flags = fenflags.substr(4); //skip first 4 letters, for castle
+    for (let c of ["w", "b"]) {
+      for (let i = 0; i < 8; i++)
+        this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1";
+    }
+  }
+
+  aggregateFlags() {
+    return [this.castleFlags, this.pawnFlags];
+  }
+
+  disaggregateFlags(flags) {
+    this.castleFlags = flags[0];
+    this.pawnFlags = flags[1];
+  }
+
+  getEpSquare(moveOrSquare) {
+    // At stage 2, all pawns can be captured en-passant
+    if (
+      this.stage == 2 ||
+      typeof moveOrSquare !== "object" ||
+      (moveOrSquare.appear.length > 0 && moveOrSquare.appear[0].c != 'c')
+    )
+      return super.getEpSquare(moveOrSquare);
+    // Checkered or switch move: no en-passant
+    return undefined;
+  }
+
+  getCmove(move) {
+    // No checkered move to undo at stage 2:
+    if (this.stage == 1 && move.vanish.length == 1 && move.appear[0].c == "c")
+      return { start: move.start, end: move.end };
+    return null;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    const color1 = this.getColor(x1, y1);
+    const color2 = this.getColor(x2, y2);
+    if (this.stage == 2) {
+      // Black & White <-- takes --> Checkered
+      const color1 = this.getColor(x1, y1);
+      const color2 = this.getColor(x2, y2);
+      return color1 != color2 && [color1, color2].includes('c');
+    }
+    // Checkered aren't captured
+    return (
+      color1 != color2 &&
+      color2 != "c" &&
+      (color1 != "c" || color2 != this.turn)
+    );
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let standardMoves = super.getPotentialMovesFrom([x, y]);
+    if (this.stage == 1) {
+      const color = this.turn;
+      // Post-processing: apply "checkerization" of standard moves
+      const lastRank = (color == "w" ? 0 : 7);
+      let moves = [];
+      // King is treated differently: it never turn checkered
+      if (this.getPiece(x, y) == V.KING) {
+        // If at least one checkered piece, allow switching:
+        if (this.board.some(b => b.some(cell => cell[0] == 'c'))) {
+          const oppCol = V.GetOppCol(color);
+          moves.push(
+            new Move({
+              start: { x: x, y: y },
+              end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] },
+              appear: [],
+              vanish: []
+            })
+          );
+        }
+        return standardMoves.concat(moves);
+      }
+      standardMoves.forEach(m => {
+        if (m.vanish[0].p == V.PAWN) {
+          if (
+            Math.abs(m.end.x - m.start.x) == 2 &&
+            !this.pawnFlags[this.turn][m.start.y]
+          ) {
+            return; //skip forbidden 2-squares jumps
+          }
+          if (
+            this.board[m.end.x][m.end.y] == V.EMPTY &&
+            m.vanish.length == 2 &&
+            this.getColor(m.start.x, m.start.y) == "c"
+          ) {
+            return; //checkered pawns cannot take en-passant
+          }
+        }
+        if (m.vanish.length == 1)
+          // No capture
+          moves.push(m);
+        else {
+          // A capture occured (m.vanish.length == 2)
+          m.appear[0].c = "c";
+          moves.push(m);
+          if (
+            // Avoid promotions (already treated):
+            m.appear[0].p != m.vanish[1].p &&
+            (m.vanish[0].p != V.PAWN || m.end.x != lastRank)
+          ) {
+            // Add transformation into captured piece
+            let m2 = JSON.parse(JSON.stringify(m));
+            m2.appear[0].p = m.vanish[1].p;
+            moves.push(m2);
+          }
+        }
+      });
+      return moves;
+    }
+    return standardMoves;
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    const color = this.getColor(x, y);
+    if (this.stage == 2) {
+      const saveTurn = this.turn;
+      if (this.sideCheckered == this.turn) {
+        // Cannot change PawnSpecs.bidirectional, so cheat a little:
+        this.turn = 'w';
+        const wMoves = super.getPotentialPawnMoves([x, y]);
+        this.turn = 'b';
+        const bMoves = super.getPotentialPawnMoves([x, y]);
+        this.turn = saveTurn;
+        return wMoves.concat(bMoves);
+      }
+      // Playing with both colors:
+      this.turn = color;
+      const moves = super.getPotentialPawnMoves([x, y]);
+      this.turn = saveTurn;
+      return moves;
+    }
+    let moves = super.getPotentialPawnMoves([x, y]);
+    // Post-process: set right color for checkered moves
+    if (color == 'c') {
+      moves.forEach(m => {
+        m.appear[0].c = 'c'; //may be done twice if capture
+        m.vanish[0].c = 'c';
+      });
+    }
+    return moves;
+  }
+
+  canIplay(side, [x, y]) {
+    if (this.stage == 2) {
+      const color = this.getColor(x, y);
+      return (
+        this.turn == this.sideCheckered
+          ? color == 'c'
+          : ['w', 'b'].includes(color)
+      );
+    }
+    return side == this.turn && [side, "c"].includes(this.getColor(x, y));
+  }
+
+  // Does m2 un-do m1 ? (to disallow undoing checkered moves)
+  oppositeMoves(m1, m2) {
+    return (
+      !!m1 &&
+      m2.appear[0].c == "c" &&
+      m2.appear.length == 1 &&
+      m2.vanish.length == 1 &&
+      m1.start.x == m2.end.x &&
+      m1.end.x == m2.start.x &&
+      m1.start.y == m2.end.y &&
+      m1.end.y == m2.start.y
+    );
+  }
+
+  filterValid(moves) {
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const L = this.cmoves.length; //at least 1: init from FEN
+    const stage = this.stage; //may change if switch
+    return moves.filter(m => {
+      // Checkered cannot be under check (no king)
+      if (stage == 2 && this.sideCheckered == color) return true;
+      this.play(m);
+      let res = true;
+      if (stage == 1) {
+        if (m.appear.length == 0 && m.vanish.length == 0) {
+          // Special "switch" move: kings must not be attacked by checkered.
+          // Not checking for oppositeMoves here: checkered are autonomous
+          res = (
+            !this.isAttacked(this.kingPos['w'], ['c']) &&
+            !this.isAttacked(this.kingPos['b'], ['c']) &&
+            this.getAllPotentialMoves().length > 0
+          );
+        }
+        else res = !this.oppositeMoves(this.cmoves[L - 1], m);
+      }
+      if (res && m.appear.length > 0) res = !this.underCheck(color);
+      // At stage 2, side with B & W can be undercheck with both kings:
+      if (res && stage == 2) res = !this.underCheck(oppCol);
+      this.undo(m);
+      return res;
+    });
+  }
+
+  getAllPotentialMoves() {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let potentialMoves = [];
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        const colIJ = this.getColor(i, j);
+        if (
+          this.board[i][j] != V.EMPTY &&
+          (
+            (this.stage == 1 && colIJ != oppCol) ||
+            (this.stage == 2 &&
+              (
+                (this.sideCheckered == color && colIJ == 'c') ||
+                (this.sideCheckered != color && ['w', 'b'].includes(colIJ))
+              )
+            )
+          )
+        ) {
+          Array.prototype.push.apply(
+            potentialMoves,
+            this.getPotentialMovesFrom([i, j])
+          );
+        }
+      }
+    }
+    return potentialMoves;
+  }
+
+  atLeastOneMove() {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        const colIJ = this.getColor(i, j);
+        if (
+          this.board[i][j] != V.EMPTY &&
+          (
+            (this.stage == 1 && colIJ != oppCol) ||
+            (this.stage == 2 &&
+              (
+                (this.sideCheckered == color && colIJ == 'c') ||
+                (this.sideCheckered != color && ['w', 'b'].includes(colIJ))
+              )
+            )
+          )
+        ) {
+          const moves = this.getPotentialMovesFrom([i, j]);
+          if (moves.length > 0) {
+            for (let k = 0; k < moves.length; k++)
+              if (this.filterValid([moves[k]]).length > 0) return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  // colors: array, 'w' and 'c' or 'b' and 'c' at stage 1,
+  // just 'c' (or unused) at stage 2
+  isAttacked(sq, colors) {
+    if (!Array.isArray(colors)) colors = [colors];
+    return (
+      this.isAttackedByPawn(sq, colors) ||
+      this.isAttackedByRook(sq, colors) ||
+      this.isAttackedByKnight(sq, colors) ||
+      this.isAttackedByBishop(sq, colors) ||
+      this.isAttackedByQueen(sq, colors) ||
+      this.isAttackedByKing(sq, colors)
+    );
+  }
+
+  isAttackedByPawn([x, y], colors) {
+    for (let c of colors) {
+      let shifts = [];
+      if (this.stage == 1) {
+        const color = (c == "c" ? this.turn : c);
+        shifts = [color == "w" ? 1 : -1];
+      }
+      else {
+        // Stage 2: checkered pawns are bidirectional
+        if (c == 'c') shifts = [-1, 1];
+        else shifts = [c == "w" ? 1 : -1];
+      }
+      for (let pawnShift of shifts) {
+        if (x + pawnShift >= 0 && x + pawnShift < 8) {
+          for (let i of [-1, 1]) {
+            if (
+              y + i >= 0 &&
+              y + i < 8 &&
+              this.getPiece(x + pawnShift, y + i) == V.PAWN &&
+              this.getColor(x + pawnShift, y + i) == c
+            ) {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
+    for (let step of steps) {
+      let rx = x + step[0],
+          ry = y + step[1];
+      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+        rx += step[0];
+        ry += step[1];
+      }
+      if (
+        V.OnBoard(rx, ry) &&
+        this.getPiece(rx, ry) === piece &&
+        colors.includes(this.getColor(rx, ry))
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  isAttackedByRook(sq, colors) {
+    return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]);
+  }
+
+  isAttackedByKnight(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.KNIGHT,
+      V.steps[V.KNIGHT],
+      "oneStep"
+    );
+  }
+
+  isAttackedByBishop(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq, colors, V.BISHOP, V.steps[V.BISHOP]);
+  }
+
+  isAttackedByQueen(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.QUEEN,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+    );
+  }
+
+  isAttackedByKing(sq, colors) {
+    return this.isAttackedBySlideNJump(
+      sq,
+      colors,
+      V.KING,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  underCheck(color) {
+    if (this.stage == 1)
+      return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]);
+    if (color == this.sideCheckered) return false;
+    return (
+      this.isAttacked(this.kingPos['w'], ["c"]) ||
+      this.isAttacked(this.kingPos['b'], ["c"])
+    );
+  }
+
+  getCheckSquares() {
+    const color = this.turn;
+    if (this.stage == 1) {
+      // Artifically change turn, for checkered pawns
+      this.turn = V.GetOppCol(color);
+      const kingAttacked =
+        this.isAttacked(
+          this.kingPos[color],
+          [V.GetOppCol(color), "c"]
+        );
+      let res = kingAttacked
+        ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
+        : [];
+      this.turn = color;
+      return res;
+    }
+    if (this.sideCheckered == color) return [];
+    let res = [];
+    for (let c of ['w', 'b']) {
+      if (this.isAttacked(this.kingPos[c], ['c']))
+        res.push(JSON.parse(JSON.stringify(this.kingPos[c])));
+    }
+    return res;
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    this.epSquares.push(this.getEpSquare(move));
+    V.PlayOnBoard(this.board, move);
+    if (move.appear.length > 0 || move.vanish.length > 0)
+    {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+    }
+    this.postPlay(move);
+  }
+
+  updateCastleFlags(move, piece) {
+    const c = V.GetOppCol(this.turn);
+    const firstRank = (c == "w" ? V.size.x - 1 : 0);
+    // Update castling flags if rooks are moved
+    const oppCol = this.turn;
+    const oppFirstRank = V.size.x - 1 - firstRank;
+    if (piece == V.KING && move.appear.length > 0)
+      this.castleFlags[c] = [V.size.y, V.size.y];
+    else if (
+      move.start.x == firstRank && //our rook moves?
+      this.castleFlags[c].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+      this.castleFlags[c][flagIdx] = V.size.y;
+    }
+    // NOTE: not "else if" because a rook could take an opposing rook
+    if (
+      move.end.x == oppFirstRank && //we took opponent rook?
+      this.castleFlags[oppCol].includes(move.end.y)
+    ) {
+      const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+      this.castleFlags[oppCol][flagIdx] = V.size.y;
+    }
+  }
+
+  postPlay(move) {
+    if (move.appear.length == 0 && move.vanish.length == 0) {
+      this.stage = 2;
+      this.sideCheckered = this.turn;
+    }
+    else {
+      const c = move.vanish[0].c;
+      const piece = move.vanish[0].p;
+      if (piece == V.KING) {
+        this.kingPos[c][0] = move.appear[0].x;
+        this.kingPos[c][1] = move.appear[0].y;
+      }
+      this.updateCastleFlags(move, piece);
+      // Does this move turn off a 2-squares pawn flag?
+      if ([1, 6].includes(move.start.x) && move.vanish[0].p == V.PAWN)
+        this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
+    }
+    this.cmoves.push(this.getCmove(move));
+  }
+
+  undo(move) {
+    this.epSquares.pop();
+    this.disaggregateFlags(JSON.parse(move.flags));
+    V.UndoOnBoard(this.board, move);
+    if (move.appear.length > 0 || move.vanish.length > 0)
+    {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    if (move.appear.length == 0 && move.vanish.length == 0) this.stage = 1;
+    else super.postUndo(move);
+    this.cmoves.pop();
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    if (this.stage == 1) {
+      if (this.atLeastOneMove()) return "*";
+      // Artifically change turn, for checkered pawns
+      this.turn = V.GetOppCol(this.turn);
+      const res =
+        this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"])
+          ? color == "w"
+            ? "0-1"
+            : "1-0"
+          : "1/2";
+      this.turn = V.GetOppCol(this.turn);
+      return res;
+    }
+    // Stage == 2:
+    if (this.sideCheckered == this.turn) {
+      // Check if remaining checkered pieces: if none, I lost
+      if (this.board.some(b => b.some(cell => cell[0] == 'c'))) {
+        if (!this.atLeastOneMove()) return "1/2";
+        return "*";
+      }
+      return color == 'w' ? "0-1" : "1-0";
+    }
+    if (this.atLeastOneMove()) return "*";
+    let res = this.isAttacked(this.kingPos['w'], ["c"]);
+    if (!res) res = this.isAttacked(this.kingPos['b'], ["c"]);
+    if (res) return color == 'w' ? "0-1" : "1-0";
+    return "1/2";
+  }
+
+  evalPosition() {
+    let evaluation = 0;
+    // Just count material for now, considering checkered neutral at stage 1.
+    const baseSign = (this.turn == 'w' ? 1 : -1);
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] != V.EMPTY) {
+          const sqColor = this.getColor(i, j);
+          if (this.stage == 1) {
+            if (["w", "b"].includes(sqColor)) {
+              const sign = sqColor == "w" ? 1 : -1;
+              evaluation += sign * V.VALUES[this.getPiece(i, j)];
+            }
+          }
+          else {
+            const sign =
+              this.sideCheckered == this.turn
+                ? (sqColor == 'c' ? 1 : -1) * baseSign
+                : (sqColor == 'c' ? -1 : 1) * baseSign;
+            evaluation += sign * V.VALUES[this.getPiece(i, j)];
+          }
+        }
+      }
+    }
+    return evaluation;
+  }
+
+  static GenRandInitFen(randomness) {
+    // Add 16 pawns flags + empty cmovei + stage == 1:
+    return ChessRules.GenRandInitFen(randomness)
+      .slice(0, -2) + "1111111111111111 - - 1";
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      {
+        cmove: fenParts[5],
+        stage: fenParts[6]
+      }
+    );
+  }
+
+  getCmoveFen() {
+    const L = this.cmoves.length;
+    return (
+      !this.cmoves[L - 1]
+        ? "-"
+        : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) +
+          ChessRules.CoordsToSquare(this.cmoves[L - 1].end)
+    );
+  }
+
+  getStageFen() {
+    return (this.stage == 1 ? "1" : "2" + this.sideCheckered);
+  }
+
+  getFen() {
+    return (
+      super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
+    );
+  }
+
+  getFenForRepeat() {
+    return (
+      super.getFenForRepeat() + "_" +
+      this.getCmoveFen() + "_" + this.getStageFen()
+    );
+  }
+
+  getFlagsFen() {
+    let fen = super.getFlagsFen();
+    // Add pawns flags
+    for (let c of ["w", "b"])
+      for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0");
+    return fen;
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+  getComputerMove() {
+    // To simplify, prevent the bot from switching (TODO...)
+    return (
+      super.getComputerMove(
+        this.getAllValidMoves().filter(m => m.appear.length > 0)
+      )
+    );
+  }
+
+  getNotation(move) {
+    if (move.appear.length == 0 && move.vanish.length == 0) return "S";
+    if (move.appear.length == 2) {
+      // Castle
+      if (move.end.y < move.start.y) return "0-0-0";
+      return "0-0";
+    }
+
+    const finalSquare = V.CoordsToSquare(move.end);
+    const piece = this.getPiece(move.start.x, move.start.y);
+    let notation = "";
+    if (piece == V.PAWN) {
+      if (move.vanish.length > 1) {
+        const startColumn = V.CoordToColumn(move.start.y);
+        notation = startColumn + "x" + finalSquare;
+      } else notation = finalSquare;
+    } else {
+      // Piece movement
+      notation =
+        piece.toUpperCase() +
+        (move.vanish.length > 1 ? "x" : "") +
+        finalSquare;
+    }
+    if (move.appear[0].p != move.vanish[0].p)
+      notation += "=" + move.appear[0].p.toUpperCase();
+    return notation;
+  }
+};
diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered2.js
similarity index 95%
rename from client/src/variants/Checkered.js
rename to client/src/variants/Checkered2.js
index ecf25817..db288766 100644
--- a/client/src/variants/Checkered.js
+++ b/client/src/variants/Checkered2.js
@@ -1,6 +1,6 @@
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
-export class CheckeredRules extends ChessRules {
+export class Checkered2Rules extends ChessRules {
   static board2fen(b) {
     const checkered_codes = {
       p: "s",
@@ -162,11 +162,12 @@ export class CheckeredRules extends ChessRules {
   getPotentialPawnMoves([x, y]) {
     let moves = super.getPotentialPawnMoves([x, y]);
     // Post-process: set right color for checkered moves
-    if (this.getColor(x, y) == 'c')
+    if (this.getColor(x, y) == 'c') {
       moves.forEach(m => {
         m.appear[0].c = 'c'; //may be done twice if capture
         m.vanish[0].c = 'c';
       });
+    }
     return moves;
   }
 
@@ -206,7 +207,7 @@ export class CheckeredRules extends ChessRules {
     let potentialMoves = [];
     for (let i = 0; i < V.size.x; i++) {
       for (let j = 0; j < V.size.y; j++) {
-        // NOTE: just testing == color isn't enough because of checkred pieces
+        // NOTE: just testing == color isn't enough because of checkered pieces
         if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
           Array.prototype.push.apply(
             potentialMoves,
@@ -330,13 +331,15 @@ export class CheckeredRules extends ChessRules {
     return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]);
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     // Artifically change turn, for checkered pawns
     this.turn = V.GetOppCol(color);
-    const kingAttacked = this.isAttacked(this.kingPos[color], [
-      V.GetOppCol(color),
-      "c"
-    ]);
+    const kingAttacked =
+      this.isAttacked(
+        this.kingPos[color],
+        [this.turn, 'c']
+      );
     let res = kingAttacked
       ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
       : [];
@@ -401,14 +404,22 @@ export class CheckeredRules extends ChessRules {
     );
   }
 
-  getFen() {
+  getCmoveFen() {
     const L = this.cmoves.length;
-    const cmoveFen =
+    return (
       !this.cmoves[L - 1]
         ? "-"
         : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) +
-          ChessRules.CoordsToSquare(this.cmoves[L - 1].end);
-    return super.getFen() + " " + cmoveFen;
+          ChessRules.CoordsToSquare(this.cmoves[L - 1].end)
+    );
+  }
+
+  getFen() {
+    return super.getFen() + " " + this.getCmoveFen();
+  }
+
+  getFenForRepeat() {
+    return super.getFenForRepeat() + "_" + this.getCmoveFen();
   }
 
   getFlagsFen() {
diff --git a/client/src/variants/Coregal.js b/client/src/variants/Coregal.js
index 43775bfd..d6c736f8 100644
--- a/client/src/variants/Coregal.js
+++ b/client/src/variants/Coregal.js
@@ -46,7 +46,8 @@ export class CoregalRules extends ChessRules {
     }
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let squares = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
diff --git a/client/src/variants/Horde.js b/client/src/variants/Horde.js
index 6305c412..aac4d27a 100644
--- a/client/src/variants/Horde.js
+++ b/client/src/variants/Horde.js
@@ -51,8 +51,8 @@ export class HordeRules extends ChessRules {
     return super.filterValid(moves);
   }
 
-  getCheckSquares(color) {
-    if (color == 'w') return [];
+  getCheckSquares() {
+    if (this.turn == 'w') return [];
     return (
       this.underCheck('b')
         ? [JSON.parse(JSON.stringify(this.kingPos['b']))]
diff --git a/client/src/variants/Marseille.js b/client/src/variants/Marseille.js
index 940d9ba9..90084dc2 100644
--- a/client/src/variants/Marseille.js
+++ b/client/src/variants/Marseille.js
@@ -14,10 +14,6 @@ export class MarseilleRules extends ChessRules {
     return true;
   }
 
-  getTurnFen() {
-    return this.turn + this.subTurn;
-  }
-
   // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
   getEnpassantFen() {
     return this.epSquares[this.epSquares.length - 1].map(
@@ -37,10 +33,8 @@ export class MarseilleRules extends ChessRules {
     this.scanKings(fen);
     // Extract subTurn from turn indicator: "w" (first move), or
     // "w1" or "w2" white subturn 1 or 2, and same for black
-    const fullTurn = V.ParseFen(fen).turn;
-    this.turn = fullTurn[0];
-    // At move 1, the subTurn doesn't need to be specified:
-    this.subTurn = fullTurn[1] || 1;
+    this.turn = parsedFen.turn;
+    this.subTurn = 1;
   }
 
   getEnpassantCaptures([x, y], shiftX) {
diff --git a/client/src/variants/Synchrone.js b/client/src/variants/Synchrone.js
index 07929019..c8f2be57 100644
--- a/client/src/variants/Synchrone.js
+++ b/client/src/variants/Synchrone.js
@@ -433,7 +433,8 @@ export class SynchroneRules extends ChessRules {
     } else this.whiteMove = move.whiteMove;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     if (color == 'b') {
       // kingPos must be reset for appropriate highlighting:
       var lastMove = JSON.parse(JSON.stringify(this.whiteMove));
diff --git a/client/src/variants/Twokings.js b/client/src/variants/Twokings.js
index 68910e74..52aebc32 100644
--- a/client/src/variants/Twokings.js
+++ b/client/src/variants/Twokings.js
@@ -36,7 +36,8 @@ export class TwokingsRules extends CoregalRules {
   // Not scanning king positions. In this variant, scan the board everytime.
   scanKings() {}
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let squares = [];
     const oppCol = V.GetOppCol(color);
     for (let i=0; i<V.size.x; i++) {
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index 0398875b..fe487653 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -7,7 +7,7 @@ main
   )
     .card.text-center
       label.modal-close(for="modalScore")
-      p
+      p.score-section
         span.score {{ game.score }}
         | &nbsp;:&nbsp;
         span.score-msg {{ st.tr[game.scoreMsg] }}
@@ -1585,8 +1585,10 @@ export default {
   padding: 15px 0
   max-width: 430px
 
-span.score
-  font-weight: bold
+p.score-section
+  font-size: 1.3em
+  span.score
+    font-weight: bold
 
 .connected
   background-color: lightgreen
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 45afa386..46e8eb75 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -22,7 +22,8 @@ insert or ignore into Variants (name, description) values
   ('Berolina', 'Pawns move diagonally'),
   ('Cannibal', 'Capture powers'),
   ('Capture', 'Mandatory captures'),
-  ('Checkered', 'Shared pieces'),
+  ('Checkered1', 'Shared pieces (v1)'),
+  ('Checkered2', 'Shared pieces (v2)'),
   ('Checkless', 'No-check mode'),
   ('Chess960', 'Standard rules'),
   ('Circular', 'Run forward'),