Add Gomoku + Atarigo
[vchess.git] / client / src / variants / Chakart.js
index 1c7225c..f170f70 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;
   }
@@ -29,7 +30,7 @@ export class ChakartRules extends ChessRules {
     return true;
   }
 
-  hoverHighlight(x, y) {
+  hoverHighlight([x, y]) {
     if (this.subTurn == 1) return false;
     const L = this.firstMove.length;
     const fm = this.firstMove[L-1];
@@ -115,8 +116,12 @@ 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);
   }
@@ -148,14 +153,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;
   }
@@ -210,24 +215,25 @@ 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 = [];
@@ -269,7 +275,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);
@@ -316,7 +326,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.
@@ -366,11 +375,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)
@@ -389,7 +399,7 @@ export class ChakartRules extends ChessRules {
       return (
         V.OnBoard(x + forward, y) &&
         (
-          this.board[x + forward][y] != oppCol ||
+          this.board[x + forward][y] == V.EMPTY ||
           (
             V.OnBoard(x + forward, y + 1) &&
             this.board[x + forward][y + 1] != V.EMPTY &&
@@ -940,48 +950,36 @@ 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.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)
+                  })
+                ]
+              })
+            );
           }
         }
       });
@@ -1316,10 +1314,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) {
@@ -1362,14 +1395,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 &&
@@ -1425,4 +1463,5 @@ export class ChakartRules extends ChessRules {
     }
     return notation;
   }
+
 };