Some fixes, draw lines on board, add 7 variants
authorBenjamin Auder <benjamin.auder@somewhere>
Mon, 20 Apr 2020 22:57:50 +0000 (00:57 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Mon, 20 Apr 2020 22:57:50 +0000 (00:57 +0200)
58 files changed:
TODO
client/public/images/pieces/Absorption/ba.svg [new file with mode: 0644]
client/public/images/pieces/Absorption/be.svg [new file with mode: 0644]
client/public/images/pieces/Absorption/bs.svg [new file with mode: 0644]
client/public/images/pieces/Absorption/wa.svg [new file with mode: 0644]
client/public/images/pieces/Absorption/we.svg [new file with mode: 0644]
client/public/images/pieces/Absorption/ws.svg [new file with mode: 0644]
client/src/base_rules.js
client/src/components/BaseGame.vue
client/src/components/Board.vue
client/src/components/MoveList.vue
client/src/styles/_rules.sass
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Absorption/en.pug [new file with mode: 0644]
client/src/translations/rules/Absorption/es.pug [new file with mode: 0644]
client/src/translations/rules/Absorption/fr.pug [new file with mode: 0644]
client/src/translations/rules/Bicolour/en.pug [new file with mode: 0644]
client/src/translations/rules/Bicolour/es.pug [new file with mode: 0644]
client/src/translations/rules/Bicolour/fr.pug [new file with mode: 0644]
client/src/translations/rules/Coronation/en.pug [new file with mode: 0644]
client/src/translations/rules/Coronation/es.pug [new file with mode: 0644]
client/src/translations/rules/Coronation/fr.pug [new file with mode: 0644]
client/src/translations/rules/Football/en.pug [new file with mode: 0644]
client/src/translations/rules/Football/es.pug [new file with mode: 0644]
client/src/translations/rules/Football/fr.pug [new file with mode: 0644]
client/src/translations/rules/Gridolina/en.pug [new file with mode: 0644]
client/src/translations/rules/Gridolina/es.pug [new file with mode: 0644]
client/src/translations/rules/Gridolina/fr.pug [new file with mode: 0644]
client/src/translations/rules/Koth/en.pug
client/src/translations/rules/Koth/es.pug
client/src/translations/rules/Koth/fr.pug
client/src/translations/rules/Madrasi/en.pug [new file with mode: 0644]
client/src/translations/rules/Madrasi/es.pug [new file with mode: 0644]
client/src/translations/rules/Madrasi/fr.pug [new file with mode: 0644]
client/src/translations/rules/Teleport/en.pug [new file with mode: 0644]
client/src/translations/rules/Teleport/es.pug [new file with mode: 0644]
client/src/translations/rules/Teleport/fr.pug [new file with mode: 0644]
client/src/variants/Absorption.js [new file with mode: 0644]
client/src/variants/Ball.js
client/src/variants/Bicolour.js [new file with mode: 0644]
client/src/variants/Chakart.js
client/src/variants/Coronation.js [new file with mode: 0644]
client/src/variants/Dynamo.js
client/src/variants/Football.js [new file with mode: 0644]
client/src/variants/Gridolina.js [new file with mode: 0644]
client/src/variants/Koth.js
client/src/variants/Madrasi.js [new file with mode: 0644]
client/src/variants/Makruk.js
client/src/variants/Rococo.js
client/src/variants/Shatranj.js
client/src/variants/Shogi.js
client/src/variants/Sittuyin.js
client/src/variants/Teleport.js [new file with mode: 0644]
client/src/views/Hall.vue
client/src/views/MyGames.vue
server/db/populate.sql

diff --git a/TODO b/TODO
index c1197fa..2fc316b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,45 +1,31 @@
 Issue: embedded rules language not updated when language is set (in Analyse, Game and Problems)
 
-Chakart :)
-https://www.chessvariants.com/d.betza/chessvar/trapdoor.html
 https://www.chessvariants.com/crossover.dir/koopachess.html
 --> Can a stunned piece capture? Maybe not. ...recover? After 5 moves? Never?
-
-Bicolour Chess (Gabriel Authier, 1958). v1 et v2 : (Roméo Bédoni, 1958) 
-Kings are subject to check and checkmate by
-own as well as opponent’s pieces. The Q and
-QN are interchanged in the array
-.
-v2: y but a player may capture his
-own men (TODO: only v2?)
-
-Berolina Grid Chess, also known as
-Gridolina (originator not noted). A
-combination of Berolina and Grid Chess.
-Better than Grid Chess since Berolina pawns
-cross grid lines more easily. Described in
-World Game Review 10 as the most popular of
-the NOST combination games. (Nost-algia
-150, also Nost-algia 112 â€˜not seen’) 
---> pourquoi pas, mais faudra pouvoir tracer des lignes sur plateau (Ball, Koth, Sittuyin, celle-là, Rococo)
++ Chakart :)
 
 https://www.chessvariants.com/diffmove.dir/checkers.html --> move forward (Multhopp)
 in 1974 by Hans Multhopp
-https://www.chessvariants.com/diffmove.dir/checkers.html
+https://www.chessvariants.com/diffmove.dir/checkers.html --> "Forward"
 
+Clorange:
 Clockwork Orange Chess (Fergus Duniho,
 1999). https://www.chessvariants.com/other.dir/clockworkorange.html
 implem : pieces code, yellow/red, easy
 
-http://abrobecker.free.fr/chess/fairyblitz.htm#football
-Le gagnant est le premier joueur Ã  marquer un but, càd celui qui arrive Ã  installer une de ses pièces dans les cages adverses: d8,e8 pour les blancs et d1,e1 pour les noirs.
+https://www.chessvariants.com/difftaking.dir/replacement.html
+
+https://www.chessvariants.com/other.dir/pocket.html
+https://www.chessvariants.com/other.dir/fugue.html
+https://www.chessvariants.com/rules/spartan-chess
+https://www.chessvariants.com/mvopponent.dir/avalanche.html
+
+https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html
+https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html
 
-Recycle1 et Recycle2 (--> celle-là)
-http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants
-Philippe Rouzaud, Phénix 151-152, mai 2006): Un camp peut, Ã  la place d'un coup orthodoxe, capturer une de ses pièces et replacer la pièce capturée immédiatement sur l'échiquier. Un roi peut rester en Ã©chec durant cette action. Une pièce déplacée sur l'échiquier peut mater. Un pion ne peut pas Ãªtre déplacé en première ou dernière rangée. Un roi peut déplacer et Ãªtre déplacé, y compris pour se soustraire Ã  un Ã©chec. Le roque ne peut se faire que de manière orthodoxe.
-Rouzaud-Banaddou: 1.Fxb2 (=f5) Dxc7 (=e6) 2.fxe6 dxe6 3.Txb1 (=b5) Dxe7 (=c7) 4.Txb2 (=a3) Dxf7 (=d6) 5.Txg1 (=g5) Dxg7 (=f6) 6.Txf1 (=f7)+ Rxf8 (=h4) 7.Fxb2 (=e8) Rxg7 (=c5) 8.Rxd1 (=f8)+ Rxh8 (=g7) 9.Dxg8+ Txg8 10.Txg8#
---> Implémenté comme Dynamo, déplacement d'une pièce Ã©ventuellement avec self-capture, puis capture (sur case vide), forcée si premier coup illégal.
+=====
 
