wget -q -O public/images/pieces/Eightpieces/tmp_png/"$color$piece".png https://vchess.club/images/pieces/Eightpieces/tmp_png/"$color$piece".png
   done
 done
+for color in "w" "b"; do
+  for piece in "a" "c" "s" "t" "u" "v" "j" "l" "m" "o" "r" "n" "b" "q" "k"; do
+    rm -f public/images/pieces/Titan/"$color$piece".png
+    wget -q -O public/images/pieces/Titan/"$color$piece".png https://vchess.club/images/pieces/Titan/"$color$piece".png
+  done
+done
 for image in "Orda" "Archer" "Lancer" "Kheshig" "Yurt"; do
   rm -f /public/images/variants/Orda/"$image".png
   wget -q -O public/images/variants/Orda/"$image".png https://vchess.club/images/variants/Orda/"$image".png
 
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Capa_1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 380 380"
+   style="enable-background:new 0 0 380 380;"
+   xml:space="preserve"
+   sodipodi:docname="castle.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata
+   id="metadata39"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs37" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1920"
+   inkscape:window-height="1060"
+   id="namedview35"
+   showgrid="false"
+   inkscape:zoom="2.3236842"
+   inkscape:cx="190"
+   inkscape:cy="155.57191"
+   inkscape:window-x="0"
+   inkscape:window-y="20"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Capa_1" />
+<path
+   d="m 358.93345,163.94827 v -15.2498 h -12.77326 v 15.2498 h -14.6918 v -15.2498 h -12.77325 v 15.2498 h -16.47997 v -15.2498 h -12.77884 v 15.2498 h -14.689 v -15.2498 h -12.77885 v 15.2498 h -2.97001 v 12.07211 h 10.63209 v 32.54831 h -41.16218 v -52.12205 h -3.54374 V 116.43308 L 193.23412,64.845953 V 40.25232 c 7.80739,-7.369721 14.39007,-7.266921 24.80611,-4.452218 10.88171,2.937517 17.56403,2.945705 25.85756,-5.475663 -19.46302,11.54538 -31.47073,-21.4787188 -50.66272,-11.522637 v -1.904974 h -4.83176 v 49.221837 l -30.91008,50.313515 v 40.01355 h -3.54557 v 52.12206 h -41.21901 v -32.54831 h 10.63211 v -12.07212 h -2.97002 v -15.2498 h -12.77046 v 15.2498 H 92.922885 v -15.2498 H 80.149634 v 15.2498 H 63.671532 v -15.2498 H 50.892691 v 15.2498 H 36.204615 v -15.2498 H 23.425774 v 15.2498 h -2.970957 v 12.07212 h 10.632114 v 186.57464 h 68.59838 13.042399 156.90285 13.04429 68.5965 V 176.02038 h 10.63212 V 163.94827 Z M 81.745009,341.22367 H 62.070568 v -30.14663 c 0,-5.30464 4.405212,-9.60765 9.837687,-9.60765 5.432473,0 9.837686,4.30301 9.837686,9.60765 v 30.14663 z M 99.900446,184.39718 h -55.9909 v -8.3768 h 55.9909 z m 91.278234,-72.36172 c 12.22935,4.89161 11.67986,12.79444 11.67986,12.79444 v 26.21204 H 191.17868 179.50254 V 124.8299 c 0,0 -0.55136,-7.90283 11.67614,-12.79444 z m 33.76274,154.32655 v 75.77957 h -33.76274 -33.76087 v -75.77957 c 0,0 -1.5842,-22.84604 33.76087,-36.98871 35.35066,14.14175 33.76274,36.98871 33.76274,36.98871 z m 95.34723,74.86166 H 300.6142 v -30.14663 c 0,-5.30464 4.40522,-9.60765 9.83582,-9.60765 5.43341,0 9.83863,4.30301 9.83863,9.60765 z m 18.16102,-156.82649 h -55.99089 v -8.3768 h 55.99089 z"
+   id="path2"
+   style="stroke-width:0.920469" />
+<g
+   id="g4"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g6"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g8"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g10"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g12"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g14"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g16"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g18"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g20"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g22"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g24"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g26"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g28"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g30"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+<g
+   id="g32"
+   transform="matrix(0.93133451,0,0,0.90972973,14.226052,16.896828)">
+</g>
+</svg>
 
 champion SVG files as well (modified from Wikipedia)
 Letter D: https://svgsilh.com/image/2051714.html
 Mammoth: https://www.flaticon.com/free-icon/mammoth_925138
+Castle icon: https://www.flaticon.com/free-icon/castle_89009
 
--- /dev/null
+#$# git-fat 56f92909d57649cf4e83fee222d4d31ce97db450                 6873
 
--- /dev/null
+#$# git-fat 11885764721183414a33ca0d4f361eef52cf59ea                 7864
 
--- /dev/null
+#$# git-fat 58e925edb232bba20e4c6b8bcd0ba030c400522c                 6496
 
--- /dev/null
+#$# git-fat 377da5b2300e17ca49be08a724d85080141f6677                14023
 
--- /dev/null
+#$# git-fat 492668b6b23979d939af8640a747cad8c02a57f0                16668
 
--- /dev/null
+#$# git-fat b25f663c757f85265d1b43057d8ab3c3a74c71a3                13518
 
--- /dev/null
+#$# git-fat 09ff40b2e6cdae5e3d9c1996b63663991d8e517d                 8711
 
--- /dev/null
+#$# git-fat 637d993756ed5916271875bfeac445aa41943838                 9702
 
--- /dev/null
+#$# git-fat 4f8983d7172fb5c940b42acc292a6f1afadc4fdc                 8166
 
--- /dev/null
+../Eightpieces/tmp_png/bp.png
\ No newline at end of file
 
--- /dev/null
+#$# git-fat 25aa2c85c5fd3aaf2bce47343b39c51edf89f342                11151
 
--- /dev/null
+#$# git-fat 788248951c09c73e32081deae6efce7c242766ac                 7581
 
--- /dev/null
+#$# git-fat bad9ab44a4634519db15e8eaec1f3402d276bb91                 9803
 
--- /dev/null
+#$# git-fat 3d5d5dcd30705eb5b3b94f5f0492242429d53bac                 9181
 
--- /dev/null
+#$# git-fat d64664f3b195b0a59412918bba2c16d052913793                 7126
 
--- /dev/null
+#$# git-fat 58d858d675e11f6fb7d8bc4884b6f425de51689b                 6636
 
--- /dev/null
+taille=64
+convert wn.png -resize "$taille"x"$taille" wn_small.png
+convert wb.png -resize "$taille"x"$taille" wb_small.png
+convert bn.png -resize "$taille"x"$taille" bn_small.png
+convert bb.png -resize "$taille"x"$taille" bb_small.png
+# GIMP: manual fill by color (yellow/red). Then:
+for color in w b; do
+  for piece in r n b q k; do
+    convert -composite -gravity center $color$piece.png "$color"n_small.png $color"$piece"_1.png
+    convert -composite -gravity center $color$piece.png "$color"b_small.png $color"$piece"_2.png
+  done
+done
+# Finally: manual renaming (TODO)
 
--- /dev/null
+#$# git-fat 2cfeae196dcf329318e9aa32b4e8fd83743cf49f                11469
 
--- /dev/null
+#$# git-fat 9b675b6208624b902fbbd6a433da8d1c8f295032                11911
 
--- /dev/null
+#$# git-fat e35d4bc6f8d216ae4bd65874699dcd5eb2d4eeac                11735
 
