Draft Ball variant + some fixes, enhancements and code cleaning
[vchess.git] / client / src / variants / Hidden.js
index 1836352..ef5b0bb 100644 (file)
@@ -1,12 +1,16 @@
-import { ChessRules } from "@/base_rules";
+import { ChessRules, PiPo, Move } from "@/base_rules";
 import { ArrayFun } from "@/utils/array";
 import { randInt } from "@/utils/alea";
 
-export const VariantRules = class HiddenRules extends ChessRules {
+export class HiddenRules extends ChessRules {
   static get HasFlags() {
     return false;
   }
 
+  static get HasCastle() {
+    return false;
+  }
+
   static get HasEnpassant() {
     return false;
   }
@@ -16,9 +20,9 @@ export const VariantRules = class HiddenRules extends ChessRules {
     return false;
   }
 
-  // Moves are revealed only when game ends
+  // Moves are revealed only when game ends, but are highlighted on board
   static get ShowMoves() {
-    return "none";
+    return "highlight";
   }
 
   static get HIDDEN_DECODE() {
@@ -42,12 +46,39 @@ export const VariantRules = class HiddenRules extends ChessRules {
     };
   }
 
+  // Turn a hidden piece or revealed piece into revealed piece:
+  static Decode(p) {
+    if (Object.keys(V.HIDDEN_DECODE).includes(p))
+      return V.HIDDEN_DECODE[p];
+    return p;
+  }
+
   static get PIECES() {
-    return ChessRules.PIECES.concat(Object.values(V.HIDDEN_CODE));
+    return ChessRules.PIECES.concat(Object.keys(V.HIDDEN_DECODE));
+  }
+
+  // Pieces can be hidden :)
+  getPiece(i, j) {
+    const piece = this.board[i][j].charAt(1);
+    if (Object.keys(V.HIDDEN_DECODE).includes(piece))
+      return V.HIDDEN_DECODE[piece];
+    return piece;
+  }
+
+  getPpath(b, color, score) {
+    if (Object.keys(V.HIDDEN_DECODE).includes(b[1])) {
+      // Supposed to be hidden.
+      if (score == "*" && (!color || color != b[0]))
+        return "Hidden/" + b[0] + "p";
+      // Else: condition OK to show the piece
+      return b[0] + V.HIDDEN_DECODE[b[1]];
+    }
+    // The piece is already not supposed to be hidden:
+    return b;
   }
 
   // Scan board for kings positions (no castling)
-  scanKingsRooks(fen) {
+  scanKings(fen) {
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
     for (let i = 0; i < fenRows.length; i++) {
@@ -72,21 +103,55 @@ export const VariantRules = class HiddenRules extends ChessRules {
     }
   }
 
-  getPpath(b, color, score) {
-    if (Object.keys(V.HIDDEN_DECODE).includes(b[1])) {
-      // Supposed to be hidden.
-      if (score == "*" && (!color || color != b[0]))
-        return "Hidden/" + b[0] + "p";
-      // Else: condition OK to show the piece
-      return b[0] + V.HIDDEN_DECODE[b[1]];
+  getBasicMove([sx, sy], [ex, ey], tr) {
+    if (
+      tr &&
+      Object.keys(V.HIDDEN_DECODE).includes(this.board[sx][sy].charAt(1))
+    ) {
+      // The transformed piece is a priori hidden
+      tr.p = V.HIDDEN_CODE[tr.p];
     }
-    // The piece is already not supposed to be hidden:
-    return b;
+    let mv = new Move({
+      appear: [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: tr ? tr.c : this.getColor(sx, sy),
+          p: tr ? tr.p : this.board[sx][sy].charAt(1)
+        })
+      ],
+      vanish: [
+        new PiPo({
+          x: sx,
+          y: sy,
+          c: this.getColor(sx, sy),
+          p: this.board[sx][sy].charAt(1)
+        })
+      ]
+    });
+
+    // The opponent piece disappears if we take it
+    if (this.board[ex][ey] != V.EMPTY) {
+      mv.vanish.push(
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: this.getColor(ex, ey),
+          p: this.board[ex][ey].charAt(1)
+        })
+      );
+      // Pieces are revealed when they capture
+      mv.appear[0].p = V.Decode(mv.appear[0].p);
+    }
+
+    return mv;
   }
 
-  //getPotentialMovesFrom: TODO: write
+  filterValid(moves) {
+    return moves;
+  }
 
-  // TODO: bishops on different colors, a1 --> h1, h2 --> a2 ?
+  // Ignore randomness here: placement is always random asymmetric
   static GenRandInitFen() {
     let pieces = { w: new Array(8), b: new Array(8) };
     // Shuffle pieces + pawns on two first ranks
@@ -111,44 +176,51 @@ export const VariantRules = class HiddenRules extends ChessRules {
       const knight2Pos = positions[randIndex];
       positions.splice(randIndex, 1);
 
-      // Get random square for queen
+      // Get random squares for rooks
       randIndex = randInt(12);
+      const rook1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(11);
+      const rook2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for queen
+      randIndex = randInt(10);
       const queenPos = positions[randIndex];
       positions.splice(randIndex, 1);
 
-      // Get random squares for pawns
-      // TODO...
+      // Get random square for king
+      randIndex = randInt(9);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-      // Rooks and king positions are now fixed,
-      // because of the ordering rook-king-rook
-      const rook1Pos = positions[0];
-      const kingPos = positions[1];
-      const rook2Pos = positions[2];
+      // Pawns position are all remaining slots:
+      for (let p of positions)
+        pieces[c][p] = "s";
 
       // Finally put the shuffled pieces in the board array
-      pieces[c][rook1Pos] = "r";
-      pieces[c][knight1Pos] = "n";
-      pieces[c][bishop1Pos] = "b";
-      pieces[c][queenPos] = "q";
-      pieces[c][kingPos] = "k";
-      pieces[c][bishop2Pos] = "b";
-      pieces[c][knight2Pos] = "n";
-      pieces[c][rook2Pos] = "r";
+      pieces[c][rook1Pos] = "u";
+      pieces[c][knight1Pos] = "o";
+      pieces[c][bishop1Pos] = "c";
+      pieces[c][queenPos] = "t";
+      pieces[c][kingPos] = "l";
+      pieces[c][bishop2Pos] = "c";
+      pieces[c][knight2Pos] = "o";
+      pieces[c][rook2Pos] = "u";
     }
-    return (
-      pieces["b"].join("") +
-      "/pppppppp/8/8/8/8/PPPPPPPP/" +
-      pieces["w"].join("").toUpperCase() +
-      " w 0"
-    );
+    let upFen = pieces["b"].join("");
+    upFen = upFen.substr(0,8) + "/" + upFen.substr(8).split("").reverse().join("");
+    let downFen = pieces["b"].join("").toUpperCase();
+    downFen = downFen.substr(0,8) + "/" + downFen.substr(8).split("").reverse().join("");
+    return upFen + "/8/8/8/8/" + downFen + " w 0";
   }
 
   getCheckSquares() {
     return [];
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (
       move.vanish.length >= 2 &&
       [V.KING,V.HIDDEN_CODE[V.KING]].includes(move.vanish[1].p)
@@ -158,8 +230,8 @@ export const VariantRules = class HiddenRules extends ChessRules {
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     const c = move.vanish[0].c;
     const oppCol = V.GetOppCol(c);
     if (this.kingPos[oppCol][0] < 0)
@@ -178,8 +250,77 @@ export const VariantRules = class HiddenRules extends ChessRules {
   }
 
   getComputerMove() {
-    // Just return a random move. TODO: something smarter...
-    const moves = this.getAllValidMoves();
-    return moves[randInt(moves.length)];
+    const color = this.turn;
+    let moves = this.getAllValidMoves();
+    for (let move of moves) {
+      move.eval = 0; //a priori...
+
+      // Can I take something ? If yes, do it with some probability
+      if (move.vanish.length == 2 && move.vanish[1].c != color) {
+        // OK this isn't a castling move
+        const myPieceVal = V.VALUES[move.appear[0].p];
+        const hisPieceVal = Object.keys(V.HIDDEN_DECODE).includes(move.vanish[1].p)
+          ? undefined
+          : V.VALUES[move.vanish[1].p];
+        if (!hisPieceVal) {
+          // Opponent's piece is unknown: do not take too much risk
+          move.eval = -myPieceVal + 1.5; //so that pawns always take
+        }
+        // Favor captures
+        else if (myPieceVal <= hisPieceVal)
+          move.eval = hisPieceVal - myPieceVal + 1;
+        else {
+          // Taking a pawn with minor piece,
+          // or minor piece or pawn with a rook,
+          // or anything but a queen with a queen,
+          // or anything with a king.
+          move.eval = hisPieceVal - myPieceVal;
+        }
+      } else {
+        // If no capture, favor small step moves,
+        // but sometimes move the knight anyway
+        const penalty = V.Decode(move.vanish[0].p) != V.KNIGHT
+          ? Math.abs(move.end.x - move.start.x) + Math.abs(move.end.y - move.start.y)
+          : (Math.random() < 0.5 ? 3 : 1);
+        move.eval -= penalty / (V.size.x + V.size.y - 1);
+      }
+
+      // TODO: also favor movements toward the center?
+    }
+
+    moves.sort((a, b) => b.eval - a.eval);
+    let candidates = [0];
+    for (let j = 1; j < moves.length && moves[j].eval == moves[0].eval; j++)
+      candidates.push(j);
+    return moves[candidates[randInt(candidates.length)]];
+  }
+
+  getNotation(move) {
+    // Translate final square
+    const finalSquare = V.CoordsToSquare(move.end);
+
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN) {
+      // Pawn move
+      let notation = "";
+      if (move.vanish.length > move.appear.length) {
+        // Capture
+        const startColumn = V.CoordToColumn(move.start.y);
+        notation = startColumn + "x" + finalSquare;
+      }
+      else notation = finalSquare;
+      if (move.appear.length > 0 && !["p","s"].includes(move.appear[0].p)) {
+        // Promotion
+        const appearPiece = V.Decode(move.appear[0].p);
+        notation += "=" + appearPiece.toUpperCase();
+      }
+      return notation;
+    }
+    // Piece movement
+    return (
+      piece.toUpperCase() +
+      (move.vanish.length > move.appear.length ? "x" : "") +
+      finalSquare
+    );
   }
 };