A few fixes + draft Interweave and Takenmake. Only 1 1/2 variant to go now :)
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 1 Apr 2020 20:10:57 +0000 (22:10 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 1 Apr 2020 20:10:57 +0000 (22:10 +0200)
17 files changed:
TODO
client/src/App.vue
client/src/components/BaseGame.vue
client/src/components/ComputerGame.vue
client/src/translations/about/en.pug
client/src/translations/about/es.pug
client/src/translations/about/fr.pug
client/src/translations/rules/Interweave/en.pug [new file with mode: 0644]
client/src/translations/rules/Interweave/es.pug [new file with mode: 0644]
client/src/translations/rules/Interweave/fr.pug [new file with mode: 0644]
client/src/translations/rules/Takenmake/en.pug [new file with mode: 0644]
client/src/translations/rules/Takenmake/es.pug [new file with mode: 0644]
client/src/translations/rules/Takenmake/fr.pug [new file with mode: 0644]
client/src/variants/Berolina.js
client/src/variants/Grand.js
client/src/variants/Interweave.js [new file with mode: 0644]
client/src/variants/Takenmake.js [new file with mode: 0644]

diff --git a/TODO b/TODO
index ecc877a..d6c6076 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,21 +1,3 @@
-# New variants
 Finish https://www.chessvariants.com/mvopponent.dir/dynamo.html
 https://echekk.fr/spip.php?page=article&id_article=599
-
-Colorbound
-https://www.chessvariants.com/d.betza/chessvar/dan/colclob.html
-https://en.wikipedia.org/wiki/Grotesque_(chess) (fun)
-
-Maxima, Interweave, Roccoco
-https://www.chessvariants.com/other.dir/rococo.html
-https://www.chessvariants.com/dpieces.dir/maxima/maxima.html
-https://www.chessvariants.com/other.dir/interweave.html
-
-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, can capture enemy.
-http://www.strategems.net/sections/fairy_defs.html
-Having captured, a unit must immediately, as part of its move, play a non-capturing move, using ONLY the powers of movement of the captured unit from the capture-square. If no such move is available, the capture is illegal. Promotion by capture occurs only when a pawn arrives on the promotion rank as the result of a Take & Make move. Checks are as in normal chess: after the notional capture of the checked King, the checking unit does not move away from the King’s square.
-
-Chakart :)
-
-https://en.wikipedia.org/wiki/Fairy_chess_piece
+And Chakart :)
index a961d7f..def4526 100644 (file)
@@ -304,6 +304,7 @@ footer
   background-color: #6f8f57
 
 div.board
+  user-select: none
   float: left
   height: 0
   display: inline-block