--- /dev/null
+#$# git-fat 313f9037f964c36f83e04345e7f5d48d033eef0a                13086
 
--- /dev/null
+#$# git-fat dce8ad8bcb0b2b000f51e4b931815069be8cdf4f                14204
 
--- /dev/null
+#$# git-fat 38d8d25e99dd6579f221e1287cae92ea0f390e03                13139
 
--- /dev/null
+#$# git-fat 2b3afd85b70abdbd3aea594aed7c997974c75c2c                11727
 
--- /dev/null
+#$# git-fat 9b7b3ada1693eab7198074517a25fff77601dcc5                11975
 
--- /dev/null
+#$# git-fat 192a9061313c6dd2c0dad24e3d4260e956452d5e                11849
 
--- /dev/null
+../Eightpieces/tmp_png/wp.png
\ No newline at end of file
 
--- /dev/null
+#$# git-fat 8d55cfeab39f3ffefe0249d3123f3492dce591d2                16221
 
--- /dev/null
+#$# git-fat 58fffbf851cf42da824c472c606af380b097f91f                 8644
 
--- /dev/null
+#$# git-fat 67b9dc06c174f688dda8a94132192089455232ae                14810
 
--- /dev/null
+#$# git-fat 53bf6c11b93148cf0cdb2135769bac7fbe2df52f                15043
 
--- /dev/null
+#$# git-fat 488f25c90f9f6b1e4c6b07563ab8d860edee678e                10146
 
--- /dev/null
+#$# git-fat 2a69102b1577f207dcab57a0e3d76826b9c0849a                10249
 
 // NOTE: x coords = top to bottom; y = left to right
 // (from white player perspective)
 export const ChessRules = class ChessRules {
+
   //////////////
   // MISC UTILS
 
 
   // Scan board for kings positions
   scanKings(fen) {
-    this.INIT_COL_KING = { w: -1, b: -1 };
     // Squares of white and black king:
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
         switch (fenRows[i].charAt(j)) {
           case "k":
             this.kingPos["b"] = [i, k];
-            this.INIT_COL_KING["b"] = k;
             break;
           case "K":
             this.kingPos["w"] = [i, k];
-            this.INIT_COL_KING["w"] = k;
             break;
           default: {
             const num = parseInt(fenRows[i].charAt(j), 10);
   // tr: transformation
   getBasicMove([sx, sy], [ex, ey], tr) {
     const initColor = this.getColor(sx, sy);
-    const initPiece = this.getPiece(sx, sy);
+    const initPiece = this.board[sx][sy].charAt(1);
     let mv = new Move({
       appear: [
         new PiPo({
           x: ex,
           y: ey,
           c: this.getColor(ex, ey),
-          p: this.getPiece(ex, ey)
+          p: this.board[ex][ey].charAt(1)
         })
       );
     }
       enpassantMove.vanish.push({
         x: x,
         y: epSquare.y,
-        // Captured piece is usually a pawn, but next line seems harmless
-        p: this.getPiece(x, epSquare.y),
+        p: this.board[x][epSquare.y].charAt(1),
         c: this.getColor(x, epSquare.y)
       });
     }
       V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
       "oneStep"
     );
-    if (V.HasCastle) moves = moves.concat(this.getCastleMoves(sq));
+    if (V.HasCastle && this.castleFlags[this.turn].some(v => v < V.size.y))
+      moves = moves.concat(this.getCastleMoves(sq));
     return moves;
   }
 
   // "castleInCheck" arg to let some variants castle under check
-  getCastleMoves([x, y], castleInCheck, castleWith) {
+  getCastleMoves([x, y], finalSquares, castleInCheck, castleWith) {
     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]
-    ];
+    finalSquares = finalSquares || [ [2, 3], [V.size.y - 2, V.size.y - 3] ];
+    const castlingKing = this.board[x][y].charAt(1);
     castlingCheck: for (
       let castleSide = 0;
       castleSide < 2;
 
       // NOTE: in some variants this is not a rook
       const rookPos = this.castleFlags[c][castleSide];
+      const castlingPiece = this.board[x][rookPos].charAt(1);
       if (
         this.board[x][rookPos] == V.EMPTY ||
         this.getColor(x, rookPos) != c ||
-        (!!castleWith && !castleWith.includes(this.getPiece(x, rookPos)))
+        (!!castleWith && !castleWith.includes(castlingPiece))
       ) {
         // Rook is not here, or changed color (see Benedict)
         continue;
       }
 
       // Nothing on the path of the king ? (and no checks)
-      const castlingPiece = this.getPiece(x, rookPos);
       const finDist = finalSquares[castleSide][0] - y;
       let step = finDist / Math.max(1, Math.abs(finDist));
       i = y;
       do {
         if (
-          // NOTE: "castling" arg is used by some variants (Monster),
-          // where "isAttacked" is overloaded in an infinite-recursive way.
-          // TODO: not used anymore (Monster + Doublemove2 are simplified).
-          (!castleInCheck && this.isAttacked([x, i], oppCol, "castling")) ||
-          (this.board[x][i] != V.EMPTY &&
+          (!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, castlingPiece].includes(this.getPiece(x, i))))
+            (this.getColor(x, i) != c || ![y, rookPos].includes(i))
+          )
         ) {
           continue castlingCheck;
         }
           finalSquares[castleSide][i] != rookPos &&
           this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
           (
-            this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
+            finalSquares[castleSide][i] != y ||
             this.getColor(x, finalSquares[castleSide][i]) != c
           )
         ) {
             new PiPo({
               x: x,
               y: finalSquares[castleSide][0],
-              p: V.KING,
+              p: castlingKing,
               c: c
             }),
             new PiPo({
             })
           ],
           vanish: [
-            new PiPo({ x: x, y: y, p: V.KING, c: c }),
+            // King might be initially disguised (Titan...)
+            new PiPo({ x: x, y: y, p: this.board[x][y][1], c: c }),
             new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
           ],
           end:
       )
     );
   }
+
 };
 
--- /dev/null
+p.boxed
+  | TODO
+
+p.
+  Besides the usual game end conditions, White can win by preventing black
+  long castle. And, Black can win by castling long.
+
+p For example after 1.e4 e5 2.Bc4, Nc6?? loses immediatly: 3.Bxf7+
+
+figure.diagram-container
+  .diagram
+    | fen:r1bqkbnr/pppp1Bpp/2n5/4p3/4P3/8/PPPP1PPP/RNBQK1NR:
+  figcaption After 1.e4 e5 2.Bc4 Nc6 3.Bxf7+ 1-0
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/winning.dir/castle.html")
+    | Castle chess
+  |  on chessvariants.com. See also 
+  a(href="http://cinquantesignes.blogspot.com/2020/09/castlechess.html")
+    | this post
+  |  giving some clarifications and advices.
+
+p Inventor: Ã‰ric Angelini (1996)
 
--- /dev/null
+p.boxed
+  | TODO
 
--- /dev/null
+p.boxed
+  | Les pièces Ã  longue portée peuvent sauter par dessus un obstacle
+  | lorsqu'elle se trouvent sur la première rangée.
+
+p TODO
 
