From: Benjamin Auder Date: Sun, 17 Jan 2021 17:17:51 +0000 (+0100) Subject: Add Konane, (very) early drafts of Emergo/Fanorona/Yote/Gomoku, fix repetitions detec... X-Git-Url: https://git.auder.net/doc/html/pieces/cp.svg?a=commitdiff_plain;h=d2af3400944331ffd0c770f83857257c2f48e487;p=vchess.git Add Konane, (very) early drafts of Emergo/Fanorona/Yote/Gomoku, fix repetitions detection in Otage/Pacosako --- diff --git a/client/public/images/pieces/Konane/SOURCE b/client/public/images/pieces/Konane/SOURCE new file mode 100644 index 00000000..df8ca5f5 --- /dev/null +++ b/client/public/images/pieces/Konane/SOURCE @@ -0,0 +1,3 @@ +https://commons.wikimedia.org/wiki/File:Go_w.svg +https://commons.wikimedia.org/wiki/File:Go_b.svg +(EDITED: remove yellow background) diff --git a/client/public/images/pieces/Konane/bp.svg b/client/public/images/pieces/Konane/bp.svg new file mode 100644 index 00000000..75e907e0 --- /dev/null +++ b/client/public/images/pieces/Konane/bp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/public/images/pieces/Konane/wp.svg b/client/public/images/pieces/Konane/wp.svg new file mode 100644 index 00000000..357079eb --- /dev/null +++ b/client/public/images/pieces/Konane/wp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index a9b6a6a1..05491398 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -136,6 +136,11 @@ export const ChessRules = class ChessRules { return false; } + // At some stages, some games could wait clicks only: + onlyClick() { + return false; + } + // Some variants use click infos: doClick() { return null; diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index b8d0fadd..cc3f6fd7 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -659,8 +659,9 @@ export default { // Emit the click event which could be used by some variants const targetId = (withPiece ? e.target.parentNode.id : e.target.id); - this.$emit("click-square", getSquareFromId(targetId)); - if (withPiece) { + const sq = getSquareFromId(targetId); + this.$emit("click-square", sq); + if (withPiece && !this.vr.onlyClick(sq)) { this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare); // For potential drag'n drop, remember start coordinates // (to center the piece on mouse cursor) diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 90a4fa15..06fadc9c 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -170,6 +170,8 @@ export const translations = { "A quantum story": "A quantum story", "A wizard in the corner": "A wizard in the corner", "Absorb powers": "Absorb powers", + "African Draughts": "African Draughts", + "Align five stones": "Align five stones", "All of the same color": "All of the same color", "Ancient rules": "Ancient rules", "As in the movie": "As in the movie", @@ -224,6 +226,7 @@ export const translations = { "Get strong at self-mate": "Get strong at self-mate", "Give three checks": "Give three checks", "Harassed kings": "Harassed kings", + "Hawaiian Checkers": "Hawaiian Checkers", "Japanese Chess": "Japanese Chess", "Jump the borders": "Jump the borders", "Keep antiking in check (v1)": "Keep antiking in check (v1)", @@ -242,7 +245,7 @@ export const translations = { "Long jumps over pieces": "Long jumps over pieces", "Long live the Queen": "Long live the Queen", "Lose all pieces": "Lose all pieces", - "Rearrange enemy pieces": "Rearrange enemy pieces", + "Malagasy Draughts": "Malagasy Draughts", "Mandatory captures": "Mandatory captures", "Mate any piece (v1)": "Mate any piece (v1)", "Mate any piece (v2)": "Mate any piece (v2)", @@ -282,6 +285,7 @@ export const translations = { "Queen versus pawns": "Queen versus pawns", "Reach the last rank (v1)": "Reach the last rank (v1)", "Reach the last rank (v2)": "Reach the last rank (v2)", + "Rearrange enemy pieces": "Rearrange enemy pieces", "Replace pieces": "Replace pieces", "Reposition pieces": "Reposition pieces", "Reuse pieces": "Reuse pieces", @@ -300,6 +304,7 @@ export const translations = { "Squares disappear": "Squares disappear", "Squat last rank (v1)": "Squat last rank (v1)", "Squat last rank (v2)": "Squat last rank (v2)", + "Stacking Checkers variant": "Stacking Checkers variant", "Standard rules": "Standard rules", "Stun & kick pieces": "Stun & kick pieces", "Thai Chess (v1)": "Thai Chess (v1)", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 650aea92..cab69cf2 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -170,6 +170,8 @@ export const translations = { "A quantum story": "Una historia cuántica", "A wizard in the corner": "Un mago en la esquina", "Absorb powers": "Absorber poderes", + "African Draughts": "Damas africanas", + "Align five stones": "Alinea cinco piedras", "All of the same color": "Todo el mismo color", "Ancient rules": "Viejas reglas", "As in the movie": "Como en la pelicula", @@ -224,6 +226,7 @@ export const translations = { "Get strong at self-mate": "Progreso en mates asistidos", "Give three checks": "Dar tres jaques", "Harassed kings": "Reyes acosados", + "Hawaiian Checkers": "Damas hawaianas", "Japanese Chess": "Ajedrez japonés", "Jump the borders": "Saltar las fronteras", "Keep antiking in check (v1)": "Mantener el antirey en jaque (v1)", @@ -242,7 +245,7 @@ export const translations = { "Long jumps over pieces": "Saltos largos sobre las piezas", "Long live the Queen": "Larga vida a la reina", "Lose all pieces": "Perder todas las piezas", - "Rearrange enemy pieces": "Reorganizar piezas opuestas", + "Malagasy Draughts": "Damas malgaches", "Mandatory captures": "Capturas obligatorias", "Mate any piece (v1)": "Matar cualquier pieza (v1)", "Mate any piece (v2)": "Matar cualquier pieza (v2)", @@ -282,6 +285,7 @@ export const translations = { "Queen versus pawns": "Dama contra peones", "Reach the last rank (v1)": "Llegar a la última fila (v1)", "Reach the last rank (v2)": "Llegar a la última fila (v2)", + "Rearrange enemy pieces": "Reorganizar piezas opuestas", "Replace pieces": "Reemplazar piezas", "Reposition pieces": "Reposicionar las piezas", "Reuse pieces": "Reutilizar piezas", @@ -300,6 +304,7 @@ export const translations = { "Squares disappear": "Las casillas desaparecen", "Squat last rank (v1)": "Ocupa la última fila (v1)", "Squat last rank (v2)": "Ocupa la última fila (v2)", + "Stacking Checkers variant": "Variante de damas con pilas", "Standard rules": "Reglas estandar", "Stun & kick pieces": "Aturdir & patear piezas", "Thai Chess (v1)": "Ajedrez tailandés (v1)", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 5a04fab5..ba812a46 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -170,6 +170,8 @@ export const translations = { "A quantum story": "Une histoire quantique", "A wizard in the corner": "Un sorcier dans le coin", "Absorb powers": "Absorber les pouvoirs", + "African Draughts": "Dames africaines", + "Align five stones": "Alignez cinq pierres", "All of the same color": "Tout de la même couleur", "Ancient rules": "Règles anciennes", "As in the movie": "Comme dans le film", @@ -224,6 +226,7 @@ export const translations = { "Get strong at self-mate": "Progressez en mats aidés", "Give three checks": "Donnez trois échecs", "Harassed kings": "Rois harcelés", + "Hawaiian Checkers": "Dames hawaïennes", "Japanese Chess": "Échecs japonais", "Jump the borders": "Sauter les frontières", "Keep antiking in check (v1)": "Gardez l'antiroi en échec (v1)", @@ -242,7 +245,7 @@ export const translations = { "Long jumps over pieces": "Sauts longs par dessus les pièces", "Long live the Queen": "Long vie à la Reine", "Lose all pieces": "Perdez toutes les pièces", - "Rearrange enemy pieces": "Réorganisez les pièces adverses", + "Malagasy Draughts": "Dames malgaches", "Mandatory captures": "Captures obligatoires", "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)", "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)", @@ -282,6 +285,7 @@ export const translations = { "Queen versus pawns": "Dame contre pions", "Reach the last rank (v1)": "Atteignez la dernière rangée (v1)", "Reach the last rank (v2)": "Atteignez la dernière rangée (v2)", + "Rearrange enemy pieces": "Réorganisez les pièces adverses", "Replace pieces": "Remplacer les pièces", "Reposition pieces": "Replacer les pièces", "Reuse pieces": "Réutiliser les pièces", @@ -300,6 +304,7 @@ export const translations = { "Squares disappear": "Les cases disparaissent", "Squat last rank (v1)": "Occupez la dernière rangée (v1)", "Squat last rank (v2)": "Occupez la dernière rangée (v2)", + "Stacking Checkers variant": "Variante des Dames avec empilements", "Standard rules": "Règles usuelles", "Stun & kick pieces": "Étourdissez & frappez les pièces", "Thai Chess (v1)": "Échecs thai (v1)", diff --git a/client/src/translations/rules/Emergo/en.pug b/client/src/translations/rules/Emergo/en.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Emergo/en.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Emergo/es.pug b/client/src/translations/rules/Emergo/es.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Emergo/es.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Emergo/fr.pug b/client/src/translations/rules/Emergo/fr.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Emergo/fr.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Fanorona/en.pug b/client/src/translations/rules/Fanorona/en.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Fanorona/en.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Fanorona/es.pug b/client/src/translations/rules/Fanorona/es.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Fanorona/es.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Fanorona/fr.pug b/client/src/translations/rules/Fanorona/fr.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Fanorona/fr.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Gomoku/en.pug b/client/src/translations/rules/Gomoku/en.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Gomoku/en.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Gomoku/es.pug b/client/src/translations/rules/Gomoku/es.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Gomoku/es.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Gomoku/fr.pug b/client/src/translations/rules/Gomoku/fr.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Gomoku/fr.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Konane/en.pug b/client/src/translations/rules/Konane/en.pug new file mode 100644 index 00000000..9a15f67a --- /dev/null +++ b/client/src/translations/rules/Konane/en.pug @@ -0,0 +1,49 @@ +p.boxed + | Capture orthogonally at each turn, "as in Draughts". + | If you cannot capture, you lose. + +p. + To initiate the game, the first player (black) must remove one of his stones + either in the upper left or lower right corner, or in the center, + as marked on the illustration. + The second player (white) then removes a stone orthogonally adjacent to + the first removed one, and the game starts. + To remove a stone at this stage, click it. + +figure.diagram-container + .diagram + | fen:PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP a8,d5,e4,h1: + figcaption Allowed first "moves" (stone removal). + +p. + Every move then is necessary a capture of at least one enemy piece. + Capture by jumping orthogonally over an adjacent stone to land right after + on an empty square. The intermediate piece is thus removed. + You may continue capturing, but only in the same direction, + with the same stone. + To stop a chain of captures (while more are available), play a move from the + current capturer until the location of the last captured stone: + g4 on this example. + +figure.diagram-container + .diagram.diag12 + | fen:PpPpPpPp/pPpPpPpP/PpPpP2p/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP: + .diagram.diag22 + | fen:PpPpPp1p/pPpPpP1P/PpPpP1Pp/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP g4,g7: + figcaption. + Before and after a first capture g8xg7. + The available continuations are indicated. + +p If a player has no possible capture, he loses. + +h3 More information + +p + | See for example + a(href="https://hawaiiancheckers.com/") hawaiiancheckers.com + | , and an + a(href="https://www.youtube.com/watch?app=desktop&v=-_y-B2wAwyw") + | example game + | . Konane is also playable on + a(href="https://brainking.com/en/GameRules?tp=94") brainking.com + | . diff --git a/client/src/translations/rules/Konane/es.pug b/client/src/translations/rules/Konane/es.pug new file mode 100644 index 00000000..7cb7f73f --- /dev/null +++ b/client/src/translations/rules/Konane/es.pug @@ -0,0 +1,49 @@ +p.boxed + | Captura ortogonalmente en cada turno, "como a las Damas". + | Si no es posible la captura, ha perdido. + +p. + Para iniciar el juego, el primer jugador (negras) debe eliminar uno de + sus piedras en la esquina superior izquierda o inferior derecha, + ya sea en el centro, como se ilustra. + El segundo jugador (blancas) luego quita una piedra ortogonalmente + adyacente al primero eliminado, y comienza el juego. + Para quitar una piedra, haga clic en ella. + +figure.diagram-container + .diagram + | fen:PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP a8,d5,e4,h1: + figcaption Los primeros "movimientos" autorizado (eliminación de piedras). + +p. + Cada movimiento implica necesariamente la captura de al menos una pieza + enemiga. Captura saltando ortogonalmente sobre un obstáculo para aterrizar + en un cuadrado vacío justo detrás. Se quita así la pieza intermedia. + Tienes derecho a seguir capturando, pero solo en el mismo + dirección, con la misma piedra. + Para detener una cadena de capturas (mientras que otras son posibles), + hacer un movimiento de la captura actual a la ubicación de la última + pieza capturada: g4 en el ejemplo. + +figure.diagram-container + .diagram.diag12 + | fen:PpPpPpPp/pPpPpPpP/PpPpP2p/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP: + .diagram.diag22 + | fen:PpPpPp1p/pPpPpP1P/PpPpP1Pp/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP g4,g7: + figcaption. + Antes y después de una primera captura g8xg7. + Se indican las continuaciones disponibles. + +p Si un jugador no tiene más capturas disponibles, pierde. + +h3 Más información + +p + | Ver por ejemplo + a(href="https://hawaiiancheckers.com/") hawaiiancheckers.com + | , y una + a(href="https://www.youtube.com/watch?app=desktop&v=-_y-B2wAwyw") + | partida ejemplo + | . Konane también se puede jugar en + a(href="https://brainking.com/en/GameRules?tp=94") brainking.com + | . diff --git a/client/src/translations/rules/Konane/fr.pug b/client/src/translations/rules/Konane/fr.pug new file mode 100644 index 00000000..23297370 --- /dev/null +++ b/client/src/translations/rules/Konane/fr.pug @@ -0,0 +1,49 @@ +p.boxed + | Capturez orthogonalement à chaque tour, "comme aux Dames". + | Si aucune capture n'est possible, vous avez perdu. + +p. + Pour initialiser la partie, le premier joueur (noirs) doit retirer une de + ses pierres soit dans le coin supérieur gauche ou inférieur droit, + soit au centre, comme illustré. + Le second joueur (blancs) retire ensuite une pierre orthogonalement + adjacente à la première supprimée, et la partie commence. + Pour enlever une pierre, cliquez dessus. + +figure.diagram-container + .diagram + | fen:PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP a8,d5,e4,h1: + figcaption Premiers "coups" autorisés (suppression de pierre). + +p. + Chaque coup comporte nécessairement au moins une capture de pièce ennemie. + Capturez en sautant orthogonalement par dessus un obstacle pour atterrir + sur une case vide juste derrière. La pièce intermédiaire est ainsi retirée. + Vous avez le droit de continuer de capturer, mais seulement dans la même + direction, avec la même pierre. + Pour arrêter une chaîne de captures (alors que d'autres sont possibles), + jouez un coup depuis le capturant actuel vers l'emplacement de la dernière + pièce capturée : g4 sur l'exemple. + +figure.diagram-container + .diagram.diag12 + | fen:PpPpPpPp/pPpPpPpP/PpPpP2p/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP: + .diagram.diag22 + | fen:PpPpPp1p/pPpPpP1P/PpPpP1Pp/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP g4,g7: + figcaption. + Avant et après une première capture g8xg7. + Les continuations disponibles sont indiquées. + +p Si un joueur n'a plus de captures à disposition, il perd. + +h3 Plus d'informations + +p + | Voir par exemple + a(href="https://hawaiiancheckers.com/") hawaiiancheckers.com + | , et une + a(href="https://www.youtube.com/watch?app=desktop&v=-_y-B2wAwyw") + | partie exemple + | . Konane est aussi jouable sur + a(href="https://brainking.com/en/GameRules?tp=94") brainking.com + | . diff --git a/client/src/translations/rules/Yote/en.pug b/client/src/translations/rules/Yote/en.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Yote/en.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Yote/es.pug b/client/src/translations/rules/Yote/es.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Yote/es.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/rules/Yote/fr.pug b/client/src/translations/rules/Yote/fr.pug new file mode 100644 index 00000000..3a33838b --- /dev/null +++ b/client/src/translations/rules/Yote/fr.pug @@ -0,0 +1,2 @@ +p.boxed + | TODO diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 542c2d48..552652ff 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -433,6 +433,21 @@ ul for v in varlist li #[a(href="/#/variants/"+v) #{v}] +h3 Non-chess + +p Some games not chess related. +- + var varlist = [ + "Emergo", + "Fanorona", + "Gomoku", + "Konane", + "Yote" + ] +ul + for v in varlist + li #[a(href="/#/variants/"+v) #{v}] + h3 Miscelleanous p. diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 09fd1169..57ba50e8 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -443,6 +443,21 @@ ul for v in varlist li #[a(href="/#/variants/"+v) #{v}] +h3 Aparte del Ajedrez + +p Algunos juegos no están relacionados con el ajedrez. +- + var varlist = [ + "Emergo", + "Fanorona", + "Gomoku", + "Konane", + "Yote" + ] +ul + for v in varlist + li #[a(href="/#/variants/"+v) #{v}] + h3 Varios p. diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 144ad2df..cebc6221 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -441,6 +441,21 @@ ul for v in varlist li #[a(href="/#/variants/"+v) #{v}] +h3 Hors Échecs + +p Quelques jeux non connectés aux échecs. +- + var varlist = [ + "Emergo", + "Fanorona", + "Gomoku", + "Konane", + "Yote" + ] +ul + for v in varlist + li #[a(href="/#/variants/"+v) #{v}] + h3 Divers p. diff --git a/client/src/variants/Bario.js b/client/src/variants/Bario.js index e895aeef..21965e16 100644 --- a/client/src/variants/Bario.js +++ b/client/src/variants/Bario.js @@ -46,6 +46,14 @@ export class BarioRules extends ChessRules { ); } + onlyClick([x, y]) { + return ( + this.movesCount <= 1 || + // TODO: next line theoretically shouldn't be required... + (this.movesCount == 2 && this.getColor(x, y) != this.turn) + ); + } + // Initiate the game by choosing a square for the king: doClick(square) { const c = this.turn; @@ -62,7 +70,9 @@ export class BarioRules extends ChessRules { appear: [ new PiPo({ x: square[0], y: square[1], c: c, p: V.KING }) ], - vanish: [], + vanish: [ + new PiPo({ x: square[0], y: square[1], c: c, p: V.UNDEFINED }) + ], start: { x: -1, y: -1 }, }); } @@ -138,7 +148,7 @@ export class BarioRules extends ChessRules { } static GenRandInitFen() { - return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -"; + return "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU w 0 - 22212221 -"; } setOtherVariables(fen) { @@ -259,7 +269,9 @@ export class BarioRules extends ChessRules { appear: [ new PiPo({ x: firstRank, y: j, c: color, p: V.KING }) ], - vanish: [], + vanish: [ + new PiPo({ x: firstRank, y: j, c: color, p: V.UNDEFINED }) + ], start: { x: -1, y: -1 } }); }); @@ -379,6 +391,11 @@ export class BarioRules extends ChessRules { return false; } + getCheckSquares() { + if (this.movesCount <= 2) return []; + return super.getCheckSquares(); + } + play(move) { move.turn = [this.turn, this.subTurn]; //easier undo (TODO?) const toNextPlayer = () => { @@ -389,40 +406,33 @@ export class BarioRules extends ChessRules { this.movesCount++; this.postPlay(move); }; - if (move.vanish.length == 0) { - if (move.appear.length == 1) toNextPlayer(); - else { - // Removal (subTurn == 0 --> 1) - this.reserve[this.turn][move.start.p]--; - this.subTurn++; - } - return; - } - const start = { x: move.vanish[0].x, y: move.vanish[0].y }; - const end = { x: move.appear[0].x, y: move.appear[0].y }; - if (start.x == end.x && start.y == end.y) { - // Specialisation (subTurn == 1 before 2) - this.reserve[this.turn][move.appear[0].p]--; - V.PlayOnBoard(this.board, move); - this.definitions.push(move.end); + if (this.movesCount <= 1) toNextPlayer(); + else if (move.vanish.length == 0) { + // Removal (subTurn == 0 --> 1) + this.reserve[this.turn][move.start.p]--; this.subTurn++; } else { - // Normal move (subTurn 1 or 2: change turn) - this.epSquares.push(this.getEpSquare(move)); - toNextPlayer(); + const start = { x: move.vanish[0].x, y: move.vanish[0].y }; + const end = { x: move.appear[0].x, y: move.appear[0].y }; + if (start.x == end.x && start.y == end.y) { + // Specialisation (subTurn == 1 before 2) + this.reserve[this.turn][move.appear[0].p]--; + V.PlayOnBoard(this.board, move); + this.definitions.push(move.end); + this.subTurn++; + } + else { + // Normal move (subTurn 1 or 2: change turn) + this.epSquares.push(this.getEpSquare(move)); + toNextPlayer(); + } } } postPlay(move) { const color = V.GetOppCol(this.turn); - if (move.vanish.length == 0) { - this.kingPos[color] = [move.end.x, move.end.y]; - const firstRank = (color == 'w' ? 7 : 0); - for (let j = 0; j < 8; j++) { - if (j != move.end.y) this.board[firstRank][j] = color + V.UNDEFINED; - } - } + if (this.movesCount <= 2) this.kingPos[color] = [move.end.x, move.end.y]; else { if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED) this.captureUndefined.push(move.end); @@ -509,35 +519,30 @@ export class BarioRules extends ChessRules { this.movesCount--; this.postUndo(move); }; - if (move.vanish.length == 0) { - if (move.appear.length == 1) toPrevPlayer(); - else { - this.reserve[this.turn][move.start.p]++; - this.subTurn = move.turn[1]; - } - return; - } - const start = { x: move.vanish[0].x, y: move.vanish[0].y }; - const end = { x: move.appear[0].x, y: move.appear[0].y }; - if (start.x == end.x && start.y == end.y) { - this.reserve[this.turn][move.appear[0].p]++; - V.UndoOnBoard(this.board, move); - this.definitions.pop(); + if (this.movesCount <= 2) toPrevPlayer(); + else if (move.vanish.length == 0) { + this.reserve[this.turn][move.start.p]++; this.subTurn = move.turn[1]; } else { - this.epSquares.pop(); - toPrevPlayer(); + const start = { x: move.vanish[0].x, y: move.vanish[0].y }; + const end = { x: move.appear[0].x, y: move.appear[0].y }; + if (start.x == end.x && start.y == end.y) { + this.reserve[this.turn][move.appear[0].p]++; + V.UndoOnBoard(this.board, move); + this.definitions.pop(); + this.subTurn = move.turn[1]; + } + else { + this.epSquares.pop(); + toPrevPlayer(); + } } } postUndo(move) { const color = this.turn; - if (move.vanish.length == 0) { - this.kingPos[color] = [-1, -1]; - const firstRank = (color == 'w' ? 7 : 0); - for (let j = 0; j < 8; j++) this.board[firstRank][j] = ""; - } + if (this.movesCount <= 1) this.kingPos[color] = [-1, -1]; else { this.captureUndefined.pop(); if (move.appear[0].p == V.KING) super.postUndo(move); diff --git a/client/src/variants/Emergo.js b/client/src/variants/Emergo.js new file mode 100644 index 00000000..0d81759c --- /dev/null +++ b/client/src/variants/Emergo.js @@ -0,0 +1,7 @@ +import { ChessRules } from "@/base_rules"; + +export class YoteRules extends ChessRules { + + // TODO + +}; diff --git a/client/src/variants/Fanorona.js b/client/src/variants/Fanorona.js new file mode 100644 index 00000000..9f1db3a1 --- /dev/null +++ b/client/src/variants/Fanorona.js @@ -0,0 +1,7 @@ +import { ChessRules } from "@/base_rules"; + +export class FanoronaRules extends ChessRules { + + // TODO + +}; diff --git a/client/src/variants/Gomoku.js b/client/src/variants/Gomoku.js new file mode 100644 index 00000000..5bf971a8 --- /dev/null +++ b/client/src/variants/Gomoku.js @@ -0,0 +1,7 @@ +import { ChessRules } from "@/base_rules"; + +export class GomokuRules extends ChessRules { + + // TODO + +}; diff --git a/client/src/variants/Konane.js b/client/src/variants/Konane.js new file mode 100644 index 00000000..8f285efc --- /dev/null +++ b/client/src/variants/Konane.js @@ -0,0 +1,206 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; + +// TODO: Maybe more flexible end of game messages (V.ColorsReversed ?!) + +export class KonaneRules extends ChessRules { + + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get PIECES() { + return V.PAWN; + } + + getPpath(b) { + return "Konane/" + b; + } + + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num) || num <= 0) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + return true; + } + + static GenRandInitFen() { + return ( + "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/" + + "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP w 0" + ); + } + + setOtherVariables(fen) { + this.captures = []; //reinit for each move + } + + hoverHighlight(x, y) { + if (this.movesCount >= 2) return false; + const c = this.turn; + if (c == 'w') return (x == y && [0, 3, 4, 7].includes(x)); + // "Black": search for empty square and allow nearby + for (let i of [0, 3, 4, 7]) { + if (this.board[i][i] == V.EMPTY) + return (Math.abs(x - i) + Math.abs(y - i) == 1) + } + } + + onlyClick([x, y]) { + return ( + this.movesCount <= 1 || + // TODO: next line theoretically shouldn't be required... + (this.movesCount == 2 && this.getColor(x, y) != this.turn) + ); + } + + doClick([x, y]) { + if (this.movesCount >= 2) return null; + const color = this.turn; + if (color == 'w') { + if (x != y || ![0, 3, 4, 7].includes(x)) return null; + return new Move({ + appear: [], + vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ], + end: { x: x, y: y } + }); + } + // "Black": search for empty square and allow nearby + for (let i of [0, 3, 4, 7]) { + if (this.board[i][i] == V.EMPTY) { + if (Math.abs(x - i) + Math.abs(y - i) != 1) return null; + return new Move({ + appear: [], + vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ], + end: { x: x, y: y } + }); + } + } + } + + getPotentialMovesFrom([x, y]) { + if (this.movesCount <= 1) { + const mv = this.doClick([x, y]); + return (!!mv ? [mv] : []); + } + const L = this.captures.length; + const c = (L > 0 ? this.captures[L-1] : null); + const color = this.turn; + const oppCol = V.GetOppCol(color); + let step = null; + let moves = []; + if (!!c) { + if (x != c.end.x || y != c.end.y) return []; + step = [(c.end.x - c.start.x) / 2, (c.end.y - c.start.y) / 2]; + // Add move to adjacent empty square to mark "end of capture" + moves.push( + new Move({ + appear: [], + vanish: [], + start: { x: x, y: y }, + end: { x: x - step[0], y: y - step[1] } + }) + ); + } + // Examine captures from here + for (let s of (!!step ? [step] : V.steps[V.ROOK])) { + let [i, j] = [x + 2*s[0], y + 2*s[1]]; + if ( + !!c || //avoid redundant checks if continuation + ( + V.OnBoard(i, j) && + this.board[i][j] == V.EMPTY && + this.board[i - s[0]][j - s[1]] != V.EMPTY && + this.getColor(i - s[0], j - s[1]) == oppCol + ) + ) { + let mv = new Move({ + appear: [ + new PiPo({ x: i, y: j, c: color, p: V.PAWN }) + ], + vanish: [ + new PiPo({ x: x, y: y, c: color, p: V.PAWN }), + new PiPo({ x: i - s[0], y: j - s[1], c: oppCol, p: V.PAWN }) + ] + }); + // Is there another capture possible then? + [i, j] = [i + 2*s[0], j + 2*s[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] == V.EMPTY && + this.board[i - s[0]][j - s[1]] != V.EMPTY && + this.getColor(i - s[0], j - s[1]) == oppCol + ) { + mv.end.moreCapture = true; + } + moves.push(mv); + } + } + return moves; + } + + filterValid(moves) { + return moves; + } + + getCheckSquares() { + return []; + } + + getCurrentScore() { + if (this.atLeastOneMove()) return "*"; + return (this.turn == "w" ? "0-1" : "1-0"); + } + + play(move) { + V.PlayOnBoard(this.board, move); + if (!move.end.moreCapture) { + this.turn = V.GetOppCol(this.turn); + this.movesCount++; + this.captures = []; + } + else { + this.captures.push( + { + start: move.start, + end: { x: move.end.x, y: move.end.y } + } + ); + } + } + + undo(move) { + V.UndoOnBoard(this.board, move); + if (!move.end.moreCapture) { + this.turn = V.GetOppCol(this.turn); + this.movesCount--; + } + else this.captures.pop(); + } + + static get SEARCH_DEPTH() { + return 4; + } + + getNotation(move) { + if (this.movesCount <= 1) return V.CoordsToSquare(move.start) + "X"; + if (move.vanish.length == 0) return "end"; + return V.CoordsToSquare(move.start) + "x" + V.CoordsToSquare(move.end); + } + +}; diff --git a/client/src/variants/Otage.js b/client/src/variants/Otage.js index 6f408bb1..742c6ee1 100644 --- a/client/src/variants/Otage.js +++ b/client/src/variants/Otage.js @@ -646,11 +646,19 @@ export class OtageRules extends ChessRules { if (!m.end.released) return true; // Check for repetitions: V.PlayOnBoard(this.board, m); - const newState = { piece: m.end.released, position: this.getBaseFen() }; + const newState = { + piece: m.end.released, + square: { x: m.end.x, y: m.end.y }, + position: this.getBaseFen() + }; const repet = this.repetitions.some(r => { return ( r.piece == newState.piece && + ( + r.square.x == newState.square.x && + r.square.y == newState.square.y && + ) && r.position == newState.position ); }); @@ -724,6 +732,7 @@ export class OtageRules extends ChessRules { this.repetitions.push( { piece: move.end.released, + square: { x: move.end.x, y: move.end.y }, position: this.getBaseFen() } ); diff --git a/client/src/variants/Pacosako.js b/client/src/variants/Pacosako.js index d4908a5a..d219916d 100644 --- a/client/src/variants/Pacosako.js +++ b/client/src/variants/Pacosako.js @@ -716,11 +716,19 @@ export class PacosakoRules extends ChessRules { if (!m.end.released) return true; // Check for repetitions: V.PlayOnBoard(this.board, m); - const newState = { piece: m.end.released, position: this.getBaseFen() }; + const newState = { + piece: m.end.released, + square: { x: m.end.x, y: m.end.y }, + position: this.getBaseFen() + }; const repet = this.repetitions.some(r => { return ( r.piece == newState.piece && + ( + r.square.x == newState.square.x && + r.square.y == newState.square.y && + ) && r.position == newState.position ); }); @@ -801,6 +809,7 @@ export class PacosakoRules extends ChessRules { this.repetitions.push( { piece: move.end.released, + square: { x: move.end.x, y: move.end.y }, position: this.getBaseFen() } ); diff --git a/client/src/variants/Selfabsorption.js b/client/src/variants/Selfabsorption.js index e7d3c071..6f15d48c 100644 --- a/client/src/variants/Selfabsorption.js +++ b/client/src/variants/Selfabsorption.js @@ -9,10 +9,14 @@ export class SelfabsorptionRules extends AbsorptionRules { const p2 = this.getPiece(x2, y2); return ( p1 != p2 && - [V.QUEEN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) && - [V.QUEEN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2) && + [V.QUEEN, V.RN, V.BN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) && + [V.QUEEN, V.RN, V.BN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2) && (p1 != V.QUEEN || p2 == V.KNIGHT) && - (p2 != V.QUEEN || p1 == V.KNIGHT) + (p2 != V.QUEEN || p1 == V.KNIGHT) && + (p1 != V.RN || p2 == V.BISHOP) && + (p2 != V.RN || p1 == V.BISHOP) && + (p1 != V.BN || p2 == V.ROOK) && + (p2 != V.BN || p1 == V.ROOK) ); } diff --git a/client/src/variants/Yote.js b/client/src/variants/Yote.js new file mode 100644 index 00000000..0d81759c --- /dev/null +++ b/client/src/variants/Yote.js @@ -0,0 +1,7 @@ +import { ChessRules } from "@/base_rules"; + +export class YoteRules extends ChessRules { + + // TODO + +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index 5322ce99..e419e902 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -57,16 +57,19 @@ insert or ignore into Variants (name, description) values ('Doublemove2', 'Double moves (v2)'), ('Dynamo', 'Push and pull'), ('Eightpieces', 'Each piece is unique'), + ('Emergo', 'Stacking Checkers variant'), ('Empire', 'Empire versus Kingdom'), ('Enpassant', 'Capture en passant'), ('Evolution', 'Faster development'), ('Extinction', 'Capture all of a kind'), + ('Fanorona', 'Malagasy Draughts'), ('Football', 'Score a goal'), ('Forward', 'Moving forward'), ('Freecapture', 'Capture both colors'), ('Fugue', 'Baroque Music'), ('Fullcavalry', 'Lancers everywhere'), ('Fusion', 'Fusion pieces (v1)'), + ('Gomoku', 'Align five stones'), ('Grand', 'Big board'), ('Grasshopper', 'Long jumps over pieces'), ('Gridolina', 'Jump the borders'), @@ -84,6 +87,7 @@ insert or ignore into Variants (name, description) values ('Knightpawns', 'Knight versus pawns'), ('Knightrelay1', 'Move like a knight (v1)'), ('Knightrelay2', 'Move like a knight (v2)'), + ('Konane', 'Hawaiian Checkers'), ('Koopa', 'Stun & kick pieces'), ('Koth', 'King of the Hill'), ('Losers', 'Get strong at self-mate'), @@ -154,4 +158,5 @@ insert or ignore into Variants (name, description) values ('Wildebeest', 'Balanced sliders & leapers'), ('Wormhole', 'Squares disappear'), ('Xiangqi', 'Chinese Chess'), + ('Yote', 'African Draughts'), ('Zen', 'Reverse captures');