From: Benjamin Auder Date: Tue, 31 Mar 2020 16:20:01 +0000 (+0200) Subject: Add Rococo variant X-Git-Url: https://git.auder.net/?p=vchess.git;a=commitdiff_plain;h=2f8dce6a81063289d9d4cbca7971f80b1b194b84 Add Rococo variant --- diff --git a/client/public/images/diag_mark.svg b/client/public/images/diag_mark.svg new file mode 100644 index 00000000..555f214c --- /dev/null +++ b/client/public/images/diag_mark.svg @@ -0,0 +1,57 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/client/public/images/mark.svg b/client/public/images/mark.svg index b7140678..9c1b930a 100644 --- a/client/public/images/mark.svg +++ b/client/public/images/mark.svg @@ -41,7 +41,7 @@ id="namedview4" showgrid="false" inkscape:zoom="0.39333333" - inkscape:cx="305.08475" + inkscape:cx="310.16949" inkscape:cy="300" inkscape:window-x="0" inkscape:window-y="20" diff --git a/client/public/images/pieces/Rococo/bm.svg b/client/public/images/pieces/Rococo/bm.svg new file mode 100644 index 00000000..fdc0ee59 --- /dev/null +++ b/client/public/images/pieces/Rococo/bm.svg @@ -0,0 +1,92 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Rococo/empty.svg b/client/public/images/pieces/Rococo/empty.svg new file mode 100644 index 00000000..08ec9068 --- /dev/null +++ b/client/public/images/pieces/Rococo/empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/images/pieces/Rococo/wm.svg b/client/public/images/pieces/Rococo/wm.svg new file mode 100644 index 00000000..bf9f16ad --- /dev/null +++ b/client/public/images/pieces/Rococo/wm.svg @@ -0,0 +1,97 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/SOURCE b/client/public/images/pieces/SOURCE index ada138cd..0417e945 100644 --- a/client/public/images/pieces/SOURCE +++ b/client/public/images/pieces/SOURCE @@ -5,6 +5,7 @@ Some fairy pieces found on the web and icon scout: https://iconscout.com/ PNG images for Eightpieces from https://greenchess.net/index.php and Jeff Kubach design. Images of the Hawk and Elephant were designed by "Couch Tomato #2218" on Discord, for the pychess-variants website (http://pychess-variants.herokuapp.com/) +He also designed all the Horde pieces in Orda. For Dynamo: https://commons.wikimedia.org/wiki/File:Font_P.svg https://commons.wikimedia.org/wiki/File:Font_R.svg diff --git a/client/src/translations/rules/Baroque/en.pug b/client/src/translations/rules/Baroque/en.pug index 1e94b81d..5e5a752c 100644 --- a/client/src/translations/rules/Baroque/en.pug +++ b/client/src/translations/rules/Baroque/en.pug @@ -1,11 +1,11 @@ p.boxed | Most pieces look the same but behave very differently. | They generally move like an orthodox queen, - | but capturing rules are complex. + | but capturing rules are more complex. p | Note: 'Baroque' is the initial name thought by the author, - | but 'Ultima' is also largely adopted. + | but 'Ultima' is also largely adopted. a(href="https://www.chessvariants.com/people.dir/abbott.html") | He prefers 'Baroque' | , and I think me too. @@ -14,12 +14,12 @@ h4 Pieces names p Pieces names refer to the way they capture, which is described later. ul - li Pawn : pawn or pincer - li Rook : coordinator - li Knight : long leaper - li Bishop : chameleon - li Queen : withdrawer - li King : king (same behavior as in standard chess) + li Pawn : Pawn or Pincer + li Rook : Coordinator + li Knight : Long Leaper + li Bishop : Chameleon + li Queen : Withdrawer + li King : King (same behavior as in standard chess) p. Besides, a new piece is introduced: the immobilizer, written by the letter 'm' in FEN diagrams and PGN games. It is represented by an upside-down rook: @@ -147,10 +147,10 @@ p. h3 End of the game p. - Checkmate or stalemate as in standard chess. Note however that checks are - more difficult to see, because of the exotic capturing rules. For example, on - the following diagram the white king cannot move to e5 because then - the black pawn could capture by moving next to it. + The game ends by checkmate or stalemate as in standard chess. Note however + that checks are more difficult to see, because of the exotic capturing rules. + For example, on the following diagram the white king cannot move to e5 + because then the black pawn could capture by moving next to it. figure.diagram-container .diagram diff --git a/client/src/translations/rules/Baroque/es.pug b/client/src/translations/rules/Baroque/es.pug index c35c6fc2..bde85c4e 100644 --- a/client/src/translations/rules/Baroque/es.pug +++ b/client/src/translations/rules/Baroque/es.pug @@ -4,7 +4,7 @@ p.boxed p | Nota: el nombre elegido inicialmente por el autor es 'Baroque', - | pero 'Ultima' también se usa ampliamente. + | pero 'Ultima' también se usa ampliamente. a(href="https://www.chessvariants.com/people.dir/abbott.html") | Él prefiere 'Baroque' | , y yo también creo. @@ -15,12 +15,12 @@ p. Los nombres de las piezas se relacionan con su modo de captura, que se describe a continuación. ul - li Peón: peón o p "pinchazo" - li Torre: coordinador - li Caballo: "saltador largo" - li Alfil: camaleón - li Dama: "supresor" - li Rey: rey (mismo comportamiento que el ajedrez ortodoxo) + li Peón: Peón o p "Pinchazo" + li Torre: Coordinador + li Caballo: "Saltador Largo" + li Alfil: Camaleón + li Dama: "Supresor" + li Rey: Rey (mismo comportamiento que el ajedrez ortodoxo) p. Además, se agrega una nueva pieza: el inmovilizador, denotado por el letra 'm' en diagramas FEN y partidas PGN. El esta representado por diff --git a/client/src/translations/rules/Baroque/fr.pug b/client/src/translations/rules/Baroque/fr.pug index 8e4ac86c..b2554faf 100644 --- a/client/src/translations/rules/Baroque/fr.pug +++ b/client/src/translations/rules/Baroque/fr.pug @@ -4,7 +4,7 @@ p.boxed p | Note : le nom initialement choisit par l'auteur est 'Baroque', - | mais 'Ultima' est également largement utilisé. + | mais 'Ultima' est également largement utilisé. a(href="https://www.chessvariants.com/people.dir/abbott.html") | Il préfère 'Baroque' | , et moi aussi je crois. @@ -15,12 +15,12 @@ p. Les noms des pièces se rapportent à leur mode de capture, qui est décrit plus loin ul - li Pion : pion ou pinceur - li Tour : coordinateur - li Cavalier : "sauteur long" - li Fou : caméléon - li Dame : "retireur" - li King : roi (même comportement qu'aux échecs orthodoxes) + li Pion : Pion ou Pinceur + li Tour : Coordinateur + li Cavalier : "Sauteur Long" + li Fou : Caméléon + li Dame : "Retireur" + li King : Roi (même comportement qu'aux échecs orthodoxes) p. En outre, une nouvelle pièce est ajoutée : l'immobiliseur, dénoté par la lettre 'm' dans les diagrammes FEN et parties PGN. Il est représenté par diff --git a/client/src/translations/rules/Rococo/en.pug b/client/src/translations/rules/Rococo/en.pug new file mode 100644 index 00000000..7000378b --- /dev/null +++ b/client/src/translations/rules/Rococo/en.pug @@ -0,0 +1,133 @@ +p.boxed. + Most pieces look as usual but behave differently. + They generally move like an orthodox queen, + but capturing rules are more complex. + +p. + This variant comes from an attempt to fix some issues with Baroque variant, + which "favors the defense over the attack, and is lacking in clarity" + (from the introduction of the Rococo rules; see the link at the bottom of + the page). + +h4 Pieces names + +p Pieces names refer to the way they capture, which is described later. +ul + li Pawn : Cannon Pawn + li Rook : Swapper + li Knight : Long Leaper + li Bishop : Vhameleon + li Queen : "Pushme-Pullyu" + li King : King (same behavior as in standard chess) +p. + Besides, a new piece is introduced: the immobilizer, written by the letter + 'M'. It is represented by an upside-down rook: + +figure.diagram-container + .diagram + | fen:91/91/91/5m4/91/91/91/4M5/91/91: + figcaption Immobilizers on e3 and f7. + +p The board is of size 10x10 to facilitate captures, as explained below. + +h3 Non-capturing moves + +p The king moves (and captures) as usual. + +p. + Cannon Pawns move without capturing two ways: either a single step in any + direction, or, they may leap over an adjacent piece of either side to the + empty square just beyond. + +figure.diagram-container + .diagram + | fen:91/91/91/91/4b5/4P5/5Q4/91/91/91 e4,d4,d5,d6,f6,f5,e7,g3: + figcaption Squares where the pawn can move to (without capturing). + +p All other pieces move like an orthodox queen. + +p. + When a piece is next to an enemy immobilizer, it cannot move but + is allowed a special "suicide" move: it can disappear by "capturing" the + immobilizer (to trigger the move on the interface). + +h3 Capturing moves + +p. + As said above, the king captures as usual. However, all other pieces have + a new way of capturing - when they can. Indeed the immobilizer doesn't + capture, and the swapper can only capture a piece when it stands next to him. + In this case he disappears as well in the process. + +p. + The edge of the board can be reached only by capturing, only if landing on + the edge is the only way to do some capture, and that there exist no other + such captures with a smallest move length. The move length is the distance + between the initial and destination squares. + +h4 (Cannon) Pawns + +p. + Cannon pawns capture by leaping over an adjacent piece (the mount), landing + on the opposing piece just beyond the mount. + +figure.diagram-container + .diagram + | fen:91/91/91/4p5/4b5/1rB1P5/5Q4/2p3q3/91/91 e7,g3: + figcaption Possible pawn captures. + +h4 "Pushme-Pullyu" (Queen) + +p. + The queen captures on the square just after where she stops (if any + opponent's piece stands there), and also on the square initially just behind + her. It is a combination of a Withdrawer and an Advancer, as described on + the rules page on chessvariants (see the link at the bottom). + +figure.diagram-container + .diagram + | fen:91/91/91/91/2bQ4r1/91/91/91/91/91 h6: + figcaption. + Any move to the right will capture the bishop; only a move to the marked + spot will also capture the rook. + +h4 Rook (Swapper) + +p. + The rook can swap its position with any enemy piece in the attacking + line of an orthodox queen, as illustrated below. + +figure.diagram-container + .diagram.diag12 + | fen:91/91/7k2/91/91/91/2n7/2R7/91/91: + .diagram.diag22 + | fen:91/91/7R2/91/91/91/2n7/2k7/91/91: + figcaption Before and after a rook swap with the king. + +p. + Moreover, if the rook stands just next to an enemy piece (like the knight + here), it can choose to capture it by a "kamikaze" attack: indeed it get + self-destroyed in the process. + +h4 Other pieces + +p. + The Long Leaper, Immobilizer and Chameleon behave as in the Baroque + variant playable on this site: please refer to these rules description. There + is only one change is in the immobilizing rules, which are simpler here: + immobilizers powers are not canceled by the chameleons. + +h3 End of the game + +p. + The game ends by checkmate or stalemate as in standard chess. + Just pay attention to the capturing rules :) + +h3 Source + +p + | The + a(href="https://www.chessvariants.com/other.dir/rococo.html") Rococo variant + |  on chessvariants.com. + +p Inventors: Peter Aronson and David Howe (2002) diff --git a/client/src/translations/rules/Rococo/es.pug b/client/src/translations/rules/Rococo/es.pug new file mode 100644 index 00000000..c60d8709 --- /dev/null +++ b/client/src/translations/rules/Rococo/es.pug @@ -0,0 +1,142 @@ +p.boxed. + La mayoría de las piezas parecen familiares pero se comportan de manera + diferente. Suelen moverse como una dama ortodoxa, + pero captura de acuerdo con reglas bastante complejas. + +p. + Esta variante proviene de un intento de resolver problemas con la + variante Barroca, que "promueve la defensa en ataque y carece de claridad" + (cf. la introducción de las reglas rococó, vinculadas al final de esta + página). + +h4 Nombre de las piezas + +p. + Los nombres de las piezas se relacionan con su forma de capturar, siendo este + descrito a continuación. +ul + Li Peón : Canon Peón + li Torre : Intercambiador + li Caballo : Saltador Largo + li Alfil : Camaleón + li Dama : "Pushme-Pullyu" + li Rey : Rey (la misma pieza que el ajedrez estándar) +p. + Además, se agrega una nueva pieza: el inmovilizador, marcado 'M'. + Está representado por una torre invertida: + +figure.diagram-container + .diagram + | fen:91/91/91/5m4/91/91/91/4M5/91/91: + figcaption inmovilizadores en e3 y f7. + +p. + El tablero tiene un tamaño de 10x10 para facilitar la captura, como se + explicó abajo. + +h3 Jugadas sin captura + +p El rey se mueve (y captura) como de costumbre. + +p. + Los peones cañón se mueven sin capturar de dos maneras: + una casilla individual en cualquier dirección, o saltando sobre + una habitación adyacente para estar justo detrás de ella. + +figure.diagram-container + .diagram + | fen:91/91/91/91/4b5/4P5/5Q4/91/91/91 e4,d4,d5,d6,f6,f5,e7,g3: + figcaption Casillas donde el peón puede ir (sin capturar). + +p Todas las otras piezas se mueven como una dama ortodoxa. + +p. + Cuando una pieza está al lado de un inmovilizador enemigo, no puede moverse + pero es posible un movimiento especial de "suicidio": puede desaparecer en + "capturando" el inmovilizador (para activar el movimiento a través de la + interfaz). + +h3 Jugadas con captura + +p. + Como se dijo anteriormente, el rey captura como de costumbre. Sin embargo, + las otras piezas capturan de manera diferente, cuando pueden. + De hecho, el inmovilizador no puede capturar, y el intercambiador + solo se puede tomar una pieza contigua. En este caso deja el tablero después + de la captura + +p. + El borde del tablero solo se puede alcanzar mediante la captura, solo si + llegar aquí es la única forma de hacer una captura determinada, y si + no hay otras capturas con una longitud de trazo más corta. + La longitud de un trazo es la distancia entre los cuadros de inicio y + finalización. + +h4 Peones (Canon) + +p. + Los peones cañón capturan saltando sobre una pieza adyacente, + aterrizando en una pieza enemiga justo detrás. + +figure.diagram-container + .diagram + | fen:91/91/91/4p5/4b5/1rB1P5/5Q4/2p3q3/91/91 e7,g3: + figcaption Posibles capturas de peones. + +h4 "Pushme-Pullyu" (Dama) + +p. + La dama captura en la plaza ubicada justo después de la que se detiene + (suponiendo que la pieza de un oponente esté allí), y también en la caja + originalmente ubicado justo detrás de ella. Es una combinación del Retractor + y Avanzado, como se describe en la página de reglas de chessvariants + (ver enlace en la parte inferior de la página). + +figure.diagram-container + .diagram + | fen:91/91/91/91/2bQ4r1/91/91/91/91/91 h6: + figcaption. + Cualquier jugada a la derecha captura al alfil; solo un movimiento hasta + la marca también capturará la torre. + +h4 Torre (Intercambio) + +p. + La torre puede intercambiar su posición con cualquier pieza enemiga en + la línea de ataque de una dama ortodoxa, como se ilustra a continuación. + +figure.diagram-container + .diagram.diag12 + | fen:91/91/7k2/91/91/91/2n7/2R7/91/91: + .diagram.diag22 + | fen:91/91/7R2/91/91/91/2n7/2k7/91/91: + figcaption Antes y después del intercambio torre-rey. + +p. + Además, si la torre está al lado de una habitación opuesta (como el caballo + aquí), ella puede elegir capturarlo con un ataque "kamikaze": + de hecho, se autodestruye durante la acción. + +h4 Otras piezas + +p. + El Saltador Largo, el Inmovilizador y el Camaleón se comportan como en la + variante Barroca jugable en este sitio: consulte la descripción de las + reglas. Solo hay un cambio en las reglas de inmovilización, más + simple aquí: los poderes de los inmovilizadores no son cancelados por los + camaleones. + +h3 Fin de la partida + +p. + El juego termina con mate o empate como en el ajedrez estándar. + Solo presta atención a las reglas de captura :) + +h3 Fuente + +p + | La + a(href="https://www.chessvariants.com/other.dir/rococo.html") variante Rococó + |  en chessvariants.com. + +p Inventores: Peter Aronson y David Howe (2002) diff --git a/client/src/translations/rules/Rococo/fr.pug b/client/src/translations/rules/Rococo/fr.pug new file mode 100644 index 00000000..4446030b --- /dev/null +++ b/client/src/translations/rules/Rococo/fr.pug @@ -0,0 +1,139 @@ +p.boxed. + La plupart des pièces semblent familières mais se comportent différemment. + Elles se déplacent en général comme une dame orthodoxe, + mais capturent selon des règles assez complexes. + +p. + Cette variante provient d'une tentative de résoudre des problèmes avec la + variante baroque, qui "favorise la défense à l'attaque, et manque de clarté" + (cf. l'introduction des règles Rococo, en lien au bas de cette page). + +h4 Nom des pièces + +p. + Les noms des pièces se rapportent à leur manière de capturer, celle-ci étant + décrite plus bas. +ul + li Pion : Pion Canon + li Tour : Échangeur + li Cavalier : Sauteur Long + li Fou : Caméléon + li Dame : "Pushme-Pullyu" + li Roi : Roi (la même pièce qu'aux échecs standards) +p. + En outre, une nouvelle pièce est ajoutée : l'immobiliseur, noté 'M'. + Il est représenté par une tour inversée : + +figure.diagram-container + .diagram + | fen:91/91/91/5m4/91/91/91/4M5/91/91: + figcaption Immobiliseurs en e3 et f7. + +p. + L'échiquier est de taille 10x10 pour faciliter les captures, comme expliqué + ci-après. + +h3 Coups non capturants + +p Le roi se déplace (et capture) comme d'habitude. + +p. + Les Pions Canons se déplacent sans capturer de deux manières : soit d'une + seule case dans n'importe quelle direction, ou bien en sautant par dessus + une pièce adjacente pour se retrouver juste derrière elle. + +figure.diagram-container + .diagram + | fen:91/91/91/91/4b5/4P5/5Q4/91/91/91 e4,d4,d5,d6,f6,f5,e7,g3: + figcaption Cases où le pion peut aller (sans capturer). + +p Toutes les autres pièces se déplacent comme une dame orthodoxe. + +p. + Quand une pièce est à côté d'un immobiliseur ennemi, elle ne peut pas bouger + mais un coup spécial "de suicide" est possible : elle peut disparaître en + "capturant" l'immobiliseur (pour déclencher le coup via l'interface). + +h3 Coups capturants + +p. + Comme dit plus haut, le roi capture comme d'habitude. Cependant, les autres + pièces capturent différemment - quand elles le peuvent. + En effet l'immobiliseur ne peut pas capturer, et l'échangeur + ne peut prendre qu'une pièce adjacente. Dans ce cas il sort de l'échiquier + après la capture. + +p. + Le bord de l'échiquier ne peut être atteint que par capture, seulement si + arriver ici est l'unique façon d'effectuer une certaine prise, et s'il + n'existe pas d'autres telles captures avec une longueur de coup plus petite. + La longueur d'un coup est la distance entre les cases de départ et d'arrivée. + +h4 Pions (Cannons) + +p. + Les pions canons capturent en sautant par dessus une pièce adjacente, + atterissant sur une pièce ennemie juste derrière. + +figure.diagram-container + .diagram + | fen:91/91/91/4p5/4b5/1rB1P5/5Q4/2p3q3/91/91 e7,g3: + figcaption Possibles captures du pion. + +h4 "Pushme-Pullyu" (Dame) + +p. + La dame capture sur la case située juste après celle où elle s'arrête + (à supposer qu'une pièce adverse s'y trouve), et aussi sur la case + initialement située juste derrière elle. C'est une combinaison du Retireur + et de l'Avanceur, comme décrits sur la page des règles sur chessvariants + (voir le lien en bas de page). + +figure.diagram-container + .diagram + | fen:91/91/91/91/2bQ4r1/91/91/91/91/91 h6: + figcaption. + Tout coup vers la droite capture le fou ; seulement le coup sur la marque + capturera aussi la tour. + +h4 Tour (Échangeur) + +p. + La tour peut échanger sa position avec n'importe quelle pièce ennemie dans + la ligne d'attaque d'une dame orthodoxe, comme illustré ci-dessous. + +figure.diagram-container + .diagram.diag12 + | fen:91/91/7k2/91/91/91/2n7/2R7/91/91: + .diagram.diag22 + | fen:91/91/7R2/91/91/91/2n7/2k7/91/91: + figcaption Avant et après l'échange tour-roi. + +p. + De plus, si la tour se trouve à côté d'une pièce adverse (comme le cavalier + ici), elle peut choisir de le capturer par une attaque "kamikaze" : en + effet elle s'auto-détruit pendant l'action. + +h4 Autres pièces + +p. + Le Sauteur Long, l'Immobiliseur et le Caméléon se comportent comme dans la + variante Baroque jouable sur ce site : référez-vous au descriptif des règles. + Il n'y a qu'un seul changement au sujet des règles d'immobilisation, plus + simples ici : les pouvoirs des immobiliseurs ne sont pas annulés par les + caméléons. + +h3 Fin de la partie + +p. + La partie s'achève par mat ou pat comme aux échecs standards. + Faites juste attention aux règles de capture :) + +h3 Source + +p + La + a(href="https://www.chessvariants.com/other.dir/rococo.html") variante Rococo + |  sur chessvariants.com. + +p Inventeurs : Peter Aronson et David Howe (2002) diff --git a/client/src/utils/printDiagram.js b/client/src/utils/printDiagram.js index cc97939a..9ebdeed6 100644 --- a/client/src/utils/printDiagram.js +++ b/client/src/utils/printDiagram.js @@ -93,7 +93,7 @@ export function getDiagram(args) { "class='piece'/>"; } if (markArray.length > 0 && markArray[i][j]) - boardDiv += ""; + boardDiv += ""; boardDiv += ""; } boardDiv += ""; diff --git a/client/src/variants/Baroque.js b/client/src/variants/Baroque.js index 016f7b7b..74617cfb 100644 --- a/client/src/variants/Baroque.js +++ b/client/src/variants/Baroque.js @@ -293,12 +293,7 @@ export class BaroqueRules extends ChessRules { mergedMoves[key].vanish.push(m.vanish[i]); } }); - // Finally return an array - moves = []; - Object.keys(mergedMoves).forEach(k => { - moves.push(mergedMoves[k]); - }); - return moves; + return Object.values(mergedMoves); } addQueenCaptures(moves, byChameleon) { @@ -486,9 +481,10 @@ export class BaroqueRules extends ChessRules { V.OnBoard(i, j) && this.board[i][j] != V.EMPTY && this.getColor(i, j) == color && - this.getPiece(i, j) == V.BISHOP + this.getPiece(i, j) == V.BISHOP && + !this.isImmobilized([i, j]) ) { - return true; //bishops are never immobilized + return true; } } return false; @@ -532,26 +528,10 @@ export class BaroqueRules extends ChessRules { return false; } - static get VALUES() { - return { - p: 1, - r: 2, - n: 5, - b: 3, - q: 3, - m: 5, - k: 1000 - }; - } - - static get SEARCH_DEPTH() { - return 2; - } - static GenRandInitFen(randomness) { if (randomness == 0) // Deterministic: - return "rnbqkbnrm/pppppppp/8/8/8/8/PPPPPPPP/MNBKQBNR w 0"; + return "rnbkqbnm/pppppppp/8/8/8/8/PPPPPPPP/MNBQKBNR w 0"; let pieces = { w: new Array(8), b: new Array(8) }; // Shuffle pieces on first and last rank @@ -574,6 +554,22 @@ export class BaroqueRules extends ChessRules { ); } + static get VALUES() { + return { + p: 1, + r: 2, + n: 5, + b: 3, + q: 3, + m: 5, + k: 1000 + }; + } + + static get SEARCH_DEPTH() { + return 2; + } + getNotation(move) { const initialSquare = V.CoordsToSquare(move.start); const finalSquare = V.CoordsToSquare(move.end); diff --git a/client/src/variants/Rococo.js b/client/src/variants/Rococo.js new file mode 100644 index 00000000..8489ae90 --- /dev/null +++ b/client/src/variants/Rococo.js @@ -0,0 +1,714 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { shuffle } from "@/utils/alea"; + +export class RococoRules extends ChessRules { + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get PIECES() { + return ChessRules.PIECES.concat([V.IMMOBILIZER]); + } + + getPpath(b) { + if (b[1] == "m") + //'m' for Immobilizer (I is too similar to 1) + return "Rococo/" + b; + return b; //usual piece + } + + 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 m.appear[0].c + m.appear[0].p; + } + + setOtherVariables(fen) { + // No castling, but checks, so keep track of kings + 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)); + if (!isNaN(num)) k += num - 1; + } + } + k++; + } + } + // Local stack of swaps: + this.smoves = []; + const smove = V.ParseFen(fen).smove; + if (smove == "-") this.smoves.push(null); + else { + this.smoves.push({ + start: ChessRules.SquareToCoords(smove.substr(0, 2)), + end: ChessRules.SquareToCoords(smove.substr(2)) + }); + } + } + + static ParseFen(fen) { + return Object.assign( + ChessRules.ParseFen(fen), + { smove: fen.split(" ")[3] } + ); + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParts = fen.split(" "); + if (fenParts.length != 4) return false; + if (fenParts[3] != "-" && !fenParts[3].match(/^([a-h][1-8]){2}$/)) + return false; + return true; + } + + getSmove(move) { + if (move.appear.length == 2) + return { start: move.start, end: move.end }; + return null; + } + + static get size() { + // Add the "capturing edge" + return { x: 10, y: 10 }; + } + + static get IMMOBILIZER() { + return "m"; + } + // Although other pieces keep their names here for coding simplicity, + // keep in mind that: + // - a "rook" is a swapper, exchanging positions and "capturing" by + // mutual destruction only. + // - a "knight" is a long-leaper, capturing as in draughts + // - a "bishop" is a chameleon, capturing as its prey + // - a "queen" is a withdrawer+advancer, capturing by moving away from + // pieces or advancing in front of them. + + // Is piece on square (x,y) immobilized? + isImmobilized([x, y]) { + const piece = this.getPiece(x, y); + 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 + ) { + const oppPiece = this.getPiece(i, j); + if (oppPiece == V.IMMOBILIZER) return [i, j]; + // Only immobilizers are immobilized by chameleons: + if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER) return [i, j]; + } + } + return null; + } + + static OnEdge(x, y) { + return x == 0 || y == 0 || x == V.size.x - 1 || y == V.size.y - 1; + } + + getPotentialMovesFrom([x, y]) { + // Pre-check: is thing on this square immobilized? + const imSq = this.isImmobilized([x, y]); + if (!!imSq) { + // Only option is suicide: + return [ + new Move({ + start: { x: x, y: y }, + end: { x: imSq[0], y: imSq[1] }, + appear: [], + vanish: [ + new PiPo({ + x: x, + y: y, + c: this.getColor(x, y), + p: this.getPiece(x, y) + }) + ] + }) + ]; + } + let moves = []; + switch (this.getPiece(x, y)) { + case V.IMMOBILIZER: + moves = this.getPotentialImmobilizerMoves([x, y]); + break; + default: + moves = super.getPotentialMovesFrom([x, y]); + } + // Post-processing: prune redundant non-minimal capturing moves, + // and non-capturing moves ending on the edge: + moves.forEach(m => { + // Useful precomputation + m.dist = Math.abs(m.end.x - m.start.x) + Math.abs(m.end.y - m.start.y); + }); + return moves.filter(m => { + if (!V.OnEdge(m.end.x, m.end.y)) return true; + // End on the edge: + if (m.vanish.length == 1) return false; + // Capture or swap: only captures get filtered + if (m.appear.length == 2) return true; + // Can we find other moves with a shorter path to achieve the same + // capture? Apply to queens and knights. + if ( + moves.some(mv => { + return ( + mv.dist < m.dist && + mv.vanish.length == m.vanish.length && + mv.vanish.every(v => { + return m.vanish.some(vv => { + return ( + vv.x == v.x && vv.y == v.y && vv.c == v.c && vv.p == v.p + ); + }); + }) + ); + }) + ) { + return false; + } + return true; + }); + // NOTE: not removing "dist" field; shouldn't matter much... + } + + 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 king can take on occupied square: + if (piece == V.KING && 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 oppCol = V.GetOppCol(this.turn); + let moves = []; + 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])); + else { + // Try to leap over: + const [ii, jj] = [i + step[0], j + step[1]]; + if (V.OnBoard(ii, jj) && this.getColor(ii, jj) == oppCol) + moves.push(this.getBasicMove([x, y], [ii, jj])); + } + } + }); + return moves; + } + + // NOTE: not really captures, but let's keep the name + getRookCaptures([x, y], byChameleon) { + 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); + if (!byChameleon || oppPiece == V.ROOK) { + 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]) { + // 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; + } + + // Swapper + getPotentialRookMoves(sq) { + return super.getPotentialQueenMoves(sq).concat(this.getRookCaptures(sq)); + } + + getKnightCaptures(startSquare, 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]]; + 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 || + (!!byChameleon && this.getPiece(i, j) != V.KNIGHT) + ) { + continue; + } + // last(thing), cur(thing) : stop if "cur" is our color, + // or beyond board limits, or if "last" isn't empty and cur neither. + // Otherwise, if cur is empty then add move until cur square; + // if cur is occupied then stop if !!byChameleon and the square not + // occupied by a leaper. + let last = [i, j]; + let cur = [i + step[0], j + step[1]]; + let vanished = [new PiPo({ x: x, y: y, c: color, p: piece })]; + while (V.OnBoard(cur[0], cur[1])) { + if (this.board[last[0]][last[1]] != V.EMPTY) { + const oppPiece = this.getPiece(last[0], last[1]); + if (!!byChameleon && oppPiece != V.KNIGHT) continue outerLoop; + // Something to eat: + vanished.push( + new PiPo({ x: last[0], y: last[1], c: oppCol, p: oppPiece }) + ); + } + if (this.board[cur[0]][cur[1]] != V.EMPTY) { + if ( + this.getColor(cur[0], cur[1]) == color || + this.board[last[0]][last[1]] != V.EMPTY + ) { + //TODO: redundant test + continue outerLoop; + } + } else { + moves.push( + new Move({ + appear: [new PiPo({ x: cur[0], y: cur[1], c: color, p: piece })], + vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required? + start: { x: x, y: y }, + end: { x: cur[0], y: cur[1] } + }) + ); + } + last = [last[0] + step[0], last[1] + step[1]]; + cur = [cur[0] + step[0], cur[1] + step[1]]; + } + } + return moves; + } + + // Long-leaper + getPotentialKnightMoves(sq) { + return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq)); + } + + // Chameleon + getPotentialBishopMoves([x, y]) { + const oppCol = V.GetOppCol(this.turn); + let moves = super + .getPotentialQueenMoves([x, y]) + .concat(this.getKnightCaptures([x, y], "asChameleon")) + .concat(this.getRookCaptures([x, y], "asChameleon")); + // No "king capture" because king cannot remain under check + this.addQueenCaptures(moves, "asChameleon"); + // Also add pawn captures (as a pawn): + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + adjacentSteps.forEach(step => { + const [i, j] = [x + step[0], y + step[1]]; + const [ii, jj] = [i + step[0], j + step[1]]; + // Try to leap over (i,j): + if ( + V.OnBoard(ii, jj) && + this.board[i][j] != V.EMPTY && + this.board[ii][jj] != V.EMPTY && + this.getColor(ii, jj) == oppCol && + this.getPiece(ii, jj) == V.PAWN + ) { + moves.push(this.getBasicMove([x, y], [ii, jj])); + } + }); + // Post-processing: merge similar moves, concatenating vanish arrays + let mergedMoves = {}; + moves.forEach(m => { + const key = m.end.x + V.size.x * m.end.y; + if (!mergedMoves[key]) mergedMoves[key] = m; + else { + for (let i = 1; i < m.vanish.length; i++) + mergedMoves[key].vanish.push(m.vanish[i]); + } + }); + return Object.values(mergedMoves); + } + + addQueenCaptures(moves, byChameleon) { + 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 && + (!byChameleon || this.getPiece(i, j) == V.QUEEN) + ) { + capturingDirStart[step[0] + "_" + step[1]] = this.getPiece(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) { + const [i, j] = [x - step[0], y - step[1]]; + m.vanish.push( + new PiPo({ + x: i, + y: j, + p: capture, + 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 && + (!byChameleon || this.getPiece(i, j) == V.QUEEN) + ) { + m.vanish.push( + new PiPo({ + x: i, + y: j, + p: this.getPiece(i, j), + c: oppCol + }) + ); + } + }); + } + + // Withdrawer + advancer: "pushme-pullyu" + getPotentialQueenMoves(sq) { + let moves = super.getPotentialQueenMoves(sq); + this.addQueenCaptures(moves); + return moves; + } + + getPotentialImmobilizerMoves(sq) { + // Immobilizer doesn't capture + return super.getPotentialQueenMoves(sq); + } + + // Does m2 un-do m1 ? (to disallow undoing swaps) + oppositeMoves(m1, m2) { + return ( + !!m1 && + m2.appear.length == 2 && + m1.start.x == m2.start.x && + m1.end.x == m2.end.x && + m1.start.y == m2.start.y && + m1.end.y == m2.end.y + ); + } + + filterValid(moves) { + if (moves.length == 0) return []; + const color = this.turn; + return ( + super.filterValid( + moves.filter(m => { + const L = this.smoves.length; //at least 1: init from FEN + return !this.oppositeMoves(this.smoves[L - 1], m); + }) + ) + ); + } + + // isAttacked() is OK because the immobilizer doesn't take + + isAttackedByPawn([x, y], color) { + // Attacked if an enemy pawn stands just behind an immediate obstacle: + 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]]; + const [ii, jj] = [i + step[0], j + step[1]]; + if ( + V.OnBoard(ii, jj) && + this.board[i][j] != V.EMPTY && + this.board[ii][jj] != V.EMPTY && + this.getColor(ii, jj) == color && + this.getPiece(ii, jj) == V.PAWN && + !this.isImmobilized([ii, jj]) + ) { + return true; + } + } + return false; + } + + isAttackedByRook([x, y], color) { + // The only way a swapper can take is by mutual destruction when the + // enemy piece stands just next: + 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) == color && + this.getPiece(i, j) == V.ROOK && + !this.isImmobilized([i, j]) + ) { + return true; + } + } + return false; + } + + isAttackedByKnight([x, y], color) { + // Square (x,y) must be on same line as a knight, + // and there must be empty square(s) behind. + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + outerLoop: for (let step of steps) { + const [i0, j0] = [x + step[0], y + step[1]]; + if (V.OnBoard(i0, j0) && this.board[i0][j0] == V.EMPTY) { + // Try in opposite direction: + let [i, j] = [x - step[0], y - step[1]]; + while (V.OnBoard(i, j)) { + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i -= step[0]; + j -= step[1]; + } + if (V.OnBoard(i, j)) { + if (this.getColor(i, j) == color) { + if ( + this.getPiece(i, j) == V.KNIGHT && + !this.isImmobilized([i, j]) + ) + return true; + continue outerLoop; + } + // [else] Our color, + // could be captured *if there was an empty space* + if (this.board[i + step[0]][j + step[1]] != V.EMPTY) + continue outerLoop; + i -= step[0]; + j -= step[1]; + } + } + } + } + return false; + } + + isAttackedByBishop([x, y], color) { + // We cheat a little here: since this function is used exclusively for + // the king, it's enough to check the immediate surrounding of the square. + 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) == color && + this.getPiece(i, j) == V.BISHOP && + !this.isImmobilized([i, j]) + ) { + return true; + } + } + return false; + } + + isAttackedByQueen([x, y], color) { + // Is there a queen in view? + const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + for (let step of adjacentSteps) { + 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.getPiece(i, j) == V.QUEEN + ) { + // Two cases: the queen is at 2 steps at least, or just close + // but maybe with enough space behind to withdraw. + let attacked = false; + if (i == x + step[0] && j == y + step[1]) { + const [ii, jj] = [i + step[0], j + step[1]]; + if (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY) + attacked = true; + } + else attacked = true; + if (attacked && !this.isImmobilized([i, j])) return true; + } + } + return false; + } + + isAttackedByKing([x, y], color) { + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + if ( + V.OnBoard(rx, ry) && + this.getPiece(rx, ry) === V.KING && + this.getColor(rx, ry) == color && + !this.isImmobilized([rx, ry]) + ) { + return true; + } + } + return false; + } + + static GenRandInitFen(randomness) { + if (randomness == 0) { + return ( + "91/1rnbkqbnm1/1pppppppp1/91/91/91/91/1PPPPPPPP1/1MNBQKBNR1/91 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 = ['r', 'm', 'n', 'n', 'q', 'q', 'b', 'k']; + for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i]; + } + return ( + "91/1" + pieces["b"].join("") + + "1/1pppppppp1/91/91/91/91/1PPPPPPPP1/1" + + pieces["w"].join("").toUpperCase() + "1/91 w 0 -" + ); + } + + getSmoveFen() { + const L = this.smoves.length; + return ( + !this.smoves[L - 1] + ? "-" + : ChessRules.CoordsToSquare(this.smoves[L - 1].start) + + ChessRules.CoordsToSquare(this.smoves[L - 1].end) + ); + } + + getFen() { + return super.getFen() + " " + this.getSmoveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getSmoveFen(); + } + + postPlay(move) { + super.postPlay(move); + this.smoves.push(this.getSmove(move)); + } + + postUndo(move) { + super.postUndo(move); + this.smoves.pop(); + } + + static get VALUES() { + return { + p: 1, + r: 2, + n: 5, + b: 3, + q: 5, + m: 5, + 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) { + // Suicide 'S' or mutual destruction 'D': + return ( + initialSquare + (move.vanish.length == 1 ? "S" : "D" + finalSquare) + ); + } + let notation = undefined; + 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) + 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"; + return notation; + } +}; diff --git a/client/src/variants/Suction.js b/client/src/variants/Suction.js index aa34f6c7..4ddffbe4 100644 --- a/client/src/variants/Suction.js +++ b/client/src/variants/Suction.js @@ -144,13 +144,22 @@ export class SuctionRules extends ChessRules { return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "- -"; } - getFen() { + getCmoveFen() { const L = this.cmoves.length; - const cmoveFen = !this.cmoves[L - 1] - ? "-" - : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + - ChessRules.CoordsToSquare(this.cmoves[L - 1].end); - return super.getFen() + " " + cmoveFen; + return ( + !this.cmoves[L - 1] + ? "-" + : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + + ChessRules.CoordsToSquare(this.cmoves[L - 1].end) + ); + } + + getFen() { + return super.getFen() + " " + this.getCmoveFen(); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getCmoveFen(); } postPlay(move) {