Add Checkered1 + fix last move highlights
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 5 Apr 2020 22:47:00 +0000 (00:47 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 5 Apr 2020 22:47:00 +0000 (00:47 +0200)
28 files changed:
TODO
client/src/base_rules.js
client/src/components/BaseGame.vue
client/src/components/Board.vue
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Checkered1/en.pug [moved from client/src/translations/rules/Checkered/en.pug with 94% similarity]
client/src/translations/rules/Checkered1/es.pug [moved from client/src/translations/rules/Checkered/es.pug with 94% similarity]
client/src/translations/rules/Checkered1/fr.pug [moved from client/src/translations/rules/Checkered/fr.pug with 94% similarity]
client/src/translations/rules/Checkered2/en.pug [new file with mode: 0644]
client/src/translations/rules/Checkered2/es.pug [new file with mode: 0644]
client/src/translations/rules/Checkered2/fr.pug [new file with mode: 0644]
client/src/variants/Alice.js
client/src/variants/Antiking1.js
client/src/variants/Antiking2.js
client/src/variants/Apocalypse.js
client/src/variants/Atomic.js
client/src/variants/Ball.js
client/src/variants/Checkered1.js [new file with mode: 0644]
client/src/variants/Checkered2.js [moved from client/src/variants/Checkered.js with 95% similarity]
client/src/variants/Coregal.js
client/src/variants/Horde.js
client/src/variants/Marseille.js
client/src/variants/Synchrone.js
client/src/variants/Twokings.js
client/src/views/Game.vue
server/db/populate.sql

diff --git a/TODO b/TODO
index c3dddb5..a4ffcfd 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,15 @@
 Chakart :)
 
 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
 
 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.
 
 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
index 167be91..734af02 100644 (file)
@@ -263,7 +263,8 @@ export const ChessRules = class ChessRules {
   }
 
   // On which squares is color under check ? (for interface)
   }
 
   // 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:
     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);
       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);
   }
     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];
   // 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
     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) {
 
   // 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);
     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;
   }
 
     return 3;
   }
 
-  getComputerMove() {
+  // 'movesList' arg for some variants to provide a custom list
+  getComputerMove(movesList) {
     const maxeval = V.INFINITY;
     const color = this.turn;
     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
 
     if (moves1.length == 0)
       // TODO: this situation should not happen
index 7156670..572c80a 100644 (file)
@@ -207,15 +207,21 @@ export default {
       const firstMoveColor = parsedFen.turn;
       this.firstMoveNumber = Math.floor(parsedFen.movesCount / 2) + 1;
       let L = this.moves.length;
       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];
         // 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);
           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") {
         });
       });
       if (firstMoveColor == "b") {
@@ -229,25 +235,13 @@ export default {
         });
         L++;
       }
         });
         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;
     },
     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 =
     },
     analyzePosition: function() {
       let newUrl =
@@ -378,10 +372,15 @@ export default {
     // For Analyse mode:
     emitFenIfAnalyze: function() {
       if (this.game.mode == "analyze") {
     // 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) {
       }
     },
     clickSquare: function(square) {
@@ -408,10 +407,17 @@ export default {
         smove.notation = this.vr.getNotation(smove);
         smove.unambiguous = V.GetUnambiguousNotation(smove);
         this.vr.play(smove);
         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?
         // 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) {
         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)
           // 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) {
       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);
         }
         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) {
           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();
           }
             this.score = computeScore();
             this.emitFenIfAnalyze();
           }
