From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 19 Mar 2020 19:43:17 +0000 (+0100)
Subject: Add Perfect Chess, fix a bug in BaseGame when moving while choosing a promotion,... 
X-Git-Url: https://git.auder.net/variants/current/doc/mini-custom.min.css?a=commitdiff_plain;h=a68362420a3a92099dfaacea10f6cbd579161183;p=vchess.git

Add Perfect Chess, fix a bug in BaseGame when moving while choosing a promotion, eliminate some redundant castling code
---

diff --git a/client/public/images/pieces/Perfect/ba.svg b/client/public/images/pieces/Perfect/ba.svg
new file mode 100644
index 00000000..0215b879
--- /dev/null
+++ b/client/public/images/pieces/Perfect/ba.svg
@@ -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/Perfect/be.svg b/client/public/images/pieces/Perfect/be.svg
new file mode 100644
index 00000000..fd548016
--- /dev/null
+++ b/client/public/images/pieces/Perfect/be.svg
@@ -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/Perfect/bs.svg b/client/public/images/pieces/Perfect/bs.svg
new file mode 100644
index 00000000..afc27f05
--- /dev/null
+++ b/client/public/images/pieces/Perfect/bs.svg
@@ -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/Perfect/wa.svg b/client/public/images/pieces/Perfect/wa.svg
new file mode 100644
index 00000000..beeaaea1
--- /dev/null
+++ b/client/public/images/pieces/Perfect/wa.svg
@@ -0,0 +1,25 @@
+<!-- Created with Inkscape (http://www.inkscape.org/) -->

+<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/">

+ <metadata>

+  <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 transform="matrix(0.75757574,0,0,0.75757574,2.8791515,-7.9393932)">

+  <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"/>

+  <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)"/>

+  <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"/>

+ </g>

+ <g transform="translate(0,-4)">

+  <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"/>

+  <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"/>

+  <use xlink:href="#path3804" transform="translate(9,-2)" height="26" width="26" y="0" x="0"/>

+  <use xlink:href="#path3804" transform="matrix(-1,0,0,1,26,0)" height="26" width="26" y="0" x="0"/>

+  <use xlink:href="#path3804" transform="matrix(-1,0,0,1,22,-2)" height="26" width="26" y="0" x="0"/>

+ </g>

+</svg>

diff --git a/client/public/images/pieces/Perfect/we.svg b/client/public/images/pieces/Perfect/we.svg
new file mode 100644
index 00000000..fd0288db
--- /dev/null
+++ b/client/public/images/pieces/Perfect/we.svg
@@ -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/Perfect/ws.svg b/client/public/images/pieces/Perfect/ws.svg
new file mode 100644
index 00000000..b45ea509
--- /dev/null
+++ b/client/public/images/pieces/Perfect/ws.svg
@@ -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>
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index cd4bb778..267b2335 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -810,7 +810,8 @@ export const ChessRules = class ChessRules {
     return moves;
   }
 
