Add Bario draft. Small bugs to fix in Refusal and Bario
[vchess.git] / client / src / variants / Refusal.js
diff --git a/client/src/variants/Refusal.js b/client/src/variants/Refusal.js
new file mode 100644 (file)
index 0000000..e4ec04d
--- /dev/null
@@ -0,0 +1,201 @@
+import { ChessRules } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+// TODO: Two moves, both promoting the same pawn, but to a different type of piece, count as two different moves.
+// ==> need to accept temporarily pawn promotions even if on forbidden square, and check afterward if promoted type changed (info in lastMove)
+
+export class RefusalRules extends ChessRules {
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    if (!V.ParseFen(fen).lastMove) return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    return Object.assign(
+      { lastMove: fen.split(" ")[5] },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  getFen() {
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return super.getFen() + " " + JSON.stringify(lm);
+  }
+
+  // NOTE: with this variant's special rule,
+  // some extra repetitions could be detected... TODO (...)
+
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness) + " null";
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.lastMove = [JSON.parse(V.ParseFen(fen).lastMove)]; //may be null
+  }
+
+  canIplay(side, [x, y]) {
+    if (super.canIplay(side, [x, y])) return true;
+    if (this.turn != side) return false;
+    // Check if playing last move, reversed:
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.getColor(x, y) != this.turn) {
+      const L = this.lastMove.length;
+      const lm = this.lastMove[L-1];
+      if (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y) {
+        let revLm = JSON.parse(JSON.stringify(lm));
+        let tmp = revLm.appear;
+        revLm.appear = revLm.vanish;
+        revLm.vanish = tmp;
+        tmp = revLm.start;
+        revLm.start = revLm.end;
+        revLm.end = tmp;
+        return [revLm];
+      }
+      return [];
+    }
+    return super.getPotentialMovesFrom([x, y]);
+  }
+
+  // NOTE: do not take refusal move into account here (two own moves)
+  atLeastTwoMoves() {
+    let movesCounter = 0;
+    const color = this.turn;
+    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) {
+          const moves = this.getPotentialMovesFrom([i, j]);
+          for (let m of moves) {
+            if (m.vanish[0].c == color && this.filterValid([m]).length > 0) {
+              movesCounter++;
+              if (movesCounter >= 2) return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  filterValid(moves) {
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return moves.filter(m => {
+      if (
+        !!lm && !!lm.refusal &&
+        m.start.x == lm.end.x && m.start.y == lm.end.y &&
+        m.end.x == lm.start.x && m.end.y == lm.start.y
+      ) {
+        return false;
+      }
+      // NOTE: not using this.play()/undo() ==> infinite loop
+      V.PlayOnBoard(this.board, m);
+      if (m.appear[0].p == V.KING)
+        this.kingPos[m.appear[0].c] = [m.appear[0].x, m.appear[0].y];
+      const res = !this.underCheck(color);
+      V.UndoOnBoard(this.board, m);
+      if (m.vanish[0].p == V.KING)
+        this.kingPos[m.vanish[0].c] = [m.vanish[0].x, m.vanish[0].y];
+      return res;
+    });
+  }
+
+  prePlay(move) {
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    if (
+      // My previous move was already refused?
+      (!!lm && this.getColor(lm.end.x, lm.end.y) == this.turn) ||
+      // I've only one move available?
+      !this.atLeastTwoMoves()
+    ) {
+      move.noRef = true;
+    }
+    // NOTE: refusal could be recomputed, but, it's easier like this
+    if (move.vanish[0].c != this.turn) move.refusal = true;
+  }
+
+  getEpSquare(move) {
+    if (!move.refusal) return super.getEpSquare(move);
+    return null; //move refusal
+  }
+
+  postPlay(move) {
+    if (!move.refusal) super.postPlay(move);
+    else {
+      const L = this.lastMove.length;
+      const lm = this.lastMove[L-1];
+      this.disaggregateFlags(JSON.parse(lm.flags));
+    }
+    // NOTE: explicitely give fields, because some are assigned in BaseGame
+    let mvInLm = {
+      start: move.start,
+      end: move.end,
+      appear: move.appear,
+      vanish: move.vanish,
+      flags: move.flags
+    };
+    if (!!move.noRef) mvInLm.noRef = true;
+    if (!!move.refusal) mvInLm.refusal = true;
+    this.lastMove.push(mvInLm);
+  }
+
+  postUndo(move) {
+    if (!move.refusal) super.postUndo(move);
+    this.lastMove.pop();
+  }
+
+  getAllPotentialMoves() {
+    const color = this.turn;
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    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 ||
+            // Add move refusal:
+            (!!lm && lm.end.x == i && lm.end.y == j)
+          )
+        ) {
+          Array.prototype.push.apply(
+            potentialMoves,
+            this.getPotentialMovesFrom([i, j])
+          );
+        }
+      }
+    }
+    return potentialMoves;
+  }
+
+  getComputerMove() {
+    // Just play at random for now... (TODO?)
+    // Refuse last move with odds 1/3.
+    const moves = this.getAllValidMoves();
+    const refusal = moves.find(m => m.vanish[0].c != this.turn);
+    if (!!refusal) {
+      if (Math.random() <= 0.33) return refusal;
+      const others = moves.filter(m => m.vanish[0].c == this.turn);
+      return others[randInt(others.length)];
+    }
+    else return moves[randInt(moves.length)];
+  }
+
+  getNotation(move) {
+    if (move.vanish[0].c != this.turn) return "Refuse";
+    return super.getNotation(move);
+  }
+
+};