From: Benjamin Auder Date: Tue, 24 Mar 2020 00:37:00 +0000 (+0100) Subject: Better Ball rules. Buggish but almost OK Synchrone variant X-Git-Url: https://git.auder.net/variants/Bario/vendor/scripts/getGraph_%22%20%20%20this.image%20%20%20%22.php?a=commitdiff_plain;h=d54f6261c9e30f4eabb402ad301dd5c5e40fb656;p=vchess.git Better Ball rules. Buggish but almost OK Synchrone variant --- diff --git a/TODO b/TODO index 819ebc87..8d3eabcf 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,6 @@ # New variants +Doublearmy: 2 kings remaining get mated. + Landing pieces from empty board: https://www.chessvariants.com/diffsetup.dir/unachess.html Parachute v1 & 2 @@ -12,10 +14,6 @@ Goal is still checkmate. 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. -Maxima, Interweave, Roccoco - -Synchrone Chess: allow to anticipate en-passant capture as well :) - Dynamo chess -Omega chess (ask first) +Maxima, Interweave, Roccoco diff --git a/client/README.md b/client/README.md index 25bf68eb..1aa3c003 100644 --- a/client/README.md +++ b/client/README.md @@ -32,8 +32,9 @@ See [Configuration Reference](https://cli.vuejs.org/config/). ## Resources - - Vue.js + Vue router + Webpack, - - mini.css, - - Google font 'Open Sans' +- Vue.js + Vue router, +- Webpack, +- mini.css Sounds and pieces images where found at various locations. +Fonts were found on Google Fonts. diff --git a/client/public/images/pieces/Ball/bd.svg b/client/public/images/pieces/Ball/bd.svg new file mode 100644 index 00000000..8c5339cc --- /dev/null +++ b/client/public/images/pieces/Ball/bd.svg @@ -0,0 +1,103 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Ball/bh.svg b/client/public/images/pieces/Ball/bh.svg new file mode 100644 index 00000000..6dbea895 --- /dev/null +++ b/client/public/images/pieces/Ball/bh.svg @@ -0,0 +1,98 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Ball/bw.svg b/client/public/images/pieces/Ball/bw.svg deleted file mode 100644 index 6d4f9604..00000000 --- a/client/public/images/pieces/Ball/bw.svg +++ /dev/null @@ -1,135 +0,0 @@ - -Madeby Gridsimage/svg+xml \ No newline at end of file diff --git a/client/public/images/pieces/Ball/by.svg b/client/public/images/pieces/Ball/by.svg deleted file mode 100644 index 06324791..00000000 --- a/client/public/images/pieces/Ball/by.svg +++ /dev/null @@ -1,159 +0,0 @@ - -Madeby Gridsimage/svg+xml \ No newline at end of file diff --git a/client/public/images/pieces/Ball/wd.svg b/client/public/images/pieces/Ball/wd.svg new file mode 100644 index 00000000..d10a14fe --- /dev/null +++ b/client/public/images/pieces/Ball/wd.svg @@ -0,0 +1,68 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/client/public/images/pieces/Ball/wh.svg b/client/public/images/pieces/Ball/wh.svg new file mode 100644 index 00000000..eac3db53 --- /dev/null +++ b/client/public/images/pieces/Ball/wh.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/images/pieces/Ball/ww.svg b/client/public/images/pieces/Ball/ww.svg deleted file mode 100644 index 561449c4..00000000 --- a/client/public/images/pieces/Ball/ww.svg +++ /dev/null @@ -1,114 +0,0 @@ - -Madeby Gridsimage/svg+xml \ No newline at end of file diff --git a/client/public/images/pieces/Ball/wy.svg b/client/public/images/pieces/Ball/wy.svg deleted file mode 100644 index e86bfe40..00000000 --- a/client/public/images/pieces/Ball/wy.svg +++ /dev/null @@ -1,162 +0,0 @@ - -Madeby Gridsimage/svg+xml \ No newline at end of file diff --git a/client/public/index.html b/client/public/index.html index f93aa0e3..2219fe01 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -14,6 +14,8 @@ +
diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 52ec2c54..448604a4 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -85,6 +85,15 @@ export const ChessRules = class ChessRules { return V.CanFlip; } + // Some variants require turn indicator + // (generally when analysis or flip is diabled) + static get ShowTurn() { + return !V.CanAnalyze || V.ShowMoves != "all" || !V.CanFlip; + } + get showTurn() { + return V.ShowTurn; + } + static get IMAGE_EXTENSION() { // All pieces should be in the SVG format return ".svg"; @@ -1284,8 +1293,8 @@ export const ChessRules = class ChessRules { } let candidates = [0]; - for (let j = 1; j < moves1.length && moves1[j].eval == moves1[0].eval; j++) - candidates.push(j); + for (let i = 1; i < moves1.length && moves1[i].eval == moves1[0].eval; i++) + candidates.push(i); return moves1[candidates[randInt(candidates.length)]]; } diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index a49c31b1..2517c78a 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -103,15 +103,13 @@ export default { showTurn: function() { return ( this.game.score == '*' && - this.vr && - (this.vr.showMoves != "all" || !this.vr.canFlip) + !!this.vr && this.vr.showTurn ); }, turn: function() { - if (!this.vr) - return ""; + if (!this.vr) return ""; if (this.vr.showMoves != "all") - return this.st.tr[(this.vr.turn == 'w' ? "White" : "Black") + " to move"] + return this.st.tr[(this.vr.turn == 'w' ? "White" : "Black") + " to move"]; // Cannot flip: racing king or circular chess return this.vr.movesCount == 0 && this.game.mycolor == "w" ? this.st.tr["It's your turn!"] diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index 921a5998..d89ee404 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -55,6 +55,10 @@ export default { this.settings.highlight && ["all","highlight"].includes(V.ShowMoves) ); + const showCheck = ( + this.settings.highlight && + ["all","highlight","byrow"].includes(V.ShowMoves) + ); const orientation = !V.CanFlip ? "w" : this.orientation; // Ensure that squares colors do not change when board is flipped const lightSquareMod = (sizeX + sizeY) % 2; @@ -149,8 +153,8 @@ export default { "in-shadow": inShadow(ci, cj), "highlight-light": inHighlight(ci, cj) && lightSquare, "highlight-dark": inHighlight(ci, cj) && !lightSquare, - "incheck-light": showLight && lightSquare && incheckSq[ci][cj], - "incheck-dark": showLight && !lightSquare && incheckSq[ci][cj] + "incheck-light": showCheck && lightSquare && incheckSq[ci][cj], + "incheck-dark": showCheck && !lightSquare && incheckSq[ci][cj] }, attrs: { id: getSquareId({ x: ci, y: cj }) diff --git a/client/src/translations/about/en.pug b/client/src/translations/about/en.pug index c6b32fba..6d551949 100644 --- a/client/src/translations/about/en.pug +++ b/client/src/translations/about/en.pug @@ -42,9 +42,10 @@ p. h3 Related links #links - a(href="https://greenchess.net/") greenchess.net a(href="https://www.chessvariants.com/") chessvariants.com - a(href="http://pychess-variants.herokuapp.com/") pychess-variants + a(href="https://greenchess.net/") greenchess.net + a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com + a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com div a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr diff --git a/client/src/translations/about/es.pug b/client/src/translations/about/es.pug index eb53940c..66aa4298 100644 --- a/client/src/translations/about/es.pug +++ b/client/src/translations/about/es.pug @@ -39,9 +39,10 @@ p h3 Enlaces relacionados #links - a(href="https://greenchess.net/") greenchess.net a(href="https://www.chessvariants.com/") chessvariants.com - a(href="http://pychess-variants.herokuapp.com/") pychess-variants + a(href="https://greenchess.net/") greenchess.net + a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com + a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com div a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr diff --git a/client/src/translations/about/fr.pug b/client/src/translations/about/fr.pug index 3197837c..b4236030 100644 --- a/client/src/translations/about/fr.pug +++ b/client/src/translations/about/fr.pug @@ -40,9 +40,10 @@ p. h3 Liens connexes #links - a(href="https://greenchess.net/") greenchess.net a(href="https://www.chessvariants.com/") chessvariants.com - a(href="http://pychess-variants.herokuapp.com/") pychess-variants + a(href="https://greenchess.net/") greenchess.net + a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com + a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr a(href="https://brainking.com/") brainking.com diff --git a/client/src/translations/rules/Ball/en.pug b/client/src/translations/rules/Ball/en.pug index 49005d8a..3525359f 100644 --- a/client/src/translations/rules/Ball/en.pug +++ b/client/src/translations/rules/Ball/en.pug @@ -12,17 +12,21 @@ ul disappear and grab the ball. li "Capturing" a friendly unit pass the ball to it. +p. + All pieces represent players on a field of some ball game, + so they can move up to two squares only for a better realism. + figure.diagram-container .diagram.diag12 - | fen:rnbqkwnbr/ppppp1ppp/9/5p3/4a4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: + | fen:rnbhq1nbr/ppppppppp/5h3/9/4a4/5P3/9/PPPPP1PPP/RNBHQHNBR: .diagram.diag22 - | fen:rnbqkwnbr/ppppp1ppp/9/9/4s4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: - figcaption Left: before ...fxe5 (taking ball). Right: after ...fxe5. + | fen:rnbhq1nbr/ppppppppp/5h3/9/4S4/9/9/PPPPP1PPP/RNBHQHNBR: + figcaption Left: before fxe5 (taking ball). Right: after fxe5. p. - The piece sitting to the right of the king is a wildebeest (W). - It moves by jumping in L like a knight, or a little bit further: - three squares in one direction then one aside. + The piece sitting next to the queen is a champion (H). + It moves by jumping two squares in any direction (potentially over pieces), + or one square orthogonally. h3 End of the game @@ -32,16 +36,16 @@ p. figure.diagram-container .diagram.diag12 - | fen:rqnkb1nb1/pp1pppp1r/2p5p/4wW1p1/4S4/1P5B1/9/P1PPP1PPP/RQNK2NBR: + | fen:3rn1r2/1pp3pbp/2hp2n2/p1b1pp1p1/P6P1/2N3P1P/1PP2N3/1RB1qPd2/1R2BH1HQ: .diagram.diag22 - | fen:rqnkb1nb1/pp1pppp1r/2p5p/4wY1p1/4P4/1P5B1/9/P1PPP1PPP/RQNK2NBR: + | fen:3rn1r2/1pp3pbp/2hp2n2/p1b1pp1p1/P6P1/2N3P1P/1PP2N3/1RB1tPh2/1R2BH1HQ: figcaption. - Left: before e5Pf6 (passing the ball). - Right: after the move. Then Wxe9# cannot be prevented. + Left: before g2Pe2 (passing the ball). + Right: after the move. Then ...Q(x)d1# cannot be prevented. p. - White pass the ball from the pawn at e5 to the wildebeest at f6. - Then, since the black bishop on e8 cannot move white will win by taking it: Wxe9#. + The black champion on g2 passes the ball to the black queen on e2. + Victory is then garanteed by playing a queen move on the first rank. h3 Source diff --git a/client/src/translations/rules/Ball/es.pug b/client/src/translations/rules/Ball/es.pug index 42922c47..cef6fa62 100644 --- a/client/src/translations/rules/Ball/es.pug +++ b/client/src/translations/rules/Ball/es.pug @@ -12,19 +12,23 @@ ul mientras lo hace desaparecer. li "Capturar" una pieza amiga le pasa la pelota. +p. + Todas las piezas representan jugadores en un área de juego de pelota, + entonces solo se mueven dos casillas como máximo para un mejor realismo. + figure.diagram-container .diagram.diag12 - | fen:rnbqkwnbr/ppppp1ppp/9/5p3/4a4/9/2N4N1/PPPPPPPPPP/R1BQKW1BR: + | fen:rnbhq1nbr/ppppppppp/5h3/9/4a4/5P3/9/PPPPP1PPP/RNBHQHNBR: .diagram.diag22 - | fen:rnbqkwnbr/ppppp1ppp/9/9/4s4/9/2N4N1/PPPPPPPPPP/R1BQKW1BR: + | fen:rnbhq1nbr/ppppppppp/5h3/9/4S4/9/9/PPPPP1PPP/RNBHQHNBR: figcaption. - Izquierda: antes de ...fxe5 (tomando la pelota). - Derecha: después de ...fxe5. + Izquierda: antes de fxe5 (tomando la pelota). + Derecha: después de fxe5. p. - La pieza a la derecha del rey es un ñu (W). - Se mueve realizando saltos en forma de L como el caballo, - o un poco más alargado: tres casillas y luego una en el lateral. + La pieza al lado de la dama es un campeón (H). + Se mueve saltando dos casillas en cualquier dirección + (potencialmente sobre piezas), o una casilla ortogonalmente. h3 Fin de la partida @@ -34,17 +38,16 @@ p. figure.diagram-container .diagram.diag12 - | fen:rqnkb1nb1/pp1pppp1r/2p5p/4wW1p1/4S4/1P5B1/9/P1PPP1PPP/RQNK2NBR: + | fen:3rn1r2/1pp3pbp/2hp2n2/p1b1pp1p1/P6P1/2N3P1P/1PP2N3/1RB1qPd2/1R2BH1HQ: .diagram.diag22 - | fen:rqnkb1nb1/pp1pppp1r/2p5p/4wY1p1/4P4/1P5B1/9/P1PPP1PPP/RQNK2NBR: + | fen:3rn1r2/1pp3pbp/2hp2n2/p1b1pp1p1/P6P1/2N3P1P/1PP2N3/1RB1tPh2/1R2BH1HQ: figcaption. - Izquierda: antes de e5Pf6 (pasando el globo). - Derecha: después del movimiento. Entonces Wxe9# no se puede prevenir. + Izquierda: antes de g2Pe2 (pasando el globo). + Derecha: después del movimiento. Entonces Q(x)d1# no se puede prevenir. p. - Las blancas pasan la pelota del peón e5 al ñu en f6. - Entonces, como el alfil negro no puede moverse, - la victoria está asegurada capturándolo con Wxe9#. + Las negras pasan el balón del campeón g2 a la dama en e2. + La victoria se asegura al jugar un movimiento de dama en la primera fila. h3 Fuente diff --git a/client/src/translations/rules/Ball/fr.pug b/client/src/translations/rules/Ball/fr.pug index 070021d2..314bc5f1 100644 --- a/client/src/translations/rules/Ball/fr.pug +++ b/client/src/translations/rules/Ball/fr.pug @@ -12,17 +12,23 @@ ul tout en la faisant disparaître. li "Capturer" une pièce amie lui passe le ballon. +p. + Toutes les pièces représentent des joueurs sur un terrain d'un certain + jeu de ballon, donc elles ne se déplacent que de deux cases au maximum + pour un meilleur réalisme. + figure.diagram-container .diagram.diag12 - | fen:rnbqkwnbr/ppppp1ppp/9/5p3/4a4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: + | fen:rnbhq1nbr/ppppppppp/5h3/9/4a4/5P3/9/PPPPP1PPP/RNBHQHNBR: .diagram.diag22 - | fen:rnbqkwnbr/ppppp1ppp/9/9/4s4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: - figcaption Gauche : avant ...fxe5 (prenant le ballon). Droite : après ...fxe5. + | fen:rnbhq1nbr/ppppppppp/5h3/9/4S4/9/9/PPPPP1PPP/RNBHQHNBR: + figcaption Gauche : avant fxe5 (prenant le ballon). Droite : après fxe5. p. - La pièce située à droite du roi est un gnou (W). - Il se déplace en effectuant des sauts en L comme le cavalier, - ou bien un peu plus allongés : trois cases puis une sur le côté. + La pièce située à côté de la dame est un champion (H). + Il se déplace en effectuant des sauts de deux cases dans n'importe + quelle direction (potentiellement par dessus des pièces), + ou d'une case orthogonalement. h3 Fin de la partie @@ -32,17 +38,17 @@ p. figure.diagram-container .diagram.diag12 - | fen:rqnkb1nb1/pp1pppp1r/2p5p/4wW1p1/4S4/1P5B1/9/P1PPP1PPP/RQNK2NBR: + | fen:3rn1r2/1pp3pbp/2hp2n2/p1b1pp1p1/P6P1/2N3P1P/1PP2N3/1RB1qPd2/1R2BH1HQ: .diagram.diag22 - | fen:rqnkb1nb1/pp1pppp1r/2p5p/4wY1p1/4P4/1P5B1/9/P1PPP1PPP/RQNK2NBR: + | fen:3rn1r2/1pp3pbp/2hp2n2/p1b1pp1p1/P6P1/2N3P1P/1PP2N3/1RB1tPh2/1R2BH1HQ: figcaption. - Gauche : avant e5Pf6 (passant le ballon). - Droite : après le coup. Ensuite Wxe9# ne peut être empêché. + Gauche : avant g2Pe2 (passant le ballon). + Droite : après le coup. Ensuite ...Q(x)d1# ne peut être empêché. p. - Les blancs passent la balle du pion e5 vers le gnou en f6. - Ensuite, puisque le fou noir ne peut pas bouger la victoire est assurée - en le capturant par Wxe9#. + Les noirs passent la balle du champion g2 vers la dame en e2. + La victoire est alors assurée en jouant ensuite un coup de dame sur la + première rangée. h3 Source diff --git a/client/src/translations/rules/Synchrone/en.pug b/client/src/translations/rules/Synchrone/en.pug new file mode 100644 index 00000000..74322081 --- /dev/null +++ b/client/src/translations/rules/Synchrone/en.pug @@ -0,0 +1,52 @@ +p.boxed + | Both players play a move "at the same time". + | Conflicts are resolved following a few simple rules. + +p. + In order to not rethink a big part of the code, the white move has to be + played before the black one. However, the black player doesn't see it. + +p So both players play "at the same time". Resolving rules: +ul + li. + If both moves arrive on the same square, both pieces disappear except + if one is a king. In this case only the king remains. + li. + If a capture was intended but the target moved, the move is still played + but doesn't capture anything. +p. + Such captures can be anticipated by capturing our own pieces. + If the enemy captures as predicted, his piece disappears. + If he doesn't, the self-capture isn't undone. + +figure.diagram-container + .diagram + | fen:rnb1kbnr/ppp1pppp/8/3qP3/8/8/PPPP1PPP/RNBQKBNR: + figcaption After 1.e4 d5 2.e5 Qxd5 (anticipating 2.exd5 which isn't played) + +h3 End of the game + +p. + Due to the simultaneity, it is possible that a king is captured for example + after escaping a check in a wrong way, as the following diagram shows. + So, capturing the king counts as a win, of highest priority. + A checkmate wins too, but if your king is captured and the other still on + the board (even if it is mated), you lose. + +figure.diagram-container + .diagram.diag12 + | fen:r3r1bb/ppqRkppp/8/2p1n3/7n/8/PPPPP1P1/RNBQNBK1: + .diagram.diag22 + | fen:r3r1bb/pp1qRppp/8/2p1n3/7n/8/PPPPP1P1/RNBQNBK1: + figcaption. + Left: before Rxe7 (white) and Qxd7 (black) + Right: after the move (1-0) + +h3 Source + +p + a(href="http://www.hexenspiel.de/engl/synchronous-chess/") Synchronous chess + | , modified to allow en-passant captures + | and disable the "exchange of captures" stage. + a(href="http://www.pion.ch/echecs/variante.php?jeu=synchro") Another description + |  (in French). diff --git a/client/src/translations/rules/Synchrone/es.pug b/client/src/translations/rules/Synchrone/es.pug new file mode 100644 index 00000000..1a98c95d --- /dev/null +++ b/client/src/translations/rules/Synchrone/es.pug @@ -0,0 +1,53 @@ +p.boxed + | Ambos jugadores hacen un movimiento "al mismo tiempo". + | Los conflictos se resuelven mediante unas pocas reglas simples. + +p. + Para no repensar una gran parte del código, el movimiento blanco debe ser + jugado antes del movimiento negro. Sin embargo, el jugador negro no lo ve. + +p Entonces los dos jugadores juegan "al mismo tiempo". Reglas de resolución: +ul + li. + Si las dos jugadas alcanzan la mismo casilla, entonces las dos piezas + desaparecerá a menos que uno de ellos sea un rey. + En este caso solo queda el rey. + li. + Si se intenta una captura pero el objetivo se mueve, el movimiento + todavía se juega pero no capturó nada. +p. + Tales capturas se pueden anticipar capturando nuestras propias piezas. + Si el oponente captura como se esperaba, entonces su pieza desaparece. + De lo contrario, la auto-captura no se deshace. + +figure.diagram-container + .diagram + | fen:rnb1kbnr/ppp1pppp/8/3qP3/8/8/PPPP1PPP/RNBQKBNR: + figcaption Después de 1.e4 d5 2.e5 Qxd5 (anticipando 2.exd5 que no se juega) + +h3 Fin de la partida + +p. + Debido a la simultaneidad, es posible que un rey sea capturado por + ejemplo cubriendo un jaque torpemente, como se muestra + el siguiente diagrama. + Jaque mate también gana, pero si tu rey es capturado mientras + que el otro todavía está en el tablero (incluso si es jaque mate), pierdes. + +figure.diagram-container + .diagram.diag12 + | fen:r3r1bb/ppqRkppp/8/2p1n3/7n/8/PPPPP1P1/RNBQNBK1: + .diagram.diag22 + | fen:r3r1bb/pp1qRppp/8/2p1n3/7n/8/PPPPP1P1/RNBQNBK1: + figcaption. + Izquierda: antes de Rxe7 (blanco) y Qxd7 (negro) + Derecha: después del movimiento (1-0) + +h3 Fuente + +p + a(href="http://www.hexenspiel.de/engl/synchronous-chess/") Ajedrez sincrónico + | , modificado para autorizar la captura en passant y cancelar la fase de + | "cambio de captura". + a(href="http://www.pion.ch/echecs/variante.php?jeu=synchro") Otra descripción + |  (en francés). diff --git a/client/src/translations/rules/Synchrone/fr.pug b/client/src/translations/rules/Synchrone/fr.pug new file mode 100644 index 00000000..1d7fe61d --- /dev/null +++ b/client/src/translations/rules/Synchrone/fr.pug @@ -0,0 +1,53 @@ +p.boxed + | Les deux joueurs jouent un coup "en même temps". + | Les conflits sont résolus par quelques règles simples. + +p. + Afin de ne pas repenser une grosse partie du code, le coup blanc doit être + joué avant le coup noir. Cependant, le joueur des noirs ne le voit pas. + +p Ainsi les deux joueurs jouent "en même temps". Règles de résolution : +ul + li. + Si les deux coups arrivent sur la même case, alors les deux pièces + disparaissent sauf si l'une des deux est un roi. + Dans ce cas seul le roi reste. + li. + Si une capture est tentée mais que la cible bouge, le coup est tout + de même joué mais ne capture rien. +p. + De telles captures peuvent être anticipées en capturant nos propres pièces. + Si l'adversaire capture comme prévu, alors sa pièce disparaît. + Sinon, l'auto-capture n'est pas défaite. + +figure.diagram-container + .diagram + | fen:rnb1kbnr/ppp1pppp/8/3qP3/8/8/PPPP1PPP/RNBQKBNR: + figcaption Après 1.e4 d5 2.e5 Qxd5 (anticipant 2.exd5 qui n'est pas joué) + +h3 Fin de la partie + +p. + À cause de la simultanéité, il est possible qu'un roi soit capturé par + exemple en couvrant un échec maladroitement, comme le montre + le diagramme suivant. + Un échec et mat gagne également, mais si votre roi est capturé tandis + que l'autre est encore sur l'échquier (même s'il est maté), vous perdez. + +figure.diagram-container + .diagram.diag12 + | fen:r3r1bb/ppqRkppp/8/2p1n3/7n/8/PPPPP1P1/RNBQNBK1: + .diagram.diag22 + | fen:r3r1bb/pp1qRppp/8/2p1n3/7n/8/PPPPP1P1/RNBQNBK1: + figcaption. + Gauche : avant Rxe7 (blancs) et Qxd7 (noirs) + Droite : après le coup (1-0) + +h3 Source + +p + a(href="http://www.hexenspiel.de/engl/synchronous-chess/") Échecs synchronisés + | , modifés pour autoriser la prise en passant et annuler la phase + | "d'échange de captures". + a(href="http://www.pion.ch/echecs/variante.php?jeu=synchro") Autre description + | . diff --git a/client/src/variants/Ball.js b/client/src/variants/Ball.js index 5a23e9f8..1bb7731c 100644 --- a/client/src/variants/Ball.js +++ b/client/src/variants/Ball.js @@ -1,5 +1,4 @@ import { ChessRules, Move, PiPo } from "@/base_rules"; -import { WildebeestRules } from "@/variants/Wildebeest"; import { ArrayFun } from "@/utils/array"; import { shuffle } from "@/utils/alea"; @@ -8,7 +7,7 @@ export class BallRules extends ChessRules { return Object.assign( {}, ChessRules.PawnSpecs, - { promotions: ChessRules.PawnSpecs.promotions.concat([V.WILDEBEEST]) } + { promotions: ChessRules.PawnSpecs.promotions.concat([V.CHAMPION]) } ); } @@ -19,8 +18,8 @@ export class BallRules extends ChessRules { return false; } - static get WILDEBEEST() { - return 'w'; + static get CHAMPION() { + return 'h'; } static get BALL() { @@ -42,7 +41,7 @@ export class BallRules extends ChessRules { 'b': 'c', 'q': 't', 'k': 'l', - 'w': 'y' + 'h': 'd' }; } @@ -54,13 +53,13 @@ export class BallRules extends ChessRules { 'c': 'b', 't': 'q', 'l': 'k', - 'y': 'w' + 'd': 'h' }; } static get PIECES() { return ChessRules.PIECES - .concat([V.WILDEBEEST]) + .concat([V.CHAMPION]) .concat(Object.keys(V.HAS_BALL_DECODE)) .concat(['a']); } @@ -109,7 +108,7 @@ export class BallRules extends ChessRules { let prefix = ""; const withPrefix = Object.keys(V.HAS_BALL_DECODE) - .concat([V.WILDEBEEST]) + .concat([V.CHAMPION]) .concat(['a']); if (withPrefix.includes(b[1])) prefix = "Ball/"; return prefix + b; @@ -129,7 +128,7 @@ export class BallRules extends ChessRules { static GenRandInitFen(randomness) { if (randomness == 0) - return "rnbqkwnbr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBQKWNBR w 0 -"; + return "rnbhqhnbr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBHQHNBR w 0 -"; let pieces = { w: new Array(9), b: new Array(9) }; for (let c of ["w", "b"]) { @@ -140,7 +139,7 @@ export class BallRules extends ChessRules { // Get random squares for every piece, totally freely let positions = shuffle(ArrayFun.range(9)); - const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q', 'w']; + const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'h', 'h', 'q']; const rem2 = positions[0] % 2; if (rem2 == positions[1] % 2) { // Fix bishops (on different colors) @@ -160,7 +159,7 @@ export class BallRules extends ChessRules { ); } - scanKings(fen) {} + scanKings() {} static get size() { return { x: 9, y: 9 }; @@ -174,7 +173,27 @@ export class BallRules extends ChessRules { } static get steps() { - return WildebeestRules.steps; + return Object.assign( + {}, + ChessRules.steps, + // Add champion moves + { + h: [ + [-2, -2], + [-2, 0], + [-2, 2], + [0, -2], + [0, 2], + [2, -2], + [2, 0], + [2, 2], + [-1, 0], + [1, 0], + [0, -1], + [0, 1] + ] + } + ); } // Because of the ball, getPiece() could be wrong: @@ -242,17 +261,33 @@ export class BallRules extends ChessRules { // So base implementation is fine. getPotentialMovesFrom([x, y]) { - if (this.getPiece(x, y) == V.WILDEBEEST) - return this.getPotentialWildebeestMoves([x, y]); + if (this.getPiece(x, y) == V.CHAMPION) + return this.getPotentialChampionMoves([x, y]); return super.getPotentialMovesFrom([x, y]); } - getPotentialWildebeestMoves(sq) { - return this.getSlideNJumpMoves( - sq, - V.steps[V.KNIGHT].concat(V.steps[WildebeestRules.CAMEL]), - "oneStep" - ); + // "Sliders": at most 2 steps + getSlideNJumpMoves([x, y], steps, oneStep) { + let moves = []; + outerLoop: for (let step of steps) { + let i = x + step[0]; + let j = y + step[1]; + let stepCount = 1; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + if (oneStep || stepCount == 2) continue outerLoop; + i += step[0]; + j += step[1]; + stepCount++; + } + if (V.OnBoard(i, j) && this.canTake([x, y], [i, j])) + moves.push(this.getBasicMove([x, y], [i, j])); + } + return moves; + } + + getPotentialChampionMoves(sq) { + return this.getSlideNJumpMoves(sq, V.steps[V.CHAMPION], "oneStep"); } filterValid(moves) { @@ -292,8 +327,7 @@ export class BallRules extends ChessRules { n: 3, b: 3, q: 9, - w: 7, - k: 5, + h: 4, a: 0 //ball: neutral }; } diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index 42df93e3..fa80824b 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -146,7 +146,7 @@ export class EightpiecesRules extends ChessRules { static GenRandInitFen(randomness) { if (randomness == 0) // Deterministic: - return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 ahah - -"; + return "jfsqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JDSQKBNR w 0 ahah - -"; let pieces = { w: new Array(8), b: new Array(8) }; let flags = ""; @@ -657,106 +657,6 @@ export class EightpiecesRules extends ChessRules { return this.filterValid(this.getPotentialMovesFrom(sentrySq)); } - prePlay(move) { - if (move.appear.length == 0 && move.vanish.length == 1) - // The sentry is about to push a piece: subTurn goes from 1 to 2 - this.sentryPos = { x: move.end.x, y: move.end.y }; - if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) { - // A piece is pushed: forbid array of squares between start and end - // of move, included (except if it's a pawn) - let squares = []; - if ([V.KNIGHT,V.KING].includes(move.vanish[0].p)) - // short-range pieces: just forbid initial square - squares.push({ x: move.start.x, y: move.start.y }); - else { - const deltaX = move.end.x - move.start.x; - const deltaY = move.end.y - move.start.y; - const step = [ - deltaX / Math.abs(deltaX) || 0, - deltaY / Math.abs(deltaY) || 0 - ]; - for ( - let sq = {x: move.start.x, y: move.start.y}; - sq.x != move.end.x || sq.y != move.end.y; - sq.x += step[0], sq.y += step[1] - ) { - squares.push({ x: sq.x, y: sq.y }); - } - } - // Add end square as well, to know if I was pushed (useful for lancers) - squares.push({ x: move.end.x, y: move.end.y }); - this.sentryPush.push(squares); - } else this.sentryPush.push(null); - } - - play(move) { - this.prePlay(move); - move.flags = JSON.stringify(this.aggregateFlags()); - this.epSquares.push(this.getEpSquare(move)); - V.PlayOnBoard(this.board, move); - // Is it a sentry push? (useful for undo) - move.sentryPush = (this.subTurn == 2); - if (this.subTurn == 1) this.movesCount++; - if (move.appear.length == 0 && move.vanish.length == 1) this.subTurn = 2; - else { - // Turn changes only if not a sentry "pre-push" - this.turn = V.GetOppCol(this.turn); - this.subTurn = 1; - } - this.postPlay(move); - } - - postPlay(move) { - if (move.vanish.length == 0 || this.subTurn == 2) - // Special pass move of the king, or sentry pre-push: nothing to update - return; - const c = move.vanish[0].c; - const piece = move.vanish[0].p; - const firstRank = c == "w" ? V.size.x - 1 : 0; - - if (piece == V.KING) { - this.kingPos[c][0] = move.appear[0].x; - this.kingPos[c][1] = move.appear[0].y; - this.castleFlags[c] = [V.size.y, V.size.y]; - return; - } - // Update castling flags if rooks are moved - const oppCol = V.GetOppCol(c); - const oppFirstRank = V.size.x - 1 - firstRank; - if ( - move.start.x == firstRank && //our rook moves? - this.castleFlags[c].includes(move.start.y) - ) { - const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); - this.castleFlags[c][flagIdx] = V.size.y; - } else if ( - move.end.x == oppFirstRank && //we took opponent rook? - this.castleFlags[oppCol].includes(move.end.y) - ) { - const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); - this.castleFlags[oppCol][flagIdx] = V.size.y; - } - } - - undo(move) { - this.epSquares.pop(); - this.disaggregateFlags(JSON.parse(move.flags)); - V.UndoOnBoard(this.board, move); - // Decrement movesCount except if the move is a sentry push - if (!move.sentryPush) this.movesCount--; - if (this.subTurn == 2) this.subTurn = 1; - else { - this.turn = V.GetOppCol(this.turn); - if (move.sentryPush) this.subTurn = 2; - } - this.postUndo(move); - } - - postUndo(move) { - super.postUndo(move); - this.sentryPush.pop(); - } - isAttacked(sq, color) { return ( super.isAttacked(sq, color) || @@ -937,6 +837,106 @@ export class EightpiecesRules extends ChessRules { // Jailer doesn't capture or give check + prePlay(move) { + if (move.appear.length == 0 && move.vanish.length == 1) + // The sentry is about to push a piece: subTurn goes from 1 to 2 + this.sentryPos = { x: move.end.x, y: move.end.y }; + if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) { + // A piece is pushed: forbid array of squares between start and end + // of move, included (except if it's a pawn) + let squares = []; + if ([V.KNIGHT,V.KING].includes(move.vanish[0].p)) + // short-range pieces: just forbid initial square + squares.push({ x: move.start.x, y: move.start.y }); + else { + const deltaX = move.end.x - move.start.x; + const deltaY = move.end.y - move.start.y; + const step = [ + deltaX / Math.abs(deltaX) || 0, + deltaY / Math.abs(deltaY) || 0 + ]; + for ( + let sq = {x: move.start.x, y: move.start.y}; + sq.x != move.end.x || sq.y != move.end.y; + sq.x += step[0], sq.y += step[1] + ) { + squares.push({ x: sq.x, y: sq.y }); + } + } + // Add end square as well, to know if I was pushed (useful for lancers) + squares.push({ x: move.end.x, y: move.end.y }); + this.sentryPush.push(squares); + } else this.sentryPush.push(null); + } + + play(move) { + this.prePlay(move); + move.flags = JSON.stringify(this.aggregateFlags()); + this.epSquares.push(this.getEpSquare(move)); + V.PlayOnBoard(this.board, move); + // Is it a sentry push? (useful for undo) + move.sentryPush = (this.subTurn == 2); + if (this.subTurn == 1) this.movesCount++; + if (move.appear.length == 0 && move.vanish.length == 1) this.subTurn = 2; + else { + // Turn changes only if not a sentry "pre-push" + this.turn = V.GetOppCol(this.turn); + this.subTurn = 1; + } + this.postPlay(move); + } + + postPlay(move) { + if (move.vanish.length == 0 || this.subTurn == 2) + // Special pass move of the king, or sentry pre-push: nothing to update + return; + const c = move.vanish[0].c; + const piece = move.vanish[0].p; + const firstRank = c == "w" ? V.size.x - 1 : 0; + + if (piece == V.KING) { + this.kingPos[c][0] = move.appear[0].x; + this.kingPos[c][1] = move.appear[0].y; + this.castleFlags[c] = [V.size.y, V.size.y]; + return; + } + // Update castling flags if rooks are moved + const oppCol = V.GetOppCol(c); + const oppFirstRank = V.size.x - 1 - firstRank; + if ( + move.start.x == firstRank && //our rook moves? + this.castleFlags[c].includes(move.start.y) + ) { + const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = V.size.y; + } else if ( + move.end.x == oppFirstRank && //we took opponent rook? + this.castleFlags[oppCol].includes(move.end.y) + ) { + const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1); + this.castleFlags[oppCol][flagIdx] = V.size.y; + } + } + + undo(move) { + this.epSquares.pop(); + this.disaggregateFlags(JSON.parse(move.flags)); + V.UndoOnBoard(this.board, move); + // Decrement movesCount except if the move is a sentry push + if (!move.sentryPush) this.movesCount--; + if (this.subTurn == 2) this.subTurn = 1; + else { + this.turn = V.GetOppCol(this.turn); + if (move.sentryPush) this.subTurn = 2; + } + this.postUndo(move); + } + + postUndo(move) { + super.postUndo(move); + this.sentryPush.pop(); + } + static get VALUES() { return Object.assign( { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations diff --git a/client/src/variants/Synchrone.js b/client/src/variants/Synchrone.js index e47941bf..372bc1ee 100644 --- a/client/src/variants/Synchrone.js +++ b/client/src/variants/Synchrone.js @@ -1,11 +1,468 @@ +// TODO: debug, and forbid self-capture of king. + import { ChessRules } from "@/base_rules"; +import { randInt } from "@/utils/alea"; export class SynchroneRules extends ChessRules { - // TODO: getNotation retourne "?" si turn == "w" - // ==> byrows disparait, juste "showAll" et "None". - // - // play: si turn == "w", enregistrer le coup (whiteMove), - // mais ne rien faire ==> résolution après le coup noir. - // - // ==> un coup sur deux (coups blancs) est "vide" du point de vue de l'exécution. + static get CanAnalyze() { + return true; //false; + } + + static get ShowMoves() { + return "byrow"; + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // 5) Check whiteMove + if ( + ( + fenParsed.turn == "w" && + // NOTE: do not check really JSON stringified move... + (!fenParsed.whiteMove || fenParsed.whiteMove == "-") + ) + || + (fenParsed.turn == "b" && fenParsed.whiteMove != "-") + ) { + return false; + } + return true; + } + + static IsGoodEnpassant(enpassant) { + const epArray = enpassant.split(","); + if (![2, 3].includes(epArray.length)) return false; + epArray.forEach(epsq => { + if (epsq != "-") { + const ep = V.SquareToCoords(epsq); + if (isNaN(ep.x) || !V.OnBoard(ep)) return false; + } + }); + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { whiteMove: fenParts[5] } + ); + } + + static GenRandInitFen(randomness) { + return ChessRules.GenRandInitFen(randomness).slice(0, -1) + "-,- -"; + } + + getFen() { + return super.getFen() + " " + this.getWhitemoveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getWhitemoveFen(); + } + + setOtherVariables(fen) { + const parsedFen = V.ParseFen(fen); + this.setFlags(parsedFen.flags); + const epArray = parsedFen.enpassant.split(","); + this.epSquares = []; + epArray.forEach(epsq => this.epSquares.push(this.getEpSquare(epsq))); + super.scanKings(fen); + // Also init whiteMove + this.whiteMove = + parsedFen.whiteMove != "-" + ? JSON.parse(parsedFen.whiteMove) + : null; + } + + // After undo(): no need to re-set INIT_COL_KING + scanKings() { + this.kingPos = { w: [-1, -1], b: [-1, -1] }; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (this.getPiece(i, j) == V.KING) + this.kingPos[this.getColor(i, j)] = [i, j]; + } + } + } + + getEnpassantFen() { + const L = this.epSquares.length; + let res = ""; + const start = L - 2 - (this.turn == 'b' ? 1 : 0); + for (let i=start; i < L; i++) { + if (!this.epSquares[i]) res += "-,"; + else res += V.CoordsToSquare(this.epSquares[i]) + ","; + } + return res.slice(0, -1); + } + + getWhitemoveFen() { + if (!this.whiteMove) return "-"; + return JSON.stringify({ + start: this.whiteMove.start, + end: this.whiteMove.end, + appear: this.whiteMove.appear, + vanish: this.whiteMove.vanish + }); + } + + // NOTE: lazy unefficient implementation (for now. TODO?) + getPossibleMovesFrom([x, y]) { + const moves = this.getAllValidMoves(); + return moves.filter(m => { + return m.start.x == x && m.start.y == y; + }); + } + + getCaptures(x, y) { + const color = this.turn; + const sliderAttack = (xx, yy, allowedSteps) => { + const deltaX = xx - x, + absDeltaX = Math.abs(deltaX); + const deltaY = yy - y, + absDeltaY = Math.abs(deltaY); + const step = [ deltaX / absDeltaX || 0, deltaY / absDeltaY || 0 ]; + if ( + // Check that the step is a priori valid: + (absDeltaX != absDeltaY && deltaX != 0 && deltaY != 0) || + allowedSteps.every(st => st[0] != step[0] || st[1] != step[1]) + ) { + return null; + } + let sq = [ x + step[0], y + step[1] ]; + while (sq[0] != xx || sq[1] != yy) { + // NOTE: no need to check OnBoard in this special case + if (this.board[sq[0]][sq[1]] != V.EMPTY) return null; + sq[0] += step[0]; + sq[1] += step[1]; + } + return this.getBasicMove([xx, yy], [x, y]); + }; + // Can I take on the square [x, y] ? + // If yes, return the (list of) capturing move(s) + let moves = []; + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if (this.getColor(i, j) == color) { + switch (this.getPiece(i, j)) { + case V.PAWN: { + // Pushed pawns move as enemy pawns + const shift = (color == 'w' ? 1 : -1); + if (x + shift == i && Math.abs(y - j) == 1) + moves.push(this.getBasicMove([i, j], [x, y])); + break; + } + case V.KNIGHT: { + const deltaX = Math.abs(i - x); + const deltaY = Math.abs(j - y); + if ( + deltaX + deltaY == 3 && + [1, 2].includes(deltaX) && + [1, 2].includes(deltaY) + ) { + moves.push(this.getBasicMove([i, j], [x, y])); + } + break; + } + case V.KING: + if (Math.abs(i - x) <= 1 && Math.abs(j - y) <= 1) + moves.push(this.getBasicMove([i, j], [x, y])); + break; + case V.ROOK: { + const mv = sliderAttack(i, j, V.steps[V.ROOK]); + if (!!mv) moves.push(mv); + break; + } + case V.BISHOP: { + const mv = sliderAttack(i, j, V.steps[V.BISHOP]); + if (!!mv) moves.push(mv); + break; + } + case V.QUEEN: { + const mv = sliderAttack( + i, j, V.steps[V.ROOK].concat(V.steps[V.BISHOP])); + if (!!mv) moves.push(mv); + break; + } + } + } + } + } + return this.filterValid(moves); + } + + getAllValidMoves() { + const color = this.turn; + // 0) Generate our possible moves + let myMoves = super.getAllValidMoves(); + // Lookup table to quickly decide if a move is already in list: + let moveSet = {}; + const getMoveHash = (move) => { + return ( + "m" + move.start.x + move.start.y + + move.end.x + move.end.y + + // Also use m.appear[0].p for pawn promotions + move.appear[0].p + ); + }; + myMoves.forEach(m => moveSet[getMoveHash(m)] = true); + // 1) Generate all opponent's moves + this.turn = V.GetOppCol(color); + const oppMoves = super.getAllValidMoves(); + this.turn = color; + // 2) Play each opponent's move, and see if captures are possible: + // --> capturing moving unit only (otherwise some issues) + oppMoves.forEach(m => { + V.PlayOnBoard(this.board, m); + // Can I take on [m.end.x, m.end.y] ? + // If yes and not already in list, add it (without the capturing part) + let capturingMoves = this.getCaptures(m.end.x, m.end.y); + capturingMoves.forEach(cm => { + const cmHash = getMoveHash(cm); + if (!moveSet[cmHash]) { + // The captured unit hasn't moved yet, so temporarily cancel capture + cm.vanish.pop(); + // If m is itself a capturing move: then replace by self-capture + if (m.vanish.length == 2) cm.vanish.push(m.vanish[1]); + myMoves.push(cm); + moveSet[cmHash] = true; + } + }); + V.UndoOnBoard(this.board, m); + }); + return myMoves; + } + + filterValid(moves) { + if (moves.length == 0) return []; + // filterValid can be called when it's "not our turn": + const color = moves[0].vanish[0].c; + return moves.filter(m => { + const piece = m.vanish[0].p; + if (piece == V.KING) { + this.kingPos[color][0] = m.appear[0].x; + this.kingPos[color][1] = m.appear[0].y; + } + V.PlayOnBoard(this.board, m); + let res = !this.underCheck(color); + V.UndoOnBoard(this.board, m); + if (piece == V.KING) this.kingPos[color] = [m.start.x, m.start.y]; + return res; + }); + } + + atLeastOneMove(color) { + const curTurn = this.turn; + this.turn = color; + const res = super.atLeastOneMove(); + this.turn = curTurn; + return res; + } + + // White and black (partial) moves were played: merge + resolveSynchroneMove(move) { + const m1 = this.whiteMove; + const m2 = move; + // For PlayOnBoard (no need for start / end, irrelevant) + let smove = { + appear: [], + vanish: [ + m1.vanish[0], + m2.vanish[0] + ] + }; + if ((m1.end.x != m2.end.x) || (m1.end.y != m2.end.y)) { + // Easy case: two independant moves (which may (self-)capture) + smove.appear.push(m1.appear[0]); + smove.appear.push(m2.appear[0]); + // "Captured" pieces may have moved: + if ( + m1.vanish.length == 2 && + ( + m2.end.x != m1.vanish[1].x || + m2.end.y != m1.vanish[1].y + ) + ) { + smove.vanish.push(m1.vanish[1]); + } + if ( + m2.vanish.length == 2 && + ( + m1.end.x != m2.vanish[1].x || + m1.end.y != m2.vanish[1].y + ) + ) { + smove.vanish.push(m2.vanish[1]); + } + } else { + // Collision: + if (m1.vanish.length == 1 && m2.vanish.length == 1) { + // Easy case: both disappear except if one is a king + const p1 = m1.vanish[0].p; + const p2 = m2.vanish[0].p; + if ([p1, p2].includes(V.KING)) { + smove.appear.push({ + x: m1.end.x, + y: m1.end.y, + p: V.KING, + c: (p1 == V.KING ? 'w' : 'b') + }); + } + } else { + // One move is a self-capture and the other a normal capture: + // only the self-capture appears + console.log(m1); + console.log(m2); + const selfCaptureMove = + m1.vanish[1].c == m1.vanish[0].c + ? m1 + : m2; + smove.appear.push({ + x: m1.end.x, + y: m1.end.y, + p: selfCaptureMove.appear[0].p, + c: selfCaptureMove.vanish[0].c + }); + } + } + return smove; + } + + play(move) { + move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) + this.epSquares.push(this.getEpSquare(move)); + // Do not play on board (would reveal the move...) + this.turn = V.GetOppCol(this.turn); + this.movesCount++; + this.postPlay(move); + } + + updateCastleFlags(move) { + const firstRank = { 'w': V.size.x - 1, 'b': 0 }; + move.appear.concat(move.vanish).forEach(av => { + for (let c of ['w', 'b']) { + if (av.x == firstRank[c] && this.castleFlags[c].includes(av.y)) { + const flagIdx = (av.y == this.castleFlags[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = 8; + } + } + }); + } + + postPlay(move) { + if (this.turn == 'b') { + // NOTE: whiteMove is used read-only, so no need to copy + this.whiteMove = move; + return; + } + + // A full turn just ended: + const smove = this.resolveSynchroneMove(move); + V.PlayOnBoard(this.board, smove); + this.whiteMove = null; + + // Update king position + flags + let kingAppear = { 'w': false, 'b': false }; + for (let i=0; i { + V.PlayOnBoard(this.board, m); + m.eval = this.evalPosition(); + V.UndoOnBoard(this.board, m); + }); + moves.sort((a, b) => { + return (color == "w" ? 1 : -1) * (b.eval - a.eval); + }); + let candidates = [0]; + for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++) + candidates.push(i); + return moves[candidates[randInt(candidates.length)]]; + } }; diff --git a/client/src/variants/Twokings.js b/client/src/variants/Twokings.js index ce30d653..68910e74 100644 --- a/client/src/variants/Twokings.js +++ b/client/src/variants/Twokings.js @@ -34,7 +34,7 @@ export class TwokingsRules extends CoregalRules { } // Not scanning king positions. In this variant, scan the board everytime. - scanKings(fen) {} + scanKings() {} getCheckSquares(color) { let squares = []; diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index 484b8046..557a2df7 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -96,7 +96,7 @@ export default { }, adjustFenSize: function() { let fenInput = document.getElementById("fen"); - fenInput.style.width = (this.curFen.length+1) + "ch"; + fenInput.style.width = (this.curFen.length+3) + "ch"; }, tryGotoFen: function() { if (V.IsGoodFen(this.curFen)) { @@ -107,3 +107,9 @@ export default { } }; + +