--- /dev/null
+p.boxed
+  | Choose squares for new pieces to appear when the initial ones are moved.
+  | One extra knight (first move), and one extra bishop (second move).
+
+p.
+  Everything is the same as in orthodox rules, except that new pieces appear
+  at initially selected locations: a knight and a bishop.
+
+p.
+  The first move of the game selects the square for the knight
+  (just click on a square on the first rank),
+  and the second move picks the square for the bishop.
+  When an "augmented piece" moves, the extra piece enter into play on the
+  initial square.
+
+figure.diagram-container
+  .diagram
+    | fen:rnaqkbor/pppppppp/8/8/8/8/PPPPPPPP/RMBQKCNR:
+  figcaption Augmented pieces on b1, f1, c8 and g8.
+
+p.
+  Castling is always possible. If both pieces involved are augmented,
+  then two extra pieces appear at the end of the move.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:rm1qkbor/ppp2ppp/4b3/3pp3/8/5NP1/PPPPPPBP/RNBQJ2V:
+  .diagram.diag22
+    | fen:rm1qkbor/ppp2ppp/4b3/3pp3/8/5NP1/PPPPPPBP/RNBQNRKB:
+  figcaption Before and after 0-0.
+
+h3 More information
+
+p
+  | The variant idea was suggested recently, and corresponds to 
+  a(href="https://musketeerchess.net/home/index.html")
+    | Musketeer Chess
+  | , using non-fairy pieces.
+
+p Inventor: Zied Haddad (2020)
 
--- /dev/null
+p.boxed
+  | Elige espacios donde aparezcan nuevas piezas,
+  | durante un primer desplazamiento desde la primera fila.
+  | Un caballo adicional (primer movimiento) y un alfil adicional
+  | (segundo movimiento).
+
+p.
+  Todo va como el ajedrez ortodoxo, excepto las nuevas
+  piezas que aparecen en lugares designados: un caballo y un alfil.
+
+p.
+  El primer movimiento del juego selecciona una casilla para el caballo
+  (simplemente haga clic en un cuadro en la primera fila),
+  y el segundo movimiento designa una casilla para el alfil.
+  Cuando se mueve una "pieza aumentada", entra la pieza adicional
+  en el juego en la casilla de salida.
+
+figure.diagram-container
+  .diagram
+    | fen:rnaqkbor/pppppppp/8/8/8/8/PPPPPPPP/RMBQKCNR:
+  figcaption Piezas aumentadas en b1, f1, c8 y g8.
+
+p.
+  El enroque siempre es posible. Si las dos piezas involucradas son
+  aumentado, luego aparecen dos nuevas piezas después del movimiento.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:rm1qkbor/ppp2ppp/4b3/3pp3/8/5NP1/PPPPPPBP/RNBQJ2V:
+  .diagram.diag22
+    | fen:rm1qkbor/ppp2ppp/4b3/3pp3/8/5NP1/PPPPPPBP/RNBQNRKB:
+  figcaption Antes y después de 0-0.
+
+h3 Más información
+
+p
+  | Esta idea ha sido sugerida recientemente y corresponde a 
+  a(href = "https://musketeerchess.net/home/index.html")
+    | Ajedrez Mosquetero
+  | , con piezas estándar.
+
+p Inventor: Zied Haddad (2020)
 
--- /dev/null
+p.boxed
+  | Choisissez des cases d'appartition de nouvelles pièces,
+  | lors d'un premier déplacement depuis la première rangée.
+  | Un cavalier (premier coup) et un fou supplémentaires (second coup).
+
+p.
+  Tout se déroule comme aux Ã©checs orthodoxes, Ã  l'exception de nouvelles
+  pièces apparaissant Ã  des endroits désignés : un cavalier et un fou.
+
+p.
+  Le premier coup de la partie sélectionne une case pour le cavalier
+  (cliquez simplement sur une case de la première rangée),
+  et le second coup désigne une case pour le fou.
+  Quand une "pièce augmentée" se déplace, la pièce supplémentaire entre
+  dans le jeu sur la case de départ.
+
+figure.diagram-container
+  .diagram
+    | fen:rnaqkbor/pppppppp/8/8/8/8/PPPPPPPP/RMBQKCNR:
+  figcaption Pièces augmentées sur b1, f1, c8 et g8.
+
+p.
+  Le roque est toujours possible. Si les deux pièces impliquées sont
+  augmentées, alors deux nouvelles pièces apparaissent après le coup.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:rm1qkbor/ppp2ppp/4b3/3pp3/8/5NP1/PPPPPPBP/RNBQJ2V:
+  .diagram.diag22
+    | fen:rm1qkbor/ppp2ppp/4b3/3pp3/8/5NP1/PPPPPPBP/RNBQNRKB:
+  figcaption Avant et après 0-0.
+
+h3 Plus d'information
+
+p
+  | Cette idée a Ã©té suggérée récemment, et correspond aux 
+  a(href="https://musketeerchess.net/home/index.html")
+    | Ã‰checs Mousquetaires
+  | , avec des pièces standard.
+
+p Inventeur : Zied Haddad (2020)
 
 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;
       notation += "=" + move.appear[0].p.toUpperCase();
     return notation;
   }