-http://abrobecker.free.fr/chess/fairyblitz.htm#madrasi
-Madrasi Chess, Abdul J. Karwathar, 1979): Deux pièces de même nature (excepté les rois) qui s'observent, se paralysent mutuellement en perdant tout pouvoir (déplacement, prise, donner Ã©chec ou mat) sauf celui de paralyser une autre pièce.
-1.e4 e5 2.Cf3 Cc6 3.Fc4 Fc5 4.Cxe5 d6 5.d4 (5.Fxf7+ Rxf7 est légal; ou 5.Dh5 Fe6 immobilisant le Fc4) 5...Fxd4 6.Dxd4 dxe5 et la Dame blanche est perdue.
+weiqi, go13, go9, gomoku, reversi
+avalam, qoridor, xiangqi, draughts, draughts8
+(puis quand hexaboards peut-être: hexavariants + Hex)
+Byo-yomi possible: 1h+b15,5m (15 pierres 5 minutes)
diff --git a/client/public/images/pieces/Absorption/ba.svg b/client/public/images/pieces/Absorption/ba.svg
new file mode 100644 (file)
index 0000000..0215b87
--- /dev/null
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<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:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="26"
+   width="26"
+   version="1.0"
+   id="svg25"
+   sodipodi:docname="Chess_Adt26.svg"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
+  <defs
+     id="defs29">
+    <filter
+       style="color-interpolation-filters:sRGB;"
+       inkscape:label="Colorize"
+       id="filter283">
+      <feComposite
+         in2="SourceGraphic"
+         operator="arithmetic"
+         k1="0"
+         k2="1"
+         result="composite1"
+         id="feComposite269" />
+      <feColorMatrix
+         in="composite1"
+         values="1"
+         type="saturate"
+         result="colormatrix1"
+         id="feColorMatrix271" />
+      <feFlood
+         flood-opacity="1"
+         flood-color="rgb(26,23,21)"
+         result="flood1"
+         id="feFlood273" />
+      <feBlend
+         in="flood1"
+         in2="colormatrix1"
+         mode="multiply"
+         result="blend1"
+         id="feBlend275" />
+      <feBlend
+         in2="blend1"
+         mode="screen"
+         result="blend2"
+         id="feBlend277" />
+      <feColorMatrix
+         in="blend2"
+         values="1"
+         type="saturate"
+         result="colormatrix2"
+         id="feColorMatrix279" />
+      <feComposite
+         in="colormatrix2"
+         in2="SourceGraphic"
+         operator="in"
+         k2="1"
+         result="composite2"
+         id="feComposite281" />
+    </filter>
+    <filter
+       style="color-interpolation-filters:sRGB;"
+       inkscape:label="Colorize"
+       id="filter299">
+      <feComposite
+         in2="SourceGraphic"
+         operator="arithmetic"
+         k1="0"
+         k2="1"
+         result="composite1"
+         id="feComposite285" />
+      <feColorMatrix
+         in="composite1"
+         values="1"
+         type="saturate"
+         result="colormatrix1"
+         id="feColorMatrix287" />
+      <feFlood
+         flood-opacity="1"
+         flood-color="rgb(26,23,21)"
+         result="flood1"
+         id="feFlood289" />
+      <feBlend
+         in="flood1"
+         in2="colormatrix1"
+         mode="multiply"
+         result="blend1"
+         id="feBlend291" />
+      <feBlend
+         in2="blend1"
+         mode="screen"
+         result="blend2"
+         id="feBlend293" />
+      <feColorMatrix
+         in="blend2"
+         values="1"
+         type="saturate"
+         result="colormatrix2"
+         id="feColorMatrix295" />
+      <feComposite
+         in="colormatrix2"
+         in2="SourceGraphic"
+         operator="in"
+         k2="1"
+         result="composite2"
+         id="feComposite297" />
+    </filter>
+  </defs>
+  <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="namedview27"
+     showgrid="false"
+     inkscape:zoom="25.673415"
+     inkscape:cx="13.220339"
+     inkscape:cy="13"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg25" />
+  <metadata
+     id="metadata2">
+    <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>
+  <g
+     transform="matrix(0.75757574,0,0,0.75757574,2.8791515,-7.9393932)"
+     id="g10"
+     style="filter:url(#filter283)">
+    <path
+       stroke-linejoin="miter"
+       d="m14.44,29.5c0.222,1.68-3.09,4.06-4.5,5-1.73,1.16-1.74,2.7-3,2.5-0.602-0.545,0.817-2.02,0-2-0.58,0,0.19,1.1-0.5,1.5-0.58,0-2.5,0.4-2.5-2.5,0-1.2,3.5-6.5,3.5-6.5s0.934-1.08,1-2c-0.419-0.575-0.258-1.48,0-2,0.365-0.731,1.5,1.5,1.5,1.5h1s0.5-1.5,1.5-2c0.517-0.258,0.5,2,0.5,2,6.07,0.578,9.29,4.37,9,16.5h-12.5c0-4.9,4.8-3.7,4.5-10.5"
+       fill-rule="evenodd"
+       stroke="#000"
+       stroke-linecap="round"
+       stroke-miterlimit="4"
+       stroke-dasharray="none"
+       stroke-width="1.31999993"
+       fill="#FFF"
+       id="path4" />
+    <path
+       fill="#000"
+       d="m9,23.5a0.5,0.5,0,1,1,-1,0,0.5,0.5,0,1,1,1,0z"
+       transform="translate(-3.4999999,10)"
+       id="path6" />
+    <path
+       fill="#000"
+       d="m9.6058,30.28a1.0281,1.4729,30,0,1,-1.7807,-1.028,1.0281,1.4729,30,0,1,1.7807,1.028z"
+       id="path8" />
+  </g>
+  <g
+     transform="translate(0,-4)"
+     id="g23"
+     style="filter:url(#filter299)">
+    <path
+       stroke-linejoin="round"
+       d="m18,18,4-8-4,4,0-6-3,5.125-2-5-2,5l-3-5.125v6l-4-4,3,8s2.0349-2.5,6-2.5,5,2.5,5,2.5z"
+       fill-rule="evenodd"
+       stroke="#000"
+       stroke-linecap="butt"
+       stroke-miterlimit="4"
+       stroke-dasharray="none"
+       stroke-width="0.80000001"
+       fill="#FFF"
+       id="path12" />
+    <path
+       id="path3804"
+       stroke-linejoin="miter"
+       d="m5.5,10a1.5,1.5,0,0,1,-3,0,1.5,1.5,0,1,1,3,0z"
+       stroke-dashoffset="0"
+       stroke="#000"
+       stroke-linecap="butt"
+       stroke-miterlimit="4"
+       stroke-dasharray="none"
+       stroke-width="0.75"
+       fill="#FFF" />
+    <use
+       xlink:href="#path3804"
+       transform="translate(4,-2)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use15" />
+    <use
+       xlink:href="#path3804"
+       transform="translate(9,-2)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use17" />
+    <use
+       xlink:href="#path3804"
+       transform="matrix(-1,0,0,1,26,0)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use19" />
+    <use
+       xlink:href="#path3804"
+       transform="matrix(-1,0,0,1,22,-2)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use21" />
+  </g>
+</svg>
diff --git a/client/public/images/pieces/Absorption/be.svg b/client/public/images/pieces/Absorption/be.svg
new file mode 100644 (file)
index 0000000..fd54801
--- /dev/null
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<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"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 2405546, 2018-03-11"
+   version="1.0"
+   sodipodi:docname="bm.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective14"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.0000004"
+     inkscape:cx="-3.9305949"
+     inkscape:cy="32.150177"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1180"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <g
+       id="g3481"
+       transform="translate(0,-0.3093592)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3127"
+         d="M 34,35.130223 L 31,32.130223 L 14,32.130223 L 11,35.130223"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         id="g3476">
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 11,35.130223 L 11,40.130223 L 15,40.130223 L 15,38.130223 L 20,38.130223 L 20,40.130223 L 25,40.130223 L 25,38.130223 L 30,38.130223 L 30,40.130223 L 34,40.130223 L 34,35.130223"
+           id="path3129"
+           sodipodi:nodetypes="cccccccccccc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 31.552703,32.417761 L 31.552703,30.302822 L 13.447297,30.302822 L 13.447297,32.417761"
+           id="path3125"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="M 11,35.130223 L 34,35.130223"
+           id="path5175" />
+      </g>
+    </g>
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.50000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+    <path
+       style="fill:none;fill-opacity:0.75000000000000000;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="stroke-width:0.12499999"
+       d="m 13.619404,13.174823 c -0.394017,-0.394017 -0.07405,-1.518272 0.664899,-2.336195 0.472422,-0.522914 0.760886,-0.615838 1.193319,-0.384407 0.55498,0.297016 0.02426,2.108462 -0.783498,2.67424 -0.332984,0.233231 -0.864904,0.256177 -1.07472,0.04636 z"
+       id="path25"
+       inkscape:connector-curvature="0" />
+    <path
+       style="stroke-width:0.12499999;fill:#ffffff"
+       d="m 13.619404,13.174823 c -0.394017,-0.394017 -0.07405,-1.518272 0.664899,-2.336195 0.472422,-0.522914 0.760886,-0.615838 1.193319,-0.384407 0.55498,0.297016 0.02426,2.108462 -0.783498,2.67424 -0.332984,0.233231 -0.864904,0.256177 -1.07472,0.04636 z"
+       id="path27"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#ffffff;stroke-width:0.12499999"
+       d="M 9.7284769,19.995465 C 9.4282138,19.613743 9.4216123,19.566478 9.6106193,19.151653 c 0.4273267,-0.937881 1.8246537,-0.466056 1.6234227,0.54817 -0.132757,0.669112 -1.067423,0.852649 -1.5055651,0.295642 z"
+       id="path29"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#000000;stroke-width:0.12499999"
+       d="m 15.741855,28.543572 c 0.220099,-1.274598 0.748632,-2.099833 2.525183,-3.942745 0.908491,-0.942427 1.837053,-1.984211 2.063469,-2.315073 0.967777,-1.414215 1.459247,-3.035319 1.585296,-5.229071 0.04816,-0.838108 0.158833,-1.500805 0.313986,-1.880033 0.240047,-0.586729 0.316295,-1.54212 0.143951,-1.803729 -0.156549,-0.237632 -0.593727,-0.307477 -0.832366,-0.13298 -0.136596,0.09988 -0.268141,0.423918 -0.329394,0.811403 -0.06397,0.40468 -0.269976,0.898212 -0.552737,1.32421 -0.616097,0.928189 -2.618232,2.816246 -4.187887,3.949269 -1.62137,1.170351 -2.353141,1.832373 -3.212442,2.90625 -0.755071,0.943617 -1.048909,1.097233 -0.66705,0.34873 0.513068,-1.005698 0.229767,-1.75498 -0.663552,-1.75498 -0.62307,0 -0.958908,0.350228 -0.958908,1 v 0.5 h -0.693468 c -0.5844284,0 -0.7403493,-0.05058 -0.9916167,-0.321708 -0.2968137,-0.320266 -0.5649148,-1.236132 -0.5649148,-1.929817 0,-0.476339 0.3438318,-1.335582 1.2999005,-3.248475 1.178757,-2.358448 2.893963,-5.320082 3.709346,-6.404903 0.897668,-1.1942942 1.247717,-2.1583929 1.00137,-2.7579542 -0.09031,-0.2198034 -0.193544,-0.5683925 -0.229404,-0.7746425 -0.06507,-0.3742752 -0.06469,-0.3745422 0.200365,-0.138169 0.146061,0.130257 0.440822,0.5099445 0.655025,0.84375 l 0.389458,0.6069189 h 1.172364 1.172363 l 0.305041,-0.5937499 c 0.353086,-0.6872679 1.014018,-1.5036985 1.127519,-1.3927915 0.04275,0.041777 0.112612,0.4494412 0.155239,0.9059202 0.04263,0.456479 0.139695,0.8814382 0.215708,0.9443539 0.07601,0.062916 0.700705,0.2099987 1.388205,0.3268512 2.952147,0.5017683 5.182687,1.6247709 6.373431,3.2088089 2.595633,3.452956 3.812697,8.42151 3.813765,15.569356 l 3.03e-4,2.03125 h -7.920436 -7.920434 z m -4.647451,-8.343749 c 0.318616,-0.318617 0.326565,-1.002752 0.0161,-1.386153 -0.460686,-0.568922 -1.5082252,-0.285298 -1.6950147,0.458931 -0.082225,0.32761 0.1938501,0.987469 0.4575301,1.09356 0.3816186,0.153543 0.9834636,0.07158 1.2213796,-0.166338 z m 3.746555,-7.078783 c 0.883721,-0.743602 1.303705,-2.321731 0.725632,-2.726629 -0.397788,-0.278621 -0.772927,-0.240934 -1.202877,0.120846 -0.480899,0.404649 -1.01931,1.483794 -1.01931,2.043017 0,0.878253 0.775188,1.169756 1.496555,0.562766 z"
+       id="path33"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#000000;stroke-width:0.12499999"
+       d="m 13.531904,33.449822 0.868295,-0.875 h 8.131705 8.131704 l 0.868295,0.875 0.868295,0.875 h -9.868294 -9.868295 z"
+       id="path39"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 11.374999,36.875 33.124998,37"
+       id="path41"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/public/images/pieces/Absorption/bs.svg b/client/public/images/pieces/Absorption/bs.svg
new file mode 100644 (file)
index 0000000..afc27f0
--- /dev/null
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<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"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+   version="1.0"
+   sodipodi:docname="Chess_adt45.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4.0000002"
+     inkscape:cx="70.546042"
+     inkscape:cy="-5.7955825"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="true"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <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>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <path
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 36,36 C 32.614745,35.027671 25.885256,36.430821 22.5,34 C 19.114744,36.430821 12.385255,35.027671 9,36 C 9,36 7.354102,36.541507 6,38 C 6.677051,38.972328 7.645898,38.986164 9,38.5 C 12.385255,37.527672 19.114744,38.958493 22.5,37.5 C 25.885256,38.958493 32.614745,37.527672 36,38.5 C 37.354102,38.986164 38.322949,38.972328 39,38 C 37.645898,36.055343 36,36 36,36 z"
+       id="path4582"
+       sodipodi:nodetypes="ccccccccc" />
+    <path
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,32 C 27.5,34.5 17.5,34.5 15,32 C 14.5,30.5 15,30 15,30 L 30,30 C 30,30 30.5,30.5 30,32 z"
+       id="path4584"
+       sodipodi:nodetypes="ccccc" />
+    <path
+       style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)"
+       style="fill:#000000;fill-opacity:1">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.50000048000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+    <path
+       style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 15,30 L 30,30"
+       id="path3369" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/public/images/pieces/Absorption/wa.svg b/client/public/images/pieces/Absorption/wa.svg
new file mode 100644 (file)
index 0000000..beeaaea
--- /dev/null
@@ -0,0 +1,25 @@
+<!-- Created with Inkscape (http://www.inkscape.org/) -->\r\r
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="26" width="26" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">\r\r
+ <metadata>\r\r
+  <rdf:RDF>\r\r
+   <cc:Work rdf:about="">\r\r
+    <dc:format>image/svg+xml</dc:format>\r\r
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\r\r
+    <dc:title/>\r\r
+   </cc:Work>\r\r
+  </rdf:RDF>\r\r
+ </metadata>\r\r
+ <g transform="matrix(0.75757574,0,0,0.75757574,2.8791515,-7.9393932)">\r\r
+  <path stroke-linejoin="miter" d="m14.44,29.5c0.222,1.68-3.09,4.06-4.5,5-1.73,1.16-1.74,2.7-3,2.5-0.602-0.545,0.817-2.02,0-2-0.58,0,0.19,1.1-0.5,1.5-0.58,0-2.5,0.4-2.5-2.5,0-1.2,3.5-6.5,3.5-6.5s0.934-1.08,1-2c-0.419-0.575-0.258-1.48,0-2,0.365-0.731,1.5,1.5,1.5,1.5h1s0.5-1.5,1.5-2c0.517-0.258,0.5,2,0.5,2,6.07,0.578,9.29,4.37,9,16.5h-12.5c0-4.9,4.8-3.7,4.5-10.5" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="1.31999993" fill="#FFF"/>\r\r
+  <path fill="#000" d="m9,23.5a0.5,0.5,0,1,1,-1,0,0.5,0.5,0,1,1,1,0z" transform="translate(-3.4999999,10)"/>\r\r
+  <path fill="#000" d="m9.6058,30.28a1.0281,1.4729,30,0,1,-1.7807,-1.028,1.0281,1.4729,30,0,1,1.7807,1.028z"/>\r\r
+ </g>\r\r
+ <g transform="translate(0,-4)">\r\r
+  <path stroke-linejoin="round" d="m18,18,4-8-4,4,0-6-3,5.125-2-5-2,5l-3-5.125v6l-4-4,3,8s2.0349-2.5,6-2.5,5,2.5,5,2.5z" fill-rule="evenodd" stroke="#000" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="0.80000001" fill="#FFF"/>\r\r
+  <path id="path3804" stroke-linejoin="miter" d="m5.5,10a1.5,1.5,0,0,1,-3,0,1.5,1.5,0,1,1,3,0z" stroke-dashoffset="0" stroke="#000" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="0.75" fill="#FFF"/>\r\r
+  <use xlink:href="#path3804" transform="translate(4,-2)" height="26" width="26" y="0" x="0"/>\r\r
+  <use xlink:href="#path3804" transform="translate(9,-2)" height="26" width="26" y="0" x="0"/>\r\r
+  <use xlink:href="#path3804" transform="matrix(-1,0,0,1,26,0)" height="26" width="26" y="0" x="0"/>\r\r
+  <use xlink:href="#path3804" transform="matrix(-1,0,0,1,22,-2)" height="26" width="26" y="0" x="0"/>\r\r
+ </g>\r\r
+</svg>\r\r
diff --git a/client/public/images/pieces/Absorption/we.svg b/client/public/images/pieces/Absorption/we.svg
new file mode 100644 (file)
index 0000000..fd0288d
--- /dev/null
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<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"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+   version="1.0"
+   sodipodi:docname="Chess_clt45.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective14"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.0000004"
+     inkscape:cx="-4.1805949"
+     inkscape:cy="32.150177"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="false"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <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>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <g
+       id="g3481"
+       transform="translate(0,-0.3093592)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3127"
+         d="M 34,35.130223 L 31,32.130223 L 14,32.130223 L 11,35.130223"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         id="g3476">
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 11,35.130223 L 11,40.130223 L 15,40.130223 L 15,38.130223 L 20,38.130223 L 20,40.130223 L 25,40.130223 L 25,38.130223 L 30,38.130223 L 30,40.130223 L 34,40.130223 L 34,35.130223"
+           id="path3129"
+           sodipodi:nodetypes="cccccccccccc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 31.552703,32.417761 L 31.552703,30.302822 L 13.447297,30.302822 L 13.447297,32.417761"
+           id="path3125"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="M 11,35.130223 L 34,35.130223"
+           id="path5175" />
+      </g>
+    </g>
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.50000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+    <path
+       style="fill:none;fill-opacity:0.75000000000000000;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/public/images/pieces/Absorption/ws.svg b/client/public/images/pieces/Absorption/ws.svg
new file mode 100644 (file)
index 0000000..b45ea50
--- /dev/null
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<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"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+   version="1.0"
+   sodipodi:docname="Chess_alt45.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.0000001"
+     inkscape:cx="59.308779"
+     inkscape:cy="58.114787"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="false"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <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>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 36,36 C 32.614745,35.027671 25.885256,36.430821 22.5,34 C 19.114744,36.430821 12.385255,35.027671 9,36 C 9,36 7.354102,36.541507 6,38 C 6.677051,38.972328 7.645898,38.986164 9,38.5 C 12.385255,37.527672 19.114744,38.958493 22.5,37.5 C 25.885256,38.958493 32.614745,37.527672 36,38.5 C 37.354102,38.986164 38.322949,38.972328 39,38 C 37.645898,36.055343 36,36 36,36 z"
+       id="path4582"
+       sodipodi:nodetypes="ccccccccc" />
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,32 C 27.5,34.5 17.5,34.5 15,32 C 14.5,30.5 15,30 15,30 L 30,30 C 30,30 30.5,30.5 30,32 z"
+       id="path4584"
+       sodipodi:nodetypes="ccccc" />
+    <path
+       style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.50000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
index 53001f6..99de626 100644 (file)
@@ -93,6 +93,25 @@ export const ChessRules = class ChessRules {
     return V.CanFlip;
   }
 
+  // For (generally old) variants without checkered board
+  static get Monochrome() {
+    return false;
+  }
+
+  // Some variants require lines drawing
+  static get Lines() {
+    if (V.Monochrome) {
+      let lines = [];
+      // Draw all inter-squares lines
+      for (let i = 0; i <= V.size.x; i++)
+        lines.push([[i, 0], [i, V.size.y]]);
+      for (let j = 0; j <= V.size.y; j++)
+        lines.push([[0, j], [V.size.x, j]]);
+      return lines;
+    }
+    return null;
+  }
+
   // Some variants use click infos:
   doClick() {
     return null;
@@ -998,9 +1017,8 @@ export const ChessRules = class ChessRules {
         if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
           const moves = this.getPotentialMovesFrom([i, j]);
           if (moves.length > 0) {
-            for (let k = 0; k < moves.length; k++) {
+            for (let k = 0; k < moves.length; k++)
               if (this.filterValid([moves[k]]).length > 0) return true;
-            }
           }
         }
       }
@@ -1042,21 +1060,15 @@ export const ChessRules = class ChessRules {
   }
 
   // Is square x,y attacked by 'color' pawns ?
-  isAttackedByPawn([x, y], color) {
+  isAttackedByPawn(sq, color) {
     const pawnShift = (color == "w" ? 1 : -1);
-    if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
-      for (let i of [-1, 1]) {
-        if (
-          y + i >= 0 &&
-          y + i < V.size.y &&
-          this.getPiece(x + pawnShift, y + i) == V.PAWN &&
-          this.getColor(x + pawnShift, y + i) == color
-        ) {
-          return true;
-        }
-      }
-    }
-    return false;
+    return this.isAttackedBySlideNJump(
+      sq,
+      color,
+      V.PAWN,
+      [[pawnShift, 1], [pawnShift, -1]],
+      "oneStep"
+    );
   }
 
   // Is square x,y attacked by 'color' rooks ?
index e309c5d..70a304b 100644 (file)
@@ -55,7 +55,7 @@ div#baseGame
         @showrules="showRules"
         @analyze="toggleAnalyze"
         @goto-move="gotoMove"
-        @reset-arrows="resetArrows"
+        @redraw-board="redrawBoard"
       )
     .clearer
 </template>
