From 2f8dce6a81063289d9d4cbca7971f80b1b194b84 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Tue, 31 Mar 2020 18:20:01 +0200 Subject: [PATCH] Add Rococo variant --- client/public/images/diag_mark.svg | 57 ++ client/public/images/mark.svg | 2 +- client/public/images/pieces/Rococo/bm.svg | 92 +++ client/public/images/pieces/Rococo/empty.svg | 1 + client/public/images/pieces/Rococo/wm.svg | 97 +++ client/public/images/pieces/SOURCE | 1 + client/src/translations/rules/Baroque/en.pug | 24 +- client/src/translations/rules/Baroque/es.pug | 14 +- client/src/translations/rules/Baroque/fr.pug | 14 +- client/src/translations/rules/Rococo/en.pug | 133 ++++ client/src/translations/rules/Rococo/es.pug | 142 ++++ client/src/translations/rules/Rococo/fr.pug | 139 ++++ client/src/utils/printDiagram.js | 2 +- client/src/variants/Baroque.js | 46 +- client/src/variants/Rococo.js | 714 +++++++++++++++++++ client/src/variants/Suction.js | 21 +- 16 files changed, 1440 insertions(+), 59 deletions(-) create mode 100644 client/public/images/diag_mark.svg create mode 100644 client/public/images/pieces/Rococo/bm.svg create mode 100644 client/public/images/pieces/Rococo/empty.svg create mode 100644 client/public/images/pieces/Rococo/wm.svg create mode 100644 client/src/translations/rules/Rococo/en.pug create mode 100644 client/src/translations/rules/Rococo/es.pug create mode 100644 client/src/translations/rules/Rococo/fr.pug create mode 100644 client/src/variants/Rococo.js 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Mark possible moves - vchess" + viewbox="0 0 599 599" + width="600" + height="600" + sodipodi:docname="diag_mark.svg" + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="960" + inkscape:window-height="1060" + id="namedview4" + showgrid="false" + inkscape:zoom="0.39333333" + inkscape:cx="310.16949" + inkscape:cy="300" + inkscape:window-x="0" + inkscape:window-y="20" + inkscape:window-maximized="0" + inkscape:current-layer="Mark possible moves - vchess" /> + <circle + id="mark_circle" + cx="300" + cy="300" + r="216" + style="fill:#9900cc;stroke-width:1.07999992" /> +</svg> 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="100%" + width="100%" + version="1.1" + viewBox="0 0 2048 2048" + id="svg44" + sodipodi:docname="bu.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"> + <metadata + id="metadata50"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs48" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="960" + inkscape:window-height="1060" + id="namedview46" + showgrid="false" + inkscape:zoom="0.11523438" + inkscape:cx="1041.3559" + inkscape:cy="1024" + inkscape:window-x="0" + inkscape:window-y="20" + inkscape:window-maximized="0" + inkscape:current-layer="svg44" /> + <path + style="color:#000000;display:block;fill:#000000;fill-rule:nonzero" + d="m 1161,1706 h 170 v 137 h 274 V 1468 L 1383,1297 V 819 L 1553,649 V 444 h 153 V 205 H 341 v 239 h 153 v 205 l 171,170 v 478 l -222,171 v 375 h 273 v -137 h 171 v 137 h 274 z M 564,460 V 358 h 920 v 102 z m 460,1092 H 512 v -46 l 73,-55 h 879 l 71,55 v 46 z m 0,-169 H 674 l 60,-47 v -57 h 580 v 57 l 60,47 z m 0,-546 H 734 v -46 l -60,-58 h 700 l -60,58 v 46 z m 0,-172 H 610 l -46,-43 v -58 h 920 v 58 l -46,43 z" + display="block" + id="path30" + inkscape:connector-curvature="0" /> + <g + id="g42" + transform="matrix(1,0,0,-1,0,2048)" + style="fill:#ffffff;fill-rule:nonzero"> + <path + style="color:#000000;display:block" + d="m 564,1588 v 102 h 920 v -102 z" + display="block" + id="path32" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1024,496 H 512 v 46 l 73,55 h 879 l 71,-55 v -46 z" + display="block" + id="path34" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1024,665 H 674 l 60,47 v 57 h 580 v -57 l 60,-47 z" + display="block" + id="path36" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1024,1211 H 734 v 46 l -60,58 h 700 l -60,-58 v -46 z" + display="block" + id="path38" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1024,1383 H 610 l -46,43 v 58 h 920 v -58 l -46,-43 z" + display="block" + id="path40" + inkscape:connector-curvature="0" /> + </g> +</svg> 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 @@ +<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="1" height="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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="100%" + width="100%" + version="1.1" + viewBox="0 0 2048 2048" + id="svg70" + sodipodi:docname="wu.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"> + <metadata + id="metadata76"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs74" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="960" + inkscape:window-height="1060" + id="namedview72" + showgrid="false" + inkscape:zoom="0.11523438" + inkscape:cx="1041.3559" + inkscape:cy="1024" + inkscape:window-x="0" + inkscape:window-y="20" + inkscape:window-maximized="0" + inkscape:current-layer="svg70" /> + <path + style="color:#000000;display:block;fill:#000000;fill-rule:nonzero" + d="m 1161,1706 h 170 v 137 h 274 V 1468 L 1383,1297 V 819 L 1553,649 V 444 h 153 V 205 H 341 v 239 h 153 v 205 l 171,170 v 478 l -222,171 v 375 h 273 v -137 h 171 v 137 h 274 z M 1639,376 H 409 V 273 H 1639 Z M 1484,580 H 564 V 444 h 920 z m -170,717 H 734 V 819 h 580 z m 222,239 v 239 h -137 v -137 h -308 v 137 H 956 V 1638 H 649 v 137 H 512 V 1536 Z M 1459,649 1356,751 H 693 L 588,649 Z m -110,716 127,103 H 572 l 128,-103 z" + display="block" + id="path54" + inkscape:connector-curvature="0" /> + <g + id="g64" + transform="matrix(1,0,0,-1,0,2048)" + style="fill:#ffffff;fill-rule:nonzero"> + <path + style="color:#000000;display:block" + d="M 1639,1672 H 409 v 103 h 1230 z" + display="block" + id="path56" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1484,1468 H 564 v 136 h 920 z" + display="block" + id="path58" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1314,751 H 734 v 478 h 580 z" + display="block" + id="path60" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block" + d="M 1536,512 V 273 H 1399 V 410 H 1091 V 273 H 956 V 410 H 649 V 273 H 512 v 239 z" + display="block" + id="path62" + inkscape:connector-curvature="0" /> + </g> + <path + style="color:#000000;display:block;fill:#ffffff;fill-rule:nonzero" + d="M 1459,649 1356,751 H 693 L 588,649 Z" + display="block" + id="path66" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;display:block;fill:#ffffff;fill-rule:nonzero" + d="m 1349,1365 127,103 H 572 l 128,-103 z" + display="block" + id="path68" + inkscape:connector-curvature="0" /> +</svg> 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 += "<img src='/images/mark.svg' class='mark-square'/>"; + boardDiv += "<img src='/images/diag_mark.svg' class='mark-square'/>"; boardDiv += "</div>"; } boardDiv += "</div>"; 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) { -- 2.44.0