+
 };
 
 // TODO? atLeastOneMove() would be more efficient if rewritten here
 // (less sideBoard computations)
 export class AliceRules extends ChessRules {
+
   static get ALICE_PIECES() {
     return {
       s: "p",
       notation += "=" + move.appear[0].p.toUpperCase();
     return notation;
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class Allmate1Rules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
 
   // No "under check" conditions in castling
   getCastleMoves(sq) {
-    return super.getCastleMoves(sq, "castleInCheck");
+    return super.getCastleMoves(sq, null, "castleInCheck");
   }
 
   // TODO: allow pieces to "commit suicide"? (Currently yes except king)
           this.kingPos[this.turn] = [-1, -1];
         // Or maybe a rook?
         else if (v.p == V.ROOK) {
-          if (v.y < this.INIT_COL_KING[v.c])
+          if (v.y < this.kingPos[v.c][1])
             this.castleFlags[v.c][0] = 8;
           else
-            // v.y > this.INIT_COL_KING[v.c]
+            // v.y > this.kingPos[v.c][1]
             this.castleFlags[v.c][1] = 8;
         }
       }
     }
     return notation;
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class Allmate2Rules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
 
   // No "under check" conditions in castling
   getCastleMoves(sq) {
-    return super.getCastleMoves(sq, "castleInCheck");
+    return super.getCastleMoves(sq, null, "castleInCheck");
   }
 
   // TODO: allow pieces to "commit suicide"? (Currently yes except king)
           this.kingPos[this.turn] = [-1, -1];
         // Or maybe a rook?
         else if (v.p == V.ROOK) {
-          if (v.y < this.INIT_COL_KING[v.c])
+          if (v.y < this.kingPos[v.c][1])
             this.castleFlags[v.c][0] = 8;
           else
-            // v.y > this.INIT_COL_KING[v.c]
+            // v.y > this.kingPos[v.c][1]
             this.castleFlags[v.c][1] = 8;
         }
       }
     }
     return notation;
   }
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class AmbiguousRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     else move.vanish[1].p = V.TARGET_CODE[move.vanish[1].p];
     return notation;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class Antiking1Rules extends BerolinaRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class Antiking2Rules extends ChessRules {
+
   static get ANTIKING() {
     return "a";
   }
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class AntimatterRules extends ChessRules {
+
   getPotentialMovesFrom([x, y]) {
     let moves = super.getPotentialMovesFrom([x, y]);
-
     // Handle "matter collisions"
     moves.forEach(m => {
       if (
         m.appear.pop();
       }
     });
-
     return moves;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class ApocalypseRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       V.CoordsToSquare(move.end)
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class ArenaRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 import { ChessRules, PiPo } from "@/base_rules";
 
 export class AtomicRules extends ChessRules {
+
   getPotentialMovesFrom([x, y]) {
     let moves = super.getPotentialMovesFrom([x, y]);
 
     if (!this.isAttacked(kp, V.GetOppCol(color))) return "1/2";
     return color == "w" ? "0-1" : "1-0"; //checkmate
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class BalaklavaRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       ChessRules.VALUES
     );
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class BallRules extends ChessRules {
+
   static get Lines() {
     return [
       // White goal:
       finalSquare
     );
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class BaroqueRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
     return notation;
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class BenedictRules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
     };
     return super.getNotation(basicMove);
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class BerolinaRules extends ChessRules {
+
   // En-passant after 2-sq jump
   getEpSquare(moveOrSquare) {
     if (!moveOrSquare) return undefined;
     }
     return super.getNotation(move); //all other pieces are orthodox
   }
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class BicolourRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       this.isAttacked(this.kingPos[color], 'b')
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class BishopawnsRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class CannibalRules extends ChessRules {
+
   static get KING_CODE() {
     return {
       'p': 's',
     }
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class CaptureRules extends ChessRules {
+
   // Trim all non-capturing moves
   static KeepCaptures(moves) {
     return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1);
       return V.KeepCaptures(moves);
     return moves;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class CastleRules extends ChessRules {
+
   getCurrentScore() {
     const baseScore = super.getCurrentScore();
     if (baseScore != '*') return baseScore;
     }
     return '*';
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class ChakartRules extends ChessRules {
+
   static get PawnSpecs() {
     return SuicideRules.PawnSpecs;
   }
     }
     return notation;
   }
+
 };
 
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class Checkered1Rules extends ChessRules {
+
   static board2fen(b) {
     const checkered_codes = {
       p: "s",
       notation += "=" + move.appear[0].p.toUpperCase();
     return notation;
   }
+
 };
 
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class Checkered2Rules extends ChessRules {
+
   static board2fen(b) {
     const checkered_codes = {
       p: "s",
       notation += "=" + move.appear[0].p.toUpperCase();
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class ChecklessRules extends ChessRules {
+
   // Cannot use super.atLeastOneMove: lead to infinite recursion
   atLeastOneMove_aux() {
     const color = this.turn;
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class CircularRules extends ChessRules {
+
   static get HasCastle() {
     return false;
   }
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class ClorangeRules extends ChessRules {
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
       move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
     return piece + "@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class ColorboundRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     );
   }
 
-  // TODO: really find a way to avoid duolicating most of the castling code
-  // each time: here just the queenside castling squares change for black.
   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 [];
-
-    const oppCol = V.GetOppCol(c);
-    let moves = [];
-    let i = 0;
-    // King, then rook:
+    const color = this.getColor(x, y);
     const finalSquares = [
       // Black castle long in an unusual way:
-      (c == 'w' ? [2, 3] : [1, 2]),
+      (color == 'w' ? [2, 3] : [1, 2]),
       [V.size.y - 2, V.size.y - 3]
     ];
-    castlingCheck: for (
-      let castleSide = 0;
-      castleSide < 2;
-      castleSide++ //large, then small
-    ) {
-      if (this.castleFlags[c][castleSide] >= V.size.y) continue;
-
-      const rookPos = this.castleFlags[c][castleSide];
-      const castlingPiece = this.getPiece(x, rookPos);
-      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, castlingPiece].includes(this.getPiece(x, i))))
-        ) {
-          continue castlingCheck;
-        }
-        i += step;
-      } while (i != finalSquares[castleSide][0]);
-
-      step = castleSide == 0 ? -1 : 1;
-      for (i = y + step; i != rookPos; i += step) {
-        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-      }
-
-      for (i = 0; i < 2; i++) {
-        if (
-          finalSquares[castleSide][i] != rookPos &&
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          (
-            this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
-            this.getColor(x, finalSquares[castleSide][i]) != c
-          )
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      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: rookPos, p: castlingPiece, c: c })
-          ],
-          end:
-            Math.abs(y - rookPos) <= 2
-              ? { x: x, y: rookPos }
-              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-        })
-      );
-    }
-
-    return moves;
+    return super.getCastleMoves([x, y], finalSquares);
   }
 
   isAttacked(sq, color) {
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { randInt, sample } from "@/utils/alea";
 
 export class CoregalRules extends ChessRules {
+
   static IsGoodPosition(position) {
     if (!ChessRules.IsGoodPosition(position)) return false;
     const rows = position.split("/");
     return !!flags.match(/^[a-z]{8,8}$/);
   }
 
-  // Scanning king position for faster updates is still interesting,
-  // but no need for INIT_COL_KING because it's given in castle flags.
+  // Scanning king position for faster updates is still interesting.
   scanKings(fen) {
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
     }
   }
 
+  getPPpath(m) {
+    if (
+      m.vanish.length == 2 &&
+      m.appear.length == 2 &&
+      m.vanish[0].p == V.QUEEN
+    ) {
+      // Large castle: show castle symbol
+      return "Coregal/castle";
+    }
+    return super.getPPpath(m);
+  }
+
   getCheckSquares() {
     const color = this.turn;
     let squares = [];
     }
   }
 
-  getPotentialQueenMoves(sq) {
-    return super.getPotentialQueenMoves(sq).concat(this.getCastleMoves(sq));
+  getPotentialQueenMoves([x, y]) {
+    let moves = super.getPotentialQueenMoves([x, y]);
+    const c = this.getColor(x, y);
+    if (this.castleFlags[c].slice(1, 3).includes(y))
+      moves = moves.concat(this.getCastleMoves([x, y]));
+    return moves;
   }
 
-  getCastleMoves([x, y]) {
+  getPotentialKingMoves([x, y]) {
+    let moves = this.getSlideNJumpMoves(
+      [x, y],
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
     const c = this.getColor(x, y);
-    if (
-      x != (c == "w" ? V.size.x - 1 : 0) ||
-      !this.castleFlags[c].slice(1, 3).includes(y)
-    ) {
-      // x isn't first rank, or piece moved
-      return [];
-    }
-    const castlingPiece = this.getPiece(x, y);
+    if (this.castleFlags[c].slice(1, 3).includes(y))
+      moves = moves.concat(this.getCastleMoves([x, y]));
+    return moves;
+  }
 
+  getCastleMoves([x, y]) {
     // Relative position of the selected piece: left or right ?
     // If left: small castle left, large castle right.
     // If right: usual situation.
+    const c = this.getColor(x, y);
     const relPos = (this.castleFlags[c][1] == y ? "left" : "right");
 
-    // Castling ?
-    const oppCol = V.GetOppCol(c);
-    let moves = [];
-    let i = 0;
-    // Castling piece, then rook:
-    const finalSquares = {
-      0: (relPos == "left" ? [1, 2] : [2, 3]),
-      3: (relPos == "right" ? [6, 5] : [5, 4])
-    };
-
-    // Left, then right castle:
-    castlingCheck: for (let castleSide of [0, 3]) {
-      if (this.castleFlags[c][castleSide] >= 8) continue;
-
-      // Rook and castling piece are on initial position
-      const rookPos = this.castleFlags[c][castleSide];
-
-      // 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));
-      i = y;
-      do {
-        if (
-          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 ||
-              ![castlingPiece, V.ROOK].includes(this.getPiece(x, i))))
-        ) {
-          continue castlingCheck;
-        }
-        i += step;
-      } while (i != finalSquares[castleSide][0]);
-
-      // Nothing on the path to the rook?
-      step = castleSide == 0 ? -1 : 1;
-      for (i = y + step; i != rookPos; i += step) {
-        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
-      }
-
-      // Nothing on final squares, except maybe castling piece and rook?
-      for (i = 0; i < 2; i++) {
-        if (
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          ![y, rookPos].includes(finalSquares[castleSide][i])
-        ) {
-          continue castlingCheck;
-        }
-      }
-
-      // If this code is reached, castle is valid
-      moves.push(
-        new Move({
-          appear: [
-            new PiPo({
-              x: x,
-              y: finalSquares[castleSide][0],
-              p: castlingPiece,
-              c: c
-            }),
-            new PiPo({
-              x: x,
-              y: finalSquares[castleSide][1],
-              p: V.ROOK,
-              c: c
-            })
-          ],
-          vanish: [
-            new PiPo({ x: x, y: y, p: castlingPiece, c: c }),
-            new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c })
-          ],
-          // In this variant, always castle by playing onto the rook
-          end: { x: x, y: rookPos }
-        })
-      );
-    }
-
+    const finalSquares = [
+      relPos == "left" ? [1, 2] : [2, 3],
+      relPos == "right" ? [6, 5] : [5, 4]
+    ];
+    const saveFlags = JSON.stringify(this.castleFlags[c]);
+    // Alter flags to follow base_rules semantic
+    this.castleFlags[c] = [0, 3].map(i => this.castleFlags[c][i]);
+    const moves = super.getCastleMoves([x, y], finalSquares);
+    this.castleFlags[c] = JSON.parse(saveFlags);
     return moves;
   }
 
     }
     return super.getNotation(move);
   }
+
 };
 
 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:
       notation += "=Q";
     return notation;
   }
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class CrazyhouseRules extends ChessRules {
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
       move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
     return piece + "@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { randInt, shuffle } from "@/utils/alea";
 
 export class CylinderRules extends ChessRules {
+
   // Output basically x % 8 (circular board)
   static ComputeY(y) {
     let res = y % V.size.y;
       k: 1000
     };
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class DarkRules extends ChessRules {
+
   // Analyse in Dark mode makes no sense
   static get CanAnalyze() {
     return false;
       candidates.push(j);
     return moves[candidates[randInt(candidates.length)]];
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class DiamondRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     }
     return super.getNotation(move); //all other pieces are orthodox
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class DiceRules extends ChessRules {
+
   static get CanAnalyze() {
     return false;
   }
   getNotation(move) {
     return super.getNotation(move) + "/" + move.end.p.toUpperCase();
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class DiscoduelRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 // ...But the middle king will get captured quickly...
 
 export class DoublearmyRules extends ChessRules {
+
   static get COMMONER() {
     return "c";
   }
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class Doublemove1Rules extends ChessRules {
+
   static IsGoodEnpassant(enpassant) {
     const squares = enpassant.split(",");
     if (squares.length > 2) return false;
     }
     return doubleMove;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class Doublemove2Rules extends ChessRules {
+
   static IsGoodEnpassant(enpassant) {
     const squares = enpassant.split(",");
     if (squares.length > 2) return false;
     // TODO: not always the best move played (why ???)
     return doubleMove;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class DynamoRules extends ChessRules {
+
   // TODO? later, allow to push out pawns on a and h files
   static get HasEnpassant() {
     return false;
       return initialSquare + "R";
     return move.appear[0].p.toUpperCase() + initialSquare + finalSquare;
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class EightpiecesRules extends ChessRules {
+
   static get JAILER() {
     return "j";
   }
     }
     return notation;
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class EnpassantRules extends ChessRules {
+
   static IsGoodEnpassant(enpassant) {
     if (enpassant != "-") {
       const squares = enpassant.split(",");
       k: 1000
     };
   }
+
 };
 
--- /dev/null
+import { ChessRules } from "@/base_rules";
+
+export class EvolutionRules extends ChessRules {
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    const c = this.getColor(x, y);
+    const piece = this.getPiece(x, y);
+    if (
+      [V.BISHOP, V.ROOK, V.QUEEN].includes(piece) &&
+      (c == 'w' && x == 7) || (c == 'b' && x == 0)
+    ) {
+      // Move from first rank
+      const forward = (c == 'w' ? -1 : 1);
+      for (let shift of [-2, 0, 2]) {
+        if (
+          (piece == V.ROOK && shift != 0) ||
+          (piece == V.BISHOP && shift == 0)
+        ) {
+          continue;
+        }
+        if (
+          V.OnBoard(x+2*forward, y+shift) &&
+          this.board[x+forward][y+shift/2] != V.EMPTY &&
+          this.getColor(x+2*forward, y+shift) != c
+        ) {
+          moves.push(this.getBasicMove([x,y], [x+2*forward,y+shift]));
+        }
+      }
+    }
+    return moves;
+  }
+
+};
 
 import { ChessRules } from "@/base_rules";
 
 export class ExtinctionRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     }
     return super.evalPosition();
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class FootballRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       " w 0 -"
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class ForwardRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   }
 
   scanKings(fen) {
-    this.INIT_COL_KING = { w: -1, b: -1 };
     // Squares of white and black king:
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
           case "k":
           case "l":
             this.kingPos["b"] = [i, k];
-            this.INIT_COL_KING["b"] = k;
             break;
           case "K":
           case "L":
             this.kingPos["w"] = [i, k];
-            this.INIT_COL_KING["w"] = k;
             break;
           default: {
             const num = parseInt(fenRows[i].charAt(j), 10);
       ChessRules.VALUES
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class FreecaptureRules extends ChessRules {
+
   canTake() {
     // Can capture both colors:
     return true;
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 // NOTE: initial setup differs from the original; see
 // https://www.chessvariants.com/large.dir/freeling.html
 export class GrandRules extends ChessRules {
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
       " w 0 " + flags + " - 00000000000000"
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class GrasshopperRules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
         "/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/"
       );
   }
+
 };
 
 import { BerolinaRules } from "@/variants/Berolina";
 
 export class GridolinaRules extends BerolinaRules {
+
   static get Lines() {
     return [
       [[2, 0], [2, 8]],
     }
     return false;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class HamiltonRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     // First game move:
     return "N@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class HiddenRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       finalSquare
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class HiddenqueenRules extends ChessRules {
+
   // Analyse in Hiddenqueen mode makes no sense
   static get CanAnalyze() {
     return false;
     else notation = finalSquare;
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class HordeRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     // From black side, just run usual checks:
     return super.getCurrentScore();
   }
+
 };
 
 import { randInt, shuffle } from "@/utils/alea";
 
 export class InterweaveRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     if (move.vanish.length >= 2) notation += "X";
     return notation;
   }
+
 };
 
 import { SuicideRules } from "@/variants/Suicide";
 
 export class KingletRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       k: 4
     };
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class KnightmateRules extends ChessRules {
+
   static get COMMONER() {
     return "c";
   }
       k: 1000
     };
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class KnightpawnsRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
 
   postPlay() {}
   postUndo() {}
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class Knightrelay1Rules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
 
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class Knightrelay2Rules extends ChessRules {
+
   getPotentialMovesFrom([x, y]) {
     let moves = super.getPotentialMovesFrom([x, y]);
 
 
     return notation;
   }
+
 };
 
 import { ChessRules, PiPo } from "@/base_rules";
 
 export class KoopaRules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
   // stand for stunned indicator.
 
   scanKings(fen) {
-    this.INIT_COL_KING = { w: -1, b: -1 };
     // Squares of white and black king:
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
           case "k":
           case "l":
             this.kingPos["b"] = [i, k];
-            this.INIT_COL_KING["b"] = k;
             break;
           case "K":
           case "L":
             this.kingPos["w"] = [i, k];
-            this.INIT_COL_KING["w"] = k;
             break;
           default: {
             const num = parseInt(fenRows[i].charAt(j), 10);
         sq,
         V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
         "oneStep"
-      ).concat(super.getCastleMoves(sq, true, ['r']))
+      ).concat(super.getCastleMoves(sq, null, true, ['r']))
     );
   }
 
     }
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class KothRules extends ChessRules {
+
   static get Lines() {
     return [
       [[3, 3], [3, 5]],
       ) / 2
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class LosersRules extends ChessRules {
+
   // Trim all non-capturing moves
   static KeepCaptures(moves) {
     return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1);
     // Less material is better (more subtle in fact but...)
     return -super.evalPosition();
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class MadhouseRules extends ChessRules {
+
   hoverHighlight(x, y) {
     // Testing move validity results in an infinite update loop.
     // TODO: find a way to test validity anyway.
       move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
     return piece + "@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class MadrasiRules extends ChessRules {
+
   isImmobilized(sq) {
     const oppCol = V.GetOppCol(this.getColor(sq[0], sq[1]));
     const piece = this.getPiece(sq[0], sq[1]);
     // Connected kings paralyze each other
     return false;
   }
+
 };
 
 import { ChessRules, PiPo } from "@/base_rules";
 
 export class MagneticRules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { randInt, shuffle } from "@/utils/alea";
 
 export class MakrukRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       k: 1000
     };
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class MaximaRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
     return notation;
   }
+
 };
 
 import { ShogiRules } from "@/variants/Shogi";
 
 export class MinishogiRules extends ShogiRules {
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
   static get SEARCH_DEPTH() {
     return 3;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class MonochromeRules extends ChessRules {
+
   static get HasEnpassant() {
     // Pawns would be on the same side
     return false;
     }
     return notation;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class MonsterRules extends ChessRules {
+
   static IsGoodFlags(flags) {
     // Only black can castle
     return !!flags.match(/^[a-z]{2,2}$/);
     const color = this.turn;
     return (color == 'w' ? getBestWhiteMove() : getBestBlackMove());
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class OmegaRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     return this.getSlideNJumpMoves(sq, V.steps[V.WIZARD], "oneStep");
   }
 
-  getCastleMoves([x, y], castleInCheck) {
-    const c = this.getColor(x, y);
-    if (x != (c == "w" ? V.size.x - 2 : 1) || 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:
+  getCastleMoves([x, y]) {
     const finalSquares = [
       [4, 5],
       [8, 7]
     ];
-    castlingCheck: for (
-      let castleSide = 0;
-      castleSide < 2;
-      castleSide++ //large, then small
-    ) {
-      if (this.castleFlags[c][castleSide] >= V.size.y) continue;
-      // If this code is reached, rook and king are on initial position
-
-      // NOTE: in some variants this is not a rook
-      const rookPos = this.castleFlags[c][castleSide];
-      if (this.board[x][rookPos] == V.EMPTY || this.getColor(x, rookPos) != c)
-        // Rook is not here, or changed color (see Benedict)
-        continue;
-
-      // Nothing on the path of the king ? (and no checks)
-      const castlingPiece = this.getPiece(x, rookPos);
-      const finDist = finalSquares[castleSide][0] - y;
-      let step = finDist / Math.max(1, Math.abs(finDist));
-      i = y;
-      do {
-        if (
-          (!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, castlingPiece].includes(this.getPiece(x, i))))
-        ) {
-          continue castlingCheck;
-        }
-        i += step;
-      } while (i != finalSquares[castleSide][0]);
-
-      // Nothing on the path to the rook?
-      step = castleSide == 0 ? -1 : 1;
-      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 (
-          finalSquares[castleSide][i] != rookPos &&
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          (
-            this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
-            this.getColor(x, finalSquares[castleSide][i]) != c
-          )
-        ) {
-          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: castlingPiece,
-              c: c
-            })
-          ],
-          vanish: [
-            new PiPo({ x: x, y: y, p: V.KING, c: c }),
-            new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
-          ],
-          end:
-            Math.abs(y - rookPos) <= 2
-              ? { x: x, y: rookPos }
-              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-        })
-      );
-    }
-
-    return moves;
+    return super.getCastleMoves([x, y], finalSquares);
   }
 
   isAttacked(sq, color) {
     }
     return evaluation;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class OrdaRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       }
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class OrdamirrorRules extends OrdaRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       k: 1000
     };
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class Pacifist1Rules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     return true;
   }
 
-  scanKings(fen) {
-    // Kings may be swapped, so they are not tracked (no kingPos)
-    this.INIT_COL_KING = { w: -1, b: -1 };
-    const fenRows = V.ParseFen(fen).position.split("/");
-    const startRow = { 'w': V.size.x - 1, 'b': 0 };
-    for (let i = 0; i < fenRows.length; i++) {
-      let k = 0; //column index on board
-      for (let j = 0; j < fenRows[i].length; j++) {
-        switch (fenRows[i].charAt(j)) {
-          case "k":
-            this.INIT_COL_KING["b"] = k;
-            break;
-          case "K":
-            this.INIT_COL_KING["w"] = k;
-            break;
-          default: {
-            const num = parseInt(fenRows[i].charAt(j), 10);
-            if (!isNaN(num)) k += num - 1;
-          }
-        }
-        k++;
-      }
-    }
-  }
+  // Kings may be swapped, so they are not tracked (no kingPos)
+  scanKings(fen) { }
 
   // Sum white pieces attacking a square, and remove black pieces count.
   sumAttacks([x, y]) {
   static get SEARCH_DEPTH() {
     return 1;
   }
+
 };
 
 import { Pacifist1Rules } from "@/variants/Pacifist1";
 
 export class Pacifist2Rules extends Pacifist1Rules {
+
   // Sum values of white pieces attacking a square,
   // and remove (sum of) black pieces values.
   sumAttacks([x, y]) {
     });
     return res;
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class ParachuteRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
     return piece + "@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class PawnmassacreRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       .concat(bFen.substr(splitIdx))
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class PawnsRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class PawnskingRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class PerfectRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       " w 0 " + flags + " -"
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class PocketknightRules extends ChessRules {
+
   hoverHighlight(x, y) {
     // Testing move validity results in an infinite update loop.
     // TODO: find a way to test validity anyway.
     // Knight landing:
     return "N@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class Progressive1Rules extends ChessRules {
+
   static get HasEnpassant() {
     return false;
   }
     for (let i=res.length - 1; i>= 0; i--) this.undo(res[i]);
     return res;
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class Progressive2Rules extends Progressive1Rules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       k: 1000
     };
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class QueenpawnsRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class RacingkingsRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     // Ponder with king position:
     return evaluation/5 + this.kingPos["b"][0] - this.kingPos["w"][0];
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class RampageRules extends ChessRules {
+
   // Sum white pieces attacking a square, and remove black pieces count.
   sumAttacks([x, y]) {
     const getSign = (color) => {
   static get SEARCH_DEPTH() {
     return 1;
   }
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class RecycleRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
     return piece + "@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class RifleRules extends ChessRules {
+
   getBasicMove([sx, sy], [ex, ey], tr) {
     let mv = new Move({
       appear: [],
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class RococoRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class RookpawnsRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   static get SEARCH_DEPTH() {
     return 4;
   }
+
 };
 
 import { randInt, shuffle } from "@/utils/alea";
 
 export class RoyalraceRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       V.CoordsToSquare(move.end)
     );
   }
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class RugbyRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     // Stalemate (will probably never happen)
     return "1/2";
   }
+
 };
 
 import { ChessRules, PiPo } from "@/base_rules";
 
 export class SchessRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     }
     return super.getNotation(move);
   }
+
 };
 
 import { randInt, sample } from "@/utils/alea";
 
 export class ShakoRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
   }
 
   getCastleMoves([x, y]) {
-    const c = this.getColor(x, y);
-    if (x != (c == "w" ? V.size.x - 2 : 1) || y != this.INIT_COL_KING[c])
-      return []; //x isn't second rank, or king has moved (shortcut)
-
-    // Castling ?
-    const oppCol = V.GetOppCol(c);
-    let moves = [];
-    let i = 0;
-    // King, then rook:
     const finalSquares = [
       [3, 4],
       [7, 6]
     ];
-    castlingCheck: for (
-      let castleSide = 0;
-      castleSide < 2;
-      castleSide++ //large, then small
-    ) {
-      if (this.castleFlags[c][castleSide] >= V.size.y) continue;
-      // If this code is reached, rook and king are on initial position
-
-      const rookPos = this.castleFlags[c][castleSide];
-
-      // Nothing on the path of the king ? (and no checks)
-      const castlingPiece = this.getPiece(x, rookPos);
-      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 &&
-            // NOTE: next check is enough, because of chessboard constraints
-            (this.getColor(x, i) != c ||
-              ![V.KING, castlingPiece].includes(this.getPiece(x, i))))
-        ) {
-          continue castlingCheck;
-        }
-        i += step;
-      } while (i != finalSquares[castleSide][0]);
-
-      // Nothing on the path to the rook?
-      step = castleSide == 0 ? -1 : 1;
-      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 (
-          finalSquares[castleSide][i] != rookPos &&
-          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          (
-            this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
-            this.getColor(x, finalSquares[castleSide][i]) != c
-          )
-        ) {
-          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: castlingPiece,
-              c: c
-            })
-          ],
-          vanish: [
-            new PiPo({ x: x, y: y, p: V.KING, c: c }),
-            new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
-          ],
-          end:
-            Math.abs(y - rookPos) <= 2
-              ? { x: x, y: rookPos }
-              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
-        })
-      );
-    }
-
-    return moves;
+    return super.getCastleMoves([x, y], finalSquares);
   }
 
   isAttacked(sq, color) {
       " w 0 " + flags + " -"
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class ShatranjRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       k: 1000
     };
   }
