From 6e47d367d4b1b4500bc46d65b44c5e55b52221bb Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 1 Apr 2020 22:10:57 +0200 Subject: [PATCH] A few fixes + draft Interweave and Takenmake. Only 1 1/2 variant to go now :) --- TODO | 20 +- client/src/App.vue | 1 + client/src/components/BaseGame.vue | 3 +- client/src/components/ComputerGame.vue | 2 +- client/src/translations/about/en.pug | 1 + client/src/translations/about/es.pug | 2 + client/src/translations/about/fr.pug | 2 + .../src/translations/rules/Interweave/en.pug | 114 +++ .../src/translations/rules/Interweave/es.pug | 120 ++++ .../src/translations/rules/Interweave/fr.pug | 117 +++ .../src/translations/rules/Takenmake/en.pug | 48 ++ .../src/translations/rules/Takenmake/es.pug | 51 ++ .../src/translations/rules/Takenmake/fr.pug | 49 ++ client/src/variants/Berolina.js | 5 +- client/src/variants/Grand.js | 45 +- client/src/variants/Interweave.js | 664 ++++++++++++++++++ client/src/variants/Takenmake.js | 147 ++++ 17 files changed, 1340 insertions(+), 51 deletions(-) create mode 100644 client/src/translations/rules/Interweave/en.pug create mode 100644 client/src/translations/rules/Interweave/es.pug create mode 100644 client/src/translations/rules/Interweave/fr.pug create mode 100644 client/src/translations/rules/Takenmake/en.pug create mode 100644 client/src/translations/rules/Takenmake/es.pug create mode 100644 client/src/translations/rules/Takenmake/fr.pug create mode 100644 client/src/variants/Interweave.js create mode 100644 client/src/variants/Takenmake.js diff --git a/TODO b/TODO index ecc877a4..d6c6076c 100644 --- 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 :) diff --git a/client/src/App.vue b/client/src/App.vue index a961d7fc..def45262 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -304,6 +304,7 @@ footer background-color: #6f8f57 div.board + user-select: none float: left height: 0 display: inline-block diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 5ab02fad..ddfe1e7e 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -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 = () => { diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue index ccf3a133..1cd37ab0 100644 --- a/client/src/components/ComputerGame.vue +++ b/client/src/components/ComputerGame.vue @@ -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; diff --git a/client/src/translations/about/en.pug b/client/src/translations/about/en.pug index daccd8ff..4f08d9ad 100644 --- a/client/src/translations/about/en.pug +++ b/client/src/translations/about/en.pug @@ -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 diff --git a/client/src/translations/about/es.pug b/client/src/translations/about/es.pug index cd84f982..1dc2b9bc 100644 --- a/client/src/translations/about/es.pug +++ b/client/src/translations/about/es.pug @@ -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 diff --git a/client/src/translations/about/fr.pug b/client/src/translations/about/fr.pug index 70ddc0ff..be36f3ea 100644 --- a/client/src/translations/about/fr.pug +++ b/client/src/translations/about/fr.pug @@ -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 index 00000000..27fc8b38 --- /dev/null +++ b/client/src/translations/rules/Interweave/en.pug @@ -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 + |  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 index 00000000..b613bb7f --- /dev/null +++ b/client/src/translations/rules/Interweave/es.pug @@ -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 + |  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 index 00000000..e4dbde0b --- /dev/null +++ b/client/src/translations/rules/Interweave/fr.pug @@ -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 + |  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 index 00000000..a65cfceb --- /dev/null +++ b/client/src/translations/rules/Takenmake/en.pug @@ -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 index 00000000..2d179d89 --- /dev/null +++ b/client/src/translations/rules/Takenmake/es.pug @@ -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 index 00000000..71e3551e --- /dev/null +++ b/client/src/translations/rules/Takenmake/fr.pug @@ -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). diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js index e5a222bb..8473b13e 100644 --- a/client/src/variants/Berolina.js +++ b/client/src/variants/Berolina.js @@ -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 diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index aeb7b6f4..743f01f9 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -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 index 00000000..c776889f --- /dev/null +++ b/client/src/variants/Interweave.js @@ -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= 2) { + for (let i=1; i 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 index 00000000..9a09b35a --- /dev/null +++ b/client/src/variants/Takenmake.js @@ -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); + } +}; -- 2.44.0