From: Benjamin Auder Date: Fri, 28 Feb 2020 22:10:03 +0000 (+0100) Subject: Some fixes, wrote some rules, implemented Wormhole variant X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/images/%7B%7B%20path%28%27mixstore_static_policy%27%29%20%7D%7D?a=commitdiff_plain;h=d1be804633f9632b35662c0b10743ca50e10030f;p=vchess.git Some fixes, wrote some rules, implemented Wormhole variant --- diff --git a/client/public/images/pieces/Wormhole/hole.svg b/client/public/images/pieces/Wormhole/hole.svg new file mode 100644 index 00000000..4f579648 --- /dev/null +++ b/client/public/images/pieces/Wormhole/hole.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index e6fd6d73..8b49436c 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -610,7 +610,7 @@ export const ChessRules = class ChessRules { let j = y + step[1]; while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { moves.push(this.getBasicMove([x, y], [i, j])); - if (oneStep !== undefined) continue outerLoop; + if (oneStep) continue outerLoop; i += step[0]; j += step[1]; } @@ -836,12 +836,10 @@ export const ChessRules = class ChessRules { // (for engine and game end) getAllValidMoves() { const color = this.turn; - const oppCol = V.GetOppCol(color); let potentialMoves = []; for (let i = 0; i < V.size.x; i++) { for (let j = 0; j < V.size.y; j++) { - // Next condition "!= oppCol" to work with checkered variant - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + if (this.getColor(i, j) == color) { Array.prototype.push.apply( potentialMoves, this.getPotentialMovesFrom([i, j]) @@ -855,10 +853,9 @@ export const ChessRules = class ChessRules { // Stop at the first move found atLeastOneMove() { const color = this.turn; - const oppCol = V.GetOppCol(color); for (let i = 0; i < V.size.x; i++) { for (let j = 0; j < V.size.y; j++) { - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + if (this.getColor(i, j) == color) { const moves = this.getPotentialMovesFrom([i, j]); if (moves.length > 0) { for (let k = 0; k < moves.length; k++) { @@ -883,10 +880,31 @@ export const ChessRules = class ChessRules { ); } + // Generic method for non-pawn pieces ("sliding or jumping"): + // is x,y attacked by a piece of color in array 'colors' ? + isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { + rx += step[0]; + ry += step[1]; + } + if ( + V.OnBoard(rx, ry) && + this.getPiece(rx, ry) === piece && + colors.includes(this.getColor(rx, ry)) + ) { + return true; + } + } + return false; + } + // Is square x,y attacked by 'colors' pawns ? isAttackedByPawn([x, y], colors) { for (let c of colors) { - let pawnShift = c == "w" ? 1 : -1; + const pawnShift = c == "w" ? 1 : -1; if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { for (let i of [-1, 1]) { if ( @@ -945,27 +963,6 @@ export const ChessRules = class ChessRules { ); } - // Generic method for non-pawn pieces ("sliding or jumping"): - // is x,y attacked by a piece of color in array 'colors' ? - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { - for (let step of steps) { - let rx = x + step[0], - ry = y + step[1]; - while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { - rx += step[0]; - ry += step[1]; - } - if ( - V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) - ) { - return true; - } - } - return false; - } - // Is color under check after his move ? underCheck(color) { return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]); diff --git a/client/src/translations/rules/Arena/en.pug b/client/src/translations/rules/Arena/en.pug index 4f56997b..3e920140 100644 --- a/client/src/translations/rules/Arena/en.pug +++ b/client/src/translations/rules/Arena/en.pug @@ -1 +1,42 @@ -p TODO +p.boxed + | Either enter the arena, or capture something inside. + | The arena is the 4x8 rectangle in the middle of the board. + +ul + li. + In addition to its usual movement, a pawn can capture diagonally backward. + li. + The king and queen are replaced with dukes. + A duke may move up to 3 spaces in any direction. + Dukes may move into check as they please, + but if a player loses both of his dukes, he loses. + +p. + The 4x8 area in the center of the board (the area in which no pieces start) + is known as the Arena. A piece that is not currently in the Arena may only move + if it is to enter the Arena. A piece which is currently in the Arena may + only move to capture something in the Arena. + +p A player wins if: +ul + li He captures both of his opponent's Dukes. + li. + His opponent has no pieces left in the Arena at the end of any turn + other than the first. + li His opponent cannot make a move. + +figure.diagram-container + .diagram + | fen:brnkqrnb/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR: + figcaption After the moves 1.e4 Nd6 2.Bg4 Nxe4. + +p. + In the diagram situation, the g4 bishop cannot take anything in the Arena, + and thus cannot move. Note that 1...d5?? 2.exd5 1-0, because black would have + no pieces in the Arena. + +h3 Source + +p + a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") Arena chess + |  on chessvariants.com. diff --git a/client/src/translations/rules/Arena/es.pug b/client/src/translations/rules/Arena/es.pug index 4f56997b..4463981d 100644 --- a/client/src/translations/rules/Arena/es.pug +++ b/client/src/translations/rules/Arena/es.pug @@ -1 +1,44 @@ -p TODO +p.boxed + | Entra en la arena o captura algo. + | La "arena" designa el rectángulo de 4x8 en el centro del tablero. + +ul + li. + Además de su moviéndose habitual, + el peón puede capturar en diagonal hacia atrás. + li. + El rey y la reina son reemplazados por duques. + Un duque puede mover uno, dos o tres casillas en todas las direcciones. + Puede estar en jaque en cualquier momento, pero perder ambos + duques significa perder la partida. + +p. + El área de tamaño 4x8 en el centro del tablero de ajedrez (inicialmente vacía) + se llama la "arena". Una pieza fuera de la arena solo puede realizar + un movimiento entrando en él. + Una pieza que ya está en la arena solo puede moverse capturando algo en esta área. + +p Un jugador gana si: +ul + li Captura a los dos duques opuestos. + li. + Su oponente no tiene más piezas en la arena después de + cualquier turno excepto el primero. + li Su oponente ya no puede jugar. + +figure.diagram-container + .diagram + | fen:brnkqrnb/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR: + figcaption Después de los movimientos 1.e4 Nd6 2.Bg4 Nxe4. + +p. + En la situación del diagrama, el alfil en g4 no puede capturar nada en la arena, + y por lo tanto no puede moverse. Tenga en cuenta que 1...d5?? 2.exd5 1-0, + porque los negros no tendría piezas en la arena. + +h3 Fuente + +p + | La + a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") variante Arena + |  en chessvariants.com. diff --git a/client/src/translations/rules/Arena/fr.pug b/client/src/translations/rules/Arena/fr.pug index 4f56997b..4cfd2ccb 100644 --- a/client/src/translations/rules/Arena/fr.pug +++ b/client/src/translations/rules/Arena/fr.pug @@ -1 +1,45 @@ -p TODO +p.boxed + | Entrez dans l'arène, ou capturez-y quelque chose. + | L'"arène" désigne le rectangle 4x8 au centre de l'échiquier. + +ul + li. + En plus de son déplacement habituel, + le pion peut capturer en diagonale vers arrière. + li. + Le roi et la reine sont remplacés par des ducs. + Un duc peut se déplacer d'une, deux ou trois cases dans toutes les directions. + Il peut se retrouver en échec à tout moment, mais perdre ses deux + ducs signifie perdre la partie. + +p. + La zone de taille 4x8 au centre de l'échiquier (initialement vide) + s'appelle l'"arène". Une pièce hors de l'arène peut seulement effectuer + un coup y entrant. + Une pièce déjà dans l'arène ne peut se déplacer qu'en capturant + quelque chose dans cette zone. + +p Un joueur gagne si : +ul + li Il capture les deux ducs adverses. + li. + Son adversaire n'a plus de pièces dans l'arène à l'issue de + n'importe quel tour excepté le premier. + li Son adversaire ne peut plus jouer. + +figure.diagram-container + .diagram + | fen:brnkqrnb/pppppppp/8/8/8/8/PPPPPPPP/QNRBBKNR: + figcaption Après les coups 1.e4 Nd6 2.Bg4 Nxe4. + +p. + Dans la situation du diagramme, le fou en g4 ne peut rien capturer dans l'arène, + et donc ne peut pas bouger. Notez que 1...d5?? 2.exd5 1-0, car les noirs + n'auraient aucune pièce dans l'arène. + +h3 Source + +p + | La + a(href="https://www.chessvariants.com/32turn.dir/arenachess.html") variante Arena + |  sur chessvariants.com. diff --git a/client/src/translations/rules/Check3/en.pug b/client/src/translations/rules/Check3/en.pug index 4f56997b..b5013b6f 100644 --- a/client/src/translations/rules/Check3/en.pug +++ b/client/src/translations/rules/Check3/en.pug @@ -1 +1,13 @@ -p TODO +p.boxed + | Win by giving three checks (or a single checkmate). + +p. + Orthodox rules apply, but giving three checks win the game. + Since it is easier than checkmate, most games would end in this way. + +h3 Source + +p + | Old variant, playable at several locations including + a(href="https://lichess.org/variant/threeCheck") lichess + | . diff --git a/client/src/translations/rules/Check3/es.pug b/client/src/translations/rules/Check3/es.pug index 4f56997b..1c0cf266 100644 --- a/client/src/translations/rules/Check3/es.pug +++ b/client/src/translations/rules/Check3/es.pug @@ -1 +1,14 @@ -p TODO +p.boxed + | Gana dando tres jaques (o un solo jaque mate). + +p. + Se aplican las reglas ortodoxas, pero dar tres jaques gana + la partida. Es más fácil que de matar, entonces la mayoría de las + partidas deberían terminar así. + +h3 Fuente + +p + | Es una variante antigua, jugable en varios lugares, incluyendo + a(href="https://lichess.org/variant/threeCheck") lichess + | . diff --git a/client/src/translations/rules/Check3/fr.pug b/client/src/translations/rules/Check3/fr.pug index 4f56997b..1195a59f 100644 --- a/client/src/translations/rules/Check3/fr.pug +++ b/client/src/translations/rules/Check3/fr.pug @@ -1 +1,14 @@ -p TODO +p.boxed + | Gagnez en donnant trois échecs (ou un seul échec et mat). + +p. + Les règles orthodoxes s'appliquent, mais donner trois échecs fait gagner + la partie. C'est plus facile que de mater, + donc la plupart des parties devraient s'achever ainsi. + +h3 Source + +p + | C'est une vieille variante, jouable à plusieurs endroits dont + a(href="https://lichess.org/variant/threeCheck") lichess + | . diff --git a/client/src/translations/rules/Knightrelay/en.pug b/client/src/translations/rules/Knightrelay/en.pug index 4f56997b..7d698841 100644 --- a/client/src/translations/rules/Knightrelay/en.pug +++ b/client/src/translations/rules/Knightrelay/en.pug @@ -1 +1,16 @@ -p TODO +p.boxed + | Any piece guarded by a friendly knight can also move like a knight. + +p. + In addition to its normal abilities, a piece guarded by a knight can move like him. + On the following diagram, 1.Nf4 would checkmate because it guard the g6 queen. + If it was black to play, then 1...Rxe2 is possible due to the c8 knight. + +figure.diagram-container + .diagram + | fen:7k/8/6Q1/1n6/8/2r5/4N3/K7: + +h3 Source + +p + a(href="TODO") TODO diff --git a/client/src/translations/rules/Wormhole/en.pug b/client/src/translations/rules/Wormhole/en.pug index 4f56997b..b6505ea4 100644 --- a/client/src/translations/rules/Wormhole/en.pug +++ b/client/src/translations/rules/Wormhole/en.pug @@ -1 +1,51 @@ -p TODO +p.boxed + | When a piece moves, the initial square disappears. It creates a + a(href="https://en.wikipedia.org/wiki/Wormhole") "wormhole" + | . + +p. + Since all initial squares vanish, the board has exactly 64 - T squares + after T turns, so the game cannot last more than 32 moves. + Indeed a vanished square can be jumped over, but cannot be used again. + Holes are indicated with the letter 'x' on FEN strings. + +p. + In the diagram situation, the black knight can go to all the marked squares: + g5 and f6 are reachable because of the holes on f4 and e5. + Indeed the knight first moves one square vertically or horizontally, + and only then one square diagonally "in the same direction". + This is the only valid description in this variant + (others would lead to different knight movements around holes). + The black king can go to c6: + it moves to the closest non-vanished square (if any). + +figure.diagram-container + .diagram + | fen:rbkxxxbn/ppxppppx/2qxxB2/4x2p/3P1x2/3n1x2/PPPxPPPP/RBxxxNKR b2,f2,b4,c5,g5,f6: + figcaption Possible moves for the knight on d3. + +p. + No castle or en passant captures are possible. + Promotion is permitted but only by capturing. + +h3 Pieces movements + +ul + li The rook moves one or two squares vertically or horizontally. + li The bishop moves one or two squares diagonally. + li The queen moves either like a rook or like a bishop. + li. + The pawn moves like in orthodox chess, but can jumped over pieces at its + initial potential 2-squares move. + +h3 End of the game + +p Win by checkmate or stalemate: if you can no longer move, you lose. + +h3 Source + +p + a(href="https://www.chessvariants.com/32turn.dir/wormhole.html") Wormhole chess + |  on chessvariants.com. + | I changed the pieces movements because I have a better feeling with the moves + | described earlier. It might evolve. diff --git a/client/src/translations/rules/Wormhole/es.pug b/client/src/translations/rules/Wormhole/es.pug index 4f56997b..07be693a 100644 --- a/client/src/translations/rules/Wormhole/es.pug +++ b/client/src/translations/rules/Wormhole/es.pug @@ -1 +1,51 @@ -p TODO +p.boxed + | Cuando una pieza se mueve, la casilla inicial desaparece. Esto crea un + a(href="https://es.wikipedia.org/wiki/Agujero_de_gusano") "agujero de gusano" + | . + +p. + Como todas la casillas iniciales desaparecen, el tablero de ajedrez tiene exactamente + 64 - T casillas después de T turnos. La partida no puede exceder los 32 movimientos. + De hecho, puedes saltar sobre una casilla faltante, pero ya no se puede usar. + Los agujeros se indican con la letra 'x' en la cadena FEN. + +p. + En la situación del diagrama, el caballo negro puede ir a las casillas marcadas: + g5 y f6 son accesibles gracias a los agujeros en f4 y e5. + De hecho, el caballo primero se mueve de una casilla horizontalmente o + verticalmente, luego diagonalmente "en la misma dirección". + Esta es la única descripción válida en esta variante (las otras + conduciría a diferentes movimientos de caballos alrededor de los agujeros). + El rey negro puede ir a c6: se mueve hacia la casilla no desaparecida + más cercano (si lo hay). + +figure.diagram de contenedores + .diagram + | fen: rbkxxxbn / ppxppppx / 2qxxB2 / 4x2p / 3P1x2 / 3n1x2 / PPPxPPPP / RBxxxNKR b2, f2, b4, c5, g5, f6: + figcaption Posibles movimientos para el caballo en d3. + +p. + El enroque y la captura en passant no están permitidos. + La promoción es posible pero solo mediante la captura. + +h3 ¿Como las piezas se mueven? + +ul + li La torre se mueve de una o dos casillas verticalmente u horizontalmente. + li El alfil se mueve de una o dos casillas en diagonal. + li La dama puede moverse como una torre o un alfil. + li. + El peón se mueve como en el ajedrez ortodoxo, y también puede saltar + por encima de una pieza durante su potencial doble movimiento inicial. + +h3 Fin de la partida + +p Gana por mate o empate: si ya no puedes jugar, pierdes. + +h3 Fuente + + | La + a(href="https://www.chessvariants.com/32turn.dir/wormhole.html") variante Wormhole + |  en chessvariants.com. + | Cambié los movimientos de las piezas porque las descritas aquí + | Parece más adecuado. Esto podría evolucionar. diff --git a/client/src/translations/rules/Wormhole/fr.pug b/client/src/translations/rules/Wormhole/fr.pug index 4f56997b..ca8b4688 100644 --- a/client/src/translations/rules/Wormhole/fr.pug +++ b/client/src/translations/rules/Wormhole/fr.pug @@ -1 +1,53 @@ -p TODO +p.boxed + | Quand une pièce se déplace, la case initiale disparaît. Cela crée un + a(href="https://fr.wikipedia.org/wiki/Trou_de_ver") "trou de ver" + | . + +p. + Puisque toutes les cases initiales disparaissent, l'échiquier a exactement + 64 - T cases après T tours. La partie ne peut donc excéder 32 coups. + En effet on peut sauter par dessus une case disparue, mais celle-ci ne + peut plus être utilisée. + Les trous sont indiqués par la lettre 'x' sur les chaînes FEN. + +p. + Dans la situation du diagramme, le cavalier noir peut aller sur toutes les + cases marquées : g5 et f6 sont accessibles grâce aux trous en f4 et e5. + En effet le cavalier se déplace d'abord d'une case horizontalement ou + verticalement, puis d'une case en diagonale "dans la même direction". + Ceci est la seule description valide dans cette variante (les autres + mèneraient à différents coups de cavaliers aux abords des trous). + Le roi noir peut aller en c6 : il se déplace vers la case non disparue + la plus proche (s'il y en a). + +figure.diagram-container + .diagram + | fen:rbkxxxbn/ppxppppx/2qxxB2/4x2p/3P1x2/3n1x2/PPPxPPPP/RBxxxNKR b2,f2,b4,c5,g5,f6: + figcaption Coups possibles pour le cavalier en d3. + +p. + Le roque ainsi que la prise en passant ne sont pas permis. + La promotion est possible mais seulement en capturant. + +h3 Déplacement des pièces + +ul + li La tour se déplace d'une ou deux cases verticalement ou horizontalement. + li Le fous se déplace d'une ou deux cases en diagonale. + li La dame peut se mouvoir comme une tour ou un fou. + li. + Le pion se déplace comme aux échecs orthodoxes, et peut aussi sauter par + dessus une pièce lors de son potentiel double coup initial. + +h3 Fin de la partie + +p Gagnez par mat ou pat : si vous ne pouvez plus jouer, vous perdez. + +h3 Source + +p + | La + a(href="https://www.chessvariants.com/32turn.dir/wormhole.html") variante Wormhole + |  sur chessvariants.com. + | J'ai changé les déplacements des pièces car ceux décrits ici me + | paraissent mieux adaptés. Ceci pourrait évoluer. diff --git a/client/src/variants/Arena.js b/client/src/variants/Arena.js index b92be1ee..42b1f4af 100644 --- a/client/src/variants/Arena.js +++ b/client/src/variants/Arena.js @@ -47,15 +47,17 @@ export const VariantRules = class ArenaRules extends ChessRules { moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); } } - // Captures + // Captures: also possible backward for (let shiftY of [-1, 1]) { - if ( - y + shiftY >= 0 && - y + shiftY < sizeY && - this.board[x + shiftX][y + shiftY] != V.EMPTY && - this.canTake([x, y], [x + shiftX, y + shiftY]) - ) { - moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY])); + if (y + shiftY >= 0 && y + shiftY < sizeY) { + for (let direction of [-1,1]) { + if ( + this.board[x + direction][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + direction, y + shiftY]) + ) { + moves.push(this.getBasicMove([x, y], [x + direction, y + shiftY])); + } + } } } diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index e930fb84..ac99602b 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -37,7 +37,7 @@ export const VariantRules = class CheckeredRules extends ChessRules { } getPpath(b) { - return b[0] == "c" ? "Checkered/" + b : b; + return (b[0] == "c" ? "Checkered/" : "") + b; } setOtherVariables(fen) { @@ -180,6 +180,41 @@ export const VariantRules = class CheckeredRules extends ChessRules { }); } + getAllValidMoves() { + const oppCol = V.GetOppCol(this.turn); + let potentialMoves = []; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + // NOTE: just testing == color isn't enough because of checkred pieces + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + Array.prototype.push.apply( + potentialMoves, + this.getPotentialMovesFrom([i, j]) + ); + } + } + } + return this.filterValid(potentialMoves); + } + + atLeastOneMove() { + const oppCol = V.GetOppCol(this.turn); + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + // NOTE: just testing == color isn't enough because of checkred pieces + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + const moves = this.getPotentialMovesFrom([i, j]); + if (moves.length > 0) { + for (let k = 0; k < moves.length; k++) { + if (this.filterValid([moves[k]]).length > 0) return true; + } + } + } + } + } + return false; + } + isAttackedByPawn([x, y], colors) { for (let c of colors) { const color = c == "c" ? this.turn : c; @@ -245,13 +280,15 @@ export const VariantRules = class CheckeredRules extends ChessRules { evalPosition() { let evaluation = 0; - //Just count material for now, considering checkered neutral (...) + // Just count material for now, considering checkered neutral (...) for (let i = 0; i < V.size.x; i++) { for (let j = 0; j < V.size.y; j++) { if (this.board[i][j] != V.EMPTY) { const sqColor = this.getColor(i, j); - const sign = sqColor == "w" ? 1 : sqColor == "b" ? -1 : 0; - evaluation += sign * V.VALUES[this.getPiece(i, j)]; + if (["w","b"].includes(sqColor)) { + const sign = sqColor == "w" ? 1 : -1; + evaluation += sign * V.VALUES[this.getPiece(i, j)]; + } } } } diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js index 14766ff3..b04efa94 100644 --- a/client/src/variants/Wormhole.js +++ b/client/src/variants/Wormhole.js @@ -1,22 +1,6 @@ -import { ChessRules, PiPo, Move } from "@/base_rules"; -import { ArrayFun } from "@/utils/array"; -import { randInt } from "@/utils/alea"; - -// TODO: -// Short-range pieces: -// rook 1 or 2 squares orthogonal -// bishop 1 or 2 diagonal -// queen = bishop + rook -// knight: one square orthogonal + 1 diagonal (only acepted desc) -// no castle or en passant. Promotion possible only by capture (otherwise hole) +import { ChessRules } from "@/base_rules"; export const VariantRules = class WormholeRules extends ChessRules { - // TODO: redefine pieces movements, taking care of holes (auxiliary func: getSquareAfter(shiftX,shiftY)) - // this aux func could return null / undefined - // revoir getPotentialMoves et isAttacked : tout ce qui touche au board avec calcul, - // car les "board[x+..][y+..]" deviennent des board[getSquareAfter...] - // Special FEN sign for holes: 'x' - static get HasFlags() { return false; } @@ -25,24 +9,284 @@ export const VariantRules = class WormholeRules extends ChessRules { return false; } - getSquareAfter(sq, shift) { - // TODO + static get HOLE() { + return "xx"; + } + + static board2fen(b) { + if (b[0] == 'x') return 'x'; + return ChessRules.board2fen(b); + } + + static fen2board(f) { + if (f == 'x') return V.HOLE; + return ChessRules.fen2board(f); } getPpath(b) { - if (b.indexOf('x') >= 0) - return "Wormhole/hole.svg"; + if (b[0] == 'x') return "Wormhole/hole"; return b; } - // TODO: postUpdateVars: board[start] = "xx"; --> V.HOLE + getSquareAfter(square, movement) { + let shift1, shift2; + if (Array.isArray(movement[0])) { + // A knight + shift1 = movement[0]; + shift2 = movement[1]; + } else { + shift1 = movement; + shift2 = null; + } + const tryMove = (init, shift) => { + let step = [ + shift[0] / Math.abs(shift[0]) || 0, + shift[1] / Math.abs(shift[1]) || 0, + ]; + const nbSteps = Math.max(Math.abs(shift[0]), Math.abs(shift[1])); + let stepsAchieved = 0; + let sq = [init[0] + step[0], init[1] + step[1]]; + while (V.OnBoard(sq[0],sq[1])) { + if (this.board[sq[0]][sq[1]] != V.HOLE) + stepsAchieved++; + if (stepsAchieved < nbSteps) { + sq[0] += step[0]; + sq[1] += step[1]; + } + else break; + } + if (stepsAchieved < nbSteps) + // The move is impossible + return null; + return sq; + }; + // First, apply shift1 + let dest = tryMove(square, shift1); + if (dest && shift2) + // A knight: apply second shift + dest = tryMove(dest, shift2); + return dest; + } + + // NOTE (TODO?): some extra work done in some function because informations + // on one step should ease the computation for a step in the same direction. + static get steps() { + return { + r: [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1], + [-2, 0], + [2, 0], + [0, -2], + [0, 2] + ], + // Decompose knight movements into one step orthogonal + one diagonal + n: [ + [[0, -1], [-1, -1]], + [[0, -1], [1, -1]], + [[-1, 0], [-1,-1]], + [[-1, 0], [-1, 1]], + [[0, 1], [-1, 1]], + [[0, 1], [1, 1]], + [[1, 0], [1, -1]], + [[1, 0], [1, 1]] + ], + b: [ + [-1, -1], + [-1, 1], + [1, -1], + [1, 1], + [-2, -2], + [-2, 2], + [2, -2], + [2, 2] + ], + k: [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1], + [-1, -1], + [-1, 1], + [1, -1], + [1, 1] + ] + }; + } + + getJumpMoves([x, y], steps) { + let moves = []; + for (let step of steps) { + const sq = this.getSquareAfter([x,y], step); + if (sq && + ( + this.board[sq[0]][sq[1]] == V.EMPTY || + this.canTake([x, y], sq) + ) + ) { + moves.push(this.getBasicMove([x, y], sq)); + } + } + return moves; + } + + // What are the pawn moves from square x,y ? + getPotentialPawnMoves([x, y]) { + const color = this.turn; + let moves = []; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + + const sq1 = this.getSquareAfter([x,y], [shiftX,0]); + if (sq1 && this.board[sq1[0]][y] == V.EMPTY) { + // One square forward (cannot be a promotion) + moves.push(this.getBasicMove([x, y], [sq1[0], y])); + if (x == startRank) { + // If two squares after is available, then move is possible + const sq2 = this.getSquareAfter([x,y], [2*shiftX,0]); + if (sq2 && this.board[sq2[0]][y] == V.EMPTY) + // Two squares jump + moves.push(this.getBasicMove([x, y], [sq2[0], y])); + } + } + // Captures + const finalPieces = x + shiftX == lastRank + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.PAWN]; + for (let shiftY of [-1, 1]) { + const sq = this.getSquareAfter([x,y], [shiftX,shiftY]); + if ( + sq && + this.board[sq[0]][sq[1]] != V.EMPTY && + this.canTake([x, y], [sq[0], sq[1]]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [sq[0], sq[1]], { + c: color, + p: piece + }) + ); + } + } + } + + return moves; + } + + getPotentialRookMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.ROOK]); + } + + getPotentialKnightMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.KNIGHT]); + } + + getPotentialBishopMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.BISHOP]); + } + + getPotentialQueenMoves(sq) { + return this.getJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); + } + + getPotentialKingMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.KING]); + } + + isAttackedByJump([x, y], colors, piece, steps) { + for (let step of steps) { + const sq = this.getSquareAfter([x,y], step); + if ( + sq && + this.getPiece(sq[0], sq[1]) === piece && + colors.includes(this.getColor(sq[0], sq[1])) + ) { + return true; + } + } + return false; + } + + isAttackedByPawn([x, y], colors) { + for (let c of colors) { + const pawnShift = c == "w" ? 1 : -1; + for (let i of [-1, 1]) { + const sq = this.getSquareAfter([x,y], [pawnShift,i]); + if ( + sq && + this.getPiece(sq[0], sq[1]) == V.PAWN && + this.getColor(sq[0], sq[1]) == c + ) { + return true; + } + } + } + return false; + } + + isAttackedByRook(sq, colors) { + return this.isAttackedByJump(sq, colors, V.ROOK, V.steps[V.ROOK]); + } + + isAttackedByKnight(sq, colors) { + // NOTE: knight attack is not symmetric in this variant: + // steps order need to be reversed. + return this.isAttackedByJump( + sq, + colors, + V.KNIGHT, + V.steps[V.KNIGHT].map(s => s.reverse()) + ); + } + + isAttackedByBishop(sq, colors) { + return this.isAttackedByJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]); + } + + isAttackedByQueen(sq, colors) { + return this.isAttackedByJump( + sq, + colors, + V.QUEEN, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); + } + + isAttackedByKing(sq, colors) { + return this.isAttackedByJump(sq, colors, V.KING, V.steps[V.KING]); + } + + static PlayOnBoard(board, move) { + board[move.vanish[0].x][move.vanish[0].y] = V.HOLE; + for (let psq of move.appear) board[psq.x][psq.y] = psq.c + psq.p; + } - updateVariables(move) { - super.updateVariables(move); + getCurrentScore() { + if (this.atLeastOneMove()) + return "*"; + // No valid move: I lose + return this.turn == "w" ? "0-1" : "1-0"; } - unupdateVariables(move) { - super.unupdateVariables(move); + evalPosition() { + let evaluation = 0; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (![V.EMPTY,V.HOLE].includes(this.board[i][j])) { + const sign = this.getColor(i, j) == "w" ? 1 : -1; + evaluation += sign * V.VALUES[this.getPiece(i, j)]; + } + } + } + return evaluation; } getNotation(move) { diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index f93b5b3a..2da8d9e0 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -294,7 +294,11 @@ export default { break; case "fullgame": // Callback "roomInit" to poll clients only after game is loaded - this.loadGame(data.data, this.roomInit); + let game = data.data; + // Move format isn't the same in storage and in browser, + // because of the 'addTime' field. + game.moves = game.moves.map(m => { return m.move || m; }); + this.loadGame(game, this.roomInit); break; case "asklastate": // Sending last state if I played a move or score != "*"