From: Benjamin Auder Date: Tue, 12 Jan 2021 23:54:26 +0000 (+0100) Subject: Add Fugue variant X-Git-Url: https://git.auder.net/variants/Chakart/pieces/R.css?a=commitdiff_plain;h=8fe025db3e9724d6b221481738a7787761793355;p=vchess.git Add Fugue variant --- diff --git a/TODO b/TODO index 2f72e33e..2107800a 100644 --- a/TODO +++ b/TODO @@ -7,16 +7,15 @@ http://history.chess.free.fr/rollerball.htm https://www.chessvariants.com/40.dir/rollerball/index.html https://www.pychess.org/variant/shogun -https://www.chessvariants.com/other.dir/fugue.html https://www.chessvariants.com/incinf.dir/bario.html https://www.chessvariants.com/index/listcomments.php?order=DESC&itemid=Bario https://www.bario-chess-checkers-chessphotography-spaceart.de/ https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg -Non-chess: -gomoku, avalam, draughts, draughts8 +Non-chess: ( won't add draughts: https://lidraughts.org/ ) +gomoku, avalam, Fanorona https://fr.wikipedia.org/wiki/Fanorona Yoté https://fr.wikipedia.org/wiki/Yot%C3%A9 http://www.zillionsofgames.com/cgi-bin/zilligames/submissions.cgi/92187?do=show;id=960 -Fanorona https://fr.wikipedia.org/wiki/Fanorona -(puis quand hexaboards peut-être: hexavariants + Hex) -qoridor (wider lines in another color ?) +Later : +Qoridor (wider lines in another color ?) +Hexagonal variants (+ Gex game) diff --git a/client/public/images/pieces/Fugue/SOURCE b/client/public/images/pieces/Fugue/SOURCE new file mode 100644 index 00000000..3465c507 --- /dev/null +++ b/client/public/images/pieces/Fugue/SOURCE @@ -0,0 +1,4 @@ +https://www.flaticon.com/free-icon/bow-and-arrow_108116 +https://www.onlinewebfonts.com/icon/432282 +https://www.flaticon.com/free-icon/frog_2016475 +https://www.iconfinder.com/icons/352155/circle_swap_vert_icon diff --git a/client/public/images/pieces/Fugue/ba.svg b/client/public/images/pieces/Fugue/ba.svg new file mode 100644 index 00000000..ededa99e --- /dev/null +++ b/client/public/images/pieces/Fugue/ba.svg @@ -0,0 +1,122 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Fugue/bi.svg b/client/public/images/pieces/Fugue/bi.svg new file mode 120000 index 00000000..c9394f1d --- /dev/null +++ b/client/public/images/pieces/Fugue/bi.svg @@ -0,0 +1 @@ +../Musketeer/by.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fugue/bl.svg b/client/public/images/pieces/Fugue/bl.svg new file mode 100644 index 00000000..053831d8 --- /dev/null +++ b/client/public/images/pieces/Fugue/bl.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Fugue/bs.svg b/client/public/images/pieces/Fugue/bs.svg new file mode 100644 index 00000000..b4a25759 --- /dev/null +++ b/client/public/images/pieces/Fugue/bs.svg @@ -0,0 +1,54 @@ + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon image/svg+xml + + diff --git a/client/public/images/pieces/Fugue/bu.svg b/client/public/images/pieces/Fugue/bu.svg new file mode 120000 index 00000000..ee01c996 --- /dev/null +++ b/client/public/images/pieces/Fugue/bu.svg @@ -0,0 +1 @@ +../Wildebeest/bw.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fugue/bw.svg b/client/public/images/pieces/Fugue/bw.svg new file mode 100644 index 00000000..dca4e1ad --- /dev/null +++ b/client/public/images/pieces/Fugue/bw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/pieces/Fugue/wa.svg b/client/public/images/pieces/Fugue/wa.svg new file mode 100644 index 00000000..a4e10521 --- /dev/null +++ b/client/public/images/pieces/Fugue/wa.svg @@ -0,0 +1,121 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Fugue/wi.svg b/client/public/images/pieces/Fugue/wi.svg new file mode 120000 index 00000000..64b1aa12 --- /dev/null +++ b/client/public/images/pieces/Fugue/wi.svg @@ -0,0 +1 @@ +../Musketeer/wy.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fugue/wl.svg b/client/public/images/pieces/Fugue/wl.svg new file mode 100644 index 00000000..d73d3bdb --- /dev/null +++ b/client/public/images/pieces/Fugue/wl.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Fugue/ws.svg b/client/public/images/pieces/Fugue/ws.svg new file mode 100644 index 00000000..47cf6a96 --- /dev/null +++ b/client/public/images/pieces/Fugue/ws.svg @@ -0,0 +1,53 @@ + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon image/svg+xml + + diff --git a/client/public/images/pieces/Fugue/wu.svg b/client/public/images/pieces/Fugue/wu.svg new file mode 120000 index 00000000..a994c13c --- /dev/null +++ b/client/public/images/pieces/Fugue/wu.svg @@ -0,0 +1 @@ +../Wildebeest/ww.svg \ No newline at end of file diff --git a/client/public/images/pieces/Fugue/ww.svg b/client/public/images/pieces/Fugue/ww.svg new file mode 100644 index 00000000..b1234733 --- /dev/null +++ b/client/public/images/pieces/Fugue/ww.svg @@ -0,0 +1,78 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Wildebeest/bc.svg b/client/public/images/pieces/Wildebeest/bc.svg index f0b8cdcf..d00d46ff 100644 --- a/client/public/images/pieces/Wildebeest/bc.svg +++ b/client/public/images/pieces/Wildebeest/bc.svg @@ -13,7 +13,7 @@ version="1.1" id="svg16" sodipodi:docname="bc.svg" - inkscape:version="0.92.4 5da689c313, 2019-01-14"> + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)"> + inkscape:current-layer="svg16" + inkscape:document-rotation="0" /> 20 -Camel- (Solid) diff --git a/client/public/images/pieces/Wildebeest/bw.svg b/client/public/images/pieces/Wildebeest/bw.svg index 6d4f9604..b804c15a 100644 --- a/client/public/images/pieces/Wildebeest/bw.svg +++ b/client/public/images/pieces/Wildebeest/bw.svg @@ -14,7 +14,7 @@ viewBox="0 0 62 64" xml:space="preserve" sodipodi:docname="bw.svg" - inkscape:version="0.92.4 5da689c313, 2019-01-14">Madeby Gridsimage/svg+xml \ No newline at end of file + inkscape:connector-curvature="0" /> diff --git a/client/public/images/pieces/Wildebeest/wc.svg b/client/public/images/pieces/Wildebeest/wc.svg index 3f1622f9..cba2e6ee 100644 --- a/client/public/images/pieces/Wildebeest/wc.svg +++ b/client/public/images/pieces/Wildebeest/wc.svg @@ -13,7 +13,7 @@ version="1.1" id="svg26" sodipodi:docname="wc.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)"> + inkscape:current-layer="svg26" + inkscape:document-rotation="0" /> 20 -Camel- (Outline) diff --git a/client/public/images/pieces/Wildebeest/ww.svg b/client/public/images/pieces/Wildebeest/ww.svg index 561449c4..47c8965f 100644 --- a/client/public/images/pieces/Wildebeest/ww.svg +++ b/client/public/images/pieces/Wildebeest/ww.svg @@ -14,7 +14,7 @@ viewBox="0 0 64 64" xml:space="preserve" sodipodi:docname="ww.svg" - inkscape:version="0.92.2 2405546, 2018-03-11">Madeby Gridsimage/svg+xml \ No newline at end of file + inkscape:connector-curvature="0" /> diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 6dcbab1f..ef2ba14e 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -174,6 +174,7 @@ export const translations = { "Attract opposite king": "Attract opposite king", "Augmented Queens": "Augmented Queens", "Balanced sliders & leapers": "Balanced sliders & leapers", + "Baroque Music": "Baroque Music", "Big board": "Big board", "Bishop versus pawns": "Bishop versus pawns", "Board upside down": "Board upside down", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 6f6181f4..6aa5c10c 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -174,6 +174,7 @@ export const translations = { "Attract opposite king": "Atraer al rey contrario", "Augmented Queens": "Damas aumentadas", "Balanced sliders & leapers": "Modos de desplazamiento equilibrados", + "Baroque Music": "Música Barroca", "Big board": "Gran tablero", "Bishop versus pawns": "Alfil contra peones", "Board upside down": "Tablero al revés", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index e4e3c34c..a79d18cb 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -174,6 +174,7 @@ export const translations = { "Attract opposite king": "Attirer le roi adverse", "Augmented Queens": "Dames augmentées", "Balanced sliders & leapers": "Modes de déplacement équilibrés", + "Baroque Music": "Musique Baroque", "Big board": "Grand échiquier", "Bishop versus pawns": "Fou contre pions", "Board upside down": "Échiquier à l'envers", diff --git a/client/src/translations/rules/Fugue/en.pug b/client/src/translations/rules/Fugue/en.pug index 21203baa..528b9ea7 100644 --- a/client/src/translations/rules/Fugue/en.pug +++ b/client/src/translations/rules/Fugue/en.pug @@ -1 +1,74 @@ -p.boxed TODO +p.boxed + | Exotic pieces capturing in quite unorthodox ways. + +p. + Based on Ultima (Baroque) and Rococo, this game has pieces that capture in + unusual ways. They are illustrated and then described below. + They all move like an orthodox queen — except the King and Pawns. + +figure.diagram-container + .diagram + | fen:wlqksaui/pppppppp/8/8/8/8/PPPPPPPP/IUASKQLW: + figcaption Deterministic initial position. + +p. + The object of the game is to capture the opposing king. + A player unable to move or who causes three time repetition loses as well. + +h3 Pieces description + +p From left to right on white side: +ul + li. + The Immobilizer (I) (spider) immobilizes all adversary pieces adjacent + to it, except another immobilizer. + li. + The Pushme-Pullyu (U) (gnu) captures pieces which are on its line of + movement, either just below the first square or right after the final + square. It cannot capture both, however. + li. + The Archer (A) (bow) captures by "shooting": it can remove any enemy + piece P standing in its range, provided that + ul + li P is standing at most two squares from the archer, or, + li. + a friendly piece is at a distance of two squares or less from P, + without obstacle between it and P. + In both cases the archer must have a clear path until the target. + li. + The Shield (S) protects all friendly pieces adjacent to it: they + cannot be captured. The shield cannot capture. + li King and Queen moves and capture as in standard chess. + li. + The Long Leaper (L) (frog) captures by jumping over a piece, + as in Draughts. It can capture only one piece like this. It can land + on any square beyond the target. + li. + The Swapper (W) can exchange its position with any enemy piece at queen's + distance. Additionally, if it is sitting next to an enemy piece it might + capture it by mutual destruction: both pieces disappear. + +p. + (Cannon) Pawns move like a king, with an additional ability. + If a piece is standing just next (friendly or not), they can leap + over it to land just behind. If this last square is occupied by the enemy, + a capture occurs. + +figure.diagram-container + .diagram + | fen:8/8/3p4/3l4/1wLP4/4Q3/1p3q2/8 b4,d6,f2: + figcaption Possible pawn captures. + +p. + Pawns can promote, optionally, when reaching the last rank, + only in a piece which is currently out of the board. + +h3 More information + +p + | See the + a(href="https://www.chessvariants.com/other.dir/fugue.html") + | chessvariants page + | . + +p Inventor: Mike Nelson (2004) diff --git a/client/src/translations/rules/Fugue/es.pug b/client/src/translations/rules/Fugue/es.pug index 21203baa..29a7f144 100644 --- a/client/src/translations/rules/Fugue/es.pug +++ b/client/src/translations/rules/Fugue/es.pug @@ -1 +1,77 @@ -p.boxed TODO +p.boxed + | Piezas exóticas capturando de forma poco ortodoxa. + +p. + Basado en Ultima (Baroque) y Rococo, este juego presenta piezas capturando + de una manera inusual. Se ilustran y luego se describen a continuación. + Todos se mueven como una dama ortodoxa — excepto el Rey y los Peones. + +figure.diagram-container + .diagram + | fen:wlqksaui/pppppppp/8/8/8/8/PPPPPPPP/IUASKQLW: + figcaption Posición inicial determinista. + +p. + El objetivo del juego es capturar al rey contrario. + Un jugador que no tiene una jugada disponible o que provoca + una triple repetición también pierde. + +h3 Descripción de piezas + +p De izquierda a derecha del lado blanco: +ul + li. + Inmovilizador (I) (araña) inmoviliza todas las piezas enemigas + adyacente, excepto por un posible otro inmovilizador. + li. + El "Pushme-Pullyu" (U) (ñu) captura las piezas que yacen en su línea + movimiento, ya sea justo antes del primer cuadro o justo después de la + casilla final. Sin embargo, no puede tomar ambos. + li. + El arquero (A) (arco) captura "disparando": puede suprimir cualquier + pieza enemiga P qué está dentro del alcance, siempre que + ul + li P está a dos casillas o menos del arquero, o + li. + una pieza amiga está a dos casillas o menos de P (en línea recta), + sin obstáculo entre ella y P. + En ambos casos, el arquero debe tener un + camino despejado hacia su objetivo. + li. + El Escudo (S) protege cualquier pieza amiga a su lado: estos no + puede ser capturado. El escudo no puede capturar. + li El Rey y la Dama se mueven y capturan como en el ajedrez estándar. + li. + El Saltador Largo (L) (rana) captura saltando sobre una habitación, + como à las Damas. Solo puede capturar una pieza de esta manera. Aterriza + en cualquier casilla más allá de su objetivo. + li. + El Intercambiador (W) puede intercambiar su posición con la de cualquier + pieza enemiga al alcance. Además, si está al lado de una pieza + oponente puede decidir capturarlo mediante destrucción mutua: + las dos piezas desaparecen. + +p. + Los Peones (Cañones) se mueven como un rey, con una habilidad + adicional. Si hay una pieza (amiga o no) junto a ella, + pueden saltar y aterrizar justo detrás. + Si esta última casilla está ocupada por el oponente, se realiza una captura. + +figure.diagram-container + .diagram + | fen:8/8/3p4/3l4/1wLP4/4Q3/1p3q2/8 b4,d6,f2: + figcaption Posibles capturas de peones. + +p. + Opcionalmente, los peones pueden promocionarse, opcionalmente, cuando + alcanzan el última fila, solo en una pieza actualmente fuera del tablero. + +h3 Más información + +p + | Ver la + a(href="https://www.chessvariants.com/other.dir/fugue.html") + | página chessvariants + | . + +p Inventor: Mike Nelson (2004) diff --git a/client/src/translations/rules/Fugue/fr.pug b/client/src/translations/rules/Fugue/fr.pug index 21203baa..fd353b65 100644 --- a/client/src/translations/rules/Fugue/fr.pug +++ b/client/src/translations/rules/Fugue/fr.pug @@ -1 +1,77 @@ -p.boxed TODO +p.boxed + | Pièces exotiques capturant de manière peu orthodoxe. + +p. + Basé sur Ultima (Baroque) et Rococo, ce jeu comporte des pièces capturant + de manière inhabituelle. Elles sont illustrées puis décrites ci-après. + Elles se déplacent toutes comme une dame orthodoxe — excepté + le Roi et les Pions. + +figure.diagram-container + .diagram + | fen:wlqksaui/pppppppp/8/8/8/8/PPPPPPPP/IUASKQLW: + figcaption Deterministic initial position. + +p. + L'objectif du jeu est de capturer le roi adverse. + Un joueur n'ayant auun coup à disposition ou provoquant + une triple répétition perd également. + +h3 Description des pièces + +p De gauche à droite côté blancs : +ul + li. + L'Immobiliseur (I) (araignée) immobilise toutes les pièces adverses + adjacentes, sauf un éventuel autre immobiliseur. + li. + Le "Pushme-Pullyu" (U) (gnou) capture les pièces se situant sur sa ligne + de déplacement, soit juste avant la première case soit juste après la + case finale. Il ne peut pas prendre les deux, ceci dit. + li. + L'Archer (A) (arc) capture en "tirant" : il peut supprimer n'importe + quelle pièce ennemie P à sa portée, à condition que + ul + li P se trouve à deux cases ou moins de l'archer, ou, + li. + une pièce amie est à deux cases ou moins de P (en ligne droite), + sans obstacle entre elle et P. + Dans les deux cas l'archer doit avoir un chemin dégagé jusqu'à sa cible. + li. + Le Bouclier (S) protège toute pièce amie à côté de lui : celles-ci ne + peuvent être capturées. Le bouclier ne peut pas capturer. + li Roi et Reine se déplacent et capturent comme aux échecs standard. + li. + Le Sauteur Long (L) (grenouille) capture en sautant par dessus une pièce, + comme aux Dames. Il ne peut capturer qu'une seule pièce ainsi. Il atterrit + sur n'importe quelle case au-delà de sa cible. + li. + L'Échangeur (W) peut échanger sa position avec celle de n'importe quelle + pièce ennemie à sa portée. De plus, s'il se trouve à côté d'une pièce + adverse il peut décider de la capturer par destruction mutuelle : + les deux pièces disparaissent. + +p. + Les Pions (Canons) se déplacent comme un roi, avec une aptitude + supplémentaire. Si une pièce (amie ou non) se trouve juste à côté, + ils peuvent sauter par dessus pour atterrir juste derrière. + Si cette dernière case est occupée par l'adversaire, une capture a lieu. + +figure.diagram-container + .diagram + | fen:8/8/3p4/3l4/1wLP4/4Q3/1p3q2/8 b4,d6,f2: + figcaption Possibles captures de pion. + +p. + Les pions peuvent se promouvoir, optionnellement, quand ils atteignent la + dernière rangée, seulement en une pièce actuellement hors de l'échiquier. + +h3 Plus d'information + +p + | Voir la + a(href="https://www.chessvariants.com/other.dir/fugue.html") + | page chessvariants + | . + +p Inventeur : Mike Nelson (2004) diff --git a/client/src/translations/rules/Schess/fr.pug b/client/src/translations/rules/Schess/fr.pug index ed55d9f5..6a5f36ae 100644 --- a/client/src/translations/rules/Schess/fr.pug +++ b/client/src/translations/rules/Schess/fr.pug @@ -7,7 +7,7 @@ p. peut être placée sur sa case de départ. Vous avez deux pièces pouvant entrer dans le jeu ainsi : ul - li L'Aigle (H) : se déplace comme un fou + cavalier. + li Le Faucon (H) : se déplace comme un fou + cavalier. li L'Éléphant (E) : se déplace comme une tour + cavalier. figure.diagram-container diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 5421bfa3..50c5c44a 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -404,6 +404,7 @@ p. "Evolution", "Forward", "Freecapture", + "Fugue", "Gridolina", "Hamilton", "Hypnotic", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 143d6212..c8b683cb 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -415,6 +415,7 @@ p. "Evolution", "Forward", "Freecapture", + "Fugue", "Gridolina", "Hamilton", "Hypnotic", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 27af18d7..93ae6730 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -414,6 +414,7 @@ p. "Evolution", "Forward", "Freecapture", + "Fugue", "Gridolina", "Hamilton", "Hypnotic", diff --git a/client/src/variants/Fugue.js b/client/src/variants/Fugue.js new file mode 100644 index 00000000..12002fa4 --- /dev/null +++ b/client/src/variants/Fugue.js @@ -0,0 +1,584 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { shuffle } from "@/utils/alea"; + +export class FugueRules extends ChessRules { + + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get LoseOnRepetition() { + return true; + } + + static get IMMOBILIZER() { + return 'i'; + } + static get PUSHME_PULLYOU() { + return 'u'; + } + static get ARCHER() { + return 'a'; + } + static get SHIELD() { + return 's'; + } + static get LONG_LEAPER() { + return 'l'; + } + static get SWAPPER() { + return 'w'; + } + + static get PIECES() { + return [ + V.QUEEN, + V.KING, + V.IMMOBILIZER, + V.PUSHME_PULLYOU, + V.ARCHER, + V.SHIELD, + V.LONG_LEAPER, + V.SWAPPER + ]; + } + + getPpath(b) { + if (['p', 'q', 'k'].includes(b[1])) return b; + return "Fugue/" + b; + } + + getPPpath(m) { + // The only "choice" case is between a swap and a mutual destruction: + // show empty square in case of mutual destruction. + if (m.appear.length == 0) return "Rococo/empty"; + return this.getPpath(m.appear[0].c + m.appear[0].p); + } + + scanKings(fen) { + // No castling, but keep track of kings for faster game end checks + this.kingPos = { w: [-1, -1], b: [-1, -1] }; + const fenParts = fen.split(" "); + const position = fenParts[0].split("/"); + for (let i = 0; i < position.length; i++) { + let k = 0; + for (let j = 0; j < position[i].length; j++) { + switch (position[i].charAt(j)) { + case "k": + this.kingPos["b"] = [i, k]; + break; + case "K": + this.kingPos["w"] = [i, k]; + break; + default: { + const num = parseInt(position[i].charAt(j), 10); + if (!isNaN(num)) k += num - 1; + } + } + k++; + } + } + } + + // Is piece on square (x,y) immobilized? + isImmobilized([x, y]) { + const piece = this.getPiece(x, y); + if (piece == V.IMMOBILIZER) return false; + const oppCol = V.GetOppCol(this.getColor(x, y)); + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + for (let step of adjacentSteps) { + const [i, j] = [x + step[0], y + step[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol + ) { + if (this.getPiece(i, j) == V.IMMOBILIZER) return true; + } + } + return false; + } + + isProtected([x, y]) { + const color = this.getColor(x, y); + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + for (let s of steps) { + const [i, j] = [x + s[0], y + s[1]]; + if ( + V.OnBoard(i, j) && + this.getColor(i, j) == color && + this.getPiece(i, j) == V.SHIELD + ) { + return true; + } + } + return false; + } + + canTake([x1, y1], [x2, y2]) { + return !this.isProtected([x2, y2]) && super.canTake([x1, y1], [x2, y2]); + } + + getPotentialMovesFrom([x, y]) { + // Pre-check: is thing on this square immobilized? + if (this.isImmobilized([x, y])) return []; + const piece = this.getPiece(x, y); + let moves = []; + switch (piece) { + case V.PAWN: return this.getPotentialPawnMoves([x, y]); + case V.IMMOBILIZER: return this.getPotentialImmobilizerMoves([x, y]); + case V.PUSHME_PULLYOU: return this.getPotentialPushmePullyuMoves([x, y]); + case V.ARCHER: return this.getPotentialArcherMoves([x, y]); + case V.SHIELD: return this.getPotentialShieldMoves([x, y]); + case V.KING: return this.getPotentialKingMoves([x, y]); + case V.QUEEN: return super.getPotentialQueenMoves([x, y]); + case V.LONG_LEAPER: return this.getPotentialLongLeaperMoves([x, y]); + case V.SWAPPER: return this.getPotentialSwapperMoves([x, y]); + } + } + + getSlideNJumpMoves([x, y], steps, oneStep) { + const piece = this.getPiece(x, y); + let moves = []; + outerLoop: for (let step of steps) { + let i = x + step[0]; + 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; + i += step[0]; + j += step[1]; + } + // Only queen and king can take on occupied square: + if ( + [V.KING, V.QUEEN].includes(piece) && + V.OnBoard(i, j) && + this.canTake([x, y], [i, j]) + ) { + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + return moves; + } + + // "Cannon/grasshopper pawn" + getPotentialPawnMoves([x, y]) { + const c = this.turn; + const oppCol = V.GetOppCol(c); + const lastRank = (c == 'w' ? 0 : 7); + let canResurect = { + [V.QUEEN]: true, + [V.IMMOBILIZER]: true, + [V.PUSHME_PULLYOU]: true, + [V.ARCHER]: true, + [V.SHIELD]: true, + [V.LONG_LEAPER]: true, + [V.SWAPPER]: true + }; + for (let i = 0; i < 8; i++) { + for (let j = 0; j < 8; j++) { + if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == c) { + const pIJ = this.getPiece(i, j); + if (![V.PAWN, V.KING].includes(pIJ)) canResurect[pIJ] = false; + } + } + } + let moves = []; + const addPromotions = sq => { + // Optional promotion + Object.keys(canResurect).forEach(p => { + if (canResurect[p]) { + moves.push( + this.getBasicMove([x, y], [sq[0], sq[1]], { c: c, p: p })); + } + }); + } + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + adjacentSteps.forEach(step => { + const [i, j] = [x + step[0], y + step[1]]; + if (V.OnBoard(i, j)) { + if (this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + if (i == lastRank) addPromotions([i, j]); + } + else { + // Try to leap over: + const [ii, jj] = [i + step[0], j + step[1]]; + if ( + V.OnBoard(ii, jj) && + ( + this.board[ii][jj] == V.EMPTY || + this.getColor(ii, jj) == oppCol && !this.isProtected([ii, jj]) + ) + ) { + moves.push(this.getBasicMove([x, y], [ii, jj])); + if (ii == lastRank) addPromotions([ii, jj]); + } + } + } + }); + return moves; + } + + getPotentialKingMoves(sq) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return this.getSlideNJumpMoves(sq, steps, "oneStep"); + } + + // NOTE: not really captures, but let's keep the name + getSwapperCaptures([x, y]) { + let moves = []; + const oppCol = V.GetOppCol(this.turn); + // Simple: if something is visible, we can swap + V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j) && this.getColor(i, j) == oppCol) { + const oppPiece = this.getPiece(i, j); + let m = this.getBasicMove([x, y], [i, j]); + m.appear.push( + new PiPo({ + x: x, + y: y, + c: oppCol, + p: this.getPiece(i, j) + }) + ); + moves.push(m); + if ( + i == x + step[0] && j == y + step[1] && + !this.isProtected([i, j]) + ) { + // Add mutual destruction option: + m = new Move({ + start: { x: x, y: y}, + end: { x: i, y: j }, + appear: [], + // TODO: is copying necessary here? + vanish: JSON.parse(JSON.stringify(m.vanish)) + }); + moves.push(m); + } + } + }); + return moves; + } + + getPotentialSwapperMoves(sq) { + return ( + super.getPotentialQueenMoves(sq).concat(this.getSwapperCaptures(sq)) + ); + } + + getLongLeaperCaptures([x, y]) { + // Look in every direction for captures + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + const color = this.turn; + const oppCol = V.GetOppCol(color); + let moves = []; + const piece = this.getPiece(x, y); + outerLoop: for (let step of steps) { + let [i, j] = [x + step[0], y + step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + if ( + !V.OnBoard(i, j) || + this.getColor(i, j) == color || + this.isProtected([i, j]) + ) { + continue; + } + let [ii, jj] = [i + step[0], j + step[1]]; + const vanished = [ + new PiPo({ x: x, y: y, c: color, p: piece }), + new PiPo({ x: i, y: j, c: oppCol, p: this.getPiece(i, j)}) + ]; + while (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY) { + moves.push( + new Move({ + appear: [new PiPo({ x: ii, y: jj, c: color, p: piece })], + vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required? + start: { x: x, y: y }, + end: { x: ii, y: jj } + }) + ); + ii += step[0]; + jj += step[1]; + } + } + return moves; + } + + getPotentialLongLeaperMoves(sq) { + return ( + super.getPotentialQueenMoves(sq).concat(this.getLongLeaperCaptures(sq)) + ); + } + + completeAndFilterPPcaptures(moves) { + if (moves.length == 0) return []; + const [x, y] = [moves[0].start.x, moves[0].start.y]; + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + let capturingDirStart = {}; + const oppCol = V.GetOppCol(this.turn); + // Useful precomputation: + adjacentSteps.forEach(step => { + const [i, j] = [x + step[0], y + step[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol + ) { + capturingDirStart[step[0] + "_" + step[1]] = { + p: this.getPiece(i, j), + canTake: !this.isProtected([i, j]) + }; + } + }); + moves.forEach(m => { + const step = [ + m.end.x != x ? (m.end.x - x) / Math.abs(m.end.x - x) : 0, + m.end.y != y ? (m.end.y - y) / Math.abs(m.end.y - y) : 0 + ]; + // TODO: this test should be done only once per direction + const capture = capturingDirStart[(-step[0]) + "_" + (-step[1])]; + if (!!capture && capture.canTake) { + const [i, j] = [x - step[0], y - step[1]]; + m.vanish.push( + new PiPo({ + x: i, + y: j, + p: capture.p, + c: oppCol + }) + ); + } + // Also test the end (advancer effect) + const [i, j] = [m.end.x + step[0], m.end.y + step[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol && + !this.isProtected([i, j]) + ) { + m.vanish.push( + new PiPo({ + x: i, + y: j, + p: this.getPiece(i, j), + c: oppCol + }) + ); + } + }); + // Forbid "double captures" + return moves.filter(m => m.vanish.length <= 2); + } + + getPotentialPushmePullyuMoves(sq) { + let moves = super.getPotentialQueenMoves(sq); + return this.completeAndFilterPPcaptures(moves); + } + + getPotentialImmobilizerMoves(sq) { + // Immobilizer doesn't capture + return super.getPotentialQueenMoves(sq); + } + + getPotentialArcherMoves([x, y]) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + let moves = this.getSlideNJumpMoves([x, y], steps); + const c = this.turn; + const oppCol = V.GetOppCol(c); + // Add captures + for (let s of steps) { + let [i, j] = [x + s[0], y + s[1]]; + let stepCounter = 1; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += s[0]; + j += s[1]; + stepCounter++; + } + if ( + V.OnBoard(i, j) && + this.getColor(i, j) == oppCol && + !this.isProtected([i, j]) + ) { + let shootOk = (stepCounter <= 2); + if (!shootOk) { + // try to find a spotting piece: + for (let ss of steps) { + let [ii, jj] = [i + ss[0], j + ss[1]]; + if (V.OnBoard(ii, jj)) { + if (this.board[ii][jj] != V.EMPTY) { + if (this.getColor(ii, jj) == c) { + shootOk = true; + break; + } + } + else { + ii += ss[0]; + jj += ss[1]; + if ( + V.OnBoard(ii, jj) && + this.board[ii][jj] != V.EMPTY && + this.getColor(ii, jj) == c + ) { + shootOk = true; + break; + } + } + } + } + } + if (shootOk) { + moves.push( + new Move({ + appear: [], + vanish: [ + new PiPo({ x: i, y: j, c: oppCol, p: this.getPiece(i, j) }) + ], + start: { x: x, y: y }, + end: { x: i, y: j } + }) + ); + } + } + } + return moves; + } + + getPotentialShieldMoves(sq) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + return this.getSlideNJumpMoves(sq, steps); + } + + getCheckSquares() { + return []; + } + + filterValid(moves) { + return moves; + } + + getCurrentScore() { + const c = this.turn; + if (this.kingPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0"); + if (this.atLeastOneMove()) return "*"; + // Stalemate, or checkmate: I lose + return (c == 'w' ? "0-1" : "1-0"); + } + + postPlay(move) { + const startIdx = (move.appear.length == 0 ? 0 : 1); + for (let i = startIdx; i < move.vanish.length; i++) { + const v = move.vanish[i]; + if (v.p == V.KING) this.kingPos[v.c] = [-1, -1]; + } + // King may have moved, or was swapped + for (let a of move.appear) { + if (a.p == V.KING) { + this.kingPos[a.c] = [a.x, a.y]; + break; + } + } + } + + postUndo(move) { + const startIdx = (move.appear.length == 0 ? 0 : 1); + for (let i = startIdx; i < move.vanish.length; i++) { + const v = move.vanish[i]; + if (v.p == V.KING) this.kingPos[v.c] = [v.x, v.y]; + } + // King may have moved, or was swapped + for (let i = 0; i < move.appear.length; i++) { + const a = move.appear[i]; + if (a.p == V.KING) { + const v = move.vanish[i]; + this.kingPos[a.c] = [v.x, v.y]; + break; + } + } + } + + static GenRandInitFen(randomness) { + if (randomness == 0) { + return ( + "wlqksaui/pppppppp/8/8/8/8/PPPPPPPP/IUASKQLW w 0" + ); + } + + let pieces = { w: new Array(8), b: new Array(8) }; + // Shuffle pieces on first and last rank + for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w']; + break; + } + + // Get random squares for every piece, totally freely + let positions = shuffle(ArrayFun.range(8)); + const composition = ['w', 'l', 'q', 'k', 's', 'a', 'u', 'i']; + for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i]; + } + return ( + pieces["b"].join("") + + "/pppppppp/8/8/8/8/PPPPPPPP/" + + pieces["w"].join("").toUpperCase() + " w 0" + ); + } + + static get VALUES() { + // Experimental... + return { + p: 1, + q: 9, + l: 5, + s: 5, + a: 5, + u: 5, + i: 12, + w: 3, + k: 1000 + }; + } + + static get SEARCH_DEPTH() { + return 2; + } + + getNotation(move) { + const initialSquare = V.CoordsToSquare(move.start); + const finalSquare = V.CoordsToSquare(move.end); + if (move.appear.length == 0) { + // Archer shooting 'S' or Mutual destruction 'D': + return ( + initialSquare + (move.vanish.length == 1 ? "S" : "D") + finalSquare + ); + } + let notation = undefined; + const symbol = move.appear[0].p.toUpperCase(); + if (symbol == 'P') + // Pawn: generally ambiguous short notation, so we use full description + notation = "P" + initialSquare + finalSquare; + else if (['Q', 'K'].includes(symbol)) + notation = symbol + (move.vanish.length > 1 ? "x" : "") + finalSquare; + else { + notation = symbol + finalSquare; + // Add a capture mark (not describing what is captured...): + if (move.vanish.length > 1) notation += "X"; + } + return notation; + } + +}; diff --git a/client/src/variants/Rococo.js b/client/src/variants/Rococo.js index 66d9615a..54533701 100644 --- a/client/src/variants/Rococo.js +++ b/client/src/variants/Rococo.js @@ -290,13 +290,12 @@ export class RococoRules extends ChessRules { return super.getPotentialQueenMoves(sq).concat(this.getRookCaptures(sq)); } - getKnightCaptures(startSquare, byChameleon) { + getKnightCaptures([x, y], byChameleon) { // Look in every direction for captures const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); const color = this.turn; const oppCol = V.GetOppCol(color); let moves = []; - const [x, y] = [startSquare[0], startSquare[1]]; const piece = this.getPiece(x, y); //might be a chameleon! outerLoop: for (let step of steps) { let [i, j] = [x + step[0], y + step[1]]; @@ -336,7 +335,8 @@ export class RococoRules extends ChessRules { //TODO: redundant test continue outerLoop; } - } else { + } + else { moves.push( new Move({ appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })], @@ -715,11 +715,14 @@ export class RococoRules extends ChessRules { if (move.appear[0].p == V.PAWN) { // Pawn: generally ambiguous short notation, so we use full description notation = "P" + initialSquare + finalSquare; - } else if (move.appear[0].p == V.KING) + } + else if (move.appear[0].p == V.KING) notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare; - else notation = move.appear[0].p.toUpperCase() + finalSquare; - // Add a capture mark (not describing what is captured...): - if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X"; + else { + notation = move.appear[0].p.toUpperCase() + finalSquare; + // Add a capture mark (not describing what is captured...): + if (move.vanish.length > 1) notation += "X"; + } return notation; } diff --git a/server/db/populate.sql b/server/db/populate.sql index 2510ace0..dd36fc9c 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -63,6 +63,7 @@ insert or ignore into Variants (name, description) values ('Football', 'Score a goal'), ('Forward', 'Moving forward'), ('Freecapture', 'Capture both colors'), + ('Fugue', 'Baroque Music'), ('Fullcavalry', 'Lancers everywhere'), ('Grand', 'Big board'), ('Grasshopper', 'Long jumps over pieces'),