Improve autoplay, debug move reception while autoplay and/or analyze is on. Add Ambig...
[vchess.git] / client / src / variants / Ambiguous.js
diff --git a/client/src/variants/Ambiguous.js b/client/src/variants/Ambiguous.js
new file mode 100644 (file)
index 0000000..dea19d5
--- /dev/null
@@ -0,0 +1,257 @@
+import { ChessRules } from "@/base_rules";
+
+export class AmbiguousRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    if (this.movesCount == 0) this.subTurn = 2;
+    else this.subTurn = 1;
+  }
+
+  // Subturn 1: play a move for the opponent on the designated square.
+  // Subturn 2: play a move for me (which just indicate a square).
+  getPotentialMovesFrom([x, y]) {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    if (this.subTurn == 2) {
+      // Just play a normal move (which in fact only indicate a square)
+      return (
+        super.getPotentialMovesFrom([x, y])
+        .map(m => {
+          if (m.vanish.length == 1) m.appear[0].p = V.GOAL;
+          else m.appear[0].p = V.TARGET_CODE[m.vanish[1].p];
+          m.appear[0].c = oppCol;
+          m.vanish.shift();
+          return m;
+        })
+      );
+    }
+    // At subTurn == 1, play a targeted move for opponent
+    // Search for target (we could also have it in a stack...)
+    let target = { x: -1, y: -1 };
+    outerLoop: 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 piece = this.board[i][j][1];
+          if (
+            piece == V.GOAL ||
+            Object.keys(V.TARGET_DECODE).includes(piece)
+          ) {
+            target = { x: i, y: j};
+            break outerLoop;
+          }
+        }
+      }
+    }
+    // TODO: could be more efficient than generating all moves.
+    this.turn = oppCol;
+    const emptyTarget = (this.board[target.x][target.y][1] == V.GOAL);
+    if (emptyTarget) this.board[target.x][target.y] = V.EMPTY;
+    let moves = super.getPotentialMovesFrom([x, y]);
+    if (emptyTarget) {
+      this.board[target.x][target.y] = color + V.GOAL;
+      moves.forEach(m => {
+        m.vanish.push({
+          x: target.x,
+          y: target.y,
+          c: color,
+          p: V.GOAL
+        });
+      });
+    }
+    this.turn = color;
+    return moves.filter(m => m.end.x == target.x && m.end.y == target.y);
+  }
+
+  canIplay(side, [x, y]) {
+    const color = this.getColor(x, y);
+    return (
+      (this.subTurn == 1 && color != side) ||
+      (this.subTurn == 2 && color == side)
+    );
+  }
+
+  getPpath(b) {
+    if (b[1] == V.GOAL || Object.keys(V.TARGET_DECODE).includes(b[1]))
+      return "Ambiguous/" + b;
+    return b;
+  }
+
+  // Code for empty square target
+  static get GOAL() {
+    return 'g';
+  }
+
+  static get TARGET_DECODE() {
+    return {
+      's': 'p',
+      't': 'q',
+      'u': 'r',
+      'o': 'n',
+      'c': 'b',
+      'l': 'k'
+    };
+  }
+
+  static get TARGET_CODE() {
+    return {
+      'p': 's',
+      'q': 't',
+      'r': 'u',
+      'n': 'o',
+      'b': 'c',
+      'k': 'l'
+    };
+  }
+
+  static get PIECES() {
+    return (
+      ChessRules.PIECES.concat(Object.keys(V.TARGET_DECODE)).concat([V.GOAL])
+    );
+  }
+
+  getAllPotentialMoves() {
+    const color = this.turn;
+    let potentialMoves = [];
+    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 &&
+          this.getColor(i, j) == color &&
+          this.board[i][j][1] != V.GOAL &&
+          !(Object.keys(V.TARGET_DECODE).includes(this.board[i][j][1]))
+        ) {
+          Array.prototype.push.apply(
+            potentialMoves,
+            this.getPotentialMovesFrom([i, j])
+          );
+        }
+      }
+    }
+    return potentialMoves;
+  }
+
+  atLeastOneMove() {
+    // Since there are no checks this seems true (same as for Magnetic...)
+    return true;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    // This function is only called at subTurn 1
+    const color = V.GetOppCol(this.turn);
+    if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0");
+    return "*";
+  }
+
+  prePlay(move) {
+    const c = V.GetOppCol(this.turn);
+    const piece = move.vanish[0].p;
+    if (piece == V.KING) {
+      // (Opp) king moves:
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+    }
+    if (move.vanish.length == 2 && [V.KING, 'l'].includes(move.vanish[1].p))
+      // (My) king is captured:
+      this.kingPos[this.turn] = [-1, -1];
+  }
+
+  play(move) {
+    let kingCaptured = false;
+    if (this.subTurn == 1) {
+      this.prePlay(move);
+      this.epSquares.push(this.getEpSquare(move));
+      kingCaptured = this.kingPos[this.turn][0] < 0;
+    }
+    if (kingCaptured) move.kingCaptured = true;
+    V.PlayOnBoard(this.board, move);
+    if (this.subTurn == 2 || kingCaptured) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+    }
+    if (!kingCaptured) this.subTurn = 3 - this.subTurn;
+  }
+
+  undo(move) {
+    if (!move.kingCaptured) this.subTurn = 3 - this.subTurn;
+    if (this.subTurn == 2 || !!move.kingCaptured) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    V.UndoOnBoard(this.board, move);
+    if (this.subTurn == 1) {
+      this.epSquares.pop();
+      this.postUndo(move);
+    }
+  }
+
+  postUndo(move) {
+    // (Potentially) Reset king(s) position
+    const c = V.GetOppCol(this.turn);
+    const piece = move.vanish[0].p;
+    if (piece == V.KING) {
+      // (Opp) king moved:
+      this.kingPos[c][0] = move.vanish[0].x;
+      this.kingPos[c][1] = move.vanish[0].y;
+    }
+    if (move.vanish.length == 2 && [V.KING, 'l'].includes(move.vanish[1].p))
+      // (My) king was captured:
+      this.kingPos[this.turn] = [move.vanish[1].x, move.vanish[1].y];
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 -";
+
+    let pieces = { w: new Array(8), b: new Array(8) };
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
+      // Get random squares for every piece, totally freely
+      let positions = shuffle(ArrayFun.range(8));
+      const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
+      const rem2 = positions[0] % 2;
+      if (rem2 == positions[1] % 2) {
+        // Fix bishops (on different colors)
+        for (let i=2; i<8; i++) {
+          if (positions[i] % 2 != rem2)
+            [positions[1], positions[i]] = [positions[i], positions[1]];
+        }
+      }
+      for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i];
+    }
+    return (
+      pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      // En-passant allowed, but no flags
+      " w 0 -"
+    );
+  }
+
+  getNotation(move) {
+    if (this.subTurn == 2) return "T:" + V.CoordsToSquare(move.end);
+    // Remove and re-add target to get a good notation:
+    const withTarget = move.vanish[1];
+    if (move.vanish[1].p == V.GOAL) move.vanish.pop();
+    else move.vanish[1].p = V.TARGET_DECODE[move.vanish[1].p];
+    const notation = super.getNotation(move);
+    if (move.vanish.length == 1) move.vanish.push(withTarget);
+    else move.vanish[1].p = V.TARGET_CODE[move.vanish[1].p];
+    return notation;
+  }
+};