index 5ab02fa..ddfe1e7 100644 (file)
@@ -423,8 +423,7 @@ export default {
           const L = this.moves.length;
           if (!Array.isArray(this.moves[L-1]))
             this.$set(this.moves, L-1, [this.moves[L-1], smove]);
-          else
-            this.$set(this.moves, L-1, this.moves.concat([smove]));
+          else this.moves[L-1].push(smove);
         }
       };
       const playMove = () => {
index ccf3a13..1cd37ab 100644 (file)
@@ -69,7 +69,7 @@ export default {
       this.compWorker.postMessage(["init", game.fen]);
       this.vr = new V(game.fen);
       game.players = [{ name: "Myself" }, { name: "Computer" }];
-      if (game.myColor == "b") game.players = game.players.reverse();
+      if (game.mycolor == "b") game.players = game.players.reverse();
       game.score = "*"; //finished games are removed
       this.currentUrl = document.location.href; //to avoid playing outside page
       this.game = game;
index daccd8f..4f08d9a 100644 (file)
@@ -54,3 +54,4 @@ h3 Related links
   a(href="https://brainking.com/") brainking.com
   a(href="https://www.facebook.com/groups/592562551198628") A Facebook group
   a(href="http://www.zillions-of-games.com/") zillions-of-games.com
+  a(href="https://en.wikipedia.org/wiki/Fairy_chess_piece") Fairy chess pieces
index cd84f98..1dc2b9b 100644 (file)
@@ -51,3 +51,5 @@ h3 Enlaces relacionados
   a(href="https://brainking.com/") brainking.com
   a(href="https://www.facebook.com/groups/592562551198628") Un grupo Facebook
   a(href="http://www.zillions-of-games.com/") zillions-of-games.com
+  a(href="https://en.wikipedia.org/wiki/Fairy_chess_piece")
+    | Piezas de ajedrez magicas
index 70ddc0f..be36f3e 100644 (file)
@@ -50,3 +50,5 @@ h3 Liens connexes
   a(href="https://brainking.com/") brainking.com
   a(href="https://www.facebook.com/groups/592562551198628") Un groupe Facebook
   a(href="http://www.zillions-of-games.com/") zillions-of-games.com
+  a(href="https://en.wikipedia.org/wiki/Fairy_chess_piece")
+    | Pièces d'échecs féériques
diff --git a/client/src/translations/rules/Interweave/en.pug b/client/src/translations/rules/Interweave/en.pug
new file mode 100644 (file)
index 0000000..27fc8b3
--- /dev/null
@@ -0,0 +1,114 @@
+p.boxed
+  Pieces are colorbound and capture on the other color.
+  The goal is to capture a king. Captures are mandatory.
+
+figure.diagram-container
+  .diagram
+    | fen:rbnkknbr/pppppppp/8/8/8/8/PPPPPPPP/RBNKKNBR:
+  figcaption Initial deterministic position
+
+p.
+  Each side has two small teams of four pieces (and four pawns), one on light
+  squares and the other, identical, on dark squares.
+  Kings have no royal status, and the goal is to capture one of them.
+  Captures are mandatory, but if there are several available you can choose.
+
+p.
+  All pieces end their move on a square of the same color as the initial one.
+  They all capture pieces only on the other color.
+
+p.
+  If after a capture the capturer can take another piece, then the move
+  continue with this new capture. As in international Draughts.
+
+h4 King
+
+p.
+  The King moves without capturing one square diagonally.
+  He captures by a short orthogonal leap. There is no castling.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/4b3/3nK3/8/8/8/8 d6,f6,f4,d4:
+  .diagram.diag22
+    | fen:8/8/4b3/3nK3/8/8/8/8 c5,e7:
+  figcaption.
+    King moves without captures on the left, and with captures on the right.
+
+h4 Rook ("Smasher")
+
+p.
+  The Smasher may slide any distance diagonally without capturing.
+  It may slide orthogonally as well, potentially capturing.
+  Captures are achieved by approach, by stopping just before the enemy piece.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/5p2/8/8/8/1R3p2/8 a1,c1,d2,a3,b4,c3,d4,e5,b6,b8:
+  .diagram.diag22
+    | fen:8/1p6/8/8/8/8/1R4p1/8 b6,f2:
+  figcaption.
+    Rook moves without captures on the left, and with captures on the right.
+
+h4 Knight ("Leaper")
+
+p.
+  The Leaper may slide any distance diagonally without capturing.
+  It may slide orthogonally as well, potentially capturing.
+  It may also make a double knight's move (a 4x2 'L'), as long as there
+  isn't a friendly piece at the midpoint of the move.
+  Captures are achieved by jumping over a piece, either with an orthogonal
+  or double-knight move.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/p7/5p2/8/8/1P/8/N5p1 e3,c1,e1,a3,a5,b2,c3,d4,e5:
+  .diagram.diag22
+    | fen:8/8/8/8/p7/8/2p5/N6b e3,a5,a7:
+  figcaption.
+    Knight moves without captures on the left, and with captures on the right.
+
+h4 Bishop ("Remover")
+
+p.
+  The Remover may slide any distance diagonally without capturing.
+  It may capture any orthogonally adjacent opposing piece without moving.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/4p3/4Bp2/8/8/8 e5,f4:
+  figcaption The bishop must capture both pawns.
+
+h4 Pawns
+
+p.
+  A Pawn may move one square diagonally forward to an empty square, or
+  optionally two squares if it hasn't moved yet (in this case it may be
+  captured en-passant by short leap).
+  Pawns capture by a short leap forward, and by orthogonal custodian capture
+  where the Pawn completes the sequence FEP (Friend, Enemy, Pawn, or
+  Pawn, Enemy, Friend).
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/3P4/8/8/8/1P6/8 a3,c3,d4,e7,c7:
+  .diagram.diag22
+    | fen:8/Nr5R/2rP3n/2N5/7b/4b2P/4P3/8 c7,e4,h5:
+  figcaption.
+    Pawn moves without captures on the left, and with captures on the right.
+
+p.
+  A Pawn that reaches the second-to-last rank of the board may promote to any
+  previously captured friendly piece (not yet returned to play).
+  Upon reaching the last rank, the Pawn is required to promote; if there are
+  no pieces available for promotion, then the move is not possible.
+
+h3 Source
+
+p
+  | The 
+  a(href="https://www.chessvariants.com/other.dir/interweave.html")
+    | Interweave variant
+  | &nbsp;on chessvariants.com.
+
+p Inventor: Peter Aronson (2002)
diff --git a/client/src/translations/rules/Interweave/es.pug b/client/src/translations/rules/Interweave/es.pug
new file mode 100644 (file)
index 0000000..b613bb7
--- /dev/null
@@ -0,0 +1,120 @@
+p.boxed.
+  Las piezas evolucionan en un solo color y se capturan en el otro.
+  El objetivo es capturar a un rey. Las capturas son obligatorias.
+
+figure.diagram-container
+  .diagram
+    | fen:rbnkknbr/pppppppp/8/8/8/8/PPPPPPPP/RBNKKNBR:
+  figcaption Posición determinista inicial
+
+p.
+  Cada campamento tiene dos pequeños equipos de cuatro piezas (y cuatro
+  peones), uno en casillas blancas y el otro, idéntico, en casillas negras.
+  Los reyes no tienen estatus real, y el objetivo es capturar uno.
+  Las capturas son obligatorias, pero si es posible realizar varias
+  se puede elegir.
+
+p.
+  Todas las piezas finalizan su movimiento en un cuadrado del mismo color que
+  el casilla inicial Todas capturan piezas de otro color.
+
+p.
+  Si después de una captura la pieza de captura puede hacer otra,
+  entonces ella debe hacerlo y la jugada continúa.
+  Como las Damas internacionales.
+
+h4 King
+
+p.
+  El rey se mueve sin capturar una casilla en diagonal.
+  Captura por un corto salto ortogonal. No hay enroque.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/4b3/3nK3/8/8/8/8 d6,f6,f4,d4:
+  .diagram.diag22
+    | fen:8/8/4b3/3nK3/8/8/8/8 c5,e7:
+  figcaption.
+    Movimientos del rey sin capturas a la izquierda y con capturas
+    a la derecha.
+
+h4 Torre ("Bateador")
+
+p.
+  El Bateador se mueve como un alfil ortodoxo sin capturar.
+  También puede moverse como una torre, posiblemente con captura.
+  Las capturas se realizan por acercamiento, deteniéndose justo antes de la
+  pieza opuesta.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/5p2/8/8/8/1R3p2/8 a1,c1,d2,a3,b4,c3,d4,e5,b6,b8:
+  .diagram.diag22
+    | fen: 8/1p6/8/8/8/8/1R4p1/8 b6,f2:
+  figcaption.
+    Movimientos de torre sin capturas a la izquierda y con capturas
+    a la derecha.
+
+h4 Caballo ("Saltador")
+
+p.
+  El Saltador se mueve como un alfil ortodoxo sin capturar.
+  También puede moverse como una torre, posiblemente con captura,
+  o hacer un doble puente (una 'L' 4x2), si no hay una pieza amiga en el medio.
+  Las capturas se realizan saltando sobre una pieza, a través de un movimiento
+  ortogonal o un doble puente.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/p7/5p2/8/8/1P/8/N5p1 e3,c1,e1,a3,a5,b2,c3,d4,e5:
+  .diagram.diag22
+    | fen:8/8/8/8/p7/8/2p5/N6b e3,a5,a7:
+  figcaption.
+    Movimiento de alfil sin capturas a la izquierda y con capturas
+    a la derecha.
+
+h4 Alfil ("Supresor")
+
+p.
+  El Supresor se mueve como un alfil ortodoxo sin capturar.
+  Puede capturar cualquier pieza opuesta ortogonalmente adyacente.
+
+figure.diagram-container
+  .diagrama
+    | fen:8/8/8/4p3/4Bp2/8/8/8 e5,f4:
+  figcaption El alfil debe capturar los dos peones.
+
+h4 Peones
+
+p.
+  Un peón se mueve una casilla diagonalmente hacia adelante, o posiblemente
+  dos espacios si aún no se ha movido (podría ser capturado
+  en passant por salto corto).
+  Los peones capturan por un salto corto hacia adelante y por capturas
+  "encuadre" ortogonal donde el peón completa la secuencia AEP (Amigo,
+  Enemigo, Peón o Peón, Enemigo, Amigo).
+
+figure.diagram-container
+  .diagram.diag12
+    | fen: 8/8/3P4/8/8/8/1P6/8 a3,c3,d4,e7,c7:
+  .diagram.diag22
+    | fen: 8/Nr5R/2rP3n/2N5/7b/4b2P/4P3/8 c7,e4,h5:
+  figcaption.
+    Movimientos de peón sin capturas a la izquierda y con capturas
+    a la derecha.
+
+p.
+  Un peón que alcanza la penúltima fila puede ser promovido en cualquier
+  pieza amiga capturó (y aún no ha puesto en juego).
+  En la última fila, la promoción se vuelve obligatoria y, por lo tanto,
+  el movimiento es prohibido si no hay piezas disponibles.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/other.dir/interweave.html")
+     variante Interweave
+  | &nbsp;en chessvariants.com.
+
+p Inventor: Peter Aronson (2002)
diff --git a/client/src/translations/rules/Interweave/fr.pug b/client/src/translations/rules/Interweave/fr.pug
new file mode 100644 (file)
index 0000000..e4dbde0
--- /dev/null
@@ -0,0 +1,117 @@
+p.boxed.
+  Les pièces n'évoluent que sur une seule couleur, et capturent sur l'autre.
+  L'objectif est de capturer un roi. Les captures sont obligatoires.
+
+figure.diagram-container
+  .diagram
+    | fen:rbnkknbr/pppppppp/8/8/8/8/PPPPPPPP/RBNKKNBR:
+  figcaption Position initiale déterministe
+
+p.
+  Chaque camp dispose de deux petites équipes de quatre pièces (et quatre
+  pions), une sur cases blanches et l'autre, identique, sur cases noires.
+  Les rois n'ont pas de statut royal, et l'objectif est d'en capturer un.
+  Les captures sont obligatoires, mais plusieurs sont possibles vous pouvez
+  choisir.
+
+p.
+  Toutes les pièces terminent leur coup sur une case de la même couleur que la
+  case initiale. Elles capturent toutes des pièces sur une autre couleur.
+
+p.
+  Si à l'issue d'une capture la pièce capturante peut en effectuer une autre,
+  alors elle doit le faire et le coup continue.
+  Comme aux Dames internationales.
+
+h4 Roi
+
+p.
+  Le roi se déplace sans capturer d'une case en diagonale.
+  Il capture par un court saut orthogonal. Il n'y a pas de roque.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/4b3/3nK3/8/8/8/8 d6,f6,f4,d4:
+  .diagram.diag22
+    | fen:8/8/4b3/3nK3/8/8/8/8 c5,e7:
+  figcaption.
+    Coups de roi sans captures à gauche, et avec captures à droite.
+
+h4 Tour ("Frappeur")
+
+p.
+  Le Frappeur se déplace comme un fou orthodoxe sans capturer.
+  Il peut aussi se déplacer comme une tour, éventuellement avec capture.
+  Les captures sont réalisées par approche, en s'arrêtant juste avant la pièce
+  adverse.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/5p2/8/8/8/1R3p2/8 a1,c1,d2,a3,b4,c3,d4,e5,b6,b8:
+  .diagram.diag22
+    | fen:8/1p6/8/8/8/8/1R4p1/8 b6,f2:
+  figcaption.
+    Coups de tour sans captures à gauche, et avec captures à droite.
+
+h4 Cavalier ("Sauteur")
+
+p.
+  Le Sauteur se déplace comme un fou orthodoxe sans capturer.
+  Il peut aussi se déplacer comme une tour, éventuellement avec capture,
+  ou effectuer un double saut de cavalier (un 'L' 4x2), du moment qu'il n'y
+  ait pas de pièce amie au milieu.
+  Les captures s'effectuent en sautant par dessus une pièce, via un coup
+  orthogonal ou un double coup de cavalier.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/p7/5p2/8/8/1P/8/N5p1 e3,c1,e1,a3,a5,b2,c3,d4,e5:
+  .diagram.diag22
+    | fen:8/8/8/8/p7/8/2p5/N6b e3,a5,a7:
+  figcaption.
+    Coups de cavalier sans captures à gauche, et avec captures à droite.
+
+h4 Fou ("Supprimeur")
+
+p.
+  Le Supprimeur se déplace comme un fou orthodoxe sans capturer.
+  Il peut capturer toute pièce adverse orthogonalement adjacente.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/4p3/4Bp2/8/8/8 e5,f4:
+  figcaption Le fou doit capturer les deux pions.
+
+h4 Pions
+
+p.
+  Un pion se déplace d'une case en diagonale vers l'avant, ou éventuellement
+  de deux cases s'il n'a pas encore bougé (il pourrait alors être capturé
+  en passant par saut court).
+  Les pions capturent par un saut court vers l'avant, et par captures
+  d'"encadrement" orthogonales où le pion complète la séquence AEP (Ami,
+  Ennemi, Pion ou Pion, Enemmi, Ami).
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:8/8/3P4/8/8/8/1P6/8 a3,c3,d4,e7,c7:
+  .diagram.diag22
+    | fen:8/Nr5R/2rP3n/2N5/7b/4b2P/4P3/8 c7,e4,h5:
+  figcaption.
+    Coups de pion sans captures à gauche, et avec captures à droite.
+
+p.
+  Un pion qui atteint l'avant-dernière rangée peut se promouvoir en n'importe
+  quelle pièce amie capturée (et pas encore remise en jeu).
+  Sur la dernière rangée la promotion devient obligatoire, et donc le coup est
+  interdit si aucune pièce n'est disponible.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/other.dir/interweave.html")
+    | variante Interweave
+  | &nbsp;sur chessvariants.com.
+
+p Inventeur : Peter Aronson (2002)
diff --git a/client/src/translations/rules/Takenmake/en.pug b/client/src/translations/rules/Takenmake/en.pug
new file mode 100644 (file)
index 0000000..a65cfce
--- /dev/null
@@ -0,0 +1,48 @@
+p.boxed.
+  After each capture, you must play again with the capturing piece,
+  moving as the captured unit.
+
+p.
+  Having captured, a unit must immediately play a non-capturing move,
+  using only the movements of the captured unit from the
+  capture-square. If no such move is available, the capture is illegal.
+  For example, on the following diagram after 4.Bxc6 the bishop will have a
+  knight move to execute, arriving either on a5, b4, d4, e7 or b8.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  .diagram.diag22
+    | fen:r1bqkb1r/pppp1ppp/5n2/B3p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  figcaption Left: before Bxc6. Right: after Bxc6,Ba5
+
+p.
+  Promotion can occur when a pawn arrives on the last rank as the result
+  of a Take & Make move. Pawns can also take on the final rank:
+  they promote, and immediatly make a move as the captured unit.
+
+p.
+  Note: the king can be under check after a capture, and cover the check with
+  the subsequent "as-captured" move, as the following diagram illustrates.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbq2nr/ppp2ppp/4p3/3p4/1k1P4/P1N2N2/1PP1PPPP/R2QKB1R c3:
+  figcaption The king can take the knight and then move to a4, b5 or e4.
+
+p.
+  Checks are as in normal chess: after the
+  notional capture of the checked King, the checking unit does not move away
+  from the King's square.
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/1ppppppp/2n2N2/p7/8/8/PPPPPPPP/R1BQKBNR:
+  figcaption The black king is in check.
+
+h3 Source
+
+p
+  | These rules are slightly modified from the definition given 
+  a(href="http://www.strategems.net/sections/fairy_defs.html") on this page
+  | . (Type 'take' in the search box).
diff --git a/client/src/translations/rules/Takenmake/es.pug b/client/src/translations/rules/Takenmake/es.pug
new file mode 100644 (file)
index 0000000..2d179d8
--- /dev/null
@@ -0,0 +1,51 @@
+p.boxed.
+  Después de cada captura, aún debes jugar con la pieza de captura,
+  luego se mueve como la pieza capturada.
+
+p.
+  Una vez capturada, una pieza debe hacer inmediatamente un movimiento
+  non capturando desde la casilla de captura, moviéndose como la pieza
+  capturado. Si tal movimiento no es posible, entonces la captura es ilegal.
+  Por ejemplo, en el siguiente diagrama después de 4.Bxc6 el alfil tendrá un
+  movimiento de caballo a ejecutar, llegando a uno de las casillas
+  a5, b4, d4, e7 o b8.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  .diagram.diag22
+    | fen:r1bqkb1r/pppp1ppp/5n2/B3p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  figcaption Izquierda: antes de Bxc6. Derecha: después de Bxc6,Ba5
+
+p.
+  Una promoción puede ocurrir cuando un peón llega a la última fila
+  siguiendo un movimiento Take & Make ("Tomar & hacer"). Un peón también
+  puede capturar en la última fila: es promovido e inmediatamente hace un
+  movimiento como la unidad capturada.
+
+p.
+  Nota: El rey puede estar en jaque después de una captura y cubrirlo para
+  usando el movimiento "como-capturado" para seguir, como se ilustra en el
+  diagrama.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbq2nr/ppp2ppp/4p3/3p4/1k1P4/P1N2N2/1PP1PPPP/R2QKB1R c3:
+  figcaption El rey puede tomar el caballo y luego ir a a4, b5 o e4.
+
+p.
+  Los jaques se entienden como en el juego ortodoxo: después de la captura
+  imaginario del rey contrario, la pieza que da jaque no tendría que
+  ejecutar un movimiento de rey.
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/1ppppppp/2n2N2/p7/8/8/PPPPPPPPP/R1BQKBNR:
+  figcaption El rey negro está en jaque.
+
+h3 Fuente
+
+p
+  | Estas reglas están ligeramente modificadas ya que la definición dada 
+  a(href="http://www.strategems.net/sections/fairy_defs.html") en esta página
+  | . (Escriba 'take' en el campo de búsqueda).
diff --git a/client/src/translations/rules/Takenmake/fr.pug b/client/src/translations/rules/Takenmake/fr.pug
new file mode 100644 (file)
index 0000000..71e3551
--- /dev/null
@@ -0,0 +1,49 @@
+p.boxed.
+  Après chaque capture, vous devez jouer encore avec la pièce capturante,
+  se déplaçant alors comme la pièce capturée.
+
+p.
+  Une fois qu'elle a capturé, une pièce doit immédiatement jour un coup
+  non-capturant depuis la case de capture, en se déplaçant comme la pièce
+  capturée. Si aucun tel coup n'est possible, alors la capture est illégale.
+  Par exemple, sur le diagramme suivant après 4.Bxc6 le fou aura un coup
+  de cavalier à exécuter, arrivant sur l'une des cases a5, b4, d4, e7 ou b8.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  .diagram.diag22
+    | fen:r1bqkb1r/pppp1ppp/5n2/B3p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  figcaption Gauche: avant Bxc6. Droite: après Bxc6,Ba5
+
+p.
+  Une promotion peut survenir quand un pion arrive sur la dernière rangée
+  suite à un coup Take & Make ("Prend & Fait"). Un pion peut aussi capturer
+  sur la dernière rangée : il est promu, et effectue immédiatement un coup
+  comme l'unité capturée.
+
+p.
+  Note : Le roi peut être en échec après une capture, et couvrir celui-ci à
+  l'aide du coup "comme-capturé" à suivre, comme illustré sur le diagramme.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbq2nr/ppp2ppp/4p3/3p4/1k1P4/P1N2N2/1PP1PPPP/R2QKB1R c3:
+  figcaption Le roi peut prendre le cavalier et ensuite aller en a4, b5 ou e4.
+
+p.
+  Les échecs s'entendent comme dans le jeu orthodoxe : après la capture
+  imaginaire du roi adverse, la pièce donnant échec n'aurait pas à exécuter un
+  coup de roi.
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/1ppppppp/2n2N2/p7/8/8/PPPPPPPP/R1BQKBNR:
+  figcaption Le roi noir est en échec.
+
+h3 Source
+
+p
+  | Ces règles sont légèrement modifiées depuis la définition donnée 
+  a(href="http://www.strategems.net/sections/fairy_defs.html") sur cette page
+  | . (Tapez 'take' dans le champ de recherche).
index e5a222b..8473b13 100644 (file)
@@ -96,10 +96,11 @@ export class BerolinaRules extends ChessRules {
       this.board[x + shiftX][y] != V.EMPTY &&
       this.canTake([x, y], [x + shiftX, y])
     ) {
-      for (let piece of finalPieces)
+      for (let piece of finalPieces) {
         moves.push(
           this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece })
         );