@@ -548,7 +559,7 @@ export default {
       this.$refs["board"].resetCurrentAttempt();
       if (this.inMultimove) {
         this.cancelCurrentMultimove();
       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 =
       } else {
         if (!move) {
           const minCursor =
@@ -563,7 +574,7 @@ export default {
         if (light) this.cursor--;
         else {
           this.positionCursorTo(this.cursor - 1);
         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();
         }
       }
           this.emitFenIfAnalyze();
         }
       }
@@ -584,7 +595,7 @@ export default {
       }
       // NOTE: next line also re-assign cursor, but it's very light
       this.positionCursorTo(index);
       }
       // 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() {
       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);
           : 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() {
       this.emitFenIfAnalyze();
     },
     gotoEnd: function() {
index 6c26722..e6a0aaf 100644 (file)
@@ -54,7 +54,16 @@ export default {
       incheckSq[sq[0]][sq[1]] = true;
     });
 
       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)
     const showLight = (
       this.settings.highlight &&
       ["all","highlight"].includes(V.ShowMoves)
@@ -74,9 +83,7 @@ export default {
       );
     };
     const inHighlight = (x, y) => {
       );
     };
     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 (
     };
     const inShadow = (x, y) => {
       return (
index 20d8f40..0aa822f 100644 (file)
@@ -214,7 +214,8 @@ export const translations = {
   "Run forward": "Run forward",
   "Score a goal": "Score a goal",
   "Seirawan-Harper Chess": "Seirawan-Harper Chess",
   "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",
   "Shoot pieces": "Shoot pieces",
   "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
index fd37ad0..4c2bb99 100644 (file)
@@ -214,7 +214,8 @@ export const translations = {
   "Run forward": "Correr hacia adelante",
   "Score a goal": "Marcar una meta",
   "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper",
   "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",
   "Shoot pieces": "Tirar de las piezas",
   "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
index f2dd3ab..663d4b8 100644 (file)
@@ -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",
   "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",
   "Shoot pieces": "Tirez sur les pièces",
   "Squares disappear": "Les cases disparaissent",
   "Standard rules": "Règles usuelles",
@@ -57,8 +57,6 @@ ul
     Checkered pawns cannot capture en passant, because while the pawn was
     "passing" they were of the same color.
 
     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.
 h3 Stalemate or checkmate?
 
 p.
@@ -82,8 +80,6 @@ p.
 
 h2.stageDelimiter Stage 2
 
 
 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.
 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.
 
   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.
 p.
   An observer could decide to join the game by taking the checkered pieces
   at any moment.
@@ -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.
 
     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.
 h3 ¿Jaque mate o empate?
 
 p.
@@ -81,8 +79,6 @@ p.
 
 h2.stageDelimiter Fase 2
 
 
 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
 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.
 
   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,
 
 p.
   Un observador puede en cualquier momento decidir unirse a la partida,
@@ -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.
 
     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.
 h3 Mat ou pat ?
 
 p.
@@ -85,8 +83,6 @@ p.
 
 h2.stageDelimiter Phase 2
 
 
 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
 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.
 
   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
 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 (file)
index 0000000..4c004a3
--- /dev/null
@@ -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 (file)
index 0000000..a93f579
--- /dev/null
@@ -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 (file)
index 0000000..5b5de76
--- /dev/null
@@ -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.
index e193bbd..d6fb0ac 100644 (file)
@@ -235,7 +235,8 @@ export class AliceRules extends ChessRules {
     return res;
   }
 
     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;
     const pieces = Object.keys(V.ALICE_CODES);
     const kp = this.kingPos[color];
     const mirrorSide = pieces.includes(this.getPiece(kp[0], kp[1])) ? 1 : 2;
index ac28c31..ab86f69 100644 (file)
@@ -176,7 +176,8 @@ export class Antiking1Rules extends BerolinaRules {
     return res;
   }
 
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let res = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
     let res = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
index 2dad5e4..a7ec2a8 100644 (file)
@@ -117,7 +117,8 @@ export class Antiking2Rules extends ChessRules {
     return res;
   }
 
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let res = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
     let res = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
index c136ff2..4587be3 100644 (file)
@@ -428,7 +428,7 @@ export class ApocalypseRules extends ChessRules {
     else this.whiteMove = move.whiteMove;
   }
 
     else this.whiteMove = move.whiteMove;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
     return [];
   }
 
     return [];
   }
 
index 90089e8..9675c58 100644 (file)
@@ -127,7 +127,8 @@ export class AtomicRules extends ChessRules {
     return res;
   }
 
     return res;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
+    const color = this.turn;
     let res = [];
     if (
       this.kingPos[color][0] >= 0 && //king might have exploded
     let res = [];
     if (
       this.kingPos[color][0] >= 0 && //king might have exploded
index e3c8e5e..e4a0b2f 100644 (file)
@@ -126,7 +126,7 @@ export class BallRules extends ChessRules {
     );
   }
 
     );
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
     return [];
   }
 
     return [];
   }
 
diff --git a/client/src/variants/Checkered1.js b/client/src/variants/Checkered1.js
new file mode 100644 (file)
index 0000000..561f02f
--- /dev/null
@@ -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;
+  }
+};
similarity index 95%
rename from client/src/variants/Checkered.js
rename to client/src/variants/Checkered2.js
index ecf2581..db28876 100644 (file)
@@ -1,6 +1,6 @@
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
 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",
   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
   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';
       });
       moves.forEach(m => {
         m.appear[0].c = 'c'; //may be done twice if capture
         m.vanish[0].c = 'c';
       });
+    }
     return moves;
   }
 
     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++) {
     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,
         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"]);
   }
 
     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);
     // 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]))]
       : [];
     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 L = this.cmoves.length;
-    const cmoveFen =
+    return (
       !this.cmoves[L - 1]
         ? "-"
         : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) +
       !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() {
   }
 
   getFlagsFen() {
index 43775bf..d6c736f 100644 (file)
@@ -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))
     let squares = [];
     const oppCol = V.GetOppCol(color);
     if (this.isAttacked(this.kingPos[color], oppCol))
index 6305c41..aac4d27 100644 (file)
@@ -51,8 +51,8 @@ export class HordeRules extends ChessRules {
     return super.filterValid(moves);
   }
 
     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']))]
     return (
       this.underCheck('b')
         ? [JSON.parse(JSON.stringify(this.kingPos['b']))]
index 940d9ba..90084dc 100644 (file)
@@ -14,10 +14,6 @@ export class MarseilleRules extends ChessRules {
     return true;
   }
 
     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(
   // 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
     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) {
   }
 
   getEnpassantCaptures([x, y], shiftX) {
index 0792901..c8f2be5 100644 (file)
@@ -433,7 +433,8 @@ export class SynchroneRules extends ChessRules {
     } else this.whiteMove = move.whiteMove;
   }
 
     } 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));
     if (color == 'b') {
       // kingPos must be reset for appropriate highlighting:
       var lastMove = JSON.parse(JSON.stringify(this.whiteMove));
index 68910e7..52aebc3 100644 (file)
@@ -36,7 +36,8 @@ export class TwokingsRules extends CoregalRules {
   // Not scanning king positions. In this variant, scan the board everytime.
   scanKings() {}
 
   // 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++) {
     let squares = [];
     const oppCol = V.GetOppCol(color);
     for (let i=0; i<V.size.x; i++) {
index 0398875..fe48765 100644 (file)
@@ -7,7 +7,7 @@ main
   )
     .card.text-center
       label.modal-close(for="modalScore")
   )
     .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] }}
         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
 
   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
 
 .connected
   background-color: lightgreen
index 45afa38..46e8eb7 100644 (file)
@@ -22,7 +22,8 @@ insert or ignore into Variants (name, description) values
   ('Berolina', 'Pawns move diagonally'),
   ('Cannibal', 'Capture powers'),
   ('Capture', 'Mandatory captures'),
   ('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'),
   ('Checkless', 'No-check mode'),
   ('Chess960', 'Standard rules'),
   ('Circular', 'Run forward'),