Improve Chakart rules (captures after banana/bomb redirection)
[vchess.git] / client / src / variants / Chakart.js
index 53af2c4..73d7cfc 100644 (file)
@@ -114,6 +114,7 @@ export class ChakartRules extends ChessRules {
   }
 
   getPPpath(m) {
+    if (!!m.promoteInto) return m.promoteInto;
     let piece = m.appear[0].p;
     if (Object.keys(V.IMMOBILIZE_DECODE).includes(piece))
       piece = V.IMMOBILIZE_DECODE[piece];
@@ -154,7 +155,7 @@ export class ChakartRules extends ChessRules {
       }
       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;
   }
@@ -213,20 +214,20 @@ export class ChakartRules extends ChessRules {
     // Initialize captured pieces' counts from FEN
     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]: parseInt(fenParsed.captured[0]),
+        [V.ROOK]: parseInt(fenParsed.captured[1]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[2]),
+        [V.BISHOP]: parseInt(fenParsed.captured[3]),
+        [V.QUEEN]: parseInt(fenParsed.captured[4]),
+        [V.KING]: parseInt(fenParsed.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]: parseInt(fenParsed.captured[6]),
+        [V.ROOK]: parseInt(fenParsed.captured[7]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[8]),
+        [V.BISHOP]: parseInt(fenParsed.captured[9]),
+        [V.QUEEN]: parseInt(fenParsed.captured[10]),
+        [V.KING]: parseInt(fenParsed.captured[11])
       }
     };
     this.firstMove = [];
@@ -268,7 +269,11 @@ 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++) {
-        if (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a') {
+        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);
@@ -315,7 +320,6 @@ export class ChakartRules extends ChessRules {
       const L = this.firstMove.length;
       const fm = this.firstMove[L-1];
       switch (fm.end.effect) {
-        // case 0: a click is required (banana or bomb)
         case "kingboo":
           // Exchange position with any piece,
           // except pawns if arriving on last rank.
@@ -365,11 +369,12 @@ export class ChakartRules extends ChessRules {
 
   // Helper for getBasicMove()
   getRandomSquare([x, y], steps) {
+    const color = this.turn;
     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')
+        (this.board[i][j] == V.EMPTY || this.getColor(i, j) != color)
       );
     });
     if (validSteps.length == 0)
@@ -386,16 +391,19 @@ export class ChakartRules extends ChessRules {
     if (piece == V.PAWN) {
       const forward = (color == 'w' ? -1 : 1);
       return (
-        this.board[x + forward][y] != oppCol ||
+        V.OnBoard(x + forward, y) &&
         (
-          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
+          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
+          )
         )
       );
     }
@@ -421,7 +429,7 @@ export class ChakartRules extends ChessRules {
   getBasicMove_aux(psq1, sq2, tr, initMove) {
     const [x1, y1] = [psq1.x, psq1.y];
     const color1 = this.turn;
-    const piece1 = psq1.p || this.getPiece(x1, y1);
+    const piece1 = (!!tr ? tr.p : (psq1.p || this.getPiece(x1, y1)));
     const oppCol = V.GetOppCol(color1);
     if (!sq2) {
       let move = {
@@ -458,6 +466,7 @@ export class ChakartRules extends ChessRules {
     const [x2, y2] = [sq2[0], sq2[1]];
     // The move starts normally, on board:
     let move = super.getBasicMove([x1, y1], [x2, y2], tr);
+    if (!!tr) move.promoteInto = tr.c + tr.p; //in case of (chomped...)
     const L = this.firstMove.length;
     if (
       [V.PAWN, V.KNIGHT].includes(piece1) &&
@@ -819,6 +828,7 @@ export class ChakartRules extends ChessRules {
     // 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;
@@ -1194,37 +1204,42 @@ 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) {
@@ -1305,10 +1320,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) {
@@ -1351,14 +1401,19 @@ export class ChakartRules extends ChessRules {
         )
       );
     }
-    if (
-      move.appear.length == 1 &&
-      move.vanish.length == 1 &&
-      move.appear[0].c == 'a' &&
-      move.vanish[0].c == 'a'
-    ) {
-      // Bonus replacement:
-      return move.appear[0].p.toUpperCase() + "@" + finalSquare;
+    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 &&