+      }
     }
 
     // Next condition so that other variants could inherit from this class
@@ -150,7 +151,7 @@ export class BerolinaRules extends ChessRules {
       const finalSquare = V.CoordsToSquare(move.end);
       let notation = "";
       if (move.vanish.length == 2)
-        //capture
+        // Capture
         notation = "Px" + finalSquare;
       else {
         // No capture: indicate the initial square for potential ambiguity
index aeb7b6f..743f01f 100644 (file)
@@ -40,14 +40,14 @@ export class GrandRules extends ChessRules {
   }
 
   getCapturedFen() {
-    let counts = [...Array(14).fill(0)];
+    let counts = [...Array(12).fill(0)];
     let i = 0;
     for (let j = 0; j < V.PIECES.length; j++) {
-      if (V.PIECES[j] == V.KING)
-        //no king captured
+      if ([V.KING, V.PAWN].includes(V.PIECES[j]))
+        // No king captured, and pawns don't promote in pawns
         continue;
-      counts[i] = this.captured["w"][V.PIECES[i]];
-      counts[7 + i] = this.captured["b"][V.PIECES[i]];
+      counts[i] = this.captured["w"][V.PIECES[j]];
+      counts[6 + i] = this.captured["b"][V.PIECES[j]];
       i++;
     }
     return counts.join("");
@@ -59,22 +59,20 @@ export class GrandRules extends ChessRules {
     // Initialize captured pieces' counts from FEN
     this.captured = {
       w: {
-        [V.PAWN]: parseInt(fenParsed.captured[0]),
-        [V.ROOK]: parseInt(fenParsed.captured[1]),
-        [V.KNIGHT]: parseInt(fenParsed.captured[2]),
-        [V.BISHOP]: parseInt(fenParsed.captured[3]),
-        [V.QUEEN]: parseInt(fenParsed.captured[4]),
-        [V.MARSHALL]: parseInt(fenParsed.captured[5]),
-        [V.CARDINAL]: parseInt(fenParsed.captured[6])
+        [V.ROOK]: parseInt(fenParsed.captured[0]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[1]),
+        [V.BISHOP]: parseInt(fenParsed.captured[2]),
+        [V.QUEEN]: parseInt(fenParsed.captured[3]),
+        [V.MARSHALL]: parseInt(fenParsed.captured[4]),
+        [V.CARDINAL]: parseInt(fenParsed.captured[5])
       },
       b: {
-        [V.PAWN]: parseInt(fenParsed.captured[7]),
-        [V.ROOK]: parseInt(fenParsed.captured[8]),
-        [V.KNIGHT]: parseInt(fenParsed.captured[9]),
-        [V.BISHOP]: parseInt(fenParsed.captured[10]),
-        [V.QUEEN]: parseInt(fenParsed.captured[11]),
-        [V.MARSHALL]: parseInt(fenParsed.captured[12]),
-        [V.CARDINAL]: parseInt(fenParsed.captured[13])
+        [V.ROOK]: parseInt(fenParsed.captured[6]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[7]),
+        [V.BISHOP]: parseInt(fenParsed.captured[8]),
+        [V.QUEEN]: parseInt(fenParsed.captured[9]),
+        [V.MARSHALL]: parseInt(fenParsed.captured[10]),
+        [V.CARDINAL]: parseInt(fenParsed.captured[11])
       }
     };
   }
@@ -290,22 +288,15 @@ export class GrandRules extends ChessRules {
 
   postPlay(move) {
     super.postPlay(move);
-    if (move.vanish.length == 2 && move.appear.length == 1) {
+    if (move.vanish.length == 2 && move.appear.length == 1)
       // Capture: update this.captured
       this.captured[move.vanish[1].c][move.vanish[1].p]++;
-    }
-    if (move.vanish[0].p != move.appear[0].p) {
-      // Promotion: update this.captured
-      this.captured[move.vanish[0].c][move.appear[0].p]--;
-    }
   }
 
   postUndo(move) {
     super.postUndo(move);
     if (move.vanish.length == 2 && move.appear.length == 1)
       this.captured[move.vanish[1].c][move.vanish[1].p]--;
-    if (move.vanish[0].p != move.appear[0].p)
-      this.captured[move.vanish[0].c][move.appear[0].p]++;
   }
 
   static get VALUES() {
diff --git a/client/src/variants/Interweave.js b/client/src/variants/Interweave.js
new file mode 100644 (file)
index 0000000..c776889
--- /dev/null
@@ -0,0 +1,664 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt, shuffle } from "@/utils/alea";
+
+export class InterweaveRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "rbnkknbr/pppppppp/8/8/8/8/PPPPPPPP/RBNKKNBR w 0 - 000000";
+
+    let pieces = { w: new Array(8), b: new Array(8) };
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
+      // Each pair of pieces on 2 colors:
+      const composition = ['r', 'n', 'b', 'k', 'r', 'n', 'b', 'k'];
+      let positions = shuffle(ArrayFun.range(4));
+      for (let i = 0; i < 4; i++)
+        pieces[c][2 * positions[i]] = composition[i];
+      positions = shuffle(ArrayFun.range(4));
+      for (let i = 0; i < 4; i++)
+        pieces[c][2 * positions[i] + 1] = composition[i];
+    }
+    return (
+      pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      // En-passant allowed, but no flags
+      " w 0 - 000000"
+    );
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 4) Check captures
+    if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{6,6}$/))
+      return false;
+    return true;
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kings = { "k": 0, "K": 0 };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (['K','k'].includes(row[i])) kings[row[i]]++;
+        if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // Both kings should be on board. Exactly two per color.
+    if (Object.values(kings).some(v => v != 2)) return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { captured: fenParts[4] }
+    );
+  }
+
+  getFen() {
+    return super.getFen() + " " + this.getCapturedFen();
+  }
+
+  getFenForRepeat() {
+    return super.getFenForRepeat() + "_" + this.getCapturedFen();
+  }
+
+  getCapturedFen() {
+    let counts = [...Array(6).fill(0)];
+    [V.ROOK, V.KNIGHT, V.BISHOP].forEach((p,idx) => {
+      counts[idx] = this.captured["w"][p];
+      counts[3 + idx] = this.captured["b"][p];
+    });
+    return counts.join("");
+  }
+
+  scanKings() {}
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Initialize captured pieces' counts from FEN
+    this.captured = {
+      w: {
+        [V.ROOK]: parseInt(fenParsed.captured[0]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[1]),
+        [V.BISHOP]: parseInt(fenParsed.captured[2]),
+      },
+      b: {
+        [V.ROOK]: parseInt(fenParsed.captured[3]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[4]),
+        [V.BISHOP]: parseInt(fenParsed.captured[5]),
+      }
+    };
+    // Stack of "last move" only for intermediate captures
+    this.lastMoveEnd = [null];
+  }
+
+  // Trim all non-capturing moves
+  static KeepCaptures(moves) {
+    return moves.filter(m => m.vanish.length >= 2 || m.appear.length == 0);
+  }
+
+  // Stop at the first capture found (if any)
+  atLeastOneCapture() {
+    const color = this.turn;
+    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) == color &&
+          V.KeepCaptures(this.getPotentialMovesFrom([i, j])).length > 0
+        ) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  // En-passant after 2-sq jump
+  getEpSquare(moveOrSquare) {
+    if (!moveOrSquare) return undefined;
+    if (typeof moveOrSquare === "string") {
+      const square = moveOrSquare;
+      if (square == "-") return undefined;
+      // Enemy pawn initial column must be given too:
+      let res = [];
+      const epParts = square.split(",");
+      res.push(V.SquareToCoords(epParts[0]));
+      res.push(V.ColumnToCoord(epParts[1]));
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx, ex, sy, ey] =
+      [move.start.x, move.end.x, move.start.y, move.end.y];
+    if (
+      move.vanish.length == 1 &&
+      this.getPiece(sx, sy) == V.PAWN &&
+      Math.abs(sx - ex) == 2 &&
+      Math.abs(sy - ey) == 2
+    ) {
+      return [
+        {
+          x: (ex + sx) / 2,
+          y: (ey + sy) / 2
+        },
+        // The arrival column must be remembered, because
+        // potentially two pawns could be candidates to be captured:
+        // one on our left, and one on our right.
+        move.end.y
+      ];
+    }
+    return undefined; //default
+  }
+
+  static IsGoodEnpassant(enpassant) {
+    if (enpassant != "-") {
+      const epParts = enpassant.split(",");
+      const epSq = V.SquareToCoords(epParts[0]);
+      if (isNaN(epSq.x) || isNaN(epSq.y) || !V.OnBoard(epSq)) return false;
+      const arrCol = V.ColumnToCoord(epParts[1]);
+      if (isNaN(arrCol) || arrCol < 0 || arrCol >= V.size.y) return false;
+    }
+    return true;
+  }
+
+  getEnpassantFen() {
+    const L = this.epSquares.length;
+    if (!this.epSquares[L - 1]) return "-"; //no en-passant
+    return (
+      V.CoordsToSquare(this.epSquares[L - 1][0]) +
+      "," +
+      V.CoordToColumn(this.epSquares[L - 1][1])
+    );
+  }
+
+  getPotentialMovesFrom([x, y], noPostprocess) {
+    const L = this.lastMoveEnd.length;
+    if (
+      !!this.lastMoveEnd[L-1] &&
+      (
+        x != this.lastMoveEnd[L-1].x ||
+        y != this.lastMoveEnd[L-1].y
+      )
+    ) {
+      // A capture must continue: wrong square
+      return [];
+    }
+    let moves = [];
+    switch (this.getPiece(x, y)) {
+      case V.PAWN:
+        moves = this.getPotentialPawnMoves([x, y]);
+        break;
+      case V.ROOK:
+        moves = this.getPotentialRookMoves([x, y]);
+        break;
+      case V.KNIGHT:
+        moves = this.getPotentialKnightMoves([x, y]);
+        break;
+      case V.BISHOP:
+        moves = this.getPotentialBishopMoves([x, y]);
+        break;
+      case V.KING:
+        moves = this.getPotentialKingMoves([x, y]);
+        break;
+      // No queens
+    }
+    if (!noPostprocess) {
+      // Post-process: if capture,
+      // can another capture be achieved with the same piece?
+      moves.forEach(m => {
+        if (m.vanish.length >= 2 || m.appear.length == 0) {
+          this.play(m);
+          const moreCaptures = (
+            V.KeepCaptures(
+              this.getPotentialMovesFrom([m.end.x, m.end.y], "noPostprocess")
+            )
+            .length > 0
+          );
+          this.undo(m);
+          if (!moreCaptures) m.last = true;
+        }
+        else m.last = true;
+      });
+    }
+    return moves;
+  }
+
+  // Special pawns movements
+  getPotentialPawnMoves([x, y]) {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    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 potentialFinalPieces =
+      [V.ROOK, V.KNIGHT, V.BISHOP].filter(p => this.captured[color][p] > 0);
+    const lastRanks = (color == "w" ? [0, 1] : [sizeX - 1, sizeX - 2]);
+    if (x + shiftX == lastRanks[0] && potentialFinalPieces.length == 0)
+      // If no captured piece is available, the pawn cannot promote
+      return [];
+
+    const finalPieces1 =
+      x + shiftX == lastRanks[0]
+        ? potentialFinalPieces
+        :
+          x + shiftX == lastRanks[1]
+            ? potentialFinalPieces.concat([V.PAWN])
+            : [V.PAWN];
+    // One square diagonally
+    for (let shiftY of [-1, 1]) {
+      if (this.board[x + shiftX][y + shiftY] == V.EMPTY) {
+        for (let piece of finalPieces1) {
+          moves.push(
+            this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
+              c: color,
+              p: piece
+            })
+          );
+        }
+        if (
+          V.PawnSpecs.twoSquares &&
+          x == startRank &&
+          y + 2 * shiftY >= 0 &&
+          y + 2 * shiftY < sizeY &&
+          this.board[x + 2 * shiftX][y + 2 * shiftY] == V.EMPTY
+        ) {
+          // Two squares jump
+          moves.push(
+            this.getBasicMove([x, y], [x + 2 * shiftX, y + 2 * shiftY])
+          );
+        }
+      }
+    }
+    // Capture
+    const finalPieces2 =
+      x + 2 * shiftX == lastRanks[0]
+        ? potentialFinalPieces
+        :
+          x + 2 * shiftX == lastRanks[1]
+            ? potentialFinalPieces.concat([V.PAWN])
+            : [V.PAWN];
+    if (
+      this.board[x + shiftX][y] != V.EMPTY &&
+      this.canTake([x, y], [x + shiftX, y]) &&
+      V.OnBoard(x + 2 * shiftX, y) &&
+      this.board[x + 2 * shiftX][y] == V.EMPTY
+    ) {
+      const oppPiece = this.getPiece(x + shiftX, y);
+      for (let piece of finalPieces2) {
+        let mv = this.getBasicMove(
+          [x, y], [x + 2 * shiftX, y], { c: color, p: piece });
+        mv.vanish.push({
+          x: x + shiftX,
+          y: y,
+          p: oppPiece,
+          c: oppCol
+        });
+        moves.push(mv);
+      }
+    }
+
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    if (
+      !!epSquare &&
+      epSquare[0].x == x + shiftX &&
+      epSquare[0].y == y &&
+      this.board[x + 2 * shiftX][y] == V.EMPTY
+    ) {
+      for (let piece of finalPieces2) {
+        let enpassantMove =
+          this.getBasicMove(
+            [x, y], [x + 2 * shiftX, y], { c: color, p: piece});
+        enpassantMove.vanish.push({
+          x: x,
+          y: epSquare[1],
+          p: "p",
+          c: this.getColor(x, epSquare[1])
+        });
+        moves.push(enpassantMove);
+      }
+    }
+
+    // Add custodian captures:
+    const steps = V.steps[V.ROOK];
+    moves.forEach(m => {
+      // Try capturing in every direction
+      for (let step of steps) {
+        const sq2 = [m.end.x + 2 * step[0], m.end.y + 2 * step[1]];
+        if (
+          V.OnBoard(sq2[0], sq2[1]) &&
+          this.board[sq2[0]][sq2[1]] != V.EMPTY &&
+          this.getColor(sq2[0], sq2[1]) == color
+        ) {
+          // Potential capture
+          const sq1 = [m.end.x + step[0], m.end.y + step[1]];
+          if (
+            this.board[sq1[0]][sq1[1]] != V.EMPTY &&
+            this.getColor(sq1[0], sq1[1]) == oppCol
+          ) {
+            m.vanish.push(
+              new PiPo({
+                x: sq1[0],
+                y: sq1[1],
+                c: oppCol,
+                p: this.getPiece(sq1[0], sq1[1])
+              })
+            );
+          }
+        }
+      }
+    });
+
+    return moves;
+  }
+
+  getSlides([x, y], steps, options) {
+    options = options || {};
+    // No captures:
+    let moves = [];
+    outerLoop: for (let step of steps) {
+      let i = x + step[0];
+      let j = y + step[1];
+      let counter = 1;
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        if (!options["doubleStep"] || counter % 2 == 0)
+          moves.push(this.getBasicMove([x, y], [i, j]));
+        if (!!options["oneStep"]) continue outerLoop;
+        i += step[0];
+        j += step[1];
+        counter++;
+      }
+    }
+    return moves;
+  }
+
+  // Smasher
+  getPotentialRookMoves([x, y]) {
+    let moves =
+      this.getSlides([x, y], V.steps[V.ROOK], { doubleStep: true })
+      .concat(this.getSlides([x, y], V.steps[V.BISHOP]));
+    // Add captures
+    const oppCol = V.GetOppCol(this.turn);
+    moves.forEach(m => {
+      const delta = [m.end.x - m.start.x, m.end.y - m.start.y];
+      const step = [
+        delta[0] / Math.abs(delta[0]) || 0,
+        delta[1] / Math.abs(delta[1]) || 0
+      ];
+      if (step[0] == 0 || step[1] == 0) {
+        // Rook-like move, candidate for capturing
+        const [i, j] = [m.end.x + step[0], m.end.y + step[1]];
+        if (
+          V.OnBoard(i, j) &&
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i, j) == oppCol
+        ) {
+          m.vanish.push({
+            x: i,
+            y: j,
+            p: this.getPiece(i, j),
+            c: oppCol
+          });
+        }
+      }
+    });
+    return moves;
+  }
+
+  // Leaper
+  getPotentialKnightMoves([x, y]) {
+    let moves =
+      this.getSlides([x, y], V.steps[V.ROOK], { doubleStep: true })
+      .concat(this.getSlides([x, y], V.steps[V.BISHOP]));
+    const oppCol = V.GetOppCol(this.turn);
+    // Look for double-knight moves (could capture):
+    for (let step of V.steps[V.KNIGHT]) {
+      const [i, j] = [x + 2 * step[0], y + 2 * step[1]];
+      if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        const [ii, jj] = [x + step[0], y + step[1]];
+        if (this.board[ii][jj] == V.EMPTY || this.getColor(ii, jj) == oppCol) {
+          let mv = this.getBasicMove([x, y], [i, j]);
+          if (this.board[ii][jj] != V.EMPTY) {
+            mv.vanish.push({
+              x: ii,
+              y: jj,
+              c: oppCol,
+              p: this.getPiece(ii, jj)
+            });
+          }
+          moves.push(mv);
+        }
+      }
+    }
+    // Look for an enemy in every orthogonal direction
+    for (let step of V.steps[V.ROOK]) {
+      let [i, j] = [x + step[0], y+ step[1]];
+      let counter = 1;
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        i += step[0];
+        j += step[1];
+        counter++;
+      }
+      if (
+        V.OnBoard(i, j) &&
+        counter % 2 == 1 &&
+        this.getColor(i, j) == oppCol
+      ) {
+        const oppPiece = this.getPiece(i, j);
+        // Candidate for capture: can I land after?
+        let [ii, jj] = [i + step[0], j + step[1]];
+        counter++;
+        while (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY) {
+          if (counter % 2 == 0) {
+            // Same color: add capture
+            let mv = this.getBasicMove([x, y], [ii, jj]);
+            mv.vanish.push({
+              x: i,
+              y: j,
+              c: oppCol,
+              p: oppPiece
+            });
+            moves.push(mv);
+          }
+          ii += step[0];
+          jj += step[1];
+          counter++;
+        }
+      }
+    }
+    return moves;
+  }
+
+  // Remover
+  getPotentialBishopMoves([x, y]) {
+    let moves = this.getSlides([x, y], V.steps[V.BISHOP]);
+    // Add captures
+    const oppCol = V.GetOppCol(this.turn);
+    let captures = [];
+    for (let step of V.steps[V.ROOK]) {
+      const [i, j] = [x + step[0], y + step[1]];
+      if (
+        V.OnBoard(i, j) &&
+        this.board[i][j] != V.EMPTY &&
+        this.getColor(i, j) == oppCol
+      ) {
+        captures.push([i, j]);
+      }
+    }
+    captures.forEach(c => {
+      moves.push({
+        start: { x: x, y: y },
+        end: { x: c[0], y: c[1] },
+        appear: [],
+        vanish: captures.map(ct => {
+          return {
+            x: ct[0],
+            y: ct[1],
+            c: oppCol,
+            p: this.getPiece(ct[0], ct[1])
+          };
+        })
+      });
+    });
+    return moves;
+  }
+
+  getPotentialKingMoves([x, y]) {
+    let moves = this.getSlides([x, y], V.steps[V.BISHOP], { oneStep: true });
+    // Add captures
+    const oppCol = V.GetOppCol(this.turn);
+    for (let step of V.steps[V.ROOK]) {
+      const [i, j] = [x + 2 * step[0], y + 2 * step[1]];
+      if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        const [ii, jj] = [x + step[0], y + step[1]];
+        if (this.board[ii][jj] != V.EMPTY && this.getColor(ii, jj) == oppCol) {
+          let mv = this.getBasicMove([x, y], [i, j]);
+          mv.vanish.push({
+            x: ii,
+            y: jj,
+            c: oppCol,
+            p: this.getPiece(ii, jj)
+          });
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  getPossibleMovesFrom(sq) {
+    const L = this.lastMoveEnd.length;
+    if (
+      !!this.lastMoveEnd[L-1] &&
+      (
+        sq[0] != this.lastMoveEnd[L-1].x ||
+        sq[1] != this.lastMoveEnd[L-1].y
+      )
+    ) {
+      return [];
+    }
+    let moves = this.getPotentialMovesFrom(sq);
+    const captureMoves = V.KeepCaptures(moves);
+    if (captureMoves.length > 0) return captureMoves;
+    if (this.atLeastOneCapture()) return [];
+    return moves;
+  }
+
+  getAllValidMoves() {
+    const moves = this.getAllPotentialMoves();
+    const captures = V.KeepCaptures(moves);
+    if (captures.length > 0) return captures;
+    return moves;
+  }
+
+  filterValid(moves) {
+    // No checks
+    return moves;
+  }
+
+  play(move) {
+    this.epSquares.push(this.getEpSquare(move));
+    V.PlayOnBoard(this.board, move);
+    if (move.vanish.length >= 2) {
+      // Capture: update this.captured
+      for (let i=1; i<move.vanish.length; i++)
+        this.captured[move.vanish[i].c][move.vanish[i].p]++;
+    }
+    if (!!move.last) {
+      // No capture, or no more capture available
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.lastMoveEnd.push(null);
+    }
+    else this.lastMoveEnd.push(move.end);
+  }
+
+  undo(move) {
+    this.epSquares.pop();
+    this.lastMoveEnd.pop();
+    V.UndoOnBoard(this.board, move);
+    if (move.vanish.length >= 2) {
+      for (let i=1; i<move.vanish.length; i++)
+        this.captured[move.vanish[i].c][move.vanish[i].p]--;
+    }
+    if (!!move.last) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    // Count kings: if one is missing, the side lost
+    let kingsCount = { 'w': 0, 'b': 0 };
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (this.board[i][j] != V.EMPTY && this.getPiece(i, j) == V.KING)
+          kingsCount[this.getColor(i, j)]++;
+      }
+    }
+    if (kingsCount['w'] < 2) return "0-1";
+    if (kingsCount['b'] < 2) return "1-0";
+    return "*";
+  }
+
+  getComputerMove() {
+    let moves = this.getAllValidMoves();
+    if (moves.length == 0) return null;
+    // Just play random moves (for now at least. TODO?)
+    let mvArray = [];
+    while (moves.length > 0) {
+      const mv = moves[randInt(moves.length)];
+      mvArray.push(mv);
+      if (!mv.last) {
+        this.play(mv);
+        moves = V.KeepCaptures(
+          this.getPotentialMovesFrom([mv.end.x, mv.end.y]));
+      }
+      else break;
+    }
+    for (let i = mvArray.length - 2; i >= 0; i--) this.undo(mvArray[i]);
+    return (mvArray.length > 1 ? mvArray : mvArray[0]);
+  }
+
+  getNotation(move) {
+    const initialSquare = V.CoordsToSquare(move.start);
+    const finalSquare = V.CoordsToSquare(move.end);
+    if (move.appear.length == 0)
+      // Remover captures 'R'
+      return initialSquare + "R";
+    let notation = move.appear[0].p.toUpperCase() + finalSquare;
+    // Add a capture mark (not describing what is captured...):
+    if (move.vanish.length >= 2) notation += "X";
+    return notation;
+  }
+};
diff --git a/client/src/variants/Takenmake.js b/client/src/variants/Takenmake.js
new file mode 100644 (file)
index 0000000..9a09b35
--- /dev/null
@@ -0,0 +1,147 @@
+import { ChessRules } from "@/base_rules";
+
+export class TakenmakeRules extends ChessRules {
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    // Stack of "last move" only for intermediate captures
+    this.lastMoveEnd = [null];
+  }
+
+  getPotentialMovesFrom([x, y], asA) {
+    const L = this.lastMoveEnd.length;
+    if (!asA && !!this.lastMoveEnd[L-1]) {
+      asA = this.lastMoveEnd[L-1].p;
+      if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y) {
+        // A capture was played: wrong square
+        return [];
+      }
+    }
+    let moves = [];
+    const piece = this.getPiece(x, y);
+    switch (asA || piece) {
+      case V.PAWN:
+        if (!asA || piece == V.PAWN)
+          moves = this.getPotentialPawnMoves([x, y]);
+        else {
+          // Special case: we don't want promotion, since just moving like
+          // a pawn, but I'm in fact not a pawn :)
+          const shiftX = (this.turn == 'w' ? -1 : 1);
+          if (this.board[x + shiftX][y] == V.EMPTY)
+            moves = [this.getBasicMove([x, y], [x + shiftX, y])];
+        }
+        break;
+      case V.ROOK:
+        moves = this.getPotentialRookMoves([x, y]);
+        break;
+      case V.KNIGHT:
+        moves = this.getPotentialKnightMoves([x, y]);
+        break;
+      case V.BISHOP:
+        moves = this.getPotentialBishopMoves([x, y]);
+        break;
+      case V.KING:
+        moves = this.getPotentialKingMoves([x, y]);
+        break;
+      case V.QUEEN:
+        moves = this.getPotentialQueenMoves([x, y]);
+        break;
+    }
+    // Post-process: if capture,
+    // can a move "as-capturer" be achieved with the same piece?
+    if (!asA) {
+      const color = this.turn;
+      return moves.filter(m => {
+        if (m.vanish.length == 2 && m.appear.length == 1) {
+          this.play(m);
+          let moveOk = true;
+          const makeMoves =
+            this.getPotentialMovesFrom([m.end.x, m.end.y], m.vanish[1].p);
+          if (
+            makeMoves.every(mm => {
+              // Cannot castle after a capturing move
+              // (with the capturing piece):
+              if (mm.vanish.length == 2) return true;
+              this.play(mm);
+              const res = this.underCheck(color);
+              this.undo(mm);
+              return res;
+            })
+          ) {
+            moveOk = false;
+          }
+          this.undo(m);
+          return moveOk;
+        }
+        return true;
+      });
+    }
+    // Moving "as a": filter out captures (no castles here)
+    return moves.filter(m => m.vanish.length == 1);
+  }
+
+  getPossibleMovesFrom(sq) {
+    const L = this.lastMoveEnd.length;
+    let asA = undefined;
+    if (!!this.lastMoveEnd[L-1]) {
+      if (
+        sq[0] != this.lastMoveEnd[L-1].x ||
+        sq[1] != this.lastMoveEnd[L-1].y
+      ) {
+        return [];
+      }
+      asA = this.lastMoveEnd[L-1].p;
+    }
+    return this.filterValid(this.getPotentialMovesFrom(sq, asA));
+  }
+
+  filterValid(moves) {
+    let noCaptureMoves = [];
+    let captureMoves = [];
+    moves.forEach(m => {
+      if (m.vanish.length == 1 || m.appear.length == 2) noCaptureMoves.push(m);
+      else captureMoves.push(m);
+    });
+    // Capturing moves were already checked in getPotentialMovesFrom()
+    return super.filterValid(noCaptureMoves).concat(captureMoves);
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    this.epSquares.push(this.getEpSquare(move));
+    V.PlayOnBoard(this.board, move);
+    if (move.vanish.length == 1 || move.appear.length == 2) {
+      // Not a capture: change turn
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.lastMoveEnd.push(null);
+    }
+    else {
+      this.lastMoveEnd.push(
+        Object.assign({}, move.end, { p: move.vanish[1].p })
+      );
+    }
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    const c = move.vanish[0].c;
+    const piece = move.vanish[0].p;
+    if (piece == V.KING && move.appear.length > 0) {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+    }
+    super.updateCastleFlags(move, piece);
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    this.epSquares.pop();
+    this.lastMoveEnd.pop();
+    V.UndoOnBoard(this.board, move);
+    if (move.vanish.length == 1 || move.appear.length == 2) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    super.postUndo(move);
+  }
+};