Add Rococo variant
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 31 Mar 2020 16:20:01 +0000 (18:20 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 31 Mar 2020 16:20:01 +0000 (18:20 +0200)
16 files changed:
client/public/images/diag_mark.svg [new file with mode: 0644]
client/public/images/mark.svg
client/public/images/pieces/Rococo/bm.svg [new file with mode: 0644]
client/public/images/pieces/Rococo/empty.svg [new file with mode: 0644]
client/public/images/pieces/Rococo/wm.svg [new file with mode: 0644]
client/public/images/pieces/SOURCE
client/src/translations/rules/Baroque/en.pug
client/src/translations/rules/Baroque/es.pug
client/src/translations/rules/Baroque/fr.pug
client/src/translations/rules/Rococo/en.pug [new file with mode: 0644]
client/src/translations/rules/Rococo/es.pug [new file with mode: 0644]
client/src/translations/rules/Rococo/fr.pug [new file with mode: 0644]
client/src/utils/printDiagram.js
client/src/variants/Baroque.js
client/src/variants/Rococo.js [new file with mode: 0644]
client/src/variants/Suction.js

diff --git a/client/public/images/diag_mark.svg b/client/public/images/diag_mark.svg
new file mode 100644 (file)
index 0000000..555f214
--- /dev/null
@@ -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>
index b714067..9c1b930 100644 (file)
@@ -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 (file)
index 0000000..fdc0ee5
--- /dev/null
@@ -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 (file)
index 0000000..08ec906
--- /dev/null
@@ -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 (file)
index 0000000..bf9f16a
--- /dev/null
@@ -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>
index ada138c..0417e94 100644 (file)
@@ -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
index 1e94b81..5e5a752 100644 (file)
@@ -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
index c35c6fc..bde85c4 100644 (file)
@@ -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
index 8e4ac86..b2554fa 100644 (file)
@@ -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 (file)
index 0000000..7000378
--- /dev/null
@@ -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
+  | &nbsp;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 (file)
index 0000000..c60d870
--- /dev/null
@@ -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ó
+  | &nbsp;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 (file)
index 0000000..4446030
--- /dev/null
@@ -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
+  | &nbsp;sur chessvariants.com.
+
+p Inventeurs : Peter Aronson et David Howe (2002)
index cc97939..9ebdeed 100644 (file)
@@ -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>";
index 016f7b7..74617cf 100644 (file)
@@ -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 (file)
index 0000000..8489ae9
--- /dev/null
@@ -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;
+  }
+};
index aa34f6c..4ddffbe 100644 (file)
@@ -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) {