+
 };
 
 import { sample, shuffle } from "@/utils/alea";
 
 export class ShogiRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       )
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class SittuyinRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
     }
     return super.getNotation(move);
   }
+
 };
 
 import { SuicideRules } from "@/variants/Suicide";
 
 export class SuctionRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       finalSquare
     );
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class SuicideRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       " w 0 -"
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class SwapRules extends ChessRules {
+
   setOtherVariables(fen) {
     super.setOtherVariables(fen);
     // Local stack of swaps
     // Swap
     return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class SwitchingRules extends ChessRules {
+
   // Build switch move between squares x1,y1 and x2,y2
        getSwitchMove_s([x1, y1], [x2, y2]) {
                const c = this.getColor(x1, y1); //same as color at square 2
     // Switch
     return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
   }
-}
+
+};
 
 import { randInt } from "@/utils/alea";
 
 export class SynchroneRules extends ChessRules {
+
   static get CanAnalyze() {
     return false;
   }
         : null;
   }
 
-  // After undo(): no need to re-set INIT_COL_KING
   scanKings() {
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     for (let i = 0; i < V.size.x; i++) {
       V.CoordsToSquare(move.end)
     );
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class TakenmakeRules extends ChessRules {
+
   setOtherVariables(fen) {
     super.setOtherVariables(fen);
     // Stack of "last move" only for intermediate captures
     delete moves[mIdx]["next"];
     return [moves[mIdx], move2];
   }
+
 };
 
 import { randInt } from "@/utils/alea";
 
 export class TeleportRules extends ChessRules {
+
   hoverHighlight(x, y) {
     // Testing move validity results in an infinite update loop.
     // TODO: find a way to test validity anyway.
       move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
     return piece + "@" + V.CoordsToSquare(move.end);
   }
+
 };
 
 import { shuffle } from "@/utils/alea";
 
 export class TencubedRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       { c: 4, w: 3, a: 6, m: 8 }
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class ThreechecksRules extends ChessRules {
+
   static IsGoodFlags(flags) {
     // 4 for castle + 2 for checks (0,1 or 2)
     return !!flags.match(/^[01]{4,4}[012]{2,2}$/);
     // Take number of checks into account
     return baseEval/5 - this.checkFlags["w"] + this.checkFlags["b"];
   }
+
 };
 
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class TitanRules extends ChessRules {
-  // Idea: yellow = bishop, orange = knight (for white)
-  // and, red = bishop + purple = knight (black side)
-  // (avoid using a bigger board, or complicated drawings)
+
+  static get IMAGE_EXTENSION() {
+    // Temporarily, for the time SVG pieces are being designed:
+    return ".png";
+  }
 
   // Decode if normal piece, or + bishop or knight
-  getPiece(i, j) {
-    const piece = this.board[i][j].charAt(1);
+  getPiece(x, y) {
+    const piece = this.board[x][y].charAt(1);
     if (ChessRules.PIECES.includes(piece)) return piece;
     // Augmented piece:
     switch (piece) {
     }
   }
 
-  // TODO: subtelty, castle forbidden if 
-
   // Code: a/c = bishop + knight/bishop j/l for king,
   // m/o for knight, s/t for queen, u/v for rook
   static get AUGMENTED_PIECES() {
     ];
   }
 
+  getPpath(b) {
+    return "Titan/" + b;
+  }
+
   // Decode above notation into additional piece
   getExtraPiece(symbol) {
     if (['a','j','m','s','u'].includes(symbol))
     return 'b';
   }
 
+  // Inverse operation: augment piece
+  getAugmented(piece) {
+    const knight = this.movesCount <= 1;
+    switch (piece) {
+      case V.ROOK: return (knight ? 'u' : 'v');
+      case V.KNIGHT: return (knight ? 'm' : 'o');
+      case V.BISHOP: return (knight ? 'a' : 'c');
+      case V.QUEEN: return (knight ? 's' : 't');
+      case V.KING: return (knight ? 'j' : 'l');
+    }
+    return '_'; //never reached
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kings = { "w": 0, "b": 0 };
+    const allPiecesCodes = V.PIECES.concat(V.AUGMENTED_PIECES);
+    const kingBlackCodes = ['j','k','l'];
+    const kingWhiteCodes = ['J','K','L'];
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (kingBlackCodes.includes(row[i])) kings['b']++;
+        else if (kingWhiteCodes.includes(row[i])) kings['w']++;
+        if (allPiecesCodes.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // Both kings should be on board, only one of each color:
+    if (Object.values(kings).some(v => v != 1)) return false;
+    return true;
+  }
+
+  // Kings may be augmented:
+  scanKings(fen) {
+    this.kingPos = { w: [-1, -1], b: [-1, -1] };
+    const rows = V.ParseFen(fen).position.split("/");
+    for (let i = 0; i < rows.length; i++) {
+      let k = 0; //column index on board
+      for (let j = 0; j < rows[i].length; j++) {
+        const piece = rows[i].charAt(j);
+        if (['j','k','l'].includes(piece.toLowerCase())) {
+          const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b');
+          this.kingPos[color] = [i, k];
+        }
+        else {
+          const num = parseInt(rows[i].charAt(j), 10);
+          if (!isNaN(num)) k += num - 1;
+        }
+        k++;
+      }
+    }
+  }
+
   // If piece not in usual list, bishop or knight appears.
   getPotentialMovesFrom([x, y]) {
-    let moves = super.getPotentialMovesFrom(sq);
+    if (this.movesCount <= 3) {
+      // Setup stage
+      const color = this.getColor(x, y);
+      const firstRank = (color == 'w' ? 7 : 0);
+      if (x != firstRank || V.AUGMENTED_PIECES.includes(this.board[x][y][1]))
+        return [];
+      const piece = this.getPiece(x, y);
+      const move = new Move({
+        appear: [
+          new PiPo({ x: x, y: y, c: color, p: this.getAugmented(piece) })
+        ],
+        vanish: [
+          new PiPo({ x: x, y: y, c: color, p: piece })
+        ],
+        start: { x: x, y: y },
+        end: { x: x, y: y }
+      });
+      return [move];
+    }
+    let moves = super.getPotentialMovesFrom([x, y]);
+    const initialPiece = this.getPiece(x, y);
     const color = this.turn;
-    
-// treat castle case here (both pieces appear!)
     if (
       V.AUGMENTED_PIECES.includes(this.board[x][y][1]) &&
       ((color == 'w' && x == 7) || (color == "b" && x == 0))
     ) {
       const newPiece = this.getExtraPiece(this.board[x][y][1]);
       moves.forEach(m => {
+        m.appear[0].p = initialPiece;
         m.appear.push(
           new PiPo({
             p: newPiece,
         );
       });
     }
+    moves.forEach(m => {
+      if (m.vanish.length <= 1) return;
+      const [vx, vy] = [m.vanish[1].x, m.vanish[1].y];
+      if (
+        m.appear.length >= 2 && //3 if the king was also augmented
+        m.vanish.length == 2 &&
+        m.vanish[1].c == color &&
+        V.AUGMENTED_PIECES.includes(this.board[vx][vy][1])
+      ) {
+        // Castle, rook is an "augmented piece"
+        m.appear[1].p = V.ROOK;
+        m.appear.push(
+          new PiPo({
+            p: this.getExtraPiece(this.board[vx][vy][1]),
+            c: color,
+            x: vx,
+            y: vy
+          })
+        );
+      }
+    });
     return moves;
   }
 
-  // TODO: special case of move 1 = choose squares, knight first, then bishop
-  // (just click ?)
+  // Special case of move 1 = choose squares, knight first, then bishop
+  doClick(square) {
+    if (this.movesCount >= 4) return null;
+    const color = this.turn;
+    const [x, y] = [square[0], square[1]];
+    if ((color == 'w' && x != 7) || (color == 'b' && x != 0)) return null;
+    const selectedPiece = this.board[x][y][1];
+    return new Move({
+      appear: [
+        new PiPo({
+          x: x,
+          y: y,
+          c: color,
+          p: this.getAugmented(selectedPiece)
+        })
+      ],
+      vanish: [
+        new PiPo({
+          x: x,
+          y: y,
+          c: color,
+          p: selectedPiece
+        })
+      ],
+      start: { x: x, y: y },
+      end: { x: x, y: y }
+    });
+  }
+
+  postPlay(move) {
+    if (this.movesCount > 4) super.postPlay(move);
+  }
+
+  postUndo(move) {
+    if (this.movesCount >= 4) super.postUndo(move);
+  }
+
+  evalPosition() {
+    let evaluation = 0;
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] != V.EMPTY) {
+          const sign = this.getColor(i, j) == "w" ? 1 : -1;
+          const piece = this.getPiece(i, j);
+          evaluation += sign * V.VALUES[piece];
+          const symbol = this.board[i][j][1];
+          if (V.AUGMENTED_PIECES.includes(symbol)) {
+            const extraPiece = this.getExtraPiece(symbol);
+            evaluation += sign * V.VALUES[extraPiece]
+          }
+        }
+      }
+    }
+    return evaluation;
+  }
+
+  getNotation(move) {
+    if (
+      move.appear[0].x != move.vanish[0].x ||
+      move.appear[0].y != move.vanish[0].y
+    ) {
+      if (
+        V.AUGMENTED_PIECES.includes(move.vanish[0].p) ||
+        (
+          move.vanish.length >= 2 &&
+          V.AUGMENTED_PIECES.includes(move.vanish[1].p)
+        )
+      ) {
+        // Simplify move before calling super.getNotation()
+        let smove = JSON.parse(JSON.stringify(move));
+        if (ChessRules.PIECES.includes(move.vanish[0].p)) {
+          // Castle with an augmented rook
+          smove.appear.pop();
+          smove.vanish[1].p = smove.appear[1].p;
+        }
+        else {
+          // Moving an augmented piece
+          smove.appear.pop();
+          smove.vanish[0].p = smove.appear[0].p;
+          if (
+            smove.vanish.length == 2 &&
+            smove.vanish[0].c == smove.vanish[1].c &&
+            V.AUGMENTED_PIECES.includes(move.vanish[1].p)
+          ) {
+            // Castle with an augmented rook
+            smove.appear.pop();
+            smove.vanish[1].p = smove.appear[1].p;
+          }
+        }
+        return super.getNotation(smove);
+      }
+      // Else, more common case:
+      return super.getNotation(move);
+    }
+    // First moves in game, placements:
+    const square = V.CoordsToSquare(move.appear[0]);
+    const reserve =
+      (['a','j','m','s','u'].includes(move.appear[0].p) ? 'N' : 'B');
+    return '+' + reserve + '@' + square;
+  }
+
 };
 
 import { CoregalRules } from "@/variants/Coregal";
 
 export class TwokingsRules extends CoregalRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     );
   }
 
+  getPotentialQueenMoves(sq) {
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+  }
+
   underCheck(color) {
     const oppCol = V.GetOppCol(color);
     for (let i=0; i<V.size.x; i++) {
   }
 
   postUndo() {}
+
 };
 
 import { ArrayFun } from "@/utils/array";
 
 export class UpsidedownRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
   static get SEARCH_DEPTH() {
     return 2;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class VchessRules extends ChessRules {
+
   static get PawnSpecs() {
     return Object.assign(
       {},
     }
     return notation;
   }
+
 };
 
 import { sample, randInt } from "@/utils/alea";
 
 export class WildebeestRules extends ChessRules {
+
   static get size() {
     return { x: 10, y: 11 };
   }
       " w 0 " + flags + " -"
     );
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class WormholeRules extends ChessRules {
+
   static get HasFlags() {
     return false;
   }
       notation += "=" + move.appear[0].p.toUpperCase();
     return notation;
   }
+
 };
 
 import { ChessRules } from "@/base_rules";
 
 export class ZenRules extends ChessRules {
+
   getEpSquare(moveOrSquare) {
     if (!moveOrSquare) return undefined;
     if (typeof moveOrSquare === "string") {
       k: 1000
     };
   }
+
 };