Some fixes, and add 2 variants: Checkless and Parachute
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 25 Mar 2020 13:52:22 +0000 (14:52 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 25 Mar 2020 13:52:22 +0000 (14:52 +0100)
35 files changed:
TODO
client/src/base_rules.js
client/src/translations/about/en.pug
client/src/translations/about/es.pug
client/src/translations/about/fr.pug
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Checkless/en.pug [new file with mode: 0644]
client/src/translations/rules/Checkless/es.pug [new file with mode: 0644]
client/src/translations/rules/Checkless/fr.pug [new file with mode: 0644]
client/src/translations/rules/Parachute/en.pug [new file with mode: 0644]
client/src/translations/rules/Parachute/es.pug [new file with mode: 0644]
client/src/translations/rules/Parachute/fr.pug [new file with mode: 0644]
client/src/variants/Atomic.js
client/src/variants/Ball.js
client/src/variants/Baroque.js
client/src/variants/Checkless.js [new file with mode: 0644]
client/src/variants/Crazyhouse.js
client/src/variants/Dynamo.js [new file with mode: 0644]
client/src/variants/Hidden.js
client/src/variants/Marseille.js
client/src/variants/Parachute.js [new file with mode: 0644]
client/src/variants/Recycle.js
client/src/variants/Rifle.js
client/src/variants/Royalrace.js
client/src/variants/Schess.js
client/src/variants/Shatranj.js
client/src/variants/Suicide.js
client/src/variants/Upsidedown.js
client/src/views/Game.vue
client/src/views/Hall.vue
server/db/populate.sql
server/models/User.js
server/sockets.js

diff --git a/TODO b/TODO
index 7d46a80..8e81cc7 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,17 +1,7 @@
 # New variants
-Landing pieces from empty board:
-https://www.chessvariants.com/diffsetup.dir/unachess.html
-Parachute v1 & 2
-
-Generator variant, called "Matrix" ?
-Peces on first rank never move but generate new pieces. Pawn don't generate.
-A generator captured and replaced by a similar piece does not generate.
-King does not generate. No castling. En passant possible?
-Goal is still checkmate.
+Maxima, Interweave, Roccoco
 
 Take(a)n(d)make : if capture a piece, take its power for the last of the turn and make a move like it.
-If a pawn taken: direction of the capturer.
+If a pawn taken: direction of the capturer, can capture enemy.
 
-Dynamo chess
-
-Maxima, Interweave, Roccoco
+Dynamo chess --> dur...
index e142839..37e756e 100644 (file)
@@ -224,9 +224,11 @@ export const ChessRules = class ChessRules {
     const s = move.start,
           e = move.end;
     if (
-      Math.abs(s.x - e.x) == 2 &&
       s.y == e.y &&
-      (move.appear.length > 0 && move.appear[0].p == V.PAWN)
+      Math.abs(s.x - e.x) == 2 &&
+      // Next conditions for variants like Atomic or Rifle, Recycle...
+      (move.appear.length > 0 && move.appear[0].p == V.PAWN) &&
+      (move.vanish.length > 0 && move.vanish[0].p == V.PAWN)
     ) {
       return {
         x: (s.x + e.x) / 2,
@@ -941,6 +943,7 @@ export const ChessRules = class ChessRules {
   }
 
   // Stop at the first move found
+  // TODO: not really, it explores all moves from a square but one would suffice.
   atLeastOneMove() {
     const color = this.turn;
     for (let i = 0; i < V.size.x; i++) {
index 6d55194..6e888b9 100644 (file)
@@ -50,4 +50,5 @@ h3 Related links
   div
     a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
     span &nbsp;(in French)
+  a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net
   a(href="https://brainking.com/") brainking.com
index 66aa429..0986e01 100644 (file)
@@ -47,4 +47,5 @@ h3 Enlaces relacionados
   div
     a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
     span &nbsp;(en francés)
+  a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net
   a(href="https://brainking.com/") brainking.com
index b423603..849aad1 100644 (file)
@@ -46,4 +46,5 @@ h3 Liens connexes
   a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
   a(href="https://schemingmind.com/") schemingmind.com
   a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
+  a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net
   a(href="https://brainking.com/") brainking.com
index 75d9b89..7ce045f 100644 (file)
@@ -177,6 +177,7 @@ export const translations = {
   "Keep antiking in check (v2)": "Keep antiking in check (v2)",
   "Kings cross the 8x8 board": "Kings cross the 8x8 board",
   "Kings cross the 11x11 board": "Kings cross the 11x11 board",
+  "Landing on the board": "Landing on the board",
   "Laws of attraction": "Laws of attraction",
   "Long jumps over pieces": "Long jumps over pieces",
   "Lose all pieces": "Lose all pieces",
@@ -189,6 +190,7 @@ export const translations = {
   "Move like a knight (v2)": "Move like a knight (v2)",
   "Move twice": "Move twice",
   "Neverending rows": "Neverending rows",
+  "No-check mode": "No-check mode",
   "Pawns move diagonally": "Pawns move diagonally",
   "Play at the same time": "Play at the same time",
   "Powerful pieces": "Powerful pieces",
index 465f39f..40ba5c8 100644 (file)
@@ -177,6 +177,7 @@ export const translations = {
   "Keep antiking in check (v2)": "Mantener el antirey en jaque (v2)",
   "Kings cross the 8x8 board": "Los reyes cruzan el 8x8 tablero",
   "Kings cross the 11x11 board": "Los reyes cruzan el 11x11 tablero",
+  "Landing on the board": "Aterrizando en el tablero",
   "Laws of attraction": "Las leyes de las atracciones",
   "Long jumps over pieces": "Saltos largos sobre las piezas",
   "Lose all pieces": "Perder todas las piezas",
@@ -189,6 +190,7 @@ export const translations = {
   "Move like a knight (v2)": "Moverse como un caballo (v2)",
   "Move twice": "Mover dos veces",
   "Neverending rows": "Filas interminables",
+  "No-check mode": "Modo sin jaque",
   "Pawns move diagonally": "Peones se mueven en diagonal",
   "Play at the same time": "Jugar al mismo tiempo",
   "Powerful pieces": "Piezas poderosas",
index 62dd3ba..8954f7b 100644 (file)
@@ -177,6 +177,7 @@ export const translations = {
   "Keep antiking in check (v2)": "Gardez l'antiroi en échec (v2)",
   "Kings cross the 8x8 board": "Les rois traversent l'échiquier 8x8",
   "Kings cross the 11x11 board": "Les rois traversent l'échiquier 11x11",
+  "Landing on the board": "Débarquement sur l'échiquier",
   "Laws of attraction": "Les lois de l'attraction",
   "Long jumps over pieces": "Sauts longs par dessus les pièces",
   "Lose all pieces": "Perdez toutes les pièces",
@@ -189,6 +190,7 @@ export const translations = {
   "Move like a knight (v2)": "Bouger comme un cavalier (v2)",
   "Move twice": "Jouer deux coups",
   "Neverending rows": "Rangées sans fin",
+  "No-check mode": "Mode sans échec",
   "Pawns move diagonally": "Les pions vont en diagonale",
   "Play at the same time": "Jouer en même temps",
   "Powerful pieces": "Pièces puissantes",
diff --git a/client/src/translations/rules/Checkless/en.pug b/client/src/translations/rules/Checkless/en.pug
new file mode 100644 (file)
index 0000000..ce3ca48
--- /dev/null
@@ -0,0 +1,42 @@
+p.boxed
+  | Giving check is forbidden, unless it is a checkmate.
+
+p.
+  Neither player is allowed to give a check, with the exception of checkmate.
+  Thus, the king is much more powerful than in orthodox chess: as long as
+  he can (potentially) escape, he doesn't fear attacks.
+  So the king can be used to defend pieces in an unusual way.
+
+p.
+  On the following diagram, 1.Qxa6 threatens 2.Bb6
+  with a mate to follow by Qxa7.
+  The black rook cannot take because it would check the white king.
+
+figure.diagram-container
+  .diagram
+    | fen:1k1r1b2/rP1b2p1/pQ1pp1nq/K4p1p/P4PnP/2PN2P1/3PP1B1/R1RN2B1:
+  figcaption 1.Qxd8+ is forbidden because 1...Bc8 would be possible.
+
+h3 Disambiguation
+
+p.
+  1.Qf7# is checkmate on the left diagram, because if the king takes
+  then the rook on h8 gives check but not checkmate.
+  However, on the right diagram 1.Qf7+ runs into 1...Kxf7#, which is now
+  a legal move because the white king is checkmated.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:K5kr/8/5Q2/8/8/8/8/8:
+  .diagram.diag22
+    | fen:K5kr/RB6/5Q2/8/8/8/8/7b:
+  figcaption 1.Qf7 mates on the left, but not on the right.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/usualeq.dir/checklss.html")
+    | Checkless chess
+  | &nbsp;on chessvariants.com, and the 
+  a(href="https://en.wikipedia.org/wiki/Checkless_chess") Wikipedia page
+  | .
diff --git a/client/src/translations/rules/Checkless/es.pug b/client/src/translations/rules/Checkless/es.pug
new file mode 100644 (file)
index 0000000..1f40860
--- /dev/null
@@ -0,0 +1,43 @@
+p.boxed
+  | Se prohíbe el jaque, a menos que sea un jaque mate.
+
+p.
+  Ningún jugador tiene derecho a dar jaque, excepto el jaque mate.
+  Por lo tanto, el rey es claramente más poderoso que en el ajedrez ortodoxo:
+  mientras pueda (potencialmente) escapar, no tiene miedo a los ataques.
+  El rey puede ser usado para defender las piezas de una manera inusual.
+
+p.
+  En el siguiente diagrama, 1.Qxa6 amenaza a 2.Bb6 con un jaque mate
+  seguido de Qxa7.
+  La torre negra no puede tomar porque daría jaque al rey blanco.
+
+figure.diagram-container
+  .diagram
+    | fen:1k1r1b2/rP1b2p1/pQ1pp1nq/K4p1p/P4PnP/2PN2P1/3PP1B1/R1RN2B1:
+  figcaption 1.Qxd8+ está prohibido porque 1...Bc8 sería posible.
+
+h3 Desambiguación
+
+p.
+  1.Qf7# mat en el diagrama de la izquierda, porque si el rey toma
+  la torre h8 da jaque pero no jaque mate.
+  Sin embargo, en el diagrama de la derecha 1.Qf7+ permite 1...Kxf7#,
+  que ahora es legal porque el rey blanco es en jaque.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:K5kr/8/5Q2/8/8/8/8/8:
+  .diagram.diag22
+    | fen:K5kr/RB6/5Q2/8/8/8/8/7b:
+  figcaption 1.Qf7 mate a la izquierda, pero no a la derecha.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/usualeq.dir/checklss.html")
+    | variante Checkless
+  | &nbsp;en chessvariants.com, y la 
+  a(href="https://en.wikipedia.org/wiki/Checkless_chess") página wikipedia
+  | .
diff --git a/client/src/translations/rules/Checkless/fr.pug b/client/src/translations/rules/Checkless/fr.pug
new file mode 100644 (file)
index 0000000..d42c937
--- /dev/null
@@ -0,0 +1,43 @@
+p.boxed
+  | Donner échec est interdit, sauf si c'est un échec et mat.
+
+p.
+  Aucun joueur n'a le droit de donner échec, à l'exception du mat.
+  Ainsi, le roi est nettement plus puissant qu'aux échecs orthodoxes :
+  tant qu'il peut (potentiellement) s'échapper, il ne craint pas les attaques.
+  Le roi peut alors être utilisé pour défendre les pièces d'une manière
+  inhabituelle.
+
+  p.
+  Sur le diagramme suivant, 1.Qxa6 menaçe 2.Bb6 avec un mat à suivre par Qxa7.
+  La tour noire ne peut pas prendre car elle mettrait le roi blanc en échec.
+
+figure.diagram-container
+  .diagram
+    | fen:1k1r1b2/rP1b2p1/pQ1pp1nq/K4p1p/P4PnP/2PN2P1/3PP1B1/R1RN2B1:
+  figcaption 1.Qxd8+ est interdit car 1...Bc8 serait possible.
+
+h3 Désambiguïsation
+
+p.
+  1.Qf7# fait mat sur le diagramme de gauche, car si le roi prend alors
+  la tour h8 donne échec mais pas mat.
+  Cependant, sur le diagramme de droite 1.Qf7+ permet 1...Kxf7#,
+  qui est maintenant légal car le roi blanc est mat.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:K5kr/8/5Q2/8/8/8/8/8:
+  .diagram.diag22
+    | fen:K5kr/RB6/5Q2/8/8/8/8/7b:
+  figcaption 1.Qf7 mate à gauche, mais pas à droite.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/usualeq.dir/checklss.html")
+    | variante Checkless
+  | &nbsp;sur chessvariants.com, et la 
+  a(href="https://en.wikipedia.org/wiki/Checkless_chess") page Wikipedia
+  | .
diff --git a/client/src/translations/rules/Parachute/en.pug b/client/src/translations/rules/Parachute/en.pug
new file mode 100644 (file)
index 0000000..61ef945
--- /dev/null
@@ -0,0 +1,35 @@
+p.boxed
+  | The board is initially empty.
+  | Add a piece (not giving check) or move one at each turn.
+
+p.
+  The king can be added at any moment, but while he hasn't landed
+  no capture can be done. So you could move or land your king "into check"
+  if your opponent didn't land his king yet.
+
+p.
+  Giving check with a landed piece is forbidden
+  (assuming both kings are on the board).
+
+p.
+  Pawns can be landed on the four first ranks only. A pawn on the first
+  rank can jump two squares, and be captured en passant.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/3b2r1/3R4/k3Q3/3R4/8/8:
+  .diagram.diag22
+    | fen:8/8/K2b2r1/3R4/k1N1Q3/3R4/5q2/8:
+  figcaption.
+    Left: no white king, so the black king is safe.
+    Right: black to move, there is no way to avoid mate.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/diffsetup.dir/unachess.html")
+    | Unachess II
+  | &nbsp;on chessvariants.com. Unachess I gives a too large advantage
+  | to white, in the few games I could play.
+
+p Inventors: Jeff Miller and Edward Jackman (1995)
diff --git a/client/src/translations/rules/Parachute/es.pug b/client/src/translations/rules/Parachute/es.pug
new file mode 100644 (file)
index 0000000..fe04d4f
--- /dev/null
@@ -0,0 +1,37 @@
+p.boxed
+  | El tablero está inicialmente vacío.
+  | Agregue una pieza (sin dar jaque) o mueva una cada turno.
+
+p.
+  Se puede agregar el rey en cualquier momento, pero hasta que haya aterrizado
+  las capturas están prohibidas. Para que puedas moverte o dejarte caer
+  su rey "en jaque" si el oponente aún no ha colocado el suyo.
+
+p.
+  No puedes dar jaque con una pieza en paracaídas
+  (asumiendo que los dos reyes están en el tablero).
+
+p.
+  Los peones se pueden soltar solo en las primeras cuatro filas.
+  Un peón en la primera fila puede saltar dos espacios,
+  y quedar atrapado en passant.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/3b2r1/3R4/k3Q3/3R4/8/8:
+  .diagram.diag22
+    | fen:8/8/K2b2r1/3R4/k1N1Q3/3R4/5q2/8:
+  figcaption.
+    Izquierda: no hay rey blanco, por lo que el rey negro está en seguridad.
+    Derecha: juegan las negras, no puede evitar el jaque mate.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffsetup.dir/unachess.html")
+    | variante Unachess II
+  | &nbsp;en chessvariants.com. Unachess I da demasiada ventaja
+  | a las blancas, en los pocos juegos que he podido jugar.
+
+p Inventores: Jeff Miller y Edward Jackman (1995)
diff --git a/client/src/translations/rules/Parachute/fr.pug b/client/src/translations/rules/Parachute/fr.pug
new file mode 100644 (file)
index 0000000..c0a2a9f
--- /dev/null
@@ -0,0 +1,37 @@
+p.boxed
+  | L'échiquier est initialement vide.
+  | Ajoutez une pièce (sans donner échec) ou déplacez-en une à chaque tour.
+
+p.
+  Le roi peut être ajouté à tout moment, mais tant qu'il n'a pas atterri
+  les captures sont interdites. Ainsi vous pourriez déplacer ou parachuter
+  votre roi "en échec" si l'adversaire n'a pas encore posé le sien.
+
+p.
+  On ne peut pas donner échec avec une pièce parachutée
+  (supposant que les deux rois sont sur l'échiquier).
+
+p.
+  Les pions peuvent être parachutés sur les quatre premières rangées seulement.
+  Un pion sur la première rangée peut sauter de deux cases,
+  et être capturé en passant.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/3b2r1/3R4/k3Q3/3R4/8/8:
+  .diagram.diag22
+    | fen:8/8/K2b2r1/3R4/k1N1Q3/3R4/5q2/8:
+  figcaption.
+    Gauche : pas de roi blanc, donc le roi noir est en sécurité.
+    Droite : trait aux noirs, on ne peut pas éviter le mat.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffsetup.dir/unachess.html")
+    | variante Unachess II
+  | &nbsp;sur chessvariants.com. Unachess I donne un trop grand avantage
+  aux blancs, dans les quelques parties que j'ai pu jouer.
+
+p Inventeurs : Jeff Miller et Edward Jackman (1995)
index 889f2df..90089e8 100644 (file)
@@ -1,13 +1,6 @@
 import { ChessRules, PiPo } from "@/base_rules";
 
 export class AtomicRules extends ChessRules {
-  getEpSquare(moveOrSquare) {
-    if (typeof moveOrSquare !== "object" || moveOrSquare.appear.length > 0)
-      return super.getEpSquare(moveOrSquare);
-    // Capturing move: no en-passant
-    return undefined;
-  }
-
   getPotentialMovesFrom([x, y]) {
     let moves = super.getPotentialMovesFrom([x, y]);
 
index cd504a9..85e33e6 100644 (file)
@@ -14,9 +14,6 @@ export class BallRules extends ChessRules {
   static get HasFlags() {
     return false;
   }
-  static get HasCastle() {
-    return false;
-  }
 
   static get CHAMPION() {
     return 'c';
index 92bae77..031ea72 100644 (file)
@@ -7,10 +7,6 @@ export class BaroqueRules extends ChessRules {
     return false;
   }
 
-  static get HasCastle() {
-    return false;
-  }
-
   static get HasEnpassant() {
     return false;
   }
diff --git a/client/src/variants/Checkless.js b/client/src/variants/Checkless.js
new file mode 100644 (file)
index 0000000..8861214
--- /dev/null
@@ -0,0 +1,44 @@
+import { ChessRules } from "@/base_rules";
+
+export class ChecklessRules extends ChessRules {
+  // Cannot use super.atLeastOneMove: lead to infinite recursion
+  atLeastOneMove_aux() {
+    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.getColor(i, j) == color) {
+          const moves = this.getPotentialMovesFrom([i, j]);
+          if (moves.length > 0) {
+            for (let k = 0; k < moves.length; k++) {
+              let res = false;
+              this.play(moves[k]);
+              res = !this.underCheck(color) && !this.underCheck(oppCol);
+              this.undo(moves[k]);
+              if (res) return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  filterValid(moves) {
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    return moves.filter(m => {
+      this.play(m);
+      // Move shouldn't result in self-check:
+      let res = !this.underCheck(color);
+      if (res) {
+        const checkOpp = this.underCheck(oppCol);
+        // If checking the opponent, he shouldn't have any legal move:
+        res = !checkOpp || checkOpp && !this.atLeastOneMove_aux();
+      }
+      this.undo(m);
+      return res;
+    });
+  }
+};
index 2307eaf..576f7d2 100644 (file)
@@ -31,13 +31,6 @@ export class CrazyhouseRules extends ChessRules {
     );
   }
 
-  getEpSquare(moveOrSquare) {
-    if (typeof moveOrSquare !== "object" || moveOrSquare.vanish.length > 0)
-      return super.getEpSquare(moveOrSquare);
-    // Landing move: no en-passant
-    return undefined;
-  }
-
   static GenRandInitFen(randomness) {
     return ChessRules.GenRandInitFen(randomness) + " 0000000000 -";
   }
diff --git a/client/src/variants/Dynamo.js b/client/src/variants/Dynamo.js
new file mode 100644 (file)
index 0000000..c76c040
--- /dev/null
@@ -0,0 +1,5 @@
+import { ChessRules } from "@/base_rules";
+
+export class DynamoRules extends ChessRules {
+  // TODO
+};
index ef5b0bb..2fcab24 100644 (file)
@@ -7,10 +7,6 @@ export class HiddenRules extends ChessRules {
     return false;
   }
 
-  static get HasCastle() {
-    return false;
-  }
-
   static get HasEnpassant() {
     return false;
   }
index ed9965b..940d9ba 100644 (file)
@@ -124,7 +124,7 @@ export class MarseilleRules extends ChessRules {
     if (piece == V.KING && move.appear.length > 0) {
       this.kingPos[c][0] = move.appear[0].x;
       this.kingPos[c][1] = move.appear[0].y;
-      if (V.HasCastle) this.castleFlags[c] = [V.size.y, V.size.y];
+      this.castleFlags[c] = [V.size.y, V.size.y];
       return;
     }
     const oppCol = V.GetOppCol(c);
diff --git a/client/src/variants/Parachute.js b/client/src/variants/Parachute.js
new file mode 100644 (file)
index 0000000..0718635
--- /dev/null
@@ -0,0 +1,210 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export class ParachuteRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check reserves
+    if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{12,12}$/))
+      return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { reserve: fenParts[4] }
+    );
+  }
+
+  static GenRandInitFen() {
+    // ChessRules.PIECES order is P, R, N, B, Q, K:
+    return "8/8/8/8/8/8/8/8 w 0 - 822211822211";
+  }
+
+  getFen() {
+    return super.getFen() + " " + this.getReserveFen();
+  }
+
+  getFenForRepeat() {
+    return super.getFenForRepeat() + "_" + this.getReserveFen();
+  }
+
+  getReserveFen() {
+    let counts = new Array(12);
+    for (let i = 0; i < V.PIECES.length; i++) {
+      counts[i] = this.reserve["w"][V.PIECES[i]];
+      counts[6 + i] = this.reserve["b"][V.PIECES[i]];
+    }
+    return counts.join("");
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Also init reserves (used by the interface to show landable pieces)
+    this.reserve = {
+      w: {
+        [V.PAWN]: parseInt(fenParsed.reserve[0]),
+        [V.ROOK]: parseInt(fenParsed.reserve[1]),
+        [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[3]),
+        [V.QUEEN]: parseInt(fenParsed.reserve[4]),
+        [V.KING]: parseInt(fenParsed.reserve[5])
+      },
+      b: {
+        [V.PAWN]: parseInt(fenParsed.reserve[6]),
+        [V.ROOK]: parseInt(fenParsed.reserve[7]),
+        [V.KNIGHT]: parseInt(fenParsed.reserve[8]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[9]),
+        [V.QUEEN]: parseInt(fenParsed.reserve[10]),
+        [V.KING]: parseInt(fenParsed.reserve[11])
+      }
+    };
+  }
+
+  getColor(i, j) {
+    if (i >= V.size.x) return i == V.size.x ? "w" : "b";
+    return this.board[i][j].charAt(0);
+  }
+
+  getPiece(i, j) {
+    if (i >= V.size.x) return V.RESERVE_PIECES[j];
+    return this.board[i][j].charAt(1);
+  }
+
+  // Used by the interface:
+  getReservePpath(index, color) {
+    return color + V.RESERVE_PIECES[index];
+  }
+
+  // Ordering on reserve pieces (matching V.PIECES order)
+  static get RESERVE_PIECES() {
+    return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
+  }
+
+  getReserveMoves([x, y]) {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const p = V.RESERVE_PIECES[y];
+    if (this.reserve[color][p] == 0) return [];
+    let moves = [];
+    let boundary =
+      p == V.PAWN
+        // Pawns can land only on 4 first ranks:
+        ? (color == 'w' ? [4, 8] : [0, 4])
+        : [0, 8];
+    for (let i = boundary[0]; i < boundary[1]; i++) {
+      for (let j = 0; j < 8; j++) {
+        if (this.board[i][j] == V.EMPTY) {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: p
+              })
+            ],
+            vanish: [],
+            start: { x: x, y: y }, //a bit artificial...
+            end: { x: i, y: j }
+          });
+          this.play(mv);
+          // Landing with check is forbidden:
+          if (!this.underCheck(oppCol)) moves.push(mv);
+          this.undo(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves =
+      x >= 8
+        ? this.getReserveMoves([x, y])
+        : super.getPotentialMovesFrom([x, y]);
+    // Forbid captures if king not landed yet:
+    if (x < 8 && moves.length > 0 && this.kingPos[moves[0].appear[0].c][0] < 0)
+      moves = moves.filter(m => m.vanish.length == 1);
+    return moves;
+  }
+
+  getAllValidMoves() {
+    let moves = super.getAllValidMoves();
+    const color = this.turn;
+    for (let i = 0; i < V.RESERVE_PIECES.length; i++)
+      moves = moves.concat(
+        this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
+      );
+    return this.filterValid(moves);
+  }
+
+  isAttacked(sq, color) {
+    // While the king hasn't landed, nothing is attacked:
+    if (this.kingPos[color][0] < 0) return false;
+    return super.isAttacked(sq, color);
+  }
+
+  atLeastOneMove() {
+    if (!super.atLeastOneMove()) {
+      // Search one reserve move
+      for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+        let moves = this.filterValid(
+          this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
+        );
+        if (moves.length > 0) return true;
+      }
+      return false;
+    }
+    return true;
+  }
+
+  prePlay(move) {
+    super.prePlay(move);
+    if (move.vanish.length == 0) this.reserve[this.turn][move.appear[0].p]--;
+  }
+
+  postUndo(move) {
+    if (move.vanish.length == 0) this.reserve[this.turn][move.appear[0].p]++;
+    // (Potentially) Reset king position
+    if (move.appear[0].p == V.KING) {
+      const c = move.appear[0].c;
+      if (move.vanish.length == 0)
+        // Landing king
+        this.kingPos[c] = [-1, -1];
+      else
+        // King movement
+        this.kingPos[c] = [move.start.x, move.start.y];
+    }
+  }
+
+  static get SEARCH_DEPTH() {
+    return 1;
+  }
+
+  evalPosition() {
+    let evaluation = super.evalPosition();
+    // Add reserves:
+    for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+      const p = V.RESERVE_PIECES[i];
+      evaluation += this.reserve["w"][p] * V.VALUES[p];
+      evaluation -= this.reserve["b"][p] * V.VALUES[p];
+    }
+    return evaluation;
+  }
+
+  getNotation(move) {
+    if (move.vanish.length > 0) return super.getNotation(move);
+    // Parachutage:
+    const piece =
+      move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
+    return piece + "@" + V.CoordsToSquare(move.end);
+  }
+};
index 8b070fa..adaceb0 100644 (file)
@@ -27,13 +27,6 @@ export class RecycleRules extends ChessRules {
     );
   }
 
-  getEpSquare(moveOrSquare) {
-    if (typeof moveOrSquare !== "object" || moveOrSquare.vanish.length > 0)
-      return super.getEpSquare(moveOrSquare);
-    // Landing move: no en-passant
-    return undefined;
-  }
-
   static GenRandInitFen(randomness) {
     return ChessRules.GenRandInitFen(randomness) + " 0000000000";
   }
index 98661ae..94a6d05 100644 (file)
@@ -1,13 +1,6 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class RifleRules extends ChessRules {
-  getEpSquare(moveOrSquare) {
-    if (typeof moveOrSquare !== "object" || moveOrSquare.appear.length > 0)
-      return super.getEpSquare(moveOrSquare);
-    // Capturing move: no en-passant
-    return undefined;
-  }
-
   getBasicMove([sx, sy], [ex, ey], tr) {
     let mv = new Move({
       appear: [],
index 39ee02a..21a62c8 100644 (file)
@@ -7,10 +7,6 @@ export class RoyalraceRules extends ChessRules {
     return false;
   }
 
-  static get HasCastle() {
-    return false;
-  }
-
   static get HasEnpassant() {
     return false;
   }
index ba5730e..07556a5 100644 (file)
@@ -50,7 +50,7 @@ export class SchessRules extends ChessRules {
   setFlags(fenflags) {
     super.setFlags(fenflags); //castleFlags
     this.pieceFlags = {
-      w: [...Array(8)], //pawns can move 2 squares?
+      w: [...Array(8)], //pieces can generate Hawk or Elephant?
       b: [...Array(8)]
     };
     const flags = fenflags.substr(4); //skip first 4 letters, for castle
@@ -293,7 +293,7 @@ export class SchessRules extends ChessRules {
     }
     this.updateCastleFlags(move, piece);
 
-    const oppCol = V.GetOppCol(color);
+    const oppCol = this.turn;
     const firstRank = (color == 'w' ? 7 : 0);
     const oppFirstRank = 7 - firstRank;
     // Does this move turn off a piece init square flag?
index 00b1456..105331f 100644 (file)
@@ -5,10 +5,6 @@ export class ShatranjRules extends ChessRules {
     return false;
   }
 
-  static get HasCastle() {
-    return false;
-  }
-
   static get HasEnpassant() {
     return false;
   }
index c81d355..2693f84 100644 (file)
@@ -7,10 +7,6 @@ export class SuicideRules extends ChessRules {
     return false;
   }
 
-  static get HasCastle() {
-    return false;
-  }
-
   static get PawnSpecs() {
     return Object.assign(
       {},
index 2076824..abffd8f 100644 (file)
@@ -7,10 +7,6 @@ export class UpsidedownRules extends ChessRules {
     return false;
   }
 
-  static get HasCastle() {
-    return false;
-  }
-
   static get HasEnpassant() {
     return false;
   }
@@ -73,8 +69,9 @@ export class UpsidedownRules extends ChessRules {
       pieces["w"].join("").toUpperCase() +
       "/PPPPPPPP/8/8/8/8/pppppppp/" +
       pieces["b"].join("") +
+      // No castle, no en-passant:
       " w 0"
-    ); //no castle, no en-passant
+    );
   }
 
   static get SEARCH_DEPTH() {
index 3171855..e5b45ec 100644 (file)
@@ -474,10 +474,12 @@ export default {
           // TODO: shuffling and random filtering on server,
           // if the room is really crowded.
           Object.keys(data.sockIds).forEach(sid => {
-            // TODO: test sid != user.sid was already done on server
             if (sid != this.st.user.sid) {
-              this.people[sid] = { tmpIds: data.sockIds[sid] };
               this.send("askidentity", { target: sid });
+              this.people[sid] = { tmpIds: data.sockIds[sid] };
+            } else {
+              // Complete my tmpIds:
+              Object.assign(this.people[sid].tmpIds, data.sockIds[sid]);
             }
           });
           break;
index 132c31b..5c5dc33 100644 (file)
@@ -588,7 +588,6 @@ export default {
           // TODO: shuffling and random filtering on server,
           // if the room is really crowded.
           Object.keys(data.sockIds).forEach(sid => {
-            // TODO: test sid != user.sid was already done on server
             if (sid != this.st.user.sid) {
               // Pick a target tmpId (+page) at random:
               const pt = Object.values(data.sockIds[sid]);
index 28d186c..1895777 100644 (file)
@@ -16,6 +16,7 @@ insert or ignore into Variants (name,description) values
   ('Cannibal', 'Capture powers'),
   ('Capture', 'Mandatory captures'),
   ('Checkered', 'Shared pieces'),
+  ('Checkless', 'No-check mode'),
   ('Chess960', 'Standard rules'),
   ('Circular', 'Run forward'),
   ('Coregal', 'Two royal pieces'),
@@ -36,6 +37,7 @@ insert or ignore into Variants (name,description) values
   ('Losers', 'Get strong at self-mate'),
   ('Magnetic', 'Laws of attraction'),
   ('Marseille', 'Move twice'),
+  ('Parachute', 'Landing on the board'),
   ('Perfect', 'Powerful pieces'),
   ('Racingkings', 'Kings cross the 8x8 board'),
   ('Rifle', 'Shoot pieces'),
index e3e5408..ab826d9 100644 (file)
@@ -149,6 +149,7 @@ const UserModel = {
           // Remove users unlogged for > 24h
           if (!u.sessionToken && tsNow - u.created > day)
           {
+            toRemove.push(u.id);
             UserModel.notify(
               u,
               "Your account has been deleted because " +
index 65e4163..25abadb 100644 (file)
@@ -124,13 +124,12 @@ module.exports = function(wss) {
           // From Game
           let sockIds = {};
           Object.keys(clients[page]).forEach(k => {
-            // Avoid polling myself: no new information to get
-            if (k != sid) {
-              sockIds[k] = {};
-              Object.keys(clients[page][k]).forEach(x => {
+            sockIds[k] = {};
+            Object.keys(clients[page][k]).forEach(x => {
+              // Avoid polling my tmpId: no information to get
+              if (k != sid || x != tmpId)
                 sockIds[k][x] = { focus: clients[page][k][x].focus };
-              });
-            }
+            });
           });
           send(socket, { code: "pollclients", sockIds: sockIds });
           break;
@@ -139,30 +138,30 @@ module.exports = function(wss) {
           // From Hall
           let sockIds = {};
           Object.keys(clients["/"]).forEach(k => {
-            // Avoid polling myself: no new information to get
-            if (k != sid) {
-              sockIds[k] = {};
-              Object.keys(clients[page][k]).forEach(x => {
+            sockIds[k] = {};
+            Object.keys(clients[page][k]).forEach(x => {
+              // Avoid polling my tmpId: no information to get
+              if (k != sid || x != tmpId) {
                 sockIds[k][x] = {
                   page: "/",
                   focus: clients[page][k][x].focus
                 };
-              });
-            }
+              }
+            });
           });
           // NOTE: a "gamer" could also just be an observer
           Object.keys(clients).forEach(p => {
             if (p.indexOf("/game/") >= 0) {
               Object.keys(clients[p]).forEach(k => {
-                if (k != sid) {
-                  if (!sockIds[k]) sockIds[k] = {};
-                  Object.keys(clients[p][k]).forEach(x => {
+                if (!sockIds[k]) sockIds[k] = {};
+                Object.keys(clients[p][k]).forEach(x => {
+                  if (k != sid || x != tmpId) {
                     sockIds[k][x] = {
                       page: p,
                       focus: clients[p][k][x].focus
                     };
-                  });
-                }
+                  }
+                });
               });
             }
           });