@@ -200,9 +200,8 @@ export default {
       if (e.deltaY < 0) this.undo();
       else if (e.deltaY > 0) this.play();
     },
-    resetArrows: function() {
-      // TODO: make arrows scale with board, and remove this
-      this.$refs["board"].cancelResetArrows();
+    redrawBoard: function() {
+      this.$refs["board"].re_setDrawings();
     },
     showRules: function() {
       // The button is here only on Game page:
index 8a42d34..29c0064 100644 (file)
@@ -22,10 +22,11 @@ export default {
       mobileBrowser: ("ontouchstart" in window),
       possibleMoves: [], //filled after each valid click/dragstart
       choices: [], //promotion pieces, or checkered captures... (as moves)
+      containerPos: null,
       selectedPiece: null, //moving piece (or clicked piece)
       start: null, //pixels coordinates + id of starting square (click or drag)
       startArrow: null,
-      movingArrow: { x: -1, y: -1 },
+      movingArrow: null,
       arrows: [], //object of {start: x,y / end: x,y}
       circles: {}, //object of squares' ID --> true (TODO: use a set?)
       click: "",
@@ -36,11 +37,10 @@ export default {
   render(h) {
     if (!this.vr) {
       // Return empty div of class 'game' to avoid error when setting size
-      return h("div", {
-        class: {
-          game: true
-        }
-      });
+      return h(
+        "div",
+        { "class": { game: true } }
+      );
     }
     const [sizeX, sizeY] = [V.size.x, V.size.y];
     // Precompute hints squares to facilitate rendering
@@ -100,6 +100,7 @@ export default {
     const gameDiv = h(
       "div",
       {
+        attrs: { id: "gamePosition" },
         "class": {
           game: true,
           clearer: true
@@ -173,8 +174,8 @@ export default {
                 "class": {
                   board: true,
                   ["board" + sizeY]: true,
-                  "light-square": lightSquare,
-                  "dark-square": !lightSquare,
+                  "light-square": lightSquare && !V.Monochrome,
+                  "dark-square": !lightSquare || !!V.Monochrome,
                   [this.settings.bcolor]: true,
                   "in-shadow": inShadow(ci, cj),
                   "highlight-light": inHighlight(ci, cj) && lightSquare,
@@ -197,6 +198,8 @@ export default {
     if (!!this.vr.reserve) {
       const playingColor = this.userColor || "w"; //default for an observer
       const shiftIdx = playingColor == "w" ? 0 : 1;
+      // Some variants have more than sizeY reserve pieces (Clorange: 10)
+      const reserveSquareNb = Math.max(sizeY, V.RESERVE_PIECES.length);
       let myReservePiecesArray = [];
       for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
         const qty = this.vr.reserve[playingColor][V.RESERVE_PIECES[i]];
@@ -204,7 +207,7 @@ export default {
           h(
             "div",
             {
-              "class": { board: true, ["board" + sizeY]: true },
+              "class": { board: true, ["board" + reserveSquareNb]: true },
               attrs: { id: getSquareId({ x: sizeX + shiftIdx, y: i }) },
               style: { opacity: qty > 0 ? 1 : 0.35 }
             },
@@ -231,7 +234,7 @@ export default {
           h(
             "div",
             {
-              "class": { board: true, ["board" + sizeY]: true },
+              "class": { board: true, ["board" + reserveSquareNb]: true },
               attrs: { id: getSquareId({ x: sizeX + (1 - shiftIdx), y: i }) },
               style: { opacity: qty > 0 ? 1 : 0.35 }
             },
@@ -256,7 +259,8 @@ export default {
       );
       // Center reserves, assuming same number of pieces for each side:
       const nbReservePieces = myReservePiecesArray.length;
-      const marginLeft = ((100 - nbReservePieces * (100 / sizeY)) / 2) + "%";
+      const marginLeft =
+        ((100 - nbReservePieces * (100 / reserveSquareNb)) / 2) + "%";
       const reserveTop =
         h(
           "div",
@@ -311,12 +315,10 @@ export default {
     }
     elementArray.push(gameDiv);
     if (!!this.vr.reserve) elementArray.push(reserveBottom);
-    const boardElt = document.querySelector(".game");
-    // boardElt might be undefine (at first drawing),
-    // but it won't be used in this case.
-    const squareWidth = (!!boardElt ? boardElt.offsetWidth / sizeY : 42);
+    const boardElt = document.getElementById("gamePosition");
+    // boardElt might be undefine (at first drawing)
     if (this.choices.length > 0 && !!boardElt) {
-      // No choices to show at first drawing
+      const squareWidth = boardElt.offsetWidth / sizeY;
       const offset = [boardElt.offsetTop, boardElt.offsetLeft];
       const maxNbeltsPerRow = Math.min(this.choices.length, sizeY);
       let topOffset = offset[0] + (sizeY / 2) * squareWidth - squareWidth / 2;
@@ -391,98 +393,6 @@ export default {
       );
       elementArray.unshift(choices);
     }
-    if (
-      !this.mobileBrowser &&
-      (this.arrows.length > 0 || this.movingArrow.x >= 0)
-    ) {
-      let svgArrows = [];
-      const arrowWidth = squareWidth / 4;
-      this.arrows.forEach(a => {
-        const endPoint = this.adjustEndArrow(a.start, a.end, squareWidth);
-        svgArrows.push(
-          h(
-            "path",
-            {
-              "class": { "svg-arrow": true },
-              attrs: {
-                d: (
-                  "M" + a.start.x + "," + a.start.y + " " +
-                  "L" + endPoint.x + "," + endPoint.y
-                ),
-                style: "stroke-width:" + arrowWidth + "px"
-              }
-            }
-          )
-        );
-      });
-      if (this.movingArrow.x >= 0) {
-        const endPoint =
-          this.adjustEndArrow(this.startArrow, this.movingArrow, squareWidth);
-        svgArrows.push(
-          h(
-            "path",
-            {
-              "class": { "svg-arrow": true },
-              attrs: {
-                d: (
-                  "M" + this.startArrow.x + "," + this.startArrow.y + " " +
-                  "L" + endPoint.x + "," + endPoint.y
-                ),
-                style: "stroke-width:" + arrowWidth + "px"
-              }
-            }
-          )
-        );
-      }
-      // Add SVG element for drawing arrows
-      elementArray.push(
-        h(
-          "svg",
-          {
-            attrs: {
-              id: "arrowCanvas",
-              stroke: "none"
-            }
-          },
-          [
-            h(
-              "defs",
-              {},
-              [
-                h(
-                  "marker",
-                  {
-                    attrs: {
-                      id: "arrow",
-                      markerWidth: (2 * arrowWidth) + "px",
-                      markerHeight: (3 * arrowWidth) + "px",
-                      markerUnits: "userSpaceOnUse",
-                      refX: "0",
-                      refY: (1.5 * arrowWidth) + "px",
-                      orient: "auto"
-                    }
-                  },
-                  [
-                    h(
-                      "path",
-                      {
-                        "class": { "arrow-head": true },
-                        attrs: {
-                          d: (
-                            "M0,0 L0," + (3 * arrowWidth) + " L" +
-                            (2 * arrowWidth) + "," + (1.5 * arrowWidth) + " z"
-                          )
-                        }
-                      }
-                    )
-                  ]
-                )
-              ]
-            )
-          ].concat(svgArrows)
-        )
-      );
-    }
     let onEvents = {};
     // NOTE: click = mousedown + mouseup
     if (this.mobileBrowser) {
@@ -503,7 +413,16 @@ export default {
         }
       };
     }
-    return h("div", onEvents, elementArray);
+    return (
+      h(
+        "div",
+        Object.assign({ attrs: { id: "rootBoardElement" } }, onEvents),
+        elementArray
+      )
+    );
+  },
+  updated: function() {
+    this.re_setDrawings();
   },
   methods: {
     blockContextMenu: function(e) {
@@ -515,23 +434,166 @@ export default {
       this.startArrow = null;
       this.arrows = [];
       this.circles = {};
+      const curCanvas = document.getElementById("arrowCanvas");
+      if (!!curCanvas) curCanvas.parentNode.removeChild(curCanvas);
+    },
+    coordsToXY: function(coords, top, left, squareWidth) {
+      return {
+        // [1] for x and [0] for y because conventions in rules are inversed.
+        x: (
+          left + window.scrollX +
+          (
+            squareWidth *
+            (this.orientation == 'w' ? coords[1] : (V.size.y - coords[1]))
+          )
+        ),
+        y: (
+          top + window.scrollY +
+          (
+            squareWidth *
+            (this.orientation == 'w' ? coords[0] : (V.size.x - coords[0]))
+          )
+        )
+      };
     },
-    adjustEndArrow: function(start, end, squareWidth) {
+    computeEndArrow: function(start, end, top, left, squareWidth) {
+      const endCoords = this.coordsToXY(end, top, left, squareWidth);
+      const delta = [endCoords.x - start.x, endCoords.y - start.y];
+      const dist = Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
       // Simple heuristic for now, just remove 1/3 square.
       // TODO: should depend on the orientation.
-      const delta = [end.x - start.x, end.y - start.y];
-      const dist = Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
       const fracSqWidth = squareWidth / 3;
       return {
-        x: end.x - delta[0] * fracSqWidth / dist,
-        y: end.y - delta[1] * fracSqWidth / dist
+        x: endCoords.x - delta[0] * fracSqWidth / dist,
+        y: endCoords.y - delta[1] * fracSqWidth / dist
       };
     },
+    drawCurrentArrow: function() {
+      const boardElt = document.getElementById("gamePosition");
+      const squareWidth = boardElt.offsetWidth / V.size.y;
+      const bPos = boardElt.getBoundingClientRect();
+      const aStart =
+        this.coordsToXY(
+          [this.startArrow[0] + 0.5, this.startArrow[1] + 0.5],
+          bPos.top, bPos.left, squareWidth);
+      const aEnd =
+        this.computeEndArrow(
+          aStart, [this.movingArrow[0] + 0.5, this.movingArrow[1] + 0.5],
+          bPos.top, bPos.left, squareWidth);
+      let currentArrow = document.getElementById("currentArrow");
+      const d =
+        "M" + aStart.x + "," + aStart.y + " " + "L" + aEnd.x + "," + aEnd.y;
+      const arrowWidth = squareWidth / 4;
+      if (!!currentArrow) currentArrow.setAttribute("d", d);
+      else {
+        let domArrow =
+          document.createElementNS("http://www.w3.org/2000/svg", "path");
+        domArrow.classList.add("svg-arrow");
+        domArrow.id = "currentArrow";
+        domArrow.setAttribute("d", d);
+        domArrow.style = "stroke-width:" + arrowWidth + "px";
+        document.getElementById("arrowCanvas")
+          .insertAdjacentElement("beforeend", domArrow);
+      }
+    },
+    addArrow: function(arrow) {
+      this.arrows.push(arrow);
+      // Also add to DOM:
+      const boardElt = document.getElementById("gamePosition");
+      const squareWidth = boardElt.offsetWidth / V.size.y;
+      const bPos = boardElt.getBoundingClientRect();
+      const newArrow =
+        this.getSvgArrow(arrow, bPos.top, bPos.left, squareWidth);
+      document.getElementById("arrowCanvas")
+        .insertAdjacentElement("beforeend", newArrow);
+    },
+    getSvgArrow: function(arrow, top, left, squareWidth) {
+      const aStart =
+        this.coordsToXY(
+          [arrow.start[0] + 0.5, arrow.start[1] + 0.5],
+          top, left, squareWidth);
+      const aEnd =
+        this.computeEndArrow(
+          aStart, [arrow.end[0] + 0.5, arrow.end[1] + 0.5],
+          top, left, squareWidth);
+      const arrowWidth = squareWidth / 4;
+      let path =
+        document.createElementNS("http://www.w3.org/2000/svg", "path");
+      path.classList.add("svg-arrow");
+      path.setAttribute(
+        "d",
+        "M" + aStart.x + "," + aStart.y + " " + "L" + aEnd.x + "," + aEnd.y
+      );
+      path.style = "stroke-width:" + arrowWidth + "px";
+      return path;
+    },
+    re_setDrawings: function() {
+      // Remove current canvas, if any
+      const curCanvas = document.getElementById("arrowCanvas");
+      if (!!curCanvas) curCanvas.parentNode.removeChild(curCanvas);
+      // Add some drawing on board (for some variants + arrows and circles)
+      const boardElt = document.getElementById("gamePosition");
+      const squareWidth = boardElt.offsetWidth / V.size.y;
+      const bPos = boardElt.getBoundingClientRect();
+      let svgArrows = [];
+      this.arrows.forEach(a => {
+        svgArrows.push(this.getSvgArrow(a, bPos.top, bPos.left, squareWidth));
+      });
+      let vLines = [];
+      if (!!V.Lines) {
+        V.Lines.forEach(line => {
+          const lStart =
+            this.coordsToXY(line[0], bPos.top, bPos.left, squareWidth);
+          const lEnd =
+            this.coordsToXY(line[1], bPos.top, bPos.left, squareWidth);
+          let path =
+            document.createElementNS("http://www.w3.org/2000/svg", "path");
+          path.classList.add("svg-line");
+          path.setAttribute(
+            "d",
+            "M" + lStart.x + "," + lStart.y + " " +
+              "L" + lEnd.x + "," + lEnd.y
+          );
+          vLines.push(path);
+        });
+      }
+      let arrowCanvas =
+        document.createElementNS("http://www.w3.org/2000/svg", "svg");
+      arrowCanvas.id = "arrowCanvas";
+      arrowCanvas.setAttribute("stroke", "none");
+      let defs =
+        document.createElementNS("http://www.w3.org/2000/svg", "defs");
+      const arrowWidth = squareWidth / 4;
+      let marker =
+        document.createElementNS("http://www.w3.org/2000/svg", "marker");
+      marker.id = "arrow";
+      marker.setAttribute("markerWidth", (2 * arrowWidth) + "px");
+      marker.setAttribute("markerHeight", (3 * arrowWidth) + "px");
+      marker.setAttribute("markerUnits", "userSpaceOnUse");
+      marker.setAttribute("refX", "0");
+      marker.setAttribute("refY", (1.5 * arrowWidth) + "px");
+      marker.setAttribute("orient", "auto");
+      let head =
+        document.createElementNS("http://www.w3.org/2000/svg", "path");
+      head.classList.add("arrow-head");
+      head.setAttribute(
+        "d",
+        "M0,0 L0," + (3 * arrowWidth) + " L" +
+          (2 * arrowWidth) + "," + (1.5 * arrowWidth) + " z"
+      );
+      marker.appendChild(head);
+      defs.appendChild(marker);
+      arrowCanvas.appendChild(defs);
+      svgArrows.concat(vLines).forEach(av => arrowCanvas.appendChild(av));
+      document.getElementById("rootBoardElement").appendChild(arrowCanvas);
+    },
     mousedown: function(e) {
       e.preventDefault();
       if (!this.mobileBrowser && e.which != 3)
         // Cancel current drawing and circles, if any
         this.cancelResetArrows();
+      this.containerPos =
+        document.getElementById("boardContainer").getBoundingClientRect();
       if (this.mobileBrowser || e.which == 1) {
         // Mouse left button
         if (!this.start) {
@@ -579,24 +641,34 @@ export default {
         let elem = e.target;
         // Next loop because of potential marks
         while (elem.tagName == "IMG") elem = elem.parentNode;
-        // To center the arrow in square:
-        const rect = elem.getBoundingClientRect();
-        this.startArrow = {
-          x: rect.x + rect.width / 2,
-          y: rect.y + rect.width / 2,
-          id: elem.id
-        };
+        this.startArrow = getSquareFromId(elem.id);
       }
     },
     mousemove: function(e) {
       if (!this.selectedPiece && !this.startArrow) return;
+      // Cancel if off boardContainer
+      const [offsetX, offsetY] =
+        this.mobileBrowser
+          ?
+            [
+              e.changedTouches[0].pageX,
+              // TODO: fixing attempt for smartphones, removing window.scrollY
+              e.changedTouches[0].pageY - window.scrollY
+            ]
+          : [e.clientX, e.clientY];
+      if (
+        offsetX < this.containerPos.left ||
+        offsetX > this.containerPos.right ||
+        offsetY < this.containerPos.top ||
+        offsetY > this.containerPos.bottom
+      ) {
+        this.selectedPiece = null;
+        this.startArrow = null;
+        return;
+      }
       e.preventDefault();
       if (!!this.selectedPiece) {
         // There is an active element: move it around
-        const [offsetX, offsetY] =
-          this.mobileBrowser
-            ? [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
-            : [e.clientX, e.clientY];
         Object.assign(
           this.selectedPiece.style,
           {
@@ -610,12 +682,13 @@ export default {
         // Next loop because of potential marks
         while (elem.tagName == "IMG") elem = elem.parentNode;
         // To center the arrow in square:
-        if (elem.id != this.startArrow.id) {
-          const rect = elem.getBoundingClientRect();
-          this.movingArrow = {
-            x: rect.x + rect.width / 2,
-            y: rect.y + rect.width / 2
-          };
+        const movingCoords = getSquareFromId(elem.id);
+        if (
+          movingCoords[0] != this.startArrow[0] ||
+          movingCoords[1] != this.startArrow[1]
+        ) {
+          this.movingArrow = movingCoords;
+          this.drawCurrentArrow();
         }
       }
     },
@@ -630,7 +703,7 @@ export default {
         this.processMoveAttempt(e);
       } else if (e.which == 3) {
         // Mouse right button
-        this.movingArrow = { x: -1, y: -1 };
+        this.movingArrow = null;
         this.processArrowAttempt(e);
       }
     },
@@ -645,7 +718,11 @@ export default {
       // Obtain the move from start and end squares
       const [offsetX, offsetY] =
         this.mobileBrowser
-          ? [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
+          ?
+            [
+              e.changedTouches[0].pageX,
+              e.changedTouches[0].pageY - window.scrollY
+            ]
           : [e.clientX, e.clientY];
       let landing = document.elementFromPoint(offsetX, offsetY);
       // Next condition: classList.contains(piece) fails because of marks
@@ -676,21 +753,21 @@ export default {
       let landing = document.elementFromPoint(offsetX, offsetY);
       // Next condition: classList.contains(piece) fails because of marks
       while (landing.tagName == "IMG") landing = landing.parentNode;
-      if (this.startArrow.id == landing.id)
+      const landingCoords = getSquareFromId(landing.id);
+      if (
+        this.startArrow[0] == landingCoords[0] &&
+        this.startArrow[1] == landingCoords[1]
+      ) {
         // Draw (or erase) a circle
         this.$set(this.circles, landing.id, !this.circles[landing.id]);
+      }
       else {
         // OK: add arrow, landing is a new square
-        const rect = landing.getBoundingClientRect();
-        this.arrows.push({
-          start: {
-            x: this.startArrow.x,
-            y: this.startArrow.y
-          },
-          end: {
-            x: rect.x + rect.width / 2,
-            y: rect.y + rect.width / 2
-          }
+        const currentArrow = document.getElementById("currentArrow");
+        currentArrow.parentNode.removeChild(currentArrow);
+        this.addArrow({
+          start: this.startArrow,
+          end: landingCoords
         });
       }
       this.startArrow = null;
@@ -710,6 +787,29 @@ export default {
 };
 </script>
 
+<style lang="sass">
+// SVG dynamically added, so not scoped
+#arrowCanvas
+  pointer-events: none
+  position: absolute
+  top: 0
+  left: 0
+  width: 100%
+  height: 100%
+
+.svg-arrow
+  opacity: 0.65
+  stroke: #5f0e78
+  fill: none
+  marker-end: url(#arrow)
+
+.svg-line
+  stroke: black
+
+.arrow-head
+  fill: #5f0e78
+</style>
+
 <style lang="sass" scoped>
 @import "@/styles/_board_squares_img.sass";
 
@@ -753,23 +853,6 @@ img.ghost
   opacity: 0.5
   top: 0
 
-#arrowCanvas
-  pointer-events: none
-  position: absolute
-  top: 0
-  left: 0
-  width: 100%
-  height: 100%
-
-.svg-arrow
-  opacity: 0.65
-  stroke: #5f0e78
-  fill: none
-  marker-end: url(#arrow)
-
-.arrow-head
-  fill: #5f0e78
-
 .incheck-light
   background-color: rgba(204, 51, 0, 0.7) !important
 .incheck-dark
index 1f94d63..9ec968f 100644 (file)
@@ -93,15 +93,10 @@ export default {
     gameContainer.style.width = boardSize + movesWidth + "px";
     document.getElementById("boardSize").value =
       (boardSize * 100) / (window.innerWidth - movesWidth);
-    // timeout to avoid calling too many time the adjust method
-    let timeoutLaunched = false;
-    window.addEventListener("resize", () => {
-      if (!timeoutLaunched) {
-        timeoutLaunched = true;
-        this.adjustBoard();
-        setTimeout(() => { timeoutLaunched = false; }, 500);
-      }
-    });
+    window.addEventListener("resize", this.adjustBoard);
+  },
+  beforeDestroy: function() {
+    window.removeEventListener("resize", this.adjustBoard);
   },
   watch: {
     cursor: function(newCursor) {
@@ -149,14 +144,12 @@ export default {
       return { tooltip: !("ontouchstart" in window) };
     },
     gotoMove: function(index) {
-      this.$emit("goto-move", index);
+      // Goto move except if click on current move:
+      if (this.cursor != index) this.$emit("goto-move", index);
     },
     adjustBoard: function() {
       const boardContainer = document.getElementById("boardContainer");
       if (!boardContainer) return; //no board on page
-      let arrows = document.getElementById("arrowCanvas");
-      // TODO: arrows on board don't scale
-      if (!!arrows) this.$emit("reset-arrows");
       const k = document.getElementById("boardSize").value;
       const movesWidth = window.innerWidth >= 768 ? 280 : 0;
       const minBoardWidth = 240; //TODO: these 240 and 280 are arbitrary...
@@ -168,6 +161,7 @@ export default {
       boardContainer.style.width = boardSize + "px";
       document.getElementById("gameContainer").style.width =
         boardSize + movesWidth + "px";
+      this.$emit("redraw-board");
     }
   }
 };
index 40397a8..2f76518 100644 (file)
@@ -6,24 +6,24 @@ figure.diagram-container
   .diagram
     display: block
     width: 50%
-    min-width: 240px
     margin-left: auto
     margin-right: auto
+    @media screen and (max-width: 630px)
+      width: 75%
   .diag12
     float: left
-    width: 40%
-    margin-left: calc(10% - 20px)
-    margin-right: 40px
+    width: 42%
+    margin: 0 2% 0 6%
     @media screen and (max-width: 630px)
-      float: none
-      margin: 0 auto 10px auto
+      width: 49%
+      margin: 0 1% 0 0
   .diag22
     float: left
-    width: 40%
-    margin-right: calc(10% - 20px)
+    width: 42%
+    margin: 0 6% 0 2%
     @media screen and (max-width: 630px)
-      float: none
-      margin: 0 auto
+      width: 49%
+      margin: 0 0 0 1%
   figcaption
     display: block
     clear: both
index 375bb4d..938e96b 100644 (file)
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 pieces on the board",
   "A pawns cloud": "A pawns cloud",
   "A wizard in the corner": "A wizard in the corner",
+  "Absorb powers": "Absorb powers",
   "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
   "Attract opposite king": "Attract opposite king",
@@ -187,7 +188,9 @@ export const translations = {
   "Interweaved colorbound teams": "Interweaved colorbound teams",
   "Get strong at self-mate": "Get strong at self-mate",
   "Give three checks": "Give three checks",
+  "Harassed kings": "Harassed kings",
   "Japanese Chess": "Japanese Chess",
+  "Jump the borders": "Jump the borders",
   "Keep antiking in check (v1)": "Keep antiking in check (v1)",
   "Keep antiking in check (v2)": "Keep antiking in check (v2)",
   "King of the Hill": "King of the Hill",
@@ -196,6 +199,7 @@ export const translations = {
   "Landing on the board": "Landing on the board",
   "Laws of attraction": "Laws of attraction",
   "Long jumps over pieces": "Long jumps over pieces",
+  "Long live the Queen": "Long live the Queen",
   "Lose all pieces": "Lose all pieces",
   "Mandatory captures": "Mandatory captures",
   "Mate any piece (v1)": "Mate any piece (v1)",
@@ -209,6 +213,7 @@ export const translations = {
   "No-check mode": "No-check mode",
   "Non-conformism and utopia": "Non-conformism and utopia",
   "Occupy the enemy palace": "Occupy the enemy palace",
+  "Paralyzed pieces": "Paralyzed pieces",
   "Pawns move diagonally": "Pawns move diagonally",
   "Play at the same time": "Play at the same time",
   "Play opponent's pieces": "Play opponent's pieces",
@@ -216,6 +221,7 @@ export const translations = {
   "Prolongated captures": "Prolongated captures",
   "Push and pull": "Push and pull",
   "Queen disguised as a pawn": "Queen disguised as a pawn",
+  "Reposition pieces": "Reposition pieces",
   "Reuse pieces": "Reuse pieces",
   "Reverse captures": "Reverse captures",
   "Rotating board": "Rotating board",
index 932bc2b..75dbf59 100644 (file)
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 piezas en el tablero",
   "A pawns cloud": "Une nube de peones",
   "A wizard in the corner": "Un mago en la esquina",
+  "Absorb powers": "Absorber poderes",
   "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
   "Attract opposite king": "Atraer al rey contrario",
@@ -187,7 +188,9 @@ export const translations = {
   "Interweaved colorbound teams": "Equipos unicolores entrelazados",
   "Get strong at self-mate": "Progreso en mates asistidos",
   "Give three checks": "Dar tres jaques",
+  "Harassed kings": "Reyes acosados",
   "Japanese Chess": "Ajedrez japonés",
+  "Jump the borders": "Saltar las fronteras",
   "Keep antiking in check (v1)": "Mantener el antirey en jaque (v1)",
   "Keep antiking in check (v2)": "Mantener el antirey en jaque (v2)",
   "King of the Hill": "Rey de la Colina",
@@ -196,6 +199,7 @@ export const translations = {
   "Landing on the board": "Aterrizando en el tablero",
   "Laws of attraction": "Las leyes de las atracciones",
   "Long jumps over pieces": "Saltos largos sobre las piezas",
+  "Long live the Queen": "Larga vida a la reina",
   "Lose all pieces": "Perder todas las piezas",
   "Mandatory captures": "Capturas obligatorias",
   "Mate any piece (v1)": "Matar cualquier pieza (v1)",
@@ -209,6 +213,7 @@ export const translations = {
   "No-check mode": "Modo sin jaque",
   "Non-conformism and utopia": "No-conformismo y utopía",
   "Occupy the enemy palace": "Ocupar el palacio enemigo",
+  "Paralyzed pieces": "Piezas paralizadas",
   "Pawns move diagonally": "Peones se mueven en diagonal",
   "Play at the same time": "Jugar al mismo tiempo",
   "Play opponent's pieces": "Jugar piezas opuestas",
@@ -216,6 +221,7 @@ export const translations = {
   "Prolongated captures": "Capturas extendidas",
   "Push and pull": "Empujar y tirar",
   "Queen disguised as a pawn": "Reina disfrazada de peón",
+  "Reposition pieces": "Reposicionar las piezas",
   "Reuse pieces": "Reutilizar piezas",
   "Reverse captures": "Capturas invertidas",
   "Rotating board": "Tablero giratorio",
index 9243c98..e6e8aa6 100644 (file)
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 pièces sur l'échiquier",
   "A pawns cloud": "Une nuée de pions",
   "A wizard in the corner": "Un sorcier dans le coin",
+  "Absorb powers": "Absorber les pouvoirs",
   "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
   "Attract opposite king": "Attirer le roi adverse",
@@ -187,7 +188,9 @@ export const translations = {
   "Interweaved colorbound teams": "Équipes unicolores entremêlées",
   "Get strong at self-mate": "Progressez en mats aidés",
   "Give three checks": "Donnez trois Ã©checs",
+  "Harassed kings": "Rois harcelés",
   "Japanese Chess": "Échecs japonais",
+  "Jump the borders": "Sauter les frontières",
   "Keep antiking in check (v1)": "Gardez l'antiroi en Ã©chec (v1)",
   "Keep antiking in check (v2)": "Gardez l'antiroi en Ã©chec (v2)",
   "King of the Hill": "Roi de la Colline",
@@ -196,6 +199,7 @@ export const translations = {
   "Landing on the board": "Débarquement sur l'échiquier",
   "Laws of attraction": "Les lois de l'attraction",
   "Long jumps over pieces": "Sauts longs par dessus les pièces",
+  "Long live the Queen": "Long vie Ã  la Reine",
   "Lose all pieces": "Perdez toutes les pièces",
   "Mandatory captures": "Captures obligatoires",
   "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
@@ -209,6 +213,7 @@ export const translations = {
   "No-check mode": "Mode sans Ã©chec",
   "Non-conformism and utopia": "Non-conformisme et utopie",
   "Occupy the enemy palace": "Occuper le palais ennemi",
+  "Paralyzed pieces": "Pièces paralysées",
   "Pawns move diagonally": "Les pions vont en diagonale",
   "Play at the same time": "Jouer en même temps",
   "Play opponent's pieces": "Jouez les pièces adverses",
@@ -216,6 +221,7 @@ export const translations = {
   "Prolongated captures": "Captures prolongées",
   "Push and pull": "Pousser et tirer",
   "Queen disguised as a pawn": "Reine déguisée en pion",
+  "Reposition pieces": "Replacer les pièces",
   "Reuse pieces": "Réutiliser les pièces",
   "Reverse captures": "Captures inversées",
   "Rotating board": "Échiquier tournant",
diff --git a/client/src/translations/rules/Absorption/en.pug b/client/src/translations/rules/Absorption/en.pug
new file mode 100644 (file)
index 0000000..7e8da9b
--- /dev/null
@@ -0,0 +1,25 @@
+p.boxed.
+  Capturing pieces absorb the movement abilities of the captured pieces,
+  except for pawns and kings.
+
+p.
+  When one non-royal piece (not a king or a pawn) captures another piece
+  (not a pawn), it acquires the movement abilities of the captured piece.
+  A pawn capturing a piece becomes a piece of that kind.
+
+p.
+  Three new pieces are likely to appear during the game: from left to right
+  the Princess, Empress and Amazon, adding the knight movement abilities
+  to the bishop, rook and queen.
+
+figure.showPieces.text-center
+  img(src="/images/pieces/Absorption/ws.svg")
+  img(src="/images/pieces/Absorption/we.svg")
+  img(src="/images/pieces/Absorption/wa.svg")
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/play/erf/Absorber.html")
+    | Absorption Chess
+  | &nbsp;on chessvariants.com.
diff --git a/client/src/translations/rules/Absorption/es.pug b/client/src/translations/rules/Absorption/es.pug
new file mode 100644 (file)
index 0000000..d44ff3c
--- /dev/null
@@ -0,0 +1,26 @@
+p.boxed.
+  Las piezas que capturan absorben las capacidades de movimiento de las piezas
+  capturadas, a excepción de los peones y reyes.
+
+p.
+  Cuando una pieza no real (no un rey o un peón) captura une otra pieza
+  (no un peón), adquiere la capacidad de mover como la pieza capturada.
+  Un peón que captura una pieza se convierte en una pieza de este tipo.
+
+p.
+  Es probable que aparezcan tres piezas nuevas durante el juego:
+  de izquierda a derecha la Princesa, la Emperatriz y la Amazona agregando
+  la capacidades de movimiento del caballo al alfil, torre y dama.
+
+figure.showPieces.text-center
+  img(src="/images/pieces/Absorption/ws.svg")
+  img(src="/images/pieces/Absorption/we.svg")
+  img(src="/images/pieces/Absorption/wa.svg")
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/play/erf/Absorber.html")
+    | variante Absorción
+  | &nbsp;en chessvariants.com.
diff --git a/client/src/translations/rules/Absorption/fr.pug b/client/src/translations/rules/Absorption/fr.pug
new file mode 100644 (file)
index 0000000..5cef341
--- /dev/null
@@ -0,0 +1,26 @@
+p.boxed.
+  Les pièces capturantes absorbent les capacités de mouvement des pièces
+  capturées, sauf concernant les pions et les rois.
+
+p.
+  Quand une pièce non royale (pas un roi ou un pion) capture une autre pièce
+  (pas un pion), elle acquière les capacités de déplacement de la pièce
+  capturée. Un pion capturant une pièce devient une pièce de ce type.
+
+p.
+  Trois nouvelles pièces sont susceptibles d'apparaître au cours du jeu : de
+  gauche Ã  droite la Princesse, l'Impératrice et l'Amazone, ajoutant les
+  capacités de déplacement du cavalier au fou, Ã  la tour et Ã  la dame.
+
+figure.showPieces.text-center
+  img(src="/images/pieces/Absorption/ws.svg")
+  img(src="/images/pieces/Absorption/we.svg")
+  img(src="/images/pieces/Absorption/wa.svg")
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/play/erf/Absorber.html")
+    | variante Absorption
+  | &nbsp;sur chessvariants.com.
diff --git a/client/src/translations/rules/Bicolour/en.pug b/client/src/translations/rules/Bicolour/en.pug
new file mode 100644 (file)
index 0000000..30c63bf
--- /dev/null
@@ -0,0 +1,30 @@
+p.boxed.
+  Kings are subject to check and checkmate by own as well as opponent's pieces.
+
+p
+  Kings are attacked by all pieces, and can capture any piece.
+  For example, on the following diagram the king can escape check by
+  capturing a rook.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/8/8/4q4/8/3RKR2:
+  figcaption Not a checkmate: Kxf1 or Kxd1.
+
+p.
+  Here however, each capture of the queen would lead to a self-check: although
+  the queen appears vulnerable this is a checkmate.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1nkbr1/1p2Qnp1/p2p1p1p/2p4P/P1P1P3/1P6/R2P1KP1/2B2BNR:
+  figcaption Checkmate.
+
+h3 Source
+
+p
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants.
+  | &nbsp;by David B. Pritchard (2007).
+
+p Inventors: Gabriel Authier and Roméo Bédoni (1958)
diff --git a/client/src/translations/rules/Bicolour/es.pug b/client/src/translations/rules/Bicolour/es.pug
new file mode 100644 (file)
index 0000000..25abe0b
--- /dev/null
@@ -0,0 +1,31 @@
+p.boxed.
+  Los reyes pueden estar en jaque y jaque mate por tus piezas o las de
+  el oponente.
+
+p.
+  Los reyes son atacados por todas las piezas y pueden capturar cualquier
+  pieza. Por ejemplo, en el siguiente diagrama, el rey puede evadirse
+  del jaque capturando una torre.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/8/8/4q4/8/3RKR2:
+  figcaption No es un jaque mate: Kxf1 o Kxd1.
+
+p.
+  Aquí, sin embargo, cada captura de la dama llevaría a un auto-jaque: aunque
+  la dama parece vulnerable, es un jaque mate.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1nkbr1/1p2Qnp1/p2p1p1p/2p4P/P1P1P3/1P6/R2P1KP1/2B2BNR:
+  figcaption jaque mate.
+
+h3 Fuente
+
+p
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants.
+  | &nbsp;por David B. Pritchard (2007).
+
+p Inventores: Gabriel Authier y Roméo Bédoni (1958)
diff --git a/client/src/translations/rules/Bicolour/fr.pug b/client/src/translations/rules/Bicolour/fr.pug
new file mode 100644 (file)
index 0000000..2bbfdef
--- /dev/null
@@ -0,0 +1,31 @@
+p.boxed.
+  Les rois peuvent Ãªtre en Ã©chec et matés par vos pièces ou celles de
+  l'adversaire.
+
+p.
+  Les rois sont attaqués par toutes les pièces, et peuvent capturer n'importe
+  quelle pièce. Par exemple, sur le diagramme ci-dessous le roi peut se
+  soustraire Ã  l'échec en capturant une tour.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/8/8/4q4/8/3RKR2:
+  figcaption Pas un mat : Kxf1 ou Kxd1.
+
+p.
+  Ici cependant, chaque capture de la dame mènerait Ã  un auto-échec : bien que
+  la dame paraisse vulnérable, c'est un Ã©chec et mat.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1nkbr1/1p2Qnp1/p2p1p1p/2p4P/P1P1P3/1P6/R2P1KP1/2B2BNR:
+  figcaption Ã‰chec et mat.
+
+h3 Source
+
+p
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants.
+  | &nbsp;par David B. Pritchard (2007).
+
+p Inventeurs : Gabriel Authier et Roméo Bédoni (1958)
diff --git a/client/src/translations/rules/Coronation/en.pug b/client/src/translations/rules/Coronation/en.pug
new file mode 100644 (file)
index 0000000..9a31a35
--- /dev/null
@@ -0,0 +1,21 @@
+p.boxed.
+  Rook and bishop can fusion to become a queen, if the queen disappeared.
+
+p.
+  If a side lacks a queen, then a special fusion move is allowed by "capturing"
+  a rook with a bishop (and conversely).
+  The result of the capture is a queen, hence the variant name.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/2R3B1:
+  .diagram.diag22
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/6Q1:
+  figcaption Left: before Rxg1=Q. Right: after the move.
+
+h3 Source
+
+p
+  | This variant is 
+  a(href="https://www.chessvariants.com/play/erf/CoronatC.html") mentioned here
+  | .
diff --git a/client/src/translations/rules/Coronation/es.pug b/client/src/translations/rules/Coronation/es.pug
new file mode 100644 (file)
index 0000000..513b36d
--- /dev/null
@@ -0,0 +1,23 @@
+p.boxed.
+  Torre y alfil pueden fusionar para convertirse en una reina,
+  si la dama falta.
+
+p.
+  Si un lado ha perdido a su dama, entonces puede hacer un movimiento especial
+  "capturando" una torre con un alfil (y viceversa).
+  El resultado de la captura es una reina, de ahí el nombre de la variante.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/2R3B1:
+  .diagram.diag22
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/6Q1:
+  figcaption Izquierda: antes de Rxg1=Q. Derecha: después del movimiento.
+
+h3 Fuente
+
+p
+  | Esta variante es 
+  a(href="https://www.chessvariants.com/play/erf/CoronatC.html")
+    | mencionada aquí
+  | .
diff --git a/client/src/translations/rules/Coronation/fr.pug b/client/src/translations/rules/Coronation/fr.pug
new file mode 100644 (file)
index 0000000..bff0ee5
--- /dev/null
@@ -0,0 +1,21 @@
+p.boxed.
+  Tour et fou peuvent fusionner pour devenir une reine, si la dame a disparu.
+
+p.
+  Si un camp a perdu sa dame, alors il peut effectuer un coup spécial en
+  "capturant" une tour avec un fou (et inversement).
+  Le résultat de la capture est une reine, d'où le nom de la variante.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/2R3B1:
+  .diagram.diag22
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/6Q1:
+  figcaption Gauche : avant Rxg1=Q. Droite : après le coup.
+
+h3 Source
+
+p
+  | Cette variante est 
+  a(href="https://www.chessvariants.com/play/erf/CoronatC.html") mentionnée ici
+  | .
diff --git a/client/src/translations/rules/Football/en.pug b/client/src/translations/rules/Football/en.pug
new file mode 100644 (file)
index 0000000..34fb52b
--- /dev/null
@@ -0,0 +1,21 @@
+p.boxed.
+  Win by playing a move on the middle of last rank.
+  The king has no royal status.
+
+p.
+  The goal for White (resp. Black) are the squares d8 and e8 (resp. d1 and
+  e1) in the middle of the opposing rank. The first player to settle a
+  piece there wins, even if it is attacked.
+
+figure.diagram-container
+  .diagram
+    | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+  figcaption 1...Nc3 and 2...N(x)d1# wins for Black.
+
+h3 Source
+
+p
+  | This variant is mentioned 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+    | on this page
+  | . It isn't specified if the winning piece needs to be safe from attacks.
diff --git a/client/src/translations/rules/Football/es.pug b/client/src/translations/rules/Football/es.pug
new file mode 100644 (file)
index 0000000..2ca6e47
--- /dev/null
@@ -0,0 +1,21 @@
+p.boxed.
+  Gana haciendo un movimiento en el centro de la Ãºltima fila.
+  El rey no tiene estatus real.
+
+p.
+  El objetivo de las blancas (resp. negras) son las casillas d8 y e8
+  (resp. d1 y e1) en el medio de la fila opuesta.
+  El primer jugador que instala una pieza ahí gana, incluso si es atacada.
+
+figure.diagram-container
+  .diagram
+    | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+  figcaption 1...Nc3 y 2...N(x)d1# gana para las negras.
+
+h3 Fuente
+
+p
+  | Esta variante se menciona 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+    | en esta pagina
+  | . No está claro si la pieza debe estar a salvo de los ataques.
diff --git a/client/src/translations/rules/Football/fr.pug b/client/src/translations/rules/Football/fr.pug
new file mode 100644 (file)
index 0000000..3af7b57
--- /dev/null
@@ -0,0 +1,21 @@
+p.boxed.
+  Gagnez en jouant un coup au centre de la dernière rangée.
+  Le roi n'a pas de statut royal.
+
+p.
+  Le but des blancs (resp. des noirs) sont les cases d8 et e8 (resp. d1 et e1)
+  au milieu de la rangée opposée. Le premier joueur Ã  y installer une pièce
+  gagne, même si celle-ci est attaquée.
+
+figure.diagram-container
+  .diagram
+    | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+  figcaption 1...Nc3 et 2...N(x)d1# gagne pour les noirs.
+
+h3 Source
+
+p
+  | Cette variante est mentionnée 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+    | sur cette page
+  | . Il n'est pas précisé si la pièce doit Ãªtre Ã  l'abri des attaques.
diff --git a/client/src/translations/rules/Gridolina/en.pug b/client/src/translations/rules/Gridolina/en.pug
new file mode 100644 (file)
index 0000000..95a6a15
--- /dev/null
@@ -0,0 +1,28 @@
+p.boxed.
+  The board is divided into 16 square cells of size 2x2.
+  A piece must change cell at each move.
+
+p.
+  The board is decomposed into 16 little 2x2 squares, starting from
+  a1,b1,a2,b2 until g7,h7,g8,h8. When a piece moves, it must cross at least
+  one boundary: it cannot remain in the same cell.
+  So, pieces in the same cell don't attack each other.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1kr3/3nbbQ1/6p1/4p3/1qp1p3/PR1P1NPP/4nP2/2B1KR1B:
+  figcaption.
+    The white king under check cannot take the knight,
+    and Rxb4 is not possible.
+
+p.
+  Pawns are Berolina pawns: they move one square diagonally forward and
+  capture one square orthogonally forward.
+
+h3 Source
+
+p
+  | This variant is mentioned in 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
diff --git a/client/src/translations/rules/Gridolina/es.pug b/client/src/translations/rules/Gridolina/es.pug
new file mode 100644 (file)
index 0000000..6e5d59c
--- /dev/null
@@ -0,0 +1,28 @@
+p.boxed.
+  El tablero de ajedrez se divide en 16 celdas cuadradas de tamaño 2x2.
+  Una pieza debe cambiar de celda cada jugada.
+
+p.
+  El tablero se divide en 16 cuadrados pequeños de 2x2, comenzando en
+  a1,b1,a2,b2 para terminar en g7,h7,g8,h8. Cuando una pieza se mueve, debe
+  cruzar al menos un borde: no puede permanecer en la misma celda.
+  Por lo tanto, las piezas de la misma célula no se atacan.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1kr3/3nbbQ1/6p1/4p3/1qp1p3/PR1P1NPP/4nP2/2B1KR1B:
+  figcaption.
+    El rey blanco en jaque no puede tomar al caballo,
+    y Rxb4 no es posible.
+
+p.
+  Los peones son peones Berolina: se mueven un espacio
+  diagonalmente hacia adelante y capturar en la casilla frente a ellos.
+
+h3 Fuente
+
+p
+  | Esta variante se menciona en 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
diff --git a/client/src/translations/rules/Gridolina/fr.pug b/client/src/translations/rules/Gridolina/fr.pug
new file mode 100644 (file)
index 0000000..a48033f
--- /dev/null
@@ -0,0 +1,28 @@
+p.boxed.
+  L'échiquier est divisé en 16 cellules carrées de taille 2x2.
+  Une pièce doit changer de cellule Ã  chaque coup.
+
+p.
+  L'échiquier est décomposé en 16 petits carrés 2x2, démarrant en a1,b1,a2,b2
+  pour finir en g7,h7,g8,h8. Quand une pièce se déplace, elle doit traverser
+  au moins une frontière : elle ne peut rester dans la même cellule.
+  Ainsi, les pièces d'une même cellule ne s'attaquent pas.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1kr3/3nbbQ1/6p1/4p3/1qp1p3/PR1P1NPP/4nP2/2B1KR1B:
+  figcaption.
+    Le roi blanc en Ã©chec ne peut pas prendre le cavalier,
+    et Rxb4 n'est pas possible.
+
+p.
+  Les pions sont des pions Berolina : ils se déplacent d'une case en
+  diagonale vers l'avant et capturent sur la case devant eux.
+
+h3 Source
+
+p
+  | Cette variante est mentionnée dans 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
index 422e8fa..02e7e89 100644 (file)
@@ -1,12 +1,10 @@
 p.boxed
-  | Bring the king to the middle to win. Giving check is forbidden.
+  | Bring the king to the middle to win. Checkmate still wins too.
 
-p Orthodox rules apply, with two changes:
-ul
-  li.
-    The goal is to bring the king on one of the central squares
-    d4, e4, d5 or e5. "Koth" stands indeed for "King of the Hill".
-  li Giving check is forbidden (thus no checkmate is possible).
+p.
+  Orthodox rules apply, with only one change: the game can also be won
+  by playing the king on one of the central squares d4, e4, d5 or e5.
+  "Koth" stands indeed for "King of the Hill".
 
 figure.diagram-container
   .diagram
@@ -16,6 +14,5 @@ figure.diagram-container
 h3 Source
 
 p
-  | Modified from Koth as playable 
-  a(href="https://lichess.org/variant/kingOfTheHill") on lichess
-  | . Experimental change: I'd like to see how it plays without checks :)
+  a(href="https://lichess.org/variant/kingOfTheHill") King of the Hill
+  | &nbsp;on lichess.org.
index 5e84f93..2fe1566 100644 (file)
@@ -1,12 +1,10 @@
 p.boxed
-  | Lleva el rey al centro para ganar. Prohibido de dar jaque.
+  | Lleva el rey al centro para ganar. El jaque mate también gana.
 
-p Se aplican las reglas ortodoxas, con dos cambios:
-ul
-  li.
-    El objetivo es llevar el rey a uno de los espacios centrales d4, e4, d5
-    o e5. "Koth" es de hecho la abreviatura inglesa para "Rey de la Colina".
-  li Los jaques son prohibidos (por lo tanto, el jaque mate es imposible).
+p.
+  Se aplican las reglas ortodoxas, con un solo cambio: puedes ganar
+  jugando el rey en uno de los espacios centrales d4, e4, d5 o e5.
+  "Koth" es de hecho la abreviatura inglesa para "Rey de la Colina".
 
 figure.diagram-container
   .diagram
@@ -16,7 +14,6 @@ figure.diagram-container
 h3 Fuente
 
 p
-  | Cambiado de la variante Koth jugable 
-  a(href="https://lichess.org/variant/kingOfTheHill") en lichess
-  | . Cambio experimental: me gustaría ver los efectos de la condición que
-  | previene jaques :)
+  | La 
+  a(href="https://lichess.org/variant/kingOfTheHill") variante KotH
+  | &nbsp;en lichess.org.
index 79c0272..ddc73ca 100644 (file)
@@ -1,12 +1,10 @@
 p.boxed
-  | Amenez le roi au centre pour gagner. Interdit de donner Ã©chec.
+  | Amenez le roi au centre pour gagner. L'échec et mat gagne aussi.
 
-p Les règles orthodoxes s'appliquent, avec deux changements :
-ul
-  li.
-    L'objectif est d'amener le roi sur l'une des cases centrales d4, e4, d5
-    ou e5. "Koth" est en effet l'abbréviation anglaise de "Roi de la Colline".
-  li Les Ã©checs sont interdits (l'échec et mat est donc imppossible).
+p.
+  Les règles orthodoxes s'appliquent, avec un seul changement : on peut
+  gagner en jouant le roi sur l'une des cases centrales d4, e4, d5 ou e5.
+  "Koth" est en effet l'abbréviation anglaise de "Roi de la Colline".
 
 figure.diagram-container
   .diagram
@@ -16,7 +14,6 @@ figure.diagram-container
 h3 Source
 
 p
-  | Modifié depuis la variante Koth jouable 
-  a(href="https://lichess.org/variant/kingOfTheHill") sur lichess
-  | . Changement expérimental : j'aimerais voir les effets de la condition
-  | empêchant les Ã©checs :)
+  | La 
+  a(href="https://lichess.org/variant/kingOfTheHill") variante KotH
+  | &nbsp;sur lichess.org.
diff --git a/client/src/translations/rules/Madrasi/en.pug b/client/src/translations/rules/Madrasi/en.pug
new file mode 100644 (file)
index 0000000..6de3c00
--- /dev/null
@@ -0,0 +1,28 @@
+p.boxed.
+  Pieces of the same kind mutually attacked are immobilized.
+
+p.
+  Two pieces of the same kind which both attack the other are paralysed.
+  They cannot move and they don't give check.
+  This rule also applies to kings, which would then stay in the same position
+  until the end of the game.
+
+figure.diagram-container
+  .diagram
+    | fen:bN2kq1r/p1pp4/2n2Qpr/4RpNp/1pPb1P1P/P2K2P1/1P1R1B2/7B:
+  figcaption e5 rook is safe and give check. 1...Kd8 is possible.
+
+p.
+  The diagram look like a checkmate because both black knight and bishop are
+  immobilized, but since the queens are paralysed the king can escape to d8.
+
+h3 Source
+
+p
+  | This variant appears on a number of web pages, like 
+  a(href="https://echekk.fr/spip.php?page=article&id_article=1") this one
+  | &nbsp;or the 
+  a(href="https://en.wikipedia.org/wiki/Madrasi_chess") Wikipedia page
+  | .
+
+p Inventor: Abdul J. Karwathar (1979)
diff --git a/client/src/translations/rules/Madrasi/es.pug b/client/src/translations/rules/Madrasi/es.pug
new file mode 100644 (file)
index 0000000..2b405b6
--- /dev/null
@@ -0,0 +1,28 @@
+p.boxed.
+  Piezas similares que se atacan mutuamente están inmovilizadas.
+
+p.
+  Dos piezas del mismo tipo que se observan se paralizan mutuamente.
+  No pueden moverse y no dan jaque.
+  Esta regla también se aplica a los reyes, que luego permanecerían en el
+  misma posición hasta el final del juego.
+
+figure.diagram-container
+  .diagram
+    | fen:bN2kq1r/p1pp4/2n2Qpr/4RpNp/1pPb1P1P/P2K2P1/1P1R1B2/7B:
+  figcaption La torre e5 es segura y da jaque. 1...Kd8 es posible.
+
+p.
+  El diagrama parece un jaque mate porque el alfil y el caballo negros son
+  inmovilizados, pero como las damas están paralizadas el rey puede huir en d8.
+
+h3 Fuente
+
+p
+  | Esta variante aparece en muchas páginas web, como 
+  a(href="https://echekk.fr/spip.php?page=article&id_article=1") este
+  | &nbsp;o la 
+  a(href="https://en.wikipedia.org/wiki/Madrasi_chess") página Wikipedia
+  | .
+
+p Inventor: Abdul J. Karwathar (1979)
diff --git a/client/src/translations/rules/Madrasi/fr.pug b/client/src/translations/rules/Madrasi/fr.pug
new file mode 100644 (file)
index 0000000..fec36ab
--- /dev/null
@@ -0,0 +1,28 @@
+p.boxed.
+  Des pièces de même nature s'attaquant mutuellement sont immobilisées.
+
+p.
+  Deux pièces de même nature qui s'observent se paralysent mutuellement.
+  Elles ne peuvent pas se déplacer et ne donnent pas Ã©chec.
+  Cette règle s'applique aussi aux rois, qui resteraient ensuite dans la
+  même position jusqu'à la fin de la partie.
+
+figure.diagram-container
+  .diagram
+    | fen:bN2kq1r/p1pp4/2n2Qpr/4RpNp/1pPb1P1P/P2K2P1/1P1R1B2/7B:
+  figcaption La tour e5 est en scurité et donne Ã©chec. 1...Kd8 est possible.
+
+p.
+  Le diagramme ressemble Ã  un mat car le cavalier et le fou noirs sont
+  immobilisés, mais puisque les dames sont paralysées le roi peut fuire en d8.
+
+h3 Source
+
+p
+  | Cette variante apparaît sur pas mal de pages web, comme 
+  a(href="https://echekk.fr/spip.php?page=article&id_article=1") celle-ci
+  | &nbsp;ou la 
+  a(href="https://en.wikipedia.org/wiki/Madrasi_chess") page Wikipedia
+  | .
+
+p Inventeur : Abdul J. Karwathar (1979)
diff --git a/client/src/translations/rules/Teleport/en.pug b/client/src/translations/rules/Teleport/en.pug
new file mode 100644 (file)
index 0000000..0d1442f
--- /dev/null
@@ -0,0 +1,35 @@
+p.boxed.
+  You can capture your pieces to teleport them instead of a regular move.
+
+p.
+  Instead of playing an orthodox move, it is possible to capture one of
+  our own pieces before repositioning it immediatly elsewhere on the board.
+  A king may remain under check while the move isn't complete.
+  A repositioned piece can give checkmate.
+  A pawn cannot be placed on the last rank.
+  A king can reposition a piece or be repositioned,
+  possibly to escape from a check.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:7k/6b1/8/4Q2N/8/8/8/K7:
+  .diagram.diag22
+    | fen:7k/6b1/5N2/7Q/8/8/8/K7:
+  figcaption Not a pin, nor a mate in 2.
+
+p.
+  1.Qxh5,N@f6+ is played from the diagram position, showing that the queen
+  isn't really pinned. Then, instead of 1...Bh6 black can either reposition
+  the bishop or the king to escape from check. 1...Kxg7,B@d4+ for example.
+
+p To reposition a piece, just click on an empty square after a self-capture.
+
+h3 Source
+
+p
+  | This variant is 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants")
+    | mentioned here
+  | .
+
+p Inventor: Philippe Rouzaud (2006)
diff --git a/client/src/translations/rules/Teleport/es.pug b/client/src/translations/rules/Teleport/es.pug
new file mode 100644 (file)
index 0000000..8b7f5a9
--- /dev/null
@@ -0,0 +1,38 @@
+p.boxed.
+  Puedes capturar tus piezas para teletransportarlas en lugar de jugar
+  un movimiento normal.
+
+p.
+  Un campamento puede, en lugar de un movimiento ortodoxo, capturar una de
+  sus piezas y reemplace la pieza capturada inmediatamente en el tablero.
+  Un rey puede quedarse en jaque durante esta acción.
+  Una pieza movida en el tablero puede dar jaque mate.
+  Un peón no se puede mover a la Ãºltima fila.
+  Un rey puede moverse y ser movido, incluso para evitar jaques.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:7k/6b1/8/4Q2N/8/8/8/K7:
+  .diagram.diag22
+    | fen:7k/6b1/5N2/7Q/8/8/8/K7:
+  figcaption No es un clavado, ni un jaque mate en 2.
+
+p.
+  1.Qxh5,N@f6+ se juega desde la posición del diagrama, mostrando que la dama
+  no está realmente clavada. Luego, para evadir el jaque las negras
+  pueden reemplazar a su alfil o rey en lugar de jugar 1...Bd6.
+  1...Kxg7,B@d4+ por ejemplo.
+
+p.
+  Para reposicionar una pieza, simplemente haga clic en una casilla vacía
+  después de una auto-captura.
+
+h3 Fuente
+
+p
+  | Esta variante es 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants")
+    | mencionada aquí
+  | .
+
+p Inventor: Philippe Rouzaud (2006)
diff --git a/client/src/translations/rules/Teleport/fr.pug b/client/src/translations/rules/Teleport/fr.pug
new file mode 100644 (file)
index 0000000..0d191e4
--- /dev/null
@@ -0,0 +1,39 @@
+p.boxed.
+  Vous pouvez capturer vos pièces pour les téléporter au lieu de jouer
+  un coup normal.
+
+p.
+  Un camp peut, Ã  la place d'un coup orthodoxe, capturer une de ses pièces et
+  replacer la pièce capturée immédiatement sur l'échiquier. Un roi peut rester
+  en Ã©chec durant cette action.
+  Une pièce déplacée sur l'échiquier peut mater.
+  Un pion ne peut pas Ãªtre déplacé en dernière rangée.
+  Un roi peut déplacer et Ãªtre déplacé,
+  y compris pour se soustraire Ã  un Ã©chec.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:7k/6b1/8/4Q2N/8/8/8/K7:
+  .diagram.diag22
+    | fen:7k/6b1/5N2/7Q/8/8/8/K7:
+  figcaption Pas un clouage, ni un mat en 2.
+
+p.
+  1.Qxh5,N@f6+ est joué depuis la position du diagramme, montrant que la dame
+  n'est pas vraiment clouée. Ensuite, pour se soustraire Ã  l'échec les noirs
+  peuvent replacer leur fou ou leur roi au lieu de jouer 1...Bh6.
+  1...Kxg7,B@d4+ par exemple.
+
+p.
+  Pour repositionner une pièce, cliquez simplement sur une case vide après
+  une auto-capture.
+
+h3 Source
+
+p
+  | Cette variante est 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants")
+    | mentionnée ici
+  | .
+
+p Inventeur : Philippe Rouzaud (2006)
diff --git a/client/src/variants/Absorption.js b/client/src/variants/Absorption.js
new file mode 100644 (file)
index 0000000..efa0103
--- /dev/null
@@ -0,0 +1,134 @@
+import { ChessRules } from "@/base_rules";
+
+export class AbsorptionRules extends ChessRules {
+  getPpath(b) {
+    if ([V.BN, V.RN, V.QN].includes(b[1])) return "Absorption/" + b;
+    return b;
+  }
+
+  // Three new pieces: rook+knight, bishop+knight and queen+knight
+  static get RN() {
+    // Empress
+    return 'e';
+  }
+  static get BN() {
+    // Princess
+    return 's';
+  }
+  static get QN() {
+    // Amazon
+    return 'a';
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.RN, V.BN, V.QN]);
+  }
+
+  static get MergeComposed() {
+    return {
+      "be": "a",
+      "bs": "s",
+      "er": "e",
+      "rs": "a",
+      "eq": "a",
+      "qs": "a",
+      "ee": "e",
+      "es": "a",
+      "ss": "s"
+    };
+  }
+
+  static Fusion(p1, p2) {
+    if (p1 == V.KING) return p1;
+    if (p1 == V.PAWN) return p2;
+    if (p2 == V.PAWN) return p1;
+    if ([p1, p2].includes(V.KNIGHT)) {
+      if ([p1, p2].includes(V.QUEEN)) return V.QN;
+      if ([p1, p2].includes(V.ROOK)) return V.RN;
+      if ([p1, p2].includes(V.BISHOP)) return V.BN;
+      // p1 or p2 already have knight + other piece
+      return (p1 == V.KNIGHT ? p2 : p1);
+    }
+    for (let p of [p1, p2]) {
+      if (p == V.QN) return V.QN;
+      if ([V.BN, V.RN].includes(p))
+        return V.MergeComposed[[p1, p2].sort().join("")];
+    }
+    // bishop + rook, or queen + [bishop or rook]
+    return V.QUEEN;
+  }
+
+  getPotentialMovesFrom(sq) {
+    let moves = [];
+    const piece = this.getPiece(sq[0], sq[1]);
+    switch (piece) {
+      case V.RN:
+        moves =
+          super.getPotentialRookMoves(sq).concat(
+          super.getPotentialKnightMoves(sq));
+        break;
+      case V.BN:
+        moves =
+          super.getPotentialBishopMoves(sq).concat(
+          super.getPotentialKnightMoves(sq));
+        break;
+      case V.QN:
+        moves =
+          super.getPotentialQueenMoves(sq).concat(
+          super.getPotentialKnightMoves(sq));
+        break;
+      default:
+        moves = super.getPotentialMovesFrom(sq);
+    }
+    moves.forEach(m => {
+      if (m.vanish.length == 2) {
+        // Augment pieces abilities in case of captures
+        const piece2 = m.vanish[1].p;
+        if (piece != piece2) m.appear[0].p = V.Fusion(piece, piece2);
+      }
+    });
+    return moves;
+  }
+
+  isAttacked(sq, color) {
+    return (
+      super.isAttacked(sq, color) ||
+      this.isAttackedByBN(sq, color) ||
+      this.isAttackedByRN(sq, color) ||
+      this.isAttackedByQN(sq, color)
+    );
+  }
+
+  isAttackedByBN(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(sq, color, V.BN, V.steps[V.BISHOP]) ||
+      this.isAttackedBySlideNJump(
+        sq, color, V.BN, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  isAttackedByRN(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(sq, color, V.RN, V.steps[V.ROOK]) ||
+      this.isAttackedBySlideNJump(
+        sq, color, V.RN, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  isAttackedByQN(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(
+        sq, color, V.QN, V.steps[V.BISHOP].concat(V.steps[V.ROOK])) ||
+      this.isAttackedBySlideNJump(
+        sq, color, V.QN, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p)
+      // Fusion (not from a pawn: handled in ChessRules)
+      notation += "=" + move.appear[0].p.toUpperCase();
+    return notation;
+  }
+};
index e4a0b2f..ceddb61 100644 (file)
@@ -3,6 +3,21 @@ import { ArrayFun } from "@/utils/array";
 import { shuffle } from "@/utils/alea";
 
 export class BallRules extends ChessRules {
+  static get Lines() {
+    return [
+      // White goal:
+      [[0, 3], [0, 6]],
+      [[0, 6], [1, 6]],
+      [[1, 6], [1, 3]],
+      [[1, 3], [0, 3]],
+      // Black goal:
+      [[9, 3], [9, 6]],
+      [[9, 6], [8, 6]],
+      [[8, 6], [8, 3]],
+      [[8, 3], [9, 3]]
+    ];
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
diff --git a/client/src/variants/Bicolour.js b/client/src/variants/Bicolour.js
new file mode 100644 (file)
index 0000000..eaa66d1
--- /dev/null
@@ -0,0 +1,111 @@
+import { ChessRules } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+import { ArrayFun } from "@/utils/array";
+
+export class BicolourRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return (
+      this.getPiece(x1, y1) == V.KING || super.canTake([x1, y1], [x2, y2])
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "rqbnkbnr/pppppppp/8/8/8/8/PPPPPPPP/RQBNKBNR w 0 -";
+
+    // Place pieces at random but the king cannot be next to a rook or queen.
+    // => One bishop and one knight should surround the king.
+    let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
+      let positions = ArrayFun.range(8);
+
+      const kingPos = randInt(8);
+      let toRemove = [kingPos];
+      let knight1Pos = undefined;
+      let bishop1Pos = undefined;
+      if (kingPos == 0) {
+        if (Math.random() < 0.5) knight1Pos = 1;
+        else bishop1Pos = 1;
+        toRemove.push(1);
+      }
+      else if (kingPos == V.size.y - 1) {
+        if (Math.random() < 0.5) knight1Pos = V.size.y - 2;
+        else bishop1Pos = V.size.y - 2;
+        toRemove.push(V.size.y - 2);
+      }
+      else {
+        knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
+        bishop1Pos = kingPos + (knight1Pos < kingPos ? 1 : -1);
+        toRemove.push(knight1Pos, bishop1Pos);
+      }
+      const firstPieces = [kingPos, knight1Pos, bishop1Pos]
+        .filter(elt => elt !== undefined);
+      firstPieces
+        .sort((a, b) => b - a)
+        .forEach(elt => positions.splice(elt, 1));
+
+      let randIndex = undefined;
+      if (bishop1Pos === undefined) {
+        const posWithIdx = positions.map((e,i) => { return { e: e, i: i }; });
+        let availableSquares = posWithIdx.filter(p => p.e % 2 == 0);
+        randIndex = randInt(availableSquares.length);
+        bishop1Pos = availableSquares[randIndex].e;
+        positions.splice(availableSquares[randIndex].i, 1);
+      }
+      const posWithIdx = positions.map((e,i) => { return { e: e, i: i }; });
+      const rem1B = bishop1Pos % 2;
+      let availableSquares = posWithIdx.filter(p => p.e % 2 == 1 - rem1B);
+      randIndex = randInt(availableSquares.length);
+      const bishop2Pos = availableSquares[randIndex].e;
+      positions.splice(availableSquares[randIndex].i, 1);
+
+      if (knight1Pos === undefined) {
+        randIndex = randInt(5);
+        knight1Pos = positions[randIndex];
+        positions.splice(randIndex, 1);
+      }
+      randIndex = randInt(4);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(3);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      const rook1Pos = positions[0];
+      const rook2Pos = positions[1];
+
+      pieces[c][rook1Pos] = "r";
+      pieces[c][knight1Pos] = "n";
+      pieces[c][bishop1Pos] = "b";
+      pieces[c][queenPos] = "q";
+      pieces[c][kingPos] = "k";
+      pieces[c][bishop2Pos] = "b";
+      pieces[c][knight2Pos] = "n";
+      pieces[c][rook2Pos] = "r";
+    }
+    return (
+      pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 -"
+    );
+  }
+
+  underCheck(color) {
+    return (
+      this.isAttacked(this.kingPos[color], 'w') ||
+      this.isAttacked(this.kingPos[color], 'b')
+    );
+  }
+};
index e8b95cb..a1caed4 100644 (file)
@@ -94,7 +94,7 @@ export class ChakartRules extends ChessRules {
     // A click to promote a piece on subTurn 2 would trigger this.
     // For now it would then return [NaN, NaN] because surrounding squares
     // have no IDs in the promotion modal. TODO: improve this?
-    if (!square[0]) return null;
+    if (isNaN(square[0])) return null;
     // If subTurn == 2:
     // if square is empty && firstMove is compatible,
     // complete the move (banana or bomb).
diff --git a/client/src/variants/Coronation.js b/client/src/variants/Coronation.js
new file mode 100644 (file)
index 0000000..9e12ea1
--- /dev/null
@@ -0,0 +1,50 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class CoronationRules extends ChessRules {
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    // If no queen on board, allow rook+bishop fusions:
+    const color = this.turn;
+    const piece = this.getPiece(x, y);
+    if (
+      [V.ROOK, V.BISHOP].includes(piece) &&
+      this.board.every(b => b.every(cell =>
+        (cell == V.EMPTY || cell[0] != color || cell[1] != V.QUEEN)
+      ))
+    ) {
+      const fusionWith = [V.ROOK, V.BISHOP][1 - "rb".indexOf(piece)];
+      // Can I "self-capture" fusionWith ?
+      for (let step of V.steps[piece]) {
+        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) == fusionWith
+        ) {
+          moves.push(
+            new Move({
+              appear: [new PiPo({ x: i, y: j, p: V.QUEEN, c: color })],
+              vanish: [
+                new PiPo({ x: x, y: y, p: piece, c: color }),
+                new PiPo({ x: i, y: j, p: fusionWith, c: color })
+              ]
+            })
+          );
+        }
+      }
+    }
+    return moves;
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    if (move.appear[0].p == V.QUEEN && move.vanish[0].p != V.QUEEN)
+      // Coronation
+      notation += "=Q";
+    return notation;
+  }
+};
index 15cfe95..cdb02c6 100644 (file)
@@ -43,7 +43,6 @@ export class DynamoRules extends ChessRules {
       });
       this.amoves.push(move);
     }
-    this.subTurn = 1;
     // Stack "first moves" (on subTurn 1) to merge and check opposite moves
     this.firstMove = [];
   }
@@ -750,7 +749,7 @@ export class DynamoRules extends ChessRules {
     // A click to promote a piece on subTurn 2 would trigger this.
     // For now it would then return [NaN, NaN] because surrounding squares
     // have no IDs in the promotion modal. TODO: improve this?
-    if (!square[0]) return null;
+    if (isNaN(square[0])) return null;
     // If subTurn == 2 && square is empty && !underCheck && !isOpposite,
     // then return an empty move, allowing to "pass" subTurn2
     const La = this.amoves.length;
diff --git a/client/src/variants/Football.js b/client/src/variants/Football.js
new file mode 100644 (file)
index 0000000..2acb24f
--- /dev/null
@@ -0,0 +1,80 @@
+import { ChessRules } from "@/base_rules";
+
+export class FootballRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) }
+    );
+  }
+
+  static get Lines() {
+    return [
+      // White goal:
+      [[0, 3], [0, 5]],
+      [[0, 5], [1, 5]],
+      [[1, 5], [1, 3]],
+      [[1, 3], [0, 3]],
+      // Black goal:
+      [[8, 3], [8, 5]],
+      [[8, 5], [7, 5]],
+      [[7, 5], [7, 3]],
+      [[7, 3], [8, 3]]
+    ];
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    // Just check that at least one piece of each color is there:
+    let pieces = { "w": 0, "b": 0 };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        const lowerRi = row[i].toLowerCase();
+        if (V.PIECES.includes(lowerRi)) {
+          pieces[row[i] == lowerRi ? "b" : "w"]++;
+          sumElts++;
+        } else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    if (Object.values(pieces).some(v => v == 0)) return false;
+    return true;
+  }
+
+  scanKings() {}
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  // No variables update because no royal king + no castling
+  prePlay() {}
+  postPlay() {}
+  preUndo() {}
+  postUndo() {}
+
+  getCurrentScore() {
+    const oppCol = V.GetOppCol(this.turn);
+    const goal = (oppCol == 'w' ? 0 : 7);
+    if (this.board[goal].slice(3, 5).some(b => b[0] == oppCol))
+      return oppCol == 'w' ? "1-0" : "0-1";
+    if (this.atLeastOneMove()) return "*";
+    return "1/2";
+  }
+};
diff --git a/client/src/variants/Gridolina.js b/client/src/variants/Gridolina.js
new file mode 100644 (file)
index 0000000..fd43be3
--- /dev/null
@@ -0,0 +1,37 @@
+import { ChessRules } from "@/base_rules";
+import { BerolinaRules } from "@/variants/Berolina";
+
+export class GridolinaRules extends BerolinaRules {
+  static get Lines() {
+    return [
+      [[2, 0], [2, 8]],
+      [[4, 0], [4, 8]],
+      [[6, 0], [6, 8]],
+      [[0, 2], [8, 2]],
+      [[0, 4], [8, 4]],
+      [[0, 6], [8, 6]]
+    ];
+  }
+
+  static OnDifferentGrids([x1, y1], [x2, y2]) {
+    return (
+        Math.abs(Math.floor(x1 / 2) - Math.floor(x2 / 2)) >= 1 ||
+        Math.abs(Math.floor(y1 / 2) - Math.floor(y2 / 2)) >= 1
+    );
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return (
+      V.OnDifferentGrids([x1, y1], [x2, y2]) &&
+      super.canTake([x1, y1], [x2, y2])
+    );
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    return (
+      super.getPotentialMovesFrom([x, y]).filter(m => {
+        return V.OnDifferentGrids([x, y], [m.end.x, m.end.y]);
+      })
+    );
+  }
+};
index e286862..42ffe7b 100644 (file)
@@ -1,17 +1,13 @@
 import { ChessRules } from "@/base_rules";
 
 export class KothRules extends ChessRules {
-  filterValid(moves) {
-    if (moves.length == 0) return [];
-    const color = this.turn;
-    const oppCol = V.GetOppCol(color);
-    return moves.filter(m => {
-      this.play(m);
-      // Giving check is forbidden as well:
-      const res = !this.underCheck(color) && !this.underCheck(oppCol);
-      this.undo(m);
-      return res;
-    });
+  static get Lines() {
+    return [
+      [[3, 3], [3, 5]],
+      [[3, 3], [5, 3]],
+      [[3, 5], [5, 5]],
+      [[5, 3], [5, 5]]
+    ];
   }
 
   getCurrentScore() {
@@ -24,9 +20,7 @@ export class KothRules extends ChessRules {
       // The middle is reached!
       return color == "w" ? "1-0" : "0-1";
     }
-    if (this.atLeastOneMove()) return "*";
-    // Stalemate (will probably never happen)
-    return "1/2";
+    return super.getCurrentScore();
   }
 
   evalPosition() {
diff --git a/client/src/variants/Madrasi.js b/client/src/variants/Madrasi.js
new file mode 100644 (file)
index 0000000..54c1862
--- /dev/null
@@ -0,0 +1,64 @@
+import { ChessRules } from "@/base_rules";
+
+export class MadrasiRules extends ChessRules {
+  isImmobilized(sq) {
+    const oppCol = V.GetOppCol(this.turn);
+    const piece = this.getPiece(sq[0], sq[1]);
+    let steps = [];
+    switch (piece) {
+      // NOTE: cannot use super.isAttackedByXXX
+      // because it would call the redefined isAttackedBySlideNJump
+      // => Infinite recursive calls.
+      case V.PAWN: {
+        const forward = (oppCol == 'w' ? 1 : -1);
+        steps = [[forward, 1], [forward, -1]];
+        break;
+      }
+      case V.ROOK:
+      case V.BISHOP:
+      case V.KNIGHT:
+        steps = V.steps[piece];
+        break;
+      case V.KING:
+      case V.QUEEN:
+        steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+        break;
+    }
+    return super.isAttackedBySlideNJump(
+      sq, oppCol, piece, steps, [V.KING, V.PAWN, V.KNIGHT].includes(piece))
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    return (
+      !this.isImmobilized([x, y])
+        ? super.getPotentialMovesFrom([x, y])
+        : []
+    );
+  }
+
+  isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
+    for (let step of steps) {
+      let rx = x + step[0],
+          ry = y + step[1];
+      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+        rx += step[0];
+        ry += step[1];
+      }
+      if (
+        V.OnBoard(rx, ry) &&
+        this.getPiece(rx, ry) == piece &&
+        this.getColor(rx, ry) == color &&
+        // If enemy is immobilized, it doesn't attack:
+        !this.isImmobilized([rx, ry])
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  isAttackedByKing() {
+    // Connected kings paralyze each other
+    return false;
+  }
+};
index 2eeeaea..6205f23 100644 (file)
@@ -11,6 +11,10 @@ export class MakrukRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
index f075fe8..e0838e1 100644 (file)
@@ -15,6 +15,15 @@ export class RococoRules extends ChessRules {
     return ChessRules.PIECES.concat([V.IMMOBILIZER]);
   }
 
+  static get Lines() {
+    return [
+      [[1, 1], [1, 9]],
+      [[1, 9], [9, 9]],
+      [[9, 9], [9, 1]],
+      [[9, 1], [1, 1]]
+    ];
+  }
+
   getPpath(b) {
     if (b[1] == "m")
       //'m' for Immobilizer (I is too similar to 1)
index 7d76b83..d8e4460 100644 (file)
@@ -9,6 +9,10 @@ export class ShatranjRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
index 50d273f..1b2ba03 100644 (file)
@@ -11,6 +11,10 @@ export class ShogiRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
index c93c3f0..61bab56 100644 (file)
@@ -10,6 +10,17 @@ export class SittuyinRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
+  static get Lines() {
+    return ChessRules.Lines.concat([
+      [[0, 0], [8, 8]],
+      [[0, 8], [8, 0]]
+    ]);
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
diff --git a/client/src/variants/Teleport.js b/client/src/variants/Teleport.js
new file mode 100644 (file)
index 0000000..b4e6d7f
--- /dev/null
@@ -0,0 +1,342 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class TeleportRules extends ChessRules {
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.subTurn = 1;
+    this.firstMove = [];
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return this.subTurn == 1;
+  }
+
+  getPPpath(m) {
+    if (
+      m.vanish.length == 2 &&
+      m.appear.length == 1 &&
+      m.vanish[0].c == m.vanish[1].c &&
+      m.appear[0].p == V.KING
+    ) {
+      // Rook teleportation with the king
+      return this.getPpath(m.vanish[1].c + m.vanish[1].p);
+    }
+    return this.getPpath(m.appear[0].c + m.appear[0].p);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.subTurn == 1) return super.getPotentialMovesFrom([x, y]);
+    // subTurn == 2: a move is a click, not handled here
+    return [];
+  }
+
+  filterValid(moves) {
+    if (this.subTurn == 2) return super.filterValid(moves);
+    const color = this.turn;
+    return moves.filter(m => {
+      this.play(m);
+      let res = false;
+      if (
+        m.vanish.length == 1 ||
+        m.appear.length == 2 ||
+        m.vanish[0].c != m.vanish[1].c
+      ) {
+        // Standard check:
+        res = !this.underCheck(color);
+      }
+      else {
+        // Self-capture: find landing square not resulting in check
+        outerLoop: for (let i=0; i<8; i++) {
+          for (let j=0; j<8; j++) {
+            if (
+              this.board[i][j] == V.EMPTY &&
+              (
+                m.vanish[1].p != V.PAWN ||
+                i != (color == 'w' ? 0 : 7)
+              )
+            ) {
+              const tMove = new Move({
+                appear: [
+                  new PiPo({
+                    x: i,
+                    y: j,
+                    c: color,
+                    // The dropped piece nature has no importance:
+                    p: V.KNIGHT
+                  })
+                ],
+                vanish: [],
+                start: { x: -1, y: -1 }
+              });
+              this.play(tMove);
+              const moveOk = !this.underCheck(color);
+              this.undo(tMove);
+              if (moveOk) {
+                res = true;
+                break outerLoop;
+              }
+            }
+          }
+        }
+      }
+      this.undo(m);
+      return res;
+    });
+  }
+
+  getAllValidMoves() {
+    if (this.subTurn == 1) return super.getAllValidMoves();
+    // Subturn == 2: only teleportations
+    let moves = [];
+    const L = this.firstMove.length;
+    const color = this.turn;
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (
+          this.board[i][j] == V.EMPTY &&
+          (
+            this.firstMove[L-1].vanish[1].p != V.PAWN ||
+            i != (color == 'w' ? 0 : 7)
+          )
+        ) {
+          const tMove = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: this.firstMove[L-1].vanish[1].p
+              })
+            ],
+            vanish: [],
+            start: { x: -1, y: -1 }
+          });
+          this.play(tMove);
+          const moveOk = !this.underCheck(color);
+          this.undo(tMove);
+          if (moveOk) moves.push(tMove);
+        }
+      }
+    }
+    return moves;
+  }
+
+  underCheck(color) {
+    if (this.kingPos[color][0] < 0)
+      // King is being moved:
+      return false;
+    return super.underCheck(color);
+  }
+
+  getCurrentScore() {
+    if (this.subTurn == 2)
+      // Move not over
+      return "*";
+    return super.getCurrentScore();
+  }
+
+  doClick(square) {
+    if (isNaN(square[0])) return null;
+    // If subTurn == 2 && square is empty && !underCheck, then teleport
+    if (this.subTurn == 2 && this.board[square[0]][square[1]] == V.EMPTY) {
+      const L = this.firstMove.length;
+      const color = this.turn;
+      if (
+        this.firstMove[L-1].vanish[1].p == V.PAWN &&
+        square[0] == (color == 'w' ? 0 : 7)
+      ) {
+        // Pawns cannot be teleported on last rank
+        return null;
+      }
+      const tMove = new Move({
+        appear: [
+          new PiPo({
+            x: square[0],
+            y: square[1],
+            c: color,
+            p: this.firstMove[L-1].vanish[1].p
+          })
+        ],
+        vanish: [],
+        start: { x: -1, y: -1 }
+      });
+      this.play(tMove);
+      const moveOk = !this.underCheck(color);
+      this.undo(tMove);
+      if (moveOk) return tMove;
+    }
+    return null;
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    if (move.vanish.length > 0) {
+      this.epSquares.push(this.getEpSquare(move));
+      this.firstMove.push(move);
+    }
+    V.PlayOnBoard(this.board, move);
+    if (
+      this.subTurn == 2 ||
+      move.vanish.length == 1 ||
+      move.appear.length == 2 ||
+      move.vanish[0].c != move.vanish[1].c
+    ) {
+      this.turn = V.GetOppCol(this.turn);
+      this.subTurn = 1;
+      this.movesCount++;
+    }
+    else this.subTurn = 2;
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      // A king is moved: temporarily off board
+      this.kingPos[move.vanish[1].c] = [-1, -1];
+    else if (move.appear[0].p == V.KING)
+      this.kingPos[move.appear[0].c] = [move.appear[0].x, move.appear[0].y];
+    this.updateCastleFlags(move);
+  }
+
+  // NOTE: no need to update if castleFlags already off
+  updateCastleFlags(move) {
+    if (move.vanish.length == 0) return;
+    const c = move.vanish[0].c;
+    if (
+      move.vanish.length == 2 &&
+      move.appear.length == 1 &&
+      move.vanish[0].c == move.vanish[1].c
+    ) {
+      // Self-capture: of the king or a rook?
+      if (move.vanish[1].p == V.KING)
+        this.castleFlags[c] = [V.size.y, V.size.y];
+      else if (move.vanish[1].p == V.ROOK) {
+        const firstRank = (c == "w" ? V.size.x - 1 : 0);
+        if (
+          move.end.x == firstRank &&
+          this.castleFlags[c].includes(move.end.y)
+        ) {
+          const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
+          this.castleFlags[c][flagIdx] = V.size.y;
+        }
+      }
+    }
+    else {
+      // Normal move
+      const firstRank = (c == "w" ? V.size.x - 1 : 0);
+      const oppCol = V.GetOppCol(c);
+      const oppFirstRank = V.size.x - 1 - firstRank;
+      if (move.vanish[0].p == V.KING && move.appear.length > 0)
+        this.castleFlags[c] = [V.size.y, V.size.y];
+      else if (
+        move.start.x == firstRank &&
+        this.castleFlags[c].includes(move.start.y)
+      ) {
+        const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+        this.castleFlags[c][flagIdx] = V.size.y;
+      }
+      if (
+        move.end.x == oppFirstRank &&
+        this.castleFlags[oppCol].includes(move.end.y)
+      ) {
+        const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+        this.castleFlags[oppCol][flagIdx] = V.size.y;
+      }
+    }
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    if (move.vanish.length > 0) {
+      this.epSquares.pop();
+      this.firstMove.pop();
+    }
+    V.UndoOnBoard(this.board, move);
+    if (this.subTurn == 2) this.subTurn = 1;
+    else {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+      this.subTurn = (move.vanish.length > 0 ? 1 : 2);
+    }
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    if (move.vanish.length == 0) {
+      if (move.appear[0].p == V.KING)
+        // A king was teleported
+        this.kingPos[move.appear[0].c] = [-1, -1];
+    }
+    else if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      // A king was (self-)taken
+      this.kingPos[move.vanish[1].c] = [move.end.x, move.end.y];
+    else super.postUndo(move);
+  }
+
+  getComputerMove() {
+    let moves = this.getAllValidMoves();
+    if (moves.length == 0) return null;
+    // Custom "search" at depth 1 (for now. TODO?)
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const initEval = this.evalPosition();
+    moves.forEach(m => {
+      this.play(m);
+      m.eval = (color == "w" ? -1 : 1) * maxeval;
+      if (
+        m.vanish.length == 2 &&
+        m.appear.length == 1 &&
+        m.vanish[0].c == m.vanish[1].c
+      ) {
+        const moves2 = this.getAllValidMoves();
+        m.next = moves2[0];
+        moves2.forEach(m2 => {
+          this.play(m2);
+          const score = this.getCurrentScore();
+          const mvEval =
+            ["1-0", "0-1"].includes(score)
+              ? (score == "1-0" ? 1 : -1) * maxeval
+              : (score == "1/2" ? 0 : initEval);
+          if (
+            (color == 'w' && mvEval > m.eval) ||
+            (color == 'b' && mvEval < m.eval)
+          ) {
+            // TODO: if many second moves have the same eval, only the
+            // first is kept. Could be randomized.
+            m.eval = mvEval;
+            m.next = m2;
+          }
+          this.undo(m2);
+        });
+      }
+      else {
+        const score = this.getCurrentScore();
+        if (score != "1/2") {
+          if (score != "*") m.eval = (score == "1-0" ? 1 : -1) * maxeval;
+          else m.eval = this.evalPosition();
+        }
+      }
+      this.undo(m);
+    });
+    moves.sort((a, b) => {
+      return (color == "w" ? 1 : -1) * (b.eval - a.eval);
+    });
+    let candidates = [0];
+    for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
+      candidates.push(i);
+    const mIdx = candidates[randInt(candidates.length)];
+    if (!moves[mIdx].next) return moves[mIdx];
+    const move2 = moves[mIdx].next;
+    delete moves[mIdx]["next"];
+    return [moves[mIdx], move2];
+  }
+
+  getNotation(move) {
+    if (move.vanish.length > 0) return super.getNotation(move);
+    // Teleportation:
+    const piece =
+      move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
+    return piece + "@" + V.CoordsToSquare(move.end);
+  }
+};
index b3172ac..71d7764 100644 (file)
@@ -1367,7 +1367,7 @@ button.player-action
   margin-left: 32px
 
 .somethingnew
-  background-color: #D6EAF8 !important
+  background-color: #90C4EC !important
 
 .tabbtn
   background-color: #f9faee
index eb23bc4..a29e0e9 100644 (file)
@@ -405,7 +405,7 @@ button#loadMoreBtn
   margin: 0 auto
 
 .somethingnew
-  background-color: #D6EAF8 !important
+  background-color: #90C4EC !important
 </style>
 
 <!-- Not scoped because acting on GameList -->
index 51dae3f..a3eb282 100644 (file)
@@ -8,6 +8,7 @@ insert or ignore into Variants (name, description, noProblems) values
   ('Synchrone', 'Play at the same time', true);
 
 insert or ignore into Variants (name, description) values
+  ('Absorption', 'Absorb powers'),
   ('Alice', 'Both sides of the mirror'),
   ('Allmate1', 'Mate any piece (v1)'),
   ('Allmate2', 'Mate any piece (v2)'),
@@ -21,6 +22,7 @@ insert or ignore into Variants (name, description) values
   ('Baroque', 'Exotic captures'),
   ('Benedict', 'Change colors'),
   ('Berolina', 'Pawns move diagonally'),
+  ('Bicolour', 'Harassed kings'),
   ('Cannibal', 'Capture powers'),
   ('Capture', 'Mandatory captures'),
   ('Checkered1', 'Shared pieces (v1)'),
@@ -30,6 +32,7 @@ insert or ignore into Variants (name, description) values
   ('Circular', 'Run forward'),
   ('Colorbound', 'The colorbound clobberers'),
   ('Coregal', 'Two royal pieces'),
+  ('Coronation', 'Long live the Queen'),
   ('Crazyhouse', 'Captures reborn'),
   ('Cylinder', 'Neverending rows'),
   ('Diamond', 'Rotating board'),
@@ -40,8 +43,10 @@ insert or ignore into Variants (name, description) values
   ('Eightpieces', 'Each piece is unique'),
   ('Enpassant', 'Capture en passant'),
   ('Extinction', 'Capture all of a kind'),
+  ('Football', 'Score a goal'),
   ('Grand', 'Big board'),
   ('Grasshopper', 'Long jumps over pieces'),
+  ('Gridolina', 'Jump the borders'),
   ('Hamilton', 'Walk on a graph'),
   ('Horde', 'A pawns cloud'),
   ('Interweave', 'Interweaved colorbound teams'),
@@ -50,6 +55,7 @@ insert or ignore into Variants (name, description) values
   ('Knightrelay2', 'Move like a knight (v2)'),
   ('Koth', 'King of the Hill'),
   ('Losers', 'Get strong at self-mate'),
+  ('Madrasi', 'Paralyzed pieces'),
   ('Magnetic', 'Laws of attraction'),
   ('Makruk', 'Thai Chess'),
   ('Maxima', 'Occupy the enemy palace'),
@@ -74,6 +80,7 @@ insert or ignore into Variants (name, description) values
   ('Suicide', 'Lose all pieces'),
   ('Suction', 'Attract opposite king'),
   ('Takenmake', 'Prolongated captures'),
+  ('Teleport', 'Reposition pieces'),
   ('Tencubed', 'Four new pieces'),
   ('Threechecks', 'Give three checks'),
   ('Twokings', 'Two kings'),