Some Chakart fixes/adjustments
[vchess.git] / client / src / variants / Chakart.js
index 45230a1..86a867a 100644 (file)
@@ -4,6 +4,7 @@ import { ArrayFun } from "@/utils/array";
 import { randInt } from "@/utils/alea";
 
 export class ChakartRules extends ChessRules {
+
   static get PawnSpecs() {
     return SuicideRules.PawnSpecs;
   }
@@ -22,29 +23,13 @@ export class ChakartRules extends ChessRules {
   }
 
   static get CanAnalyze() {
-    return true; //false;
+    return false;
   }
 
   static get SomeHiddenMoves() {
     return true;
   }
 
-  hoverHighlight(x, y) {
-    if (this.subTurn == 1) return false;
-    const L = this.firstMove.length;
-    const fm = this.firstMove[L-1];
-    if (fm.end.effect != 0) return false;
-    const deltaX = Math.abs(fm.appear[0].x - x);
-    const deltaY = Math.abs(fm.appear[0].y - y);
-    return (
-      (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') &&
-      (
-        (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) ||
-        (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1)
-      )
-    );
-  }
-
   static get IMMOBILIZE_CODE() {
     return {
       'p': 's',
@@ -114,8 +99,13 @@ export class ChakartRules extends ChessRules {
   }
 
   getPPpath(m) {
+    if (!!m.promoteInto) return m.promoteInto;
+    if (m.appear.length == 0 && m.vanish.length == 1)
+      // King 'remote shell capture', on an adjacent square:
+      return this.getPpath(m.vanish[0].c + m.vanish[0].p);
     let piece = m.appear[0].p;
     if (Object.keys(V.IMMOBILIZE_DECODE).includes(piece))
+      // Promotion by capture into immobilized piece: do not reveal!
       piece = V.IMMOBILIZE_DECODE[piece];
     return this.getPpath(m.appear[0].c + piece);
   }
@@ -147,14 +137,14 @@ export class ChakartRules extends ChessRules {
         if (['K', 'k', 'L', 'l'].includes(row[i])) kings[row[i]]++;
         if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
         else {
-          const num = parseInt(row[i]);
+          const num = parseInt(row[i], 10);
           if (isNaN(num)) return false;
           sumElts += num;
         }
       }
       if (sumElts != V.size.y) return false;
     }
-    if (kings['k'] + kings['l'] != 1 || kings['K'] + kings['L'] != 1)
+    if (kings['k'] + kings['l'] == 0 || kings['K'] + kings['L'] == 0)
       return false;
     return true;
   }
@@ -209,27 +199,28 @@ export class ChakartRules extends ChessRules {
 
   setOtherVariables(fen) {
     super.setOtherVariables(fen);
-    const fenParsed = V.ParseFen(fen);
     // Initialize captured pieces' counts from FEN
+    const captured =
+      V.ParseFen(fen).captured.split("").map(x => parseInt(x, 10));
     this.captured = {
       w: {
-        [V.ROOK]: parseInt(fenParsed.captured[0]),
-        [V.KNIGHT]: parseInt(fenParsed.captured[1]),
-        [V.BISHOP]: parseInt(fenParsed.captured[2]),
-        [V.QUEEN]: parseInt(fenParsed.captured[3]),
-        [V.KING]: parseInt(fenParsed.captured[4]),
-        [V.PAWN]: parseInt(fenParsed.captured[5]),
+        [V.PAWN]: captured[0],
+        [V.ROOK]: captured[1],
+        [V.KNIGHT]: captured[2],
+        [V.BISHOP]: captured[3],
+        [V.QUEEN]: captured[4],
+        [V.KING]: captured[5]
       },
       b: {
-        [V.ROOK]: parseInt(fenParsed.captured[6]),
-        [V.KNIGHT]: parseInt(fenParsed.captured[7]),
-        [V.BISHOP]: parseInt(fenParsed.captured[8]),
-        [V.QUEEN]: parseInt(fenParsed.captured[9]),
-        [V.KING]: parseInt(fenParsed.captured[10]),
-        [V.PAWN]: parseInt(fenParsed.captured[11]),
+        [V.PAWN]: captured[6],
+        [V.ROOK]: captured[7],
+        [V.KNIGHT]: captured[8],
+        [V.BISHOP]: captured[9],
+        [V.QUEEN]: captured[10],
+        [V.KING]: captured[11]
       }
     };
-    this.firstMove = [];
+    this.effects = [];
     this.subTurn = 1;
   }
 
@@ -268,50 +259,14 @@ export class ChakartRules extends ChessRules {
     const end = (color == 'b' && p == V.PAWN ? 7 : 8);
     for (let i = start; i < end; i++) {
       for (let j = 0; j < V.size.y; j++) {
-        const colIJ = this.getColor(i, j);
-        if (this.board[i][j] == V.EMPTY || colIJ == 'a') {
-          let mv = new Move({
-            appear: [
-              new PiPo({
-                x: i,
-                y: j,
-                c: color,
-                p: p
-              })
-            ],
-            vanish: [],
-            start: { x: x, y: y }, //a bit artificial...
-            end: { x: i, y: j }
-          });
-          if (colIJ == 'a') {
-            const pieceIJ = this.getPiece(i, j);
-            mv.vanish.push(
-              new PiPo({
-                x: i,
-                y: j,
-                c: colIJ,
-                p: pieceIJ
-              })
-            );
-            if ([V.BANANA, V.BOMB].includes(pieceIJ)) {
-              // Apply first effect, remove bonus from board, and then
-              // relay to getBasicMove. Finally merge mv.appear/vanish and
-              // put back the bonus on the board:
-              const bSteps = V.steps[pieceIJ == V.BANANA ? V.ROOK : V.BISHOP];
-              const sqEffect = this.getRandomSquare([i, j], bSteps);
-              if (sqEffect[0] != i || sqEffect[1] != j) {
-                this.board[i][j] = color + p;
-                const bMove =
-                  this.getBasicMove([i, j], [sqEffect[0], sqEffect[1]]);
-                this.board[i][j] = 'a' + pieceIJ;
-                mv.appear[0].x = bMove.appear[0].x;
-                mv.appear[0].y = bMove.appear[0].y;
-                for (let k = 1; k < bMove.vanish.length; k++)
-                  mv.vanish.push(bMove.vanish[k]);
-              }
-            }
-          }
-          moves.push(mv);
+        if (
+          this.board[i][j] == V.EMPTY ||
+          this.getColor(i, j) == 'a' ||
+          this.getPiece(i, j) == V.INVISIBLE_QUEEN
+        ) {
+          let m = this.getBasicMove({ p: p, x: i, y: j});
+          m.start = { x: x, y: y };
+          moves.push(m);
         }
       }
     }
@@ -352,12 +307,10 @@ export class ChakartRules extends ChessRules {
     }
     else {
       // Subturn == 2
-      const L = this.firstMove.length;
-      const fm = this.firstMove[L-1];
-      switch (fm.end.effect) {
-        // case 0: a click is required (banana or bomb)
+      const L = this.effects.length;
+      switch (this.effects[L-1]) {
         case "kingboo":
-          // Exchange position with any piece,
+          // Exchange position with any visible piece,
           // except pawns if arriving on last rank.
           const lastRank = { 'w': 0, 'b': 7 };
           const color = this.turn;
@@ -365,12 +318,13 @@ export class ChakartRules extends ChessRules {
           for (let i=0; i<8; i++) {
             for (let j=0; j<8; j++) {
               const colIJ = this.getColor(i, j);
+              const pieceIJ = this.getPiece(i, j);
               if (
                 (i != x || j != y) &&
                 this.board[i][j] != V.EMPTY &&
+                pieceIJ != V.INVISIBLE_QUEEN &&
                 colIJ != 'a'
               ) {
-                const pieceIJ = this.getPiece(i, j);
                 if (
                   (pieceIJ != V.PAWN || x != lastRank[colIJ]) &&
                   (allowLastRank || i != lastRank[color])
@@ -381,7 +335,7 @@ export class ChakartRules extends ChessRules {
                     c: colIJ,
                     p: this.getPiece(i, j)
                   });
-                  let mMove = this.getBasicMove([x, y], [i, j]);
+                  let mMove = this.getBasicMove({ x: x, y: y }, [i, j]);
                   mMove.appear.push(movedUnit);
                   moves.push(mMove);
                 }
@@ -394,77 +348,70 @@ export class ChakartRules extends ChessRules {
           if (x >= V.size.x) moves = this.getReserveMoves([x, y]);
           break;
         case "daisy":
-          // Play again with the same piece
-          if (fm.appear[0].x == x && fm.appear[0].y == y)
-            moves = super.getPotentialMovesFrom([x, y]);
+          // Play again with any piece
+          moves = super.getPotentialMovesFrom([x, y]);
           break;
       }
     }
     return moves;
   }
 
-  // Helper for getBasicMove()
+  // Helper for getBasicMove(): banana/bomb effect
   getRandomSquare([x, y], steps) {
-    const validSteps = steps.filter(s => {
-      const [i, j] = [x + s[0], y + s[1]];
-      return (
-        V.OnBoard(i, j) &&
-        (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
-      );
-    });
-    if (validSteps.length == 0)
-      // Can happen after mushroom jump
-      return [x, y];
+    const validSteps = steps.filter(s => V.OnBoard(x + s[0], y + s[1]));
     const step = validSteps[randInt(validSteps.length)];
     return [x + step[0], y + step[1]];
   }
 
-  canMove([x, y], piece) {
-    const color = this.getColor(x, y);
-    const oppCol = V.GetOppCol(color);
-    piece = piece || this.getPiece(x, y);
-    if (piece == V.PAWN) {
-      const forward = (color == 'w' ? -1 : 1);
-      return (
-        this.board[x + forward][y] != oppCol ||
-        (
-          V.OnBoard(x + forward, y + 1) &&
-          this.board[x + forward][y + 1] != V.EMPTY &&
-          this.getColor[x + forward, y + 1] == oppCol
-        ) ||
-        (
-          V.OnBoard(x + forward, y - 1) &&
-          this.board[x + forward][y - 1] != V.EMPTY &&
-          this.getColor[x + forward, y - 1] == oppCol
-        )
-      );
-    }
-    // Checking one step is enough:
-    const steps =
-      [V.KING, V.QUEEN].includes(piece)
-        ? V.steps[V.ROOK].concat(V.steps[V.BISHOP])
-        : V.steps[piece];
-    if (!Array.isArray(steps)) debugger;
-    for (let step of steps) {
-      const [i, j] = [x + step[0], y + step[1]];
-      if (
-        V.OnBoard(i, j) &&
-        (this.board[i][j] == V.EMPTY || this.getColor(i, j) != color)
-      ) {
-        return true;
+  // Apply mushroom, bomb or banana effect (hidden to the player).
+  // Determine egg effect, too, and apply its first part if possible.
+  getBasicMove_aux(psq1, sq2, tr, initMove) {
+    const [x1, y1] = [psq1.x, psq1.y];
+    const color1 = this.turn;
+    const piece1 = (!!tr ? tr.p : (psq1.p || this.getPiece(x1, y1)));
+    const oppCol = V.GetOppCol(color1);
+    if (!sq2) {
+      let move = {
+        appear: [],
+        vanish: []
+      };
+      // banana or bomb defines next square, or the move ends there
+      move.appear = [
+        new PiPo({
+          x: x1,
+          y: y1,
+          c: color1,
+          p: piece1
+        })
+      ];
+      if (this.board[x1][y1] != V.EMPTY) {
+        const initP1 = this.getPiece(x1, y1);
+        move.vanish = [
+          new PiPo({
+            x: x1,
+            y: y1,
+            c: this.getColor(x1, y1),
+            p: initP1
+          })
+        ];
+        if ([V.BANANA, V.BOMB].includes(initP1)) {
+          const steps = V.steps[initP1 == V.BANANA ? V.ROOK : V.BISHOP];
+          move.next = this.getRandomSquare([x1, y1], steps);
+        }
       }
+      move.end = { x: x1, y: y1 };
+      return move;
     }
-    return false;
-  }
-
-  getBasicMove([x1, y1], [x2, y2], tr) {
-    // Apply mushroom, bomb or banana effect (hidden to the player).
-    // Determine egg effect, too, and apply its first part if possible.
+    const [x2, y2] = [sq2[0], sq2[1]];
+    // The move starts normally, on board:
     let move = super.getBasicMove([x1, y1], [x2, y2], tr);
-    const color1 = this.getColor(x1, y1);
-    const piece1 = this.getPiece(x1, y1);
-    const oppCol = V.GetOppCol(color1);
-    if ([V.PAWN, V.KNIGHT].includes(piece1)) {
+    if (!!tr) move.promoteInto = tr.c + tr.p; //in case of (chomped...)
+    const L = this.effects.length;
+    if (
+      [V.PAWN, V.KNIGHT].includes(piece1) &&
+      !!initMove &&
+      (this.subTurn == 1 || this.effects[L-1] == "daisy")
+    ) {
       switch (piece1) {
         case V.PAWN: {
           const twoSquaresMove = (Math.abs(x2 - x1) == 2);
@@ -525,62 +472,25 @@ export class ChakartRules extends ChessRules {
         }
       }
     }
-    // Avoid looping back on effect already applied:
-    let usedEffect = ArrayFun.init(8, 8, false);
-    const applyBeffect = (steps) => {
-      const [x, y] = [move.appear[0].x, move.appear[0].y];
-      if (usedEffect[x][y]) return;
-      usedEffect[x][y] = true;
-      const moveTo = this.getRandomSquare([x, y], steps);
-      move.appear[0].x = moveTo[0];
-      move.appear[0].y = moveTo[1];
-      if (
-        this.board[moveTo[0]][moveTo[1]] != V.EMPTY &&
-        this.getColor(moveTo[0], moveTo[1]) == 'a'
-      ) {
-        move.vanish.push(
-          new PiPo({
-            x: moveTo[0],
-            y: moveTo[1],
-            c: 'a',
-            p: this.getPiece(moveTo[0], moveTo[1])
-          })
-        );
-        // Artificially change direction, before applying new effects:
-        x1 = x;
-        y1 = y;
-        x2 = moveTo[0];
-        y2 = moveTo[1];
-        switch (this.getPiece(moveTo[0], moveTo[1])) {
-          case V.BANANA:
-            applyBeffect(V.steps[V.ROOK]);
-            break;
-          case V.BOMB:
-            applyBeffect(V.steps[V.BISHOP]);
-            break;
-          case V.MUSHROOM:
-            applyMushroomEffect();
-            break;
-          case V.EGG:
-            applyEggEffect();
-            break;
-        }
-      }
-    };
     // For (wa)luigi effect:
     const changePieceColor = (color) => {
       let pieces = [];
       const oppLastRank = (color == 'w' ? 7 : 0);
       for (let i=0; i<8; i++) {
         for (let j=0; j<8; j++) {
-          if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
-            const piece = this.getPiece(i, j);
+          const piece = this.getPiece(i, j);
+          if (
+            (i != move.vanish[0].x || j != move.vanish[0].y) &&
+            this.board[i][j] != V.EMPTY &&
+            piece != V.INVISIBLE_QUEEN &&
+            this.getColor(i, j) == color
+          ) {
             if (piece != V.KING && (piece != V.PAWN || i != oppLastRank))
               pieces.push({ x: i, y: j, p: piece });
           }
         }
       }
-      // Special case of the current piece (temporarily off board):
+      // Special case of the current piece (still at its initial position)
       if (color == color1)
         pieces.push({ x: move.appear[0].x, y: move.appear[0].y, p: piece1 });
       const cp = pieces[randInt(pieces.length)];
@@ -594,7 +504,7 @@ export class ChakartRules extends ChessRules {
           })
         );
       }
-      else move.appear.pop();
+      else move.appear.shift();
       move.appear.push(
         new PiPo({
           x: cp.x,
@@ -609,29 +519,10 @@ export class ChakartRules extends ChessRules {
         // No egg effects at subTurn 2
         return;
       // 1) Determine the effect (some may be impossible)
-      let effects = ["kingboo", "koopa", "chomp", "bowser"];
+      let effects = ["kingboo", "koopa", "chomp", "bowser", "daisy"];
       if (Object.values(this.captured[color1]).some(c => c >= 1))
         effects.push("toadette");
       const lastRank = { 'w': 0, 'b': 7 };
-      let canPlayAgain = undefined;
-      if (
-        move.appear[0].p == V.PAWN &&
-        move.appear[0].x == lastRank[color1]
-      ) {
-        // Always possible: promote into a queen rook or king
-        canPlayAgain = true;
-      }
-      else {
-        move.end.effect = "daisy";
-        this.board[move.start.x][move.start.y] = saveCurrent;
-        V.PlayOnBoard(this.board, move);
-        const square = [move.appear[0].x, move.appear[0].y];
-        canPlayAgain = this.canMove(square, piece1);
-        V.UndoOnBoard(this.board, move);
-        this.board[move.start.x][move.start.y] = V.EMPTY;
-        delete move.end["effect"];
-      }
-      if (canPlayAgain) effects.push("daisy");
       if (
         this.board.some((b,i) =>
           b.some(cell => {
@@ -689,8 +580,6 @@ export class ChakartRules extends ChessRules {
       }
     };
     const applyMushroomEffect = () => {
-      if (usedEffect[move.appear[0].x][move.appear[0].y]) return;
-      usedEffect[move.appear[0].x][move.appear[0].y] = true;
       if ([V.PAWN, V.KING, V.KNIGHT].includes(piece1)) {
         // Just make another similar step, if possible (non-capturing)
         const [i, j] = [
@@ -699,26 +588,30 @@ export class ChakartRules extends ChessRules {
         ];
         if (
           V.OnBoard(i, j) &&
-          (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
+          (
+            this.board[i][j] == V.EMPTY ||
+            this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
+            this.getColor(i, j) == 'a'
+          )
         ) {
           move.appear[0].x = i;
           move.appear[0].y = j;
           if (this.board[i][j] != V.EMPTY) {
             const object = this.getPiece(i, j);
+            const color = this.getColor(i, j);
             move.vanish.push(
               new PiPo({
                 x: i,
                 y: j,
-                c: 'a',
+                c: color,
                 p: object
               })
             );
             switch (object) {
               case V.BANANA:
-                applyBeffect(V.steps[V.ROOK]);
-                break;
               case V.BOMB:
-                applyBeffect(V.steps[V.BISHOP]);
+                const steps = V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
+                move.next = this.getRandomSquare([i, j], steps);
                 break;
               case V.EGG:
                 applyEggEffect();
@@ -740,11 +633,12 @@ export class ChakartRules extends ChessRules {
         if (
           V.OnBoard(next[0], next[1]) &&
           this.board[next[0]][next[1]] != V.EMPTY &&
+          this.getPiece(next[0], next[1]) != V.INVISIBLE_QUEEN &&
           this.getColor(next[0], next[1]) != 'a'
         ) {
           const afterNext = [next[0] + step[0], next[1] + step[1]];
           if (V.OnBoard(afterNext[0], afterNext[1])) {
-            const afterColor = this.getColor(afterNext[0], afterNext[1])
+            const afterColor = this.getColor(afterNext[0], afterNext[1]);
             if (
               this.board[afterNext[0]][afterNext[1]] == V.EMPTY ||
               afterColor != color1
@@ -764,10 +658,11 @@ export class ChakartRules extends ChessRules {
                 );
                 switch (object) {
                   case V.BANANA:
-                    applyBeffect(V.steps[V.ROOK]);
-                    break;
                   case V.BOMB:
-                    applyBeffect(V.steps[V.BISHOP]);
+                    const steps =
+                      V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
+                    move.next = this.getRandomSquare(
+                      [afterNext[0], afterNext[1]], steps);
                     break;
                   case V.EGG:
                     applyEggEffect();
@@ -784,18 +679,12 @@ export class ChakartRules extends ChessRules {
     };
     const color2 = this.getColor(x2, y2);
     const piece2 = this.getPiece(x2, y2);
-    // In case of (bonus effects might go through initial square):
-    // TODO: push the idea further, objects left initially should alter the
-    // trajectory or move as well (mushroom or egg).
-    const saveCurrent = this.board[move.start.x][move.start.y];
-    this.board[move.start.x][move.start.y] = V.EMPTY;
     if (color2 == 'a') {
       switch (piece2) {
         case V.BANANA:
-          applyBeffect(V.steps[V.ROOK]);
-          break;
         case V.BOMB:
-          applyBeffect(V.steps[V.BISHOP]);
+          const steps = V.steps[piece2 == V.BANANA ? V.ROOK : V.BISHOP];
+          move.next = this.getRandomSquare([x2, y2], steps);
           break;
         case V.MUSHROOM:
           applyMushroomEffect();
@@ -809,6 +698,7 @@ export class ChakartRules extends ChessRules {
     }
     if (
       this.subTurn == 1 &&
+      !move.next &&
       move.appear.length > 0 &&
       [V.ROOK, V.BISHOP].includes(piece1)
     ) {
@@ -823,14 +713,15 @@ export class ChakartRules extends ChessRules {
             const [i, j] = [finalSquare[0] + s[0], finalSquare[1] + s[1]];
             return (
               V.OnBoard(i, j) &&
+              // NOTE: do not place a bomb or banana on the invisible queen!
               (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
             );
           });
-        if (validSteps.length >= 2) move.end.effect = 0;
-        else if (validSteps.length == 1) {
+        if (validSteps.length >= 1) {
+          const randIdx = randInt(validSteps.length);
           const [x, y] = [
-            finalSquare[0] + validSteps[0][0],
-            finalSquare[1] + validSteps[0][1]
+            finalSquare[0] + validSteps[randIdx][0],
+            finalSquare[1] + validSteps[randIdx][1]
           ];
           move.appear.push(
             new PiPo({
@@ -847,17 +738,78 @@ export class ChakartRules extends ChessRules {
         }
       }
     }
-    this.board[move.start.x][move.start.y] = saveCurrent;
-    if (
-      move.appear.length == 2 &&
-      move.appear[0].x == move.appear[1].x &&
-      move.appear[0].y == move.appear[1].y
-    ) {
-      // Arrive on bonus left initially:
-      move.appear.pop();
+    return move;
+  }
+
+  getBasicMove(psq1, sq2, tr) {
+    let moves = [];
+    if (Array.isArray(psq1)) psq1 = { x: psq1[0], y: psq1[1] };
+    let m = this.getBasicMove_aux(psq1, sq2, tr, "initMove");
+    while (!!m.next) {
+      // Last move ended on bomb or banana, direction change
+      V.PlayOnBoard(this.board, m);
+      moves.push(m);
+      m = this.getBasicMove_aux(
+        { x: m.appear[0].x, y: m.appear[0].y }, m.next);
+    }
+    for (let i=moves.length-1; i>=0; i--) V.UndoOnBoard(this.board, moves[i]);
+    moves.push(m);
+    // Now merge moves into one
+    let move = {};
+    // start is wrong for Toadette moves --> it's fixed later
+    move.start = { x: psq1.x, y: psq1.y };
+    move.end = !!sq2 ? { x: sq2[0], y: sq2[1] } : { x: psq1.x, y: psq1.y };
+    if (!!tr) move.promoteInto = moves[0].promoteInto;
+    let lm = moves[moves.length-1];
+    if (this.subTurn == 1 && !!lm.end.effect)
+      move.end.effect = lm.end.effect;
+    if (moves.length == 1) {
+      move.appear = moves[0].appear;
+      move.vanish = moves[0].vanish;
+    }
+    else {
+      // Keep first vanish and last appear (if any)
+      move.appear = lm.appear;
+      move.vanish = moves[0].vanish;
+      if (
+        move.vanish.length >= 1 &&
+        move.appear.length >= 1 &&
+        move.vanish[0].x == move.appear[0].x &&
+        move.vanish[0].y == move.appear[0].y
+      ) {
+        // Loopback on initial square:
+        move.vanish.shift();
+        move.appear.shift();
+      }
+      for (let i=1; i < moves.length - 1; i++) {
+        for (let v of moves[i].vanish) {
+          // Only vanishing objects, not appearing at init move
+          if (
+            v.c == 'a' &&
+            (
+              moves[0].appear.length == 1 ||
+              moves[0].appear[1].x != v.x ||
+              moves[0].appear[1].y != v.y
+            )
+          ) {
+            move.vanish.push(v);
+          }
+        }
+      }
+      // Final vanish is our piece, but others might be relevant
+      // (for some egg bonuses at least).
+      for (let i=1; i < lm.vanish.length; i++) {
+        if (
+          lm.vanish[i].c != 'a' ||
+          moves[0].appear.length == 1 ||
+          moves[0].appear[1].x != lm.vanish[i].x ||
+          moves[0].appear[1].y != lm.vanish[i].y
+        ) {
+          move.vanish.push(lm.vanish[i]);
+        }
+      }
     }
     return move;
-    // TODO: if loopback to initial square, simplify move.
   }
 
   getPotentialPawnMoves([x, y]) {
@@ -869,17 +821,19 @@ export class ChakartRules extends ChessRules {
     let moves = [];
     if (
       this.board[x + shiftX][y] == V.EMPTY ||
-      this.getColor(x + shiftX, y) == 'a'
+      this.getColor(x + shiftX, y) == 'a' ||
+      this.getPiece(x + shiftX, y) == V.INVISIBLE_QUEEN
     ) {
       this.addPawnMoves([x, y], [x + shiftX, y], moves);
       if (
         [firstRank, firstRank + shiftX].includes(x) &&
         (
           this.board[x + 2 * shiftX][y] == V.EMPTY ||
-          this.getColor(x + 2 * shiftX, y) == 'a'
+          this.getColor(x + 2 * shiftX, y) == 'a' ||
+          this.getPiece(x + 2 * shiftX, y) == V.INVISIBLE_QUEEN
         )
       ) {
-        moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
+        moves.push(this.getBasicMove({ x: x, y: y }, [x + 2 * shiftX, y]));
       }
     }
     for (let shiftY of [-1, 1]) {
@@ -887,6 +841,8 @@ export class ChakartRules extends ChessRules {
         y + shiftY >= 0 &&
         y + shiftY < sizeY &&
         this.board[x + shiftX][y + shiftY] != V.EMPTY &&
+        // Pawns cannot capture invisible queen this way!
+        this.getPiece(x + shiftX, y + shiftY) != V.INVISIBLE_QUEEN &&
         ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY))
       ) {
         this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves);
@@ -923,48 +879,37 @@ export class ChakartRules extends ChessRules {
     // If flag allows it, add 'remote shell captures'
     if (this.powerFlags[this.turn][V.KING]) {
       V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
-        const [nextX, nextY] = [x + step[0], y + step[1]];
-        if (
-          V.OnBoard(nextX, nextY) &&
+        let [i, j] = [x + step[0], y + step[1]];
+        while (
+          V.OnBoard(i, j) &&
           (
-            this.board[nextX][nextY] == V.EMPTY ||
+            this.board[i][j] == V.EMPTY ||
+            this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
             (
-              this.getColor(nextX, nextY) == 'a' &&
-              [V.EGG, V.MUSHROOM].includes(this.getPiece(nextX, nextY))
+              this.getColor(i, j) == 'a' &&
+              [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
             )
           )
         ) {
-          let [i, j] = [x + 2 * step[0], y + 2 * step[1]];
-          while (
-            V.OnBoard(i, j) &&
-            (
-              this.board[i][j] == V.EMPTY ||
-              (
-                this.getColor(i, j) == 'a' &&
-                [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
-              )
-            )
-          ) {
-            i += step[0];
-            j += step[1];
-          }
-          if (V.OnBoard(i, j)) {
-            const colIJ = this.getColor(i, j);
-            if (colIJ != color) {
-              // May just destroy a bomb or banana:
-              moves.push(
-                new Move({
-                  start: { x: x, y: y},
-                  end: { x: i, y: j },
-                  appear: [],
-                  vanish: [
-                    new PiPo({
-                      x: i, y: j, c: colIJ, p: this.getPiece(i, j)
-                    })
-                  ]
-                })
-              );
-            }
+          i += step[0];
+          j += step[1];
+        }
+        if (V.OnBoard(i, j)) {
+          const colIJ = this.getColor(i, j);
+          if (colIJ != color) {
+            // May just destroy a bomb or banana:
+            moves.push(
+              new Move({
+                start: { x: x, y: y},
+                end: { x: i, y: j },
+                appear: [],
+                vanish: [
+                  new PiPo({
+                    x: i, y: j, c: colIJ, p: this.getPiece(i, j)
+                  })
+                ]
+              })
+            );
           }
         }
       });
@@ -981,19 +926,20 @@ export class ChakartRules extends ChessRules {
         V.OnBoard(i, j) &&
         (
           this.board[i][j] == V.EMPTY ||
+          this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
           (
             this.getColor(i, j) == 'a' &&
             [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
           )
         )
       ) {
-        moves.push(this.getBasicMove([x, y], [i, j]));
+        moves.push(this.getBasicMove({ x: x, y: y }, [i, j]));
         if (oneStep) continue outerLoop;
         i += step[0];
         j += step[1];
       }
       if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
-        moves.push(this.getBasicMove([x, y], [i, j]));
+        moves.push(this.getBasicMove({ x: x, y: y }, [i, j]));
     }
     return moves;
   }
@@ -1001,66 +947,42 @@ export class ChakartRules extends ChessRules {
   getAllPotentialMoves() {
     if (this.subTurn == 1) return super.getAllPotentialMoves();
     let moves = [];
-    const L = this.firstMove.length;
-    const fm = this.firstMove[L-1];
-    switch (fm.end.effect) {
-      case 0:
-        moves.push({
-          start: { x: -1, y: -1 },
-          end: { x: -1, y: -1 },
-          appear: [],
-          vanish: []
-        });
-        for (
-          let step of
-          (fm.vanish[0].p == V.ROOK ? V.steps[V.BISHOP] : V.steps[V.ROOK])
-        ) {
-          const [i, j] = [fm.appear[0].x + step[0], fm.appear[0].y + step[1]];
-          if (
-            V.OnBoard(i, j) &&
-            (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
-          ) {
-            let m = new Move({
-              start: { x: -1, y: -1 },
-              end: { x: i, y: j },
-              appear: [
-                new PiPo({
-                  x: i,
-                  y: j,
-                  c: 'a',
-                  p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB)
-                })
-              ],
-              vanish: []
-            });
-            if (this.board[i][j] != V.EMPTY) {
-              m.vanish.push(
-                new PiPo({ x: i, y: j, c: 'a', p: this.getPiece(i, j) }));
-            }
-            moves.push(m);
-          }
-        }
-        break;
+    const color = this.turn;
+    const L = this.effects.length;
+    switch (this.effects[L-1]) {
       case "kingboo": {
-        const [x, y] = [fm.appear[0].x, fm.appear[0].y];
+        let allPieces = [];
         for (let i=0; i<8; i++) {
           for (let j=0; j<8; j++) {
             const colIJ = this.getColor(i, j);
+            const pieceIJ = this.getPiece(i, j);
             if (
-              i != x &&
-              j != y &&
+              i != x && j != y &&
               this.board[i][j] != V.EMPTY &&
-              colIJ != 'a'
+              colIJ != 'a' &&
+              pieceIJ != V.INVISIBLE_QUEEN
             ) {
-              const movedUnit = new PiPo({
-                x: x,
-                y: y,
-                c: colIJ,
-                p: this.getPiece(i, j)
+              allPieces.push({ x: i, y: j, c: colIJ, p: pieceIJ });
+            }
+          }
+        }
+        for (let x=0; x<8; x++) {
+          for (let y=0; y<8; y++) {
+            if (this.getColor(i, j) == color) {
+              // Add exchange with something
+              allPieces.forEach(pp => {
+                if (pp.x != i || pp.y != j) {
+                  const movedUnit = new PiPo({
+                    x: x,
+                    y: y,
+                    c: pp.c,
+                    p: pp.p
+                  });
+                  let mMove = this.getBasicMove({ x: x, y: y }, [pp.x, pp.y]);
+                  mMove.appear.push(movedUnit);
+                  moves.push(mMove);
+                }
               });
-              let mMove = this.getBasicMove([x, y], [i, j]);
-              mMove.appear.push(movedUnit);
-              moves.push(mMove);
             }
           }
         }
@@ -1073,68 +995,12 @@ export class ChakartRules extends ChessRules {
         break;
       }
       case "daisy":
-        moves = super.getPotentialMovesFrom([fm.appear[0].x, fm.appear[0].y]);
+        moves = super.getAllPotentialMoves();
         break;
     }
     return moves;
   }
 
-  doClick(square) {
-    const L = this.firstMove.length;
-    const fm = (L > 0 ? this.firstMove[L-1] : null);
-    if (
-      isNaN(square[0]) ||
-      this.subTurn == 1 ||
-      !([0, "daisy"].includes(fm.end.effect))
-    ) {
-      return null;
-    }
-    const [x, y] = [square[0], square[1]];
-    const deltaX = Math.abs(fm.appear[0].x - x);
-    const deltaY = Math.abs(fm.appear[0].y - y);
-    if (
-      fm.end.effect == 0 &&
-      (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') &&
-      (
-        (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) ||
-        (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1)
-      )
-    ) {
-      let m = new Move({
-        start: { x: -1, y: -1 },
-        end: { x: x, y: y },
-        appear: [
-          new PiPo({
-            x: x,
-            y: y,
-            c: 'a',
-            p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB)
-          })
-        ],
-        vanish: []
-      });
-      if (this.board[x][y] != V.EMPTY) {
-        m.vanish.push(
-          new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) }));
-      }
-      return m;
-    }
-    else if (
-      fm.end.effect == "daisy" &&
-      deltaX == 0 && deltaY == 0 &&
-      !this.canMove([x, y])
-    ) {
-      // No possible move: return empty move
-      return {
-        start: { x: -1, y: -1 },
-        end: { x: -1, y: -1 },
-        appear: [],
-        vanish: []
-      };
-    }
-    return null;
-  }
-
   play(move) {
 //    if (!this.states) this.states = [];
 //    const stateFen = this.getFen();
@@ -1143,8 +1009,8 @@ export class ChakartRules extends ChessRules {
     move.flags = JSON.stringify(this.aggregateFlags());
     V.PlayOnBoard(this.board, move);
     move.turn = [this.turn, this.subTurn];
-    if ([0, "kingboo", "toadette", "daisy"].includes(move.end.effect)) {
-      this.firstMove.push(move);
+    if (["kingboo", "toadette", "daisy"].includes(move.end.effect)) {
+      this.effects.push(move.end.effect);
       this.subTurn = 2;
     }
     else {
@@ -1159,9 +1025,18 @@ export class ChakartRules extends ChessRules {
     if (move.end.effect == "toadette") this.reserve = this.captured;
     else this.reserve = undefined;
     const color = move.turn[0];
-    if (move.vanish.length == 2 && move.vanish[1].c != 'a')
+    if (
+      move.vanish.length == 2 &&
+      move.vanish[1].c != 'a' &&
+      move.appear.length == 1 //avoid king Boo!
+    ) {
       // Capture: update this.captured
-      this.captured[move.vanish[1].c][move.vanish[1].p]++;
+      let capturedPiece = move.vanish[1].p;
+      if (capturedPiece == V.INVISIBLE_QUEEN) capturedPiece = V.QUEEN;
+      else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece))
+        capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece];
+      this.captured[move.vanish[1].c][capturedPiece]++;
+    }
     else if (move.vanish.length == 0) {
       if (move.appear.length == 0 || move.appear[0].c == 'a') return;
       // A piece is back on board
@@ -1177,44 +1052,49 @@ export class ChakartRules extends ChessRules {
     }
     else if (move.appear[0].p == V.INVISIBLE_QUEEN)
       this.powerFlags[move.appear[0].c][V.QUEEN] = false;
-    if (move.turn[1] == 2) return;
+    if (this.subTurn == 2) return;
     if (
+      move.turn[1] == 1 &&
       move.appear.length == 0 ||
       !(Object.keys(V.IMMOBILIZE_DECODE).includes(move.appear[0].p))
     ) {
       // Look for an immobilized piece of my color: it can now move
-      // Also make opponent invisible queen visible again, if any
-      const oppCol = V.GetOppCol(color);
       for (let i=0; i<8; i++) {
         for (let j=0; j<8; j++) {
           if (this.board[i][j] != V.EMPTY) {
-            const colIJ = this.getColor(i, j);
             const piece = this.getPiece(i, j);
             if (
-              colIJ == color &&
+              this.getColor(i, j) == color &&
               Object.keys(V.IMMOBILIZE_DECODE).includes(piece)
             ) {
               this.board[i][j] = color + V.IMMOBILIZE_DECODE[piece];
               move.wasImmobilized = [i, j];
             }
-            else if (
-              colIJ == oppCol &&
-              piece == V.INVISIBLE_QUEEN
-            ) {
-              this.board[i][j] = oppCol + V.QUEEN;
-              move.wasInvisible = [i, j];
-            }
           }
         }
       }
     }
+    // Also make opponent invisible queen visible again, if any
+    const oppCol = V.GetOppCol(color);
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i, j) == oppCol &&
+          this.getPiece(i, j) == V.INVISIBLE_QUEEN
+        ) {
+          this.board[i][j] = oppCol + V.QUEEN;
+          move.wasInvisible = [i, j];
+        }
+      }
+    }
   }
 
   undo(move) {
     this.disaggregateFlags(JSON.parse(move.flags));
     V.UndoOnBoard(this.board, move);
-    if ([0, "kingboo", "toadette", "daisy"].includes(move.end.effect))
-      this.firstMove.pop();
+    if (["kingboo", "toadette", "daisy"].includes(move.end.effect))
+      this.effects.pop();
     else this.movesCount--;
     this.turn = move.turn[0];
     this.subTurn = move.turn[1];
@@ -1235,8 +1115,13 @@ export class ChakartRules extends ChessRules {
       const [i, j] = move.wasInvisible;
       this.board[i][j] = this.getColor(i, j) + V.INVISIBLE_QUEEN;
     }
-    if (move.vanish.length == 2 && move.vanish[1].c != 'a')
-      this.captured[move.vanish[1].c][move.vanish[1].p]--;
+    if (move.vanish.length == 2 && move.vanish[1].c != 'a') {
+      let capturedPiece = move.vanish[1].p;
+      if (capturedPiece == V.INVISIBLE_QUEEN) capturedPiece = V.QUEEN;
+      else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece))
+        capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece];
+      this.captured[move.vanish[1].c][capturedPiece]--;
+    }
     else if (move.vanish.length == 0) {
       if (move.appear.length == 0 || move.appear[0].c == 'a') return;
       // A piece was back on board
@@ -1271,9 +1156,9 @@ export class ChakartRules extends ChessRules {
     return "*";
   }
 
-  static GenRandInitFen(randomness) {
+  static GenRandInitFen(options) {
     return (
-      SuicideRules.GenRandInitFen(randomness).slice(0, -1) +
+      SuicideRules.GenRandInitFen(options).slice(0, -1) +
       // Add Peach + Mario flags + capture counts
       "1111 000000000000"
     );
@@ -1283,10 +1168,45 @@ export class ChakartRules extends ChessRules {
     return moves;
   }
 
+  static get VALUES() {
+    return Object.assign(
+      {},
+      ChessRules.VALUES,
+      {
+        s: 1,
+        u: 5,
+        o: 3,
+        c: 3,
+        t: 9,
+        l: 1000,
+        e: 0,
+        d: 0,
+        w: 0,
+        m: 0
+      }
+    );
+  }
+
+  static get SEARCH_DEPTH() {
+    return 1;
+  }
+
   getComputerMove() {
-    // Random mover:
     const moves = this.getAllValidMoves();
-    let move1 = moves[randInt(moves.length)];
+    // Split into "normal" and "random" moves:
+    // (Next splitting condition is OK because cannot take self object
+    // without a banana or bomb on the way).
+    const deterministicMoves = moves.filter(m => {
+      return m.vanish.every(a => a.c != 'a' || a.p == V.MUSHROOM);
+    });
+    const randomMoves = moves.filter(m => {
+      return m.vanish.some(a => a.c == 'a' && a.p != V.MUSHROOM);
+    });
+    if (Math.random() < deterministicMoves.length / randomMoves.length)
+      // Play a deterministic one: capture king or material if possible
+      return super.getComputerMove(deterministicMoves);
+    // Play a random effect move, at random:
+    let move1 = randomMoves[randInt(randomMoves.length)];
     this.play(move1);
     let move2 = undefined;
     if (this.subTurn == 2) {
@@ -1298,16 +1218,8 @@ export class ChakartRules extends ChessRules {
     return [move1, move2];
   }
 
-  // TODO: king chomped, king koopa: notation is incomplete.
-  // Also, king boo effect should be better written like "e4Sg1".
-  // Toadette placements on bonus square are badly written as well.
   getNotation(move) {
-    if (move.vanish.length == 0) {
-      if (move.appear.length == 0) return "-";
-      const piece =
-        move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
-      return piece + "@" + V.CoordsToSquare(move.end);
-    }
+    if (move.vanish.length == 0 && move.appear.length == 0) return "-";
     if (
       !move.end.effect &&
       move.appear.length > 0 &&
@@ -1316,31 +1228,55 @@ export class ChakartRules extends ChessRules {
       return "Q??";
     }
     const finalSquare = V.CoordsToSquare(move.end);
-    let piece = undefined;
-    if (move.appear.length == 0) {
-      piece = this.getPiece(move.start.x, move.start.y);
-      if (piece == V.KING) return "Kx" + finalSquare;
-      // Koopa or Chomp:
+    // Next condition also includes Toadette placements:
+    if (move.appear.length > 0 && move.vanish.every(a => a.c == 'a')) {
+      const piece =
+        move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
+      return piece + "@" + finalSquare;
+    }
+    else if (move.appear.length == 0) {
+      const piece = this.getPiece(move.start.x, move.start.y);
+      if (piece == V.KING && !move.end.effect)
+        // King remote capture
+        return "Kx" + finalSquare;
+      // Koopa or Chomp, or loopback after bananas, bombs & mushrooms:
       return (
         piece.toUpperCase() + "x" + finalSquare +
-        "*" + (move.end.effect == "koopa" ? "K" : "C")
+        (
+          !!move.end.effect
+            ? "*" + (move.end.effect == "koopa" ? "K" : "C")
+            : ""
+        )
       );
     }
-    else if (
-      move.appear.length == 1 &&
-      move.vanish.length == 1 &&
-      move.appear[0].c == 'a' &&
-      move.vanish[0].c == 'a'
+    if (move.appear.length == 1 && move.vanish.length == 1) {
+      const moveStart = move.appear[0].p.toUpperCase() + "@";
+      if (move.appear[0].c == 'a' && move.vanish[0].c == 'a')
+        // Bonus replacement:
+        return moveStart + finalSquare;
+      if (
+        move.vanish[0].p == V.INVISIBLE_QUEEN &&
+        move.appear[0].x == move.vanish[0].x &&
+        move.appear[0].y == move.vanish[0].y
+      ) {
+        // Toadette takes invisible queen
+        return moveStart + "Q" + finalSquare;
+      }
+    }
+    if (
+      move.appear.length == 2 &&
+      move.vanish.length == 2 &&
+      move.appear.every(a => a.c != 'a') &&
+      move.vanish.every(v => v.c != 'a')
     ) {
-      // Bonus replacement:
-      piece = move.appear[0].p.toUpperCase();
-      return piece + "@" + finalSquare;
+      // King Boo exchange
+      return V.CoordsToSquare(move.start) + finalSquare;
     }
-    piece = move.vanish[0].p;
+    const piece = move.vanish[0].p;
     let notation = undefined;
     if (piece == V.PAWN) {
       // Pawn move
-      if (move.vanish.length >= 2) {
+      if (this.board[move.end.x][move.end.y] != V.EMPTY) {
         // Capture
         const startColumn = V.CoordToColumn(move.start.y);
         notation = startColumn + "x" + finalSquare;
@@ -1353,7 +1289,7 @@ export class ChakartRules extends ChessRules {
     else {
       notation =
         piece.toUpperCase() +
-        (move.vanish.length >= 2 ? "x" : "") +
+        (this.board[move.end.x][move.end.y] != V.EMPTY ? "x" : "") +
         finalSquare;
     }
     if (!!move.end.effect) {
@@ -1371,13 +1307,15 @@ export class ChakartRules extends ChessRules {
           notation += "*M";
           break;
         case "luigi":
-          notation += "*L";
-          break;
         case "waluigi":
-          notation += "*W";
+          const lastAppear = move.appear[move.appear.length - 1];
+          const effectOn =
+            V.CoordsToSquare({ x: lastAppear.x, y : lastAppear.y });
+          notation += "*" + move.end.effect[0].toUpperCase() + effectOn;
           break;
       }
     }
     return notation;
   }
+
 };