-  getCastleMoves([x, y]) {
+  // "castleInCheck" arg to let some variants castle under check
+  getCastleMoves([x, y], castleInCheck) {
     const c = this.getColor(x, y);
     if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
       return []; //x isn't first rank, or king has moved (shortcut)
@@ -832,7 +833,9 @@ export const ChessRules = class ChessRules {
       if (this.castleFlags[c][castleSide] >= V.size.y) continue;
       // If this code is reached, rooks and king are on initial position
 
+      // NOTE: in some variants this is not a rook, but let's keep variable name
       const rookPos = this.castleFlags[c][castleSide];
+      const castlingPiece = this.getPiece(x, rookPos);
       if (this.getColor(x, rookPos) != c)
         // Rook is here but changed color (see Benedict)
         continue;
@@ -843,11 +846,11 @@ export const ChessRules = class ChessRules {
       i = y;
       do {
         if (
-          this.isAttacked([x, i], oppCol) ||
+          (!castleInCheck && this.isAttacked([x, i], oppCol)) ||
           (this.board[x][i] != V.EMPTY &&
             // NOTE: next check is enough, because of chessboard constraints
             (this.getColor(x, i) != c ||
-              ![V.KING, V.ROOK].includes(this.getPiece(x, i))))
+              ![V.KING, castlingPiece].includes(this.getPiece(x, i))))
         ) {
           continue castlingCheck;
         }
@@ -876,11 +879,11 @@ export const ChessRules = class ChessRules {
         new Move({
           appear: [
             new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
-            new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
+            new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c })
           ],
           vanish: [
             new PiPo({ x: x, y: y, p: V.KING, c: c }),
-            new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
+            new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
           ],
           end:
             Math.abs(y - rookPos) <= 2
diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue
index 3446a036..65e96cf6 100644
--- a/client/src/components/BaseGame.vue
+++ b/client/src/components/BaseGame.vue
@@ -11,6 +11,7 @@ div#baseGame
   #gameContainer
     #boardContainer
       Board(
+        ref="board"
         :vr="vr"
         :last-move="lastMove"
         :analyze="game.mode=='analyze'"
@@ -306,6 +307,8 @@ export default {
     },
     // "light": if gotoMove() or gotoEnd()
     play: function(move, received, light, noemit) {
+      // Freeze while choices are shown:
+      if (this.$refs["board"].choices.length > 0) return;
       if (!!noemit) {
         if (this.inPlay) {
           // Received moves in observed games can arrive too fast:
@@ -445,6 +448,8 @@ export default {
     },
     // "light": if gotoMove() or gotoBegin()
     undo: function(move, light) {
+      // Freeze while choices are shown:
+      if (this.$refs["board"].choices.length > 0) return;
       if (this.inMultimove) {
         this.cancelCurrentMultimove();
         this.incheck = this.vr.getCheckSquares(this.vr.turn);
@@ -467,6 +472,7 @@ export default {
       }
     },
     gotoMove: function(index) {
+      if (this.$refs["board"].choices.length > 0) return;
       if (this.inMultimove) this.cancelCurrentMultimove();
       if (index == this.cursor) return;
       if (index < this.cursor) {
@@ -484,6 +490,7 @@ export default {
       this.emitFenIfAnalyze();
     },
     gotoBegin: function() {
+      if (this.$refs["board"].choices.length > 0) return;
       if (this.inMultimove) this.cancelCurrentMultimove();
       const minCursor =
         this.moves.length > 0 && this.moves[0].notation == "..."
@@ -495,11 +502,13 @@ export default {
       this.emitFenIfAnalyze();
     },
     gotoEnd: function() {
+      if (this.$refs["board"].choices.length > 0) return;
       if (this.cursor == this.moves.length - 1) return;
       this.gotoMove(this.moves.length - 1);
       this.emitFenIfAnalyze();
     },
     flip: function() {
+      if (this.$refs["board"].choices.length > 0) return;
       this.orientation = V.GetOppCol(this.orientation);
     }
   }
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 328f0895..5d093742 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -187,6 +187,7 @@ export const translations = {
   "Move twice": "Move twice",
   "Neverending rows": "Neverending rows",
   "Pawns move diagonally": "Pawns move diagonally",
+  "Powerful pieces": "Powerful pieces",
   "Queen disguised as a pawn": "Queen disguised as a pawn",
   "Reuse pieces": "Reuse pieces",
   "Reverse captures": "Reverse captures",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 78c0b054..877f8587 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -187,6 +187,7 @@ export const translations = {
   "Move twice": "Mover dos veces",
   "Neverending rows": "Filas interminables",
   "Pawns move diagonally": "Peones se mueven en diagonal",
+  "Powerful pieces": "Piezas poderosas",
   "Queen disguised as a pawn": "Reina disfrazada de peón",
   "Reuse pieces": "Reutilizar piezas",
   "Reverse captures": "Capturas invertidas",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 8c782953..cf32f6e4 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -187,6 +187,7 @@ export const translations = {
   "Move twice": "Jouer deux coups",
   "Neverending rows": "Rangées sans fin",
   "Pawns move diagonally": "Les pions vont en diagonale",
+  "Powerful pieces": "Pièces puissantes",
   "Queen disguised as a pawn": "Reine déguisée en pion",
   "Reuse pieces": "Réutiliser les pièces",
   "Reverse captures": "Captures inversées",
diff --git a/client/src/translations/rules/Perfect/en.pug b/client/src/translations/rules/Perfect/en.pug
new file mode 100644
index 00000000..5973aa8f
--- /dev/null
+++ b/client/src/translations/rules/Perfect/en.pug
@@ -0,0 +1,31 @@
+p.boxed
+  | Three new pieces appear, combining the movements of the knight
+  | with those of the queen, rook or bishop.
+
+figure.diagram-container
+  .diagram
+    | fen:esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR:
+  figcaption.
+    Standard initial position: empresses, princesses and amazons
+    respectively on a, b and d files.
+
+p As the pictures suggest,
+ul
+  li The Amazon (A) moves like a queen + knight,
+  li The Empress (E) moves like a rook + knight,
+  li the Princess (S) moves like a bishop + knight.
+
+p.
+  Note: in the original version of the rules the fairy pieces
+  are called General, Chancellor and Minister.
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/diffmove.dir/perfectchess.html") Perfect chess
+  | &nbsp;on chessvariants.com.
+  | You can also play this variant 
+  a(href="https://greenchess.net/rules.php?v=perfect") on greenchess
+  | .
+
+p Inventor: Köksal Karakus (2000)
diff --git a/client/src/translations/rules/Perfect/es.pug b/client/src/translations/rules/Perfect/es.pug
new file mode 100644
index 00000000..f5a5240e
--- /dev/null
+++ b/client/src/translations/rules/Perfect/es.pug
@@ -0,0 +1,32 @@
+p.boxed
+  | Aparecen tres piezas nuevas que combinan los movimientos del caballo
+  | con los de la dama, la torre o el loco.
+
+figure.diagram-container
+  .diagram
+    | fen:esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR:
+  figcaption.
+    Posición inicial estándar: emperatriz, princesas y amazonas.
+    están respectivamente en las columnas a, b y d.
+
+p Como sugiere la imagen,
+ul
+  li The Amazon (A) se mueve como una dama + caballo,
+  li La emperatriz (E) se mueve como una torre + caballo,
+  li La princesa (S) se mueve como un alfil + caballo.
+
+p.
+  Nota: en la versión original de estas reglas, las piezas mágicas son
+  llamado General, Canciller y Ministro.
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffmove.dir/perfectchess.html") variante Perfect
+  | &nbsp;en chessvariants.com.
+  | Esta variante también es jugable 
+  a(href="https://greenchess.net/rules.php?v=perfect") en greenchess
+  | .
+
+p Inventor: Köksal Karakus (2000)
diff --git a/client/src/translations/rules/Perfect/fr.pug b/client/src/translations/rules/Perfect/fr.pug
new file mode 100644
index 00000000..07e3b38a
--- /dev/null
+++ b/client/src/translations/rules/Perfect/fr.pug
@@ -0,0 +1,32 @@
+p.boxed
+  | Trois nouvelles pièces apparaissent, combinant les déplacements du cavalier
+  | avec ceux de la dame, de la tour ou du fou.
+
+figure.diagram-container
+  .diagram
+    | fen:esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR:
+  figcaption.
+    Position de départ standard : les impératrices, princesses et amazones
+    sont respectivement sur les colonnes a, b et d.
+
+p Comme suggéré par l'image,
+ul
+  li L'Amazone (A) se déplace comme une dame + cavalier,
+  li L'Impératrice (E) se déplace comme une tour + cavalier,
+  li La Princesse (S) se déplace comme un fou + cavalier.
+
+p.
+  Note : dans la version d'ortigine de ces règles, les pièces féériques sont
+  appelées Général, Chancelier et Ministre.
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffmove.dir/perfectchess.html") variante Perfect
+  | &nbsp;sur chessvariants.com.
+  | Cette variante est également jouable 
+  a(href="https://greenchess.net/rules.php?v=perfect") sur greenchess
+  | .
+
+p Inventeur : Köksal Karakus (2000)
diff --git a/client/src/variants/Allmate1.js b/client/src/variants/Allmate1.js
index 65e20b79..3d843005 100644
--- a/client/src/variants/Allmate1.js
+++ b/client/src/variants/Allmate1.js
@@ -99,80 +99,8 @@ export class Allmate1Rules extends ChessRules {
   }
 
   // No "under check" conditions in castling
-  getCastleMoves([x, y]) {
-    const c = this.getColor(x, y);
-    if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
-      return []; //x isn't first rank, or king has moved (shortcut)
-
-    // Castling ?
-    const oppCol = V.GetOppCol(c);
-    let moves = [];
-    let i = 0;
-    // King, then rook:
-    const finalSquares = [
-      [2, 3],
-      [V.size.y - 2, V.size.y - 3]
-    ];
-    castlingCheck: for (
-      let castleSide = 0;
-      castleSide < 2;
-      castleSide++ //large, then small
-    ) {
-      if (this.castleFlags[c][castleSide] >= 8) continue;
-      // If this code is reached, rooks and king are on initial position
-
-      // Nothing on the path of the king ? (and no checks)
-      const finDist = finalSquares[castleSide][0] - y;
-      let step = finDist / Math.max(1, Math.abs(finDist));
-      for (let i = y; i != finalSquares[castleSide][0]; i += step) {
-        if (
-          this.board[x][i] != V.EMPTY &&
-            // NOTE: next check is enough, because of chessboard constraints
-            (this.getColor(x, i) != c ||
-              ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      // Nothing on the path to the rook?
-      step = castleSide == 0 ? -1 : 1;
-      const rookPos = this.castleFlags[c][castleSide];
-      for (i = y + step; i != rookPos; i += step) {
-        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-      }
-
-      // Nothing on final squares, except maybe king and castling rook?
-      for (i = 0; i < 2; i++) {
-        if (
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
-          finalSquares[castleSide][i] != rookPos
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      // If this code is reached, castle is valid
-      moves.push(
-        new Move({
-          appear: [
-            new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
-            new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
-          ],
-          vanish: [
-            new PiPo({ x: x, y: y, p: V.KING, c: c }),
-            new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
-          ],
-          end:
-            Math.abs(y - rookPos) <= 2
-              ? { x: x, y: rookPos }
-              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-        })
-      );
-    }
-
-    return moves;
+  getCastleMoves(sq) {
+    return super.getCastleMoves(sq, "castleInCheck");
   }
 
   // TODO: allow pieces to "commit suicide"? (Currently yes except king)
diff --git a/client/src/variants/Allmate2.js b/client/src/variants/Allmate2.js
index 386bf697..424ff61b 100644
--- a/client/src/variants/Allmate2.js
+++ b/client/src/variants/Allmate2.js
@@ -103,80 +103,8 @@ export class Allmate2Rules extends ChessRules {
   }
 
   // No "under check" conditions in castling
-  getCastleMoves([x, y]) {
-    const c = this.getColor(x, y);
-    if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
-      return []; //x isn't first rank, or king has moved (shortcut)
-
-    // Castling ?
-    const oppCol = V.GetOppCol(c);
-    let moves = [];
-    let i = 0;
-    // King, then rook:
-    const finalSquares = [
-      [2, 3],
-      [V.size.y - 2, V.size.y - 3]
-    ];
-    castlingCheck: for (
-      let castleSide = 0;
-      castleSide < 2;
-      castleSide++ //large, then small
-    ) {
-      if (this.castleFlags[c][castleSide] >= 8) continue;
-      // If this code is reached, rooks and king are on initial position
-
-      // Nothing on the path of the king ? (and no checks)
-      const finDist = finalSquares[castleSide][0] - y;
-      let step = finDist / Math.max(1, Math.abs(finDist));
-      for (let i = y; i != finalSquares[castleSide][0]; i += step) {
-        if (
-          this.board[x][i] != V.EMPTY &&
-            // NOTE: next check is enough, because of chessboard constraints
-            (this.getColor(x, i) != c ||
-              ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      // Nothing on the path to the rook?
-      step = castleSide == 0 ? -1 : 1;
-      const rookPos = this.castleFlags[c][castleSide];
-      for (i = y + step; i != rookPos; i += step) {
-        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-      }
-
-      // Nothing on final squares, except maybe king and castling rook?
-      for (i = 0; i < 2; i++) {
-        if (
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
-          finalSquares[castleSide][i] != rookPos
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      // If this code is reached, castle is valid
-      moves.push(
-        new Move({
-          appear: [
-            new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
-            new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c })
-          ],
-          vanish: [
-            new PiPo({ x: x, y: y, p: V.KING, c: c }),
-            new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
-          ],
-          end:
-            Math.abs(y - rookPos) <= 2
-              ? { x: x, y: rookPos }
-              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-        })
-      );
-    }
-
-    return moves;
+  getCastleMoves(sq) {
+    return super.getCastleMoves(sq, "castleInCheck");
   }
 
   // TODO: allow pieces to "commit suicide"? (Currently yes except king)
diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js
index bea261a3..81d69d81 100644
--- a/client/src/variants/Eightpieces.js
+++ b/client/src/variants/Eightpieces.js
@@ -195,7 +195,7 @@ export class EightpiecesRules extends ChessRules {
       positions.splice(randIndex, 1);
 
       // Rook, jailer and king positions are now almost fixed,
-      // only the ordering rook-> jailer or jailer->rook must be decided.
+      // only the ordering rook->jailer or jailer->rook must be decided.
       let rookPos = positions[0];
       let jailerPos = positions[2];
       const kingPos = positions[1];
@@ -610,81 +610,6 @@ export class EightpiecesRules extends ChessRules {
     );
   }
 
-  // Adapted: castle with jailer possible
-  getCastleMoves([x, y]) {
-    const c = this.getColor(x, y);
-    const firstRank = (c == "w" ? V.size.x - 1 : 0);
-    if (x != firstRank || y != this.INIT_COL_KING[c])
-      return [];
-
-    const oppCol = V.GetOppCol(c);
-    let moves = [];
-    let i = 0;
-    // King, then rook or jailer:
-    const finalSquares = [
-      [2, 3],
-      [V.size.y - 2, V.size.y - 3]
-    ];
-    castlingCheck: for (
-      let castleSide = 0;
-      castleSide < 2;
-      castleSide++
-    ) {
-      if (this.castleFlags[c][castleSide] >= 8) continue;
-      // Rook (or jailer) and king are on initial position
-      const finDist = finalSquares[castleSide][0] - y;
-      let step = finDist / Math.max(1, Math.abs(finDist));
-      i = y;
-      do {
-        if (
-          this.isAttacked([x, i], oppCol) ||
-          (this.board[x][i] != V.EMPTY &&
-            (this.getColor(x, i) != c ||
-              ![V.KING, V.ROOK, V.JAILER].includes(this.getPiece(x, i))))
-        ) {
-          continue castlingCheck;
-        }
-        i += step;
-      } while (i != finalSquares[castleSide][0]);
-      step = castleSide == 0 ? -1 : 1;
-      const rookOrJailerPos = this.castleFlags[c][castleSide];
-      for (i = y + step; i != rookOrJailerPos; i += step)
-        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-
-      // Nothing on final squares, except maybe king and castling rook or jailer?
-      for (i = 0; i < 2; i++) {
-        if (
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
-          finalSquares[castleSide][i] != rookOrJailerPos
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      // If this code is reached, castle is valid
-      const castlingPiece = this.getPiece(firstRank, rookOrJailerPos);
-      moves.push(
-        new Move({
-          appear: [
-            new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
-            new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c })
-          ],
-          vanish: [
-            new PiPo({ x: x, y: y, p: V.KING, c: c }),
-            new PiPo({ x: x, y: rookOrJailerPos, p: castlingPiece, c: c })
-          ],
-          end:
-            Math.abs(y - rookOrJailerPos) <= 2
-              ? { x: x, y: rookOrJailerPos }
-              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-        })
-      );
-    }
-
-    return moves;
-  }
-
   atLeastOneMove() {
     // If in second-half of a move, we already know that a move is possible
     if (this.subTurn == 2) return true;
@@ -764,10 +689,6 @@ export class EightpiecesRules extends ChessRules {
   }
 
   play(move) {
-//    if (!this.states) this.states = [];
-//    const stateFen = this.getFen();
-//    this.states.push(stateFen);
-
     this.prePlay(move);
     move.flags = JSON.stringify(this.aggregateFlags());
     this.epSquares.push(this.getEpSquare(move));
@@ -828,10 +749,6 @@ export class EightpiecesRules extends ChessRules {
       if (move.sentryPush) this.subTurn = 2;
     }
     this.postUndo(move);
-
-//    const stateFen = this.getFen();
-//    if (stateFen != this.states[this.states.length-1]) debugger;
-//    this.states.pop();
   }
 
   postUndo(move) {
diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js
index 5523244b..f4a96ce1 100644
--- a/client/src/variants/Grand.js
+++ b/client/src/variants/Grand.js
@@ -87,12 +87,15 @@ export class GrandRules extends ChessRules {
     return { x: 10, y: 10 };
   }
 
+  // Rook + knight:
   static get MARSHALL() {
     return "m";
-  } //rook+knight
+  }
+
+  // Bishop + knight
   static get CARDINAL() {
     return "c";
-  } //bishop+knight
+  }
 
   static get PIECES() {
     return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]);
diff --git a/client/src/variants/Perfect.js b/client/src/variants/Perfect.js
new file mode 100644
index 00000000..a0c75c57
--- /dev/null
+++ b/client/src/variants/Perfect.js
@@ -0,0 +1,202 @@
+import { ChessRules } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt } from "@/utils/alea";
+
+export class PerfectRules extends ChessRules {
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      {
+        promotions:
+          ChessRules.PawnSpecs.promotions
+          .concat([V.AMAZON, V.EMPRESS, V.PRINCESS])
+      }
+    );
+  }
+
+  getPpath(b) {
+    return (
+      [V.AMAZON, V.EMPRESS, V.PRINCESS].includes(b[1])
+        ? "Perfect/"
+        : ""
+    ) + b;
+  }
+
+  // Queen + knight
+  static get AMAZON() {
+    return "a";
+  }
+
+  // Rook + knight
+  static get EMPRESS() {
+    return "e";
+  }
+
+  // Bishop + knight
+  static get PRINCESS() {
+    return "s";
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.AMAZON, V.EMPRESS, V.PRINCESS]);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    switch (this.getPiece(x, y)) {
+      case V.AMAZON:
+        return this.getPotentialAmazonMoves([x, y]);
+      case V.EMPRESS:
+        return this.getPotentialEmpressMoves([x, y]);
+      case V.PRINCESS:
+        return this.getPotentialPrincessMoves([x, y]);
+      default:
+        return super.getPotentialMovesFrom([x, y]);
+    }
+  }
+
+  getPotentialAmazonMoves(sq) {
+    return super.getPotentialQueenMoves(sq).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  getPotentialEmpressMoves(sq) {
+    return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  getPotentialPrincessMoves(sq) {
+    return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  isAttacked(sq, color) {
+    return (
+      super.isAttacked(sq, color) ||
+      this.isAttackedByAmazon(sq, color) ||
+      this.isAttackedByEmpress(sq, color) ||
+      this.isAttackedByPrincess(sq, color)
+    );
+  }
+
+  isAttackedByAmazon(sq, color) {
+    return (
+      super.isAttackedByQueen(sq, color) ||
+      this.isAttackedBySlideNJump(
+        sq,
+        color,
+        V.MARSHALL,
+        V.steps[V.KNIGHT],
+        "oneStep"
+      )
+    );
+  }
+
+  isAttackedByEmpress(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) ||
+      this.isAttackedBySlideNJump(
+        sq,
+        color,
+        V.MARSHALL,
+        V.steps[V.KNIGHT],
+        "oneStep"
+      )
+    );
+  }
+
+  isAttackedByPrincess(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) ||
+      this.isAttackedBySlideNJump(
+        sq,
+        color,
+        V.CARDINAL,
+        V.steps[V.KNIGHT],
+        "oneStep"
+      )
+    );
+  }
+
+  static get VALUES() {
+    return Object.assign(
+      { a: 12, e: 7, s: 5 }, //experimental
+      ChessRules.VALUES
+    );
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR w 0 ahah -";
+
+    let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
+    let whiteBishopPos = -1;
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        flags += flags;
+        break;
+      }
+
+      let positions = ArrayFun.range(8);
+
+      // Get random squares for bishop: if black, pick a different color
+      // than where the white one stands.
+      let randIndex =
+        c == 'w'
+          ? randInt(8)
+          : 2 * randInt(4) + (1 - whiteBishopPos % 2);
+      if (c == 'w') whiteBishopPos = randIndex;
+      const bishopPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(7);
+      const knightPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(6);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(5);
+      const amazonPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(4);
+      const princessPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Rook, empress and king positions are now almost fixed,
+      // only the ordering rook->empress or empress->rook must be decided.
+      let rookPos = positions[0];
+      let empressPos = positions[2];
+      const kingPos = positions[1];
+      flags += V.CoordToColumn(rookPos) + V.CoordToColumn(empressPos);
+      if (Math.random() < 0.5) [rookPos, empressPos] = [empressPos, rookPos];
+
+      pieces[c][rookPos] = "r";
+      pieces[c][knightPos] = "n";
+      pieces[c][bishopPos] = "b";
+      pieces[c][queenPos] = "q";
+      pieces[c][kingPos] = "k";
+      pieces[c][amazonPos] = "a";
+      pieces[c][princessPos] = "s";
+      pieces[c][empressPos] = "e";
+    }
+    // Add turn + flags + enpassant
+    return (
+      pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 " + flags + " -"
+    );
+  }
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index cee29708..2f1dcf58 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -33,6 +33,7 @@ insert or ignore into Variants (name,description) values
   ('Losers', 'Get strong at self-mate'),
   ('Magnetic', 'Laws of attraction'),
   ('Marseille', 'Move twice'),
+  ('Perfect', 'Powerful pieces'),
   ('Racingkings', 'Kings cross the 8x8 board'),
   ('Rifle', 'Shoot pieces'),
   ('Recycle', 'Reuse pieces'),