Some fixes, wrote some rules, implemented Wormhole variant
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 28 Feb 2020 22:10:03 +0000 (23:10 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 28 Feb 2020 22:10:03 +0000 (23:10 +0100)
16 files changed:
client/public/images/pieces/Wormhole/hole.svg [new file with mode: 0644]
client/src/base_rules.js
client/src/translations/rules/Arena/en.pug
client/src/translations/rules/Arena/es.pug
client/src/translations/rules/Arena/fr.pug
client/src/translations/rules/Check3/en.pug
client/src/translations/rules/Check3/es.pug
client/src/translations/rules/Check3/fr.pug
client/src/translations/rules/Knightrelay/en.pug
client/src/translations/rules/Wormhole/en.pug
client/src/translations/rules/Wormhole/es.pug
client/src/translations/rules/Wormhole/fr.pug
client/src/variants/Arena.js
client/src/variants/Checkered.js
client/src/variants/Wormhole.js
client/src/views/Game.vue

diff --git a/client/public/images/pieces/Wormhole/hole.svg b/client/public/images/pieces/Wormhole/hole.svg
new file mode 100644 (file)
index 0000000..4f57964
--- /dev/null
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="iso-8859-1"?>\r
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\r
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
+        viewBox="0 0 198 198" style="enable-background:new 0 0 198 198;" xml:space="preserve">\r
+<path d="M192.384,99.825c-9.361-22.131-28.78-38.577-51.252-46.075c5.413-0.686,10.891-0.927,16.379-0.688\r
+       c9.065,0.395,17.464,2.395,25.745,5.045c5.602,1.793,9.756-5.12,5.588-9.271c-6.584-6.556-14.508-11.75-23.228-15.286\r
+       c-22.268-9.029-47.629-6.927-68.82,3.66c3.342-4.313,7.046-8.356,11.095-12.067c6.689-6.13,14.043-10.656,21.772-14.637\r
+       c5.229-2.694,3.278-10.519-2.604-10.507c-9.292,0.02-18.568,1.95-27.234,5.616C77.695,14.977,61.248,34.396,53.75,56.868\r
+       c-0.686-5.413-0.927-10.891-0.687-16.379c0.395-9.065,2.395-17.464,5.045-25.745c1.793-5.602-5.12-9.756-9.271-5.588\r
+       c-6.556,6.584-11.75,14.508-15.286,23.228c-9.029,22.268-6.927,47.629,3.66,68.82c-4.313-3.342-8.356-7.046-12.067-11.095\r
+       c-6.13-6.689-10.656-14.042-14.637-21.772C7.813,63.107-0.012,65.059,0,70.941c0.02,9.292,1.95,18.568,5.616,27.234\r
+       c9.361,22.131,28.78,38.577,51.251,46.075c-5.413,0.686-10.891,0.927-16.378,0.687c-9.065-0.395-17.464-2.395-25.745-5.045\r
+       c-5.602-1.793-9.756,5.12-5.588,9.271c6.584,6.556,14.508,11.75,23.228,15.286c22.268,9.029,47.629,6.927,68.82-3.66\r
+       c-3.342,4.313-7.046,8.356-11.095,12.067c-6.689,6.13-14.042,10.656-21.772,14.637c-5.229,2.694-3.278,10.519,2.604,10.507\r
+       c9.292-0.02,18.568-1.95,27.234-5.616c22.131-9.361,38.577-28.781,46.075-51.252c0.686,5.413,0.927,10.891,0.687,16.379\r
+       c-0.395,9.065-2.395,17.464-5.045,25.745c-1.793,5.602,5.12,9.756,9.271,5.588c6.556-6.584,11.75-14.509,15.286-23.228\r
+       c9.029-22.268,6.927-47.629-3.66-68.82c4.313,3.342,8.356,7.046,12.067,11.095c6.13,6.689,10.656,14.042,14.637,21.772\r
+       c2.694,5.229,10.519,3.278,10.507-2.604C197.98,117.767,196.05,108.491,192.384,99.825z M99,122.917\r
+       c-13.209,0-23.917-10.708-23.917-23.917S85.791,75.083,99,75.083S122.917,85.791,122.917,99S112.209,122.917,99,122.917z"/>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+<g>\r
+</g>\r
+</svg>\r
index e6fd6d7..8b49436 100644 (file)
@@ -610,7 +610,7 @@ export const ChessRules = class ChessRules {
       let j = y + step[1];
       while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
         moves.push(this.getBasicMove([x, y], [i, j]));
-        if (oneStep !== undefined) continue outerLoop;
+        if (oneStep) continue outerLoop;
         i += step[0];
         j += step[1];
       }
@@ -836,12 +836,10 @@ export const ChessRules = class ChessRules {
   // (for engine and game end)
   getAllValidMoves() {
     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++) {
-        // Next condition "!= oppCol" to work with checkered variant
-        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
+        if (this.getColor(i, j) == color) {
           Array.prototype.push.apply(
             potentialMoves,
             this.getPotentialMovesFrom([i, j])
@@ -855,10 +853,9 @@ export const ChessRules = class ChessRules {
   // Stop at the first move found
   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++) {
-        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
+        if (this.getColor(i, j) == color) {
           const moves = this.getPotentialMovesFrom([i, j]);
           if (moves.length > 0) {
             for (let k = 0; k < moves.length; k++) {
@@ -883,10 +880,31 @@ export const ChessRules = class ChessRules {
     );
   }
 
+  // Generic method for non-pawn pieces ("sliding or jumping"):
+  // is x,y attacked by a piece of color in array 'colors' ?
+  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;
+  }
+
   // Is square x,y attacked by 'colors' pawns ?
   isAttackedByPawn([x, y], colors) {
     for (let c of colors) {
-      let pawnShift = c == "w" ? 1 : -1;
+      const pawnShift = c == "w" ? 1 : -1;
       if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
         for (let i of [-1, 1]) {
           if (
@@ -945,27 +963,6 @@ export const ChessRules = class ChessRules {
     );
   }
 
-  // Generic method for non-pawn pieces ("sliding or jumping"):
-  // is x,y attacked by a piece of color in array 'colors' ?
-  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;
-  }
-
   // Is color under check after his move ?
   underCheck(color) {
     return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]);
index 4f56997..3e92014 100644 (file)
@@ -1 +1,42 @@
-p TODO
+p.boxed
+  | Either enter the arena, or capture something inside.
+  | The arena is the 4x8 rectangle in the middle of the board.
+
+ul
+  li.
+    In addition to its usual movement, a pawn can capture diagonally backward.
+  li.
+    The king and queen are replaced with dukes.
+    A duke may move up to 3 spaces in any direction.
+    Dukes may move into check as they please,
+    but if a player loses both of his dukes, he loses.
+
+p.
+  The 4x8 area in the center of the board (the area in which no pieces start)
+  is known as the Arena. A piece that is not currently in the Arena may only move
+  if it is to enter the Arena. A piece which is currently in the Arena may
+  only move to capture something in the Arena.
+
+p A player wins if:
+ul
+  li He captures both of his opponent's Dukes.
+  li.
+    His opponent has no pieces left in the Arena at the end of any turn
+    other than the first.
+  li His opponent cannot make a move.
+
+figure.diagram-container
+  .diagram
+    | fen:brnkqrnb/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR:
+  figcaption After the moves 1.e4 Nd6 2.Bg4 Nxe4.
+
+p.
+  In the diagram situation, the g4 bishop cannot take anything in the Arena,
+  and thus cannot move. Note that 1...d5?? 2.exd5 1-0, because black would have
+  no pieces in the Arena.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") Arena chess
+  | &nbsp;on chessvariants.com.
index 4f56997..4463981 100644 (file)
@@ -1 +1,44 @@
-p TODO
+p.boxed
+  | Entra en la arena o captura algo.
+  | La "arena" designa el rectángulo de 4x8 en el centro del tablero.
+
+ul
+  li.
+    Además de su moviéndose habitual,
+    el peón puede capturar en diagonal hacia atrás.
+  li.
+    El rey y la reina son reemplazados por duques.
+    Un duque puede mover uno, dos o tres casillas en todas las direcciones.
+    Puede estar en jaque en cualquier momento, pero perder ambos
+    duques significa perder la partida.
+
+p.
+  El área de tamaño 4x8 en el centro del tablero de ajedrez (inicialmente vacía)
+  se llama la "arena". Una pieza fuera de la arena solo puede realizar
+  un movimiento entrando en él.
+  Una pieza que ya está en la arena solo puede moverse capturando algo en esta área.
+
+p Un jugador gana si:
+ul
+  li Captura a los dos duques opuestos.
+  li.
+    Su oponente no tiene más piezas en la arena después de
+    cualquier turno excepto el primero.
+  li Su oponente ya no puede jugar.
+
+figure.diagram-container
+  .diagram
+    | fen:brnkqrnb/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR:
+  figcaption Después de los movimientos 1.e4 Nd6 2.Bg4 Nxe4.
+
+p.
+  En la situación del diagrama, el alfil en g4 no puede capturar nada en la arena,
+  y por lo tanto no puede moverse. Tenga en cuenta que 1...d5?? 2.exd5 1-0, 
+  porque los negros no tendría piezas en la arena.
+
+h3 Fuente
+
+p
+  | La
+  a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") variante Arena
+  | &nbsp;en chessvariants.com.
index 4f56997..4cfd2cc 100644 (file)
@@ -1 +1,45 @@
-p TODO
+p.boxed
+  | Entrez dans l'arène, ou capturez-y quelque chose.
+  | L'"arène" désigne le rectangle 4x8 au centre de l'échiquier.
+
+ul
+  li.
+    En plus de son déplacement habituel,
+    le pion peut capturer en diagonale vers arrière.
+  li.
+    Le roi et la reine sont remplacés par des ducs.
+    Un duc peut se déplacer d'une, deux ou trois cases dans toutes les directions.
+    Il peut se retrouver en échec à tout moment, mais perdre ses deux
+    ducs signifie perdre la partie.
+
+p.
+  La zone de taille 4x8 au centre de l'échiquier (initialement vide)
+  s'appelle l'"arène". Une pièce hors de l'arène peut seulement effectuer
+  un coup y entrant.
+  Une pièce déjà dans l'arène ne peut se déplacer qu'en capturant
+  quelque chose dans cette zone.
+
+p Un joueur gagne si :
+ul
+  li Il capture les deux ducs adverses.
+  li.
+    Son adversaire n'a plus de pièces dans l'arène à l'issue de
+    n'importe quel tour excepté le premier.
+  li Son adversaire ne peut plus jouer.
+
+figure.diagram-container
+  .diagram
+    | fen:brnkqrnb/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR:
+  figcaption Après les coups 1.e4 Nd6 2.Bg4 Nxe4.
+
+p.
+  Dans la situation du diagramme, le fou en g4 ne peut rien capturer dans l'arène,
+  et donc ne peut pas bouger. Notez que 1...d5?? 2.exd5 1-0, car les noirs
+  n'auraient aucune pièce dans l'arène.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") variante Arena
+  | &nbsp;sur chessvariants.com.
index 4f56997..b5013b6 100644 (file)
@@ -1 +1,13 @@
-p TODO
+p.boxed
+  | Win by giving three checks (or a single checkmate).
+
+p.
+  Orthodox rules apply, but giving three checks win the game.
+  Since it is easier than checkmate, most games would end in this way.
+
+h3 Source
+
+p
+  | Old variant, playable at several locations including 
+  a(href="https://lichess.org/variant/threeCheck") lichess
+  | .
index 4f56997..1c0cf26 100644 (file)
@@ -1 +1,14 @@
-p TODO
+p.boxed
+  | Gana dando tres jaques (o un solo jaque mate).
+
+p.
+  Se aplican las reglas ortodoxas, pero dar tres jaques gana
+  la partida. Es más fácil que de matar, entonces la mayoría de las
+  partidas deberían terminar así.
+
+h3 Fuente
+
+p
+  | Es una variante antigua, jugable en varios lugares, incluyendo
+  a(href="https://lichess.org/variant/threeCheck") lichess
+  | .
index 4f56997..1195a59 100644 (file)
@@ -1 +1,14 @@
-p TODO
+p.boxed
+  | Gagnez en donnant trois échecs (ou un seul échec et mat).
+
+p.
+  Les règles orthodoxes s'appliquent, mais donner trois échecs fait gagner
+  la partie. C'est plus facile que de mater,
+  donc la plupart des parties devraient s'achever ainsi.
+
+h3 Source
+
+p
+  | C'est une vieille variante, jouable à plusieurs endroits dont 
+  a(href="https://lichess.org/variant/threeCheck") lichess
+  | .
index 4f56997..7d69884 100644 (file)
@@ -1 +1,16 @@
-p TODO
+p.boxed
+  | Any piece guarded by a friendly knight can also move like a knight.
+
+p.
+  In addition to its normal abilities, a piece guarded by a knight can move like him.
+  On the following diagram, 1.Nf4 would checkmate because it guard the g6 queen.
+  If it was black to play, then 1...Rxe2 is possible due to the c8 knight.
+
+figure.diagram-container
+  .diagram
+    | fen:7k/8/6Q1/1n6/8/2r5/4N3/K7:
+
+h3 Source
+
+p
+  a(href="TODO") TODO
index 4f56997..b6505ea 100644 (file)
@@ -1 +1,51 @@
-p TODO
+p.boxed
+  | When a piece moves, the initial square disappears. It creates a 
+  a(href="https://en.wikipedia.org/wiki/Wormhole") "wormhole"
+  | .
+
+p.
+  Since all initial squares vanish, the board has exactly 64 - T squares
+  after T turns, so the game cannot last more than 32 moves.
+  Indeed a vanished square can be jumped over, but cannot be used again.
+  Holes are indicated with the letter 'x' on FEN strings.
+
+p.
+  In the diagram situation, the black knight can go to all the marked squares:
+  g5 and f6 are reachable because of the holes on f4 and e5.
+  Indeed the knight first moves one square vertically or horizontally,
+  and only then one square diagonally "in the same direction".
+  This is the only valid description in this variant
+  (others would lead to different knight movements around holes).
+  The black king can go to c6:
+  it moves to the closest non-vanished square (if any).
+
+figure.diagram-container
+  .diagram
+    | fen:rbkxxxbn/ppxppppx/2qxxB2/4x2p/3P1x2/3n1x2/PPPxPPPP/RBxxxNKR b2,f2,b4,c5,g5,f6:
+  figcaption Possible moves for the knight on d3.
+
+p.
+  No castle or en passant captures are possible.
+  Promotion is permitted but only by capturing.
+
+h3 Pieces movements
+
+ul
+  li The rook moves one or two squares vertically or horizontally.
+  li The bishop moves one or two squares diagonally.
+  li The queen moves either like a rook or like a bishop.
+  li.
+    The pawn moves like in orthodox chess, but can jumped over pieces at its
+    initial potential 2-squares move.
+
+h3 End of the game
+
+p Win by checkmate or stalemate: if you can no longer move, you lose.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/32turn.dir/wormhole.html") Wormhole chess
+  | &nbsp;on chessvariants.com.
+  | I changed the pieces movements because I have a better feeling with the moves
+  | described earlier. It might evolve.
index 4f56997..07be693 100644 (file)
@@ -1 +1,51 @@
-p TODO
+p.boxed
+  | Cuando una pieza se mueve, la casilla inicial desaparece. Esto crea un
+  a(href="https://es.wikipedia.org/wiki/Agujero_de_gusano") "agujero de gusano"
+  | .
+
+p.
+  Como todas la casillas iniciales desaparecen, el tablero de ajedrez tiene exactamente
+  64 - T casillas después de T turnos. La partida no puede exceder los 32 movimientos.
+  De hecho, puedes saltar sobre una casilla faltante, pero ya no se puede usar.
+  Los agujeros se indican con la letra 'x' en la cadena FEN.
+
+p.
+  En la situación del diagrama, el caballo negro puede ir a las casillas marcadas:
+  g5 y f6 son accesibles gracias a los agujeros en f4 y e5.
+  De hecho, el caballo primero se mueve de una casilla horizontalmente o
+  verticalmente, luego diagonalmente "en la misma dirección".
+  Esta es la única descripción válida en esta variante (las otras
+  conduciría a diferentes movimientos de caballos alrededor de los agujeros).
+  El rey negro puede ir a c6: se mueve hacia la casilla no desaparecida
+  más cercano (si lo hay).
+
+figure.diagram de contenedores
+  .diagram
+    | fen: rbkxxxbn / ppxppppx / 2qxxB2 / 4x2p / 3P1x2 / 3n1x2 / PPPxPPPP / RBxxxNKR b2, f2, b4, c5, g5, f6:
+  figcaption Posibles movimientos para el caballo en d3.
+
+p.
+  El enroque y la captura en passant no están permitidos.
+  La promoción es posible pero solo mediante la captura.
+
+h3 ¿Como las piezas se mueven?
+
+ul
+  li La torre se mueve de una o dos casillas verticalmente u horizontalmente.
+  li El alfil se mueve de una o dos casillas en diagonal.
+  li La dama puede moverse como una torre o un alfil.
+  li.
+    El peón se mueve como en el ajedrez ortodoxo, y también puede saltar
+    por encima de una pieza durante su potencial doble movimiento inicial.
+
+h3 Fin de la partida
+
+p Gana por mate o empate: si ya no puedes jugar, pierdes.
+
+h3 Fuente
+
+  | La 
+  a(href="https://www.chessvariants.com/32turn.dir/wormhole.html") variante Wormhole
+  | &nbsp;en chessvariants.com.
+  | Cambié los movimientos de las piezas porque las descritas aquí
+  | Parece más adecuado. Esto podría evolucionar.
index 4f56997..ca8b468 100644 (file)
@@ -1 +1,53 @@
-p TODO
+p.boxed
+  | Quand une pièce se déplace, la case initiale disparaît. Cela crée un
+  a(href="https://fr.wikipedia.org/wiki/Trou_de_ver") "trou de ver"
+  | .
+
+p.
+  Puisque toutes les cases initiales disparaissent, l'échiquier a exactement
+  64 - T cases après T tours. La partie ne peut donc excéder 32 coups.
+  En effet on peut sauter par dessus une case disparue, mais celle-ci ne
+  peut plus être utilisée.
+  Les trous sont indiqués par la lettre 'x' sur les chaînes FEN.
+
+p.
+  Dans la situation du diagramme, le cavalier noir peut aller sur toutes les
+  cases marquées : g5 et f6 sont accessibles grâce aux trous en f4 et e5.
+  En effet le cavalier se déplace d'abord d'une case horizontalement ou
+  verticalement, puis d'une case en diagonale "dans la même direction".
+  Ceci est la seule description valide dans cette variante (les autres
+  mèneraient à différents coups de cavaliers aux abords des trous).
+  Le roi noir peut aller en c6 : il se déplace vers la case non disparue
+  la plus proche (s'il y en a).
+
+figure.diagram-container
+  .diagram
+    | fen:rbkxxxbn/ppxppppx/2qxxB2/4x2p/3P1x2/3n1x2/PPPxPPPP/RBxxxNKR b2,f2,b4,c5,g5,f6:
+  figcaption Coups possibles pour le cavalier en d3.
+
+p.
+  Le roque ainsi que la prise en passant ne sont pas permis.
+  La promotion est possible mais seulement en capturant.
+
+h3 Déplacement des pièces
+
+ul
+  li La tour se déplace d'une ou deux cases verticalement ou horizontalement.
+  li Le fous se déplace d'une ou deux cases en diagonale.
+  li La dame peut se mouvoir comme une tour ou un fou.
+  li.
+    Le pion se déplace comme aux échecs orthodoxes, et peut aussi sauter par
+    dessus une pièce lors de son potentiel double coup initial.
+
+h3 Fin de la partie
+
+p Gagnez par mat ou pat : si vous ne pouvez plus jouer, vous perdez.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/32turn.dir/wormhole.html") variante Wormhole
+  | &nbsp;sur chessvariants.com.
+  | J'ai changé les déplacements des pièces car ceux décrits ici me
+  | paraissent mieux adaptés. Ceci pourrait évoluer.
index b92be1e..42b1f4a 100644 (file)
@@ -47,15 +47,17 @@ export const VariantRules = class ArenaRules extends ChessRules {
         moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
       }
     }
-    // Captures
+    // Captures: also possible backward
     for (let shiftY of [-1, 1]) {
-      if (
-        y + shiftY >= 0 &&
-        y + shiftY < sizeY &&
-        this.board[x + shiftX][y + shiftY] != V.EMPTY &&
-        this.canTake([x, y], [x + shiftX, y + shiftY])
-      ) {
-        moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
+      if (y + shiftY >= 0 && y + shiftY < sizeY) {
+        for (let direction of [-1,1]) {
+          if (
+            this.board[x + direction][y + shiftY] != V.EMPTY &&
+            this.canTake([x, y], [x + direction, y + shiftY])
+          ) {
+            moves.push(this.getBasicMove([x, y], [x + direction, y + shiftY]));
+          }
+        }
       }
     }
 
index e930fb8..ac99602 100644 (file)
@@ -37,7 +37,7 @@ export const VariantRules = class CheckeredRules extends ChessRules {
   }
 
   getPpath(b) {
-    return b[0] == "c" ? "Checkered/" + b : b;
+    return (b[0] == "c" ? "Checkered/" : "") + b;
   }
 
   setOtherVariables(fen) {
@@ -180,6 +180,41 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     });
   }
 
+  getAllValidMoves() {
+    const oppCol = V.GetOppCol(this.turn);
+    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
+        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
+          Array.prototype.push.apply(
+            potentialMoves,
+            this.getPotentialMovesFrom([i, j])
+          );
+        }
+      }
+    }
+    return this.filterValid(potentialMoves);
+  }
+
+  atLeastOneMove() {
+    const oppCol = V.GetOppCol(this.turn);
+    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
+        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
+          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;
+  }
+
   isAttackedByPawn([x, y], colors) {
     for (let c of colors) {
       const color = c == "c" ? this.turn : c;
@@ -245,13 +280,15 @@ export const VariantRules = class CheckeredRules extends ChessRules {
 
   evalPosition() {
     let evaluation = 0;
-    //Just count material for now, considering checkered neutral (...)
+    // Just count material for now, considering checkered neutral (...)
     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);
-          const sign = sqColor == "w" ? 1 : sqColor == "b" ? -1 : 0;
-          evaluation += sign * V.VALUES[this.getPiece(i, j)];
+          if (["w","b"].includes(sqColor)) {
+            const sign = sqColor == "w" ? 1 : -1;
+            evaluation += sign * V.VALUES[this.getPiece(i, j)];
+          }
         }
       }
     }
index 14766ff..b04efa9 100644 (file)
@@ -1,22 +1,6 @@
-import { ChessRules, PiPo, Move } from "@/base_rules";
-import { ArrayFun } from "@/utils/array";
-import { randInt } from "@/utils/alea";
-
-// TODO:
-// Short-range pieces:
-// rook 1 or 2 squares orthogonal
-// bishop 1 or 2 diagonal
-// queen = bishop + rook
-// knight: one square orthogonal + 1 diagonal (only acepted desc)
-// no castle or en passant. Promotion possible only by capture (otherwise hole)
+import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class WormholeRules extends ChessRules {
-  // TODO: redefine pieces movements, taking care of holes (auxiliary func: getSquareAfter(shiftX,shiftY))
-  // this aux func could return null / undefined
-  // revoir getPotentialMoves et isAttacked : tout ce qui touche au board avec calcul,
-  // car les "board[x+..][y+..]" deviennent des board[getSquareAfter...]
-  // Special FEN sign for holes: 'x'
-
   static get HasFlags() {
     return false;
   }
@@ -25,24 +9,284 @@ export const VariantRules = class WormholeRules extends ChessRules {
     return false;
   }
 
-  getSquareAfter(sq, shift) {
-    // TODO
+  static get HOLE() {
+    return "xx";
+  }
+
+  static board2fen(b) {
+    if (b[0] == 'x') return 'x';
+    return ChessRules.board2fen(b);
+  }
+
+  static fen2board(f) {
+    if (f == 'x') return V.HOLE;
+    return ChessRules.fen2board(f);
   }
 
   getPpath(b) {
-    if (b.indexOf('x') >= 0)
-      return "Wormhole/hole.svg";
+    if (b[0] == 'x') return "Wormhole/hole";
     return b;
   }
 
-  // TODO: postUpdateVars: board[start] = "xx"; --> V.HOLE
+  getSquareAfter(square, movement) {
+    let shift1, shift2;
+    if (Array.isArray(movement[0])) {
+      // A knight
+      shift1 = movement[0];
+      shift2 = movement[1];
+    } else {
+      shift1 = movement;
+      shift2 = null;
+    }
+    const tryMove = (init, shift) => {
+      let step = [
+        shift[0] / Math.abs(shift[0]) || 0,
+        shift[1] / Math.abs(shift[1]) || 0,
+      ];
+      const nbSteps = Math.max(Math.abs(shift[0]), Math.abs(shift[1]));
+      let stepsAchieved = 0;
+      let sq = [init[0] + step[0], init[1] + step[1]];
+      while (V.OnBoard(sq[0],sq[1])) {
+        if (this.board[sq[0]][sq[1]] != V.HOLE)
+          stepsAchieved++;
+        if (stepsAchieved < nbSteps) {
+          sq[0] += step[0];
+          sq[1] += step[1];
+        }
+        else break;
+      }
+      if (stepsAchieved < nbSteps)
+        // The move is impossible
+        return null;
+      return sq;
+    };
+    // First, apply shift1
+    let dest = tryMove(square, shift1);
+    if (dest && shift2)
+      // A knight: apply second shift
+      dest = tryMove(dest, shift2);
+    return dest;
+  }
+
+  // NOTE (TODO?): some extra work done in some function because informations
+  // on one step should ease the computation for a step in the same direction.
+  static get steps() {
+    return {
+      r: [
+        [-1, 0],
+        [1, 0],
+        [0, -1],
+        [0, 1],
+        [-2, 0],
+        [2, 0],
+        [0, -2],
+        [0, 2]
+      ],
+      // Decompose knight movements into one step orthogonal + one diagonal
+      n: [
+        [[0, -1], [-1, -1]],
+        [[0, -1], [1, -1]],
+        [[-1, 0], [-1,-1]],
+        [[-1, 0], [-1, 1]],
+        [[0, 1], [-1, 1]],
+        [[0, 1], [1, 1]],
+        [[1, 0], [1, -1]],
+        [[1, 0], [1, 1]]
+      ],
+      b: [
+        [-1, -1],
+        [-1, 1],
+        [1, -1],
+        [1, 1],
+        [-2, -2],
+        [-2, 2],
+        [2, -2],
+        [2, 2]
+      ],
+      k: [
+        [-1, 0],
+        [1, 0],
+        [0, -1],
+        [0, 1],
+        [-1, -1],
+        [-1, 1],
+        [1, -1],
+        [1, 1]
+      ]
+    };
+  }
+
+  getJumpMoves([x, y], steps) {
+    let moves = [];
+    for (let step of steps) {
+      const sq = this.getSquareAfter([x,y], step);
+      if (sq &&
+        (
+          this.board[sq[0]][sq[1]] == V.EMPTY ||
+          this.canTake([x, y], sq)
+        )
+      ) {
+          moves.push(this.getBasicMove([x, y], sq));
+      }
+    }
+    return moves;
+  }
+
+  // What are the pawn moves from square x,y ?
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const shiftX = color == "w" ? -1 : 1;
+    const startRank = color == "w" ? sizeX - 2 : 1;
+    const lastRank = color == "w" ? 0 : sizeX - 1;
+
+    const sq1 = this.getSquareAfter([x,y], [shiftX,0]);
+    if (sq1 && this.board[sq1[0]][y] == V.EMPTY) {
+      // One square forward (cannot be a promotion)
+      moves.push(this.getBasicMove([x, y], [sq1[0], y]));
+      if (x == startRank) {
+        // If two squares after is available, then move is possible
+        const sq2 = this.getSquareAfter([x,y], [2*shiftX,0]);
+        if (sq2 && this.board[sq2[0]][y] == V.EMPTY)
+          // Two squares jump
+          moves.push(this.getBasicMove([x, y], [sq2[0], y]));
+      }
+    }
+    // Captures
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
+      : [V.PAWN];
+    for (let shiftY of [-1, 1]) {
+      const sq = this.getSquareAfter([x,y], [shiftX,shiftY]);
+      if (
+        sq &&
+        this.board[sq[0]][sq[1]] != V.EMPTY &&
+        this.canTake([x, y], [sq[0], sq[1]])
+      ) {
+        for (let piece of finalPieces) {
+          moves.push(
+            this.getBasicMove([x, y], [sq[0], sq[1]], {
+              c: color,
+              p: piece
+            })
+          );
+        }
+      }
+    }
+
+    return moves;
+  }
+
+  getPotentialRookMoves(sq) {
+    return this.getJumpMoves(sq, V.steps[V.ROOK]);
+  }
+
+  getPotentialKnightMoves(sq) {
+    return this.getJumpMoves(sq, V.steps[V.KNIGHT]);
+  }
+
+  getPotentialBishopMoves(sq) {
+    return this.getJumpMoves(sq, V.steps[V.BISHOP]);
+  }
+
+  getPotentialQueenMoves(sq) {
+    return this.getJumpMoves(
+      sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+    );
+  }
+
+  getPotentialKingMoves(sq) {
+    return this.getJumpMoves(sq, V.steps[V.KING]);
+  }
+
+  isAttackedByJump([x, y], colors, piece, steps) {
+    for (let step of steps) {
+      const sq = this.getSquareAfter([x,y], step);
+      if (
+        sq &&
+        this.getPiece(sq[0], sq[1]) === piece &&
+        colors.includes(this.getColor(sq[0], sq[1]))
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  isAttackedByPawn([x, y], colors) {
+    for (let c of colors) {
+      const pawnShift = c == "w" ? 1 : -1;
+      for (let i of [-1, 1]) {
+        const sq = this.getSquareAfter([x,y], [pawnShift,i]);
+        if (
+          sq &&
+          this.getPiece(sq[0], sq[1]) == V.PAWN &&
+          this.getColor(sq[0], sq[1]) == c
+        ) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByRook(sq, colors) {
+    return this.isAttackedByJump(sq, colors, V.ROOK, V.steps[V.ROOK]);
+  }
+
+  isAttackedByKnight(sq, colors) {
+    // NOTE: knight attack is not symmetric in this variant:
+    // steps order need to be reversed.
+    return this.isAttackedByJump(
+      sq,
+      colors,
+      V.KNIGHT,
+      V.steps[V.KNIGHT].map(s => s.reverse())
+    );
+  }
+
+  isAttackedByBishop(sq, colors) {
+    return this.isAttackedByJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]);
+  }
+
+  isAttackedByQueen(sq, colors) {
+    return this.isAttackedByJump(
+      sq,
+      colors,
+      V.QUEEN,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+    );
+  }
+
+  isAttackedByKing(sq, colors) {
+    return this.isAttackedByJump(sq, colors, V.KING, V.steps[V.KING]);
+  }
+
+  static PlayOnBoard(board, move) {
+    board[move.vanish[0].x][move.vanish[0].y] = V.HOLE;
+    for (let psq of move.appear) board[psq.x][psq.y] = psq.c + psq.p;
+  }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  getCurrentScore() {
+    if (this.atLeastOneMove())
+      return "*";
+    // No valid move: I lose
+    return this.turn == "w" ? "0-1" : "1-0";
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  evalPosition() {
+    let evaluation = 0;
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (![V.EMPTY,V.HOLE].includes(this.board[i][j])) {
+          const sign = this.getColor(i, j) == "w" ? 1 : -1;
+          evaluation += sign * V.VALUES[this.getPiece(i, j)];
+        }
+      }
+    }
+    return evaluation;
   }
 
   getNotation(move) {
index f93b5b3..2da8d9e 100644 (file)
@@ -294,7 +294,11 @@ export default {
           break;
         case "fullgame":
           // Callback "roomInit" to poll clients only after game is loaded
-          this.loadGame(data.data, this.roomInit);
+          let game = data.data;
+          // Move format isn't the same in storage and in browser,
+          // because of the 'addTime' field.
+          game.moves = game.moves.map(m => { return m.move || m; });
+          this.loadGame(game, this.roomInit);
           break;
         case "asklastate":
           // Sending last state if I played a move or score != "*"