Experimental change: options replacing randomness (more general)
[vchess.git] / client / src / variants / Allmate.js
diff --git a/client/src/variants/Allmate.js b/client/src/variants/Allmate.js
new file mode 100644 (file)
index 0000000..86409c2
--- /dev/null
@@ -0,0 +1,253 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export class AllmateRules extends ChessRules {
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  getCheckSquares() {
+    // No notion of check
+    return [];
+  }
+
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness).slice(0, -2);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    // Remove standard captures (without removing castling):
+    moves = moves.filter(m => {
+      return m.vanish.length == 1 || m.appear.length == 2;
+    });
+
+    // Augment moves with "mate-captures":
+    // TODO: this is coded in a highly inefficient way...
+    const color = this.turn;
+    const oppCol = V.GetOppCol(this.turn);
+    moves.forEach(m => {
+      this.play(m);
+      let allAttacks = [];
+
+      // While something has been captured:
+      // remove it, and keep looking for captures
+      outerLoop: while (true) {
+
+        // 1) What is attacked?
+        let attacked = {};
+        for (let i=0; i<V.size.x; i++) {
+          for (let j=0; j<V.size.y; j++) {
+            if (this.getColor(i,j) == oppCol && this.isAttacked([i,j], color))
+              attacked[i+"_"+j] = [i,j];
+          }
+        }
+        if (Object.keys(attacked).length == 0) break outerLoop;
+
+        // 2) Among attacked pieces, which cannot escape capture?
+        // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
+        for (let i=0; i<V.size.x; i++) {
+          for (let j=0; j<V.size.y; j++) {
+            if (this.getColor(i,j) == oppCol) {
+              let oppMoves = [];
+              switch (this.getPiece(i, j)) {
+                case V.PAWN:
+                  oppMoves = this.getPotentialPawnMoves([i, j]);
+                  break;
+                case V.ROOK:
+                  oppMoves = this.getPotentialRookMoves([i, j]);
+                  break;
+                case V.KNIGHT:
+                  oppMoves = this.getPotentialKnightMoves([i, j]);
+                  break;
+                case V.BISHOP:
+                  oppMoves = this.getPotentialBishopMoves([i, j]);
+                  break;
+                case V.QUEEN:
+                  oppMoves = this.getPotentialQueenMoves([i, j]);
+                  break;
+                case V.KING:
+                  // Do not allow castling to escape from check
+                  oppMoves = super.getSlideNJumpMoves(
+                    [i, j],
+                    V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+                    "oneStep"
+                  );
+                  break;
+              }
+              for (let om of oppMoves) {
+                V.PlayOnBoard(this.board, om);
+                Object.values(attacked).forEach(sq => {
+                  const origSq = [sq[0], sq[1]];
+                  if (om.start.x == sq[0] && om.start.y == sq[1])
+                    // Piece moved:
+                    sq = [om.appear[0].x, om.appear[0].y];
+                  if (!this.isAttacked(sq, color))
+                    delete attacked[origSq[0]+"_"+origSq[1]];
+                });
+                V.UndoOnBoard(this.board, om);
+                if (Object.keys(attacked).length == 0)
+                  // No need to explore more moves
+                  break outerLoop;
+              }
+            }
+          }
+        }
+
+        // 3) Add mate-captures and remove pieces from board:
+        Object.keys(attacked).forEach(k => {
+          allAttacks.push({
+            sq: attacked[k],
+            p: this.getPiece(attacked[k][0], attacked[k][1])
+          });
+          this.board[attacked[k][0]][attacked[k][1]] = V.EMPTY;
+        });
+      }
+
+      // Put removed pieces back on board:
+      allAttacks.forEach(v => {
+        this.board[v.sq[0]][v.sq[1]] = oppCol + v.p;
+      });
+      this.undo(m);
+      allAttacks.forEach(v => {
+        m.vanish.push(
+          new PiPo({
+            x: v.sq[0],
+            y: v.sq[1],
+            c: oppCol,
+            p: v.p
+          })
+        );
+      });
+    });
+
+    return moves;
+  }
+
+  // No "under check" conditions in castling
+  getCastleMoves(sq) {
+    return super.getCastleMoves(sq, null, "castleInCheck");
+  }
+
+  // TODO: allow pieces to "commit suicide"? (Currently yes except king)
+  filterValid(moves) {
+    // Remove moves which let the king mate-captured:
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    return moves.filter(m => {
+      let res = true;
+      this.play(m);
+      if (this.underCheck(color)) {
+        res = false;
+        const attacked = this.kingPos[color];
+        // Try to find a move to escape check
+        // TODO: very inefficient method.
+        outerLoop: for (let i=0; i<V.size.x; i++) {
+          for (let j=0; j<V.size.y; j++) {
+            if (this.getColor(i,j) == color) {
+              let emoves = [];
+              // Artficial turn change to "play twice":
+              this.turn = color;
+              switch (this.getPiece(i, j)) {
+                case V.PAWN:
+                  emoves = this.getPotentialPawnMoves([i, j]);
+                  break;
+                case V.ROOK:
+                  emoves = this.getPotentialRookMoves([i, j]);
+                  break;
+                case V.KNIGHT:
+                  emoves = this.getPotentialKnightMoves([i, j]);
+                  break;
+                case V.BISHOP:
+                  emoves = this.getPotentialBishopMoves([i, j]);
+                  break;
+                case V.QUEEN:
+                  emoves = this.getPotentialQueenMoves([i, j]);
+                  break;
+                case V.KING:
+                  emoves = this.getPotentialKingMoves([i, j]);
+                  break;
+              }
+              this.turn = oppCol;
+              for (let em of emoves) {
+                V.PlayOnBoard(this.board, em);
+                let sq = attacked;
+                if (em.start.x == attacked[0] && em.start.y == attacked[1])
+                  // King moved:
+                  sq = [em.appear[0].x, em.appear[0].y];
+                if (!this.isAttacked(sq, oppCol))
+                  res = true;
+                V.UndoOnBoard(this.board, em);
+                if (res)
+                  // No need to explore more moves
+                  break outerLoop;
+              }
+            }
+          }
+        }
+      }
+      this.undo(m);
+      return res;
+    });
+  }
+
+  postPlay(move) {
+    super.postPlay(move);
+    if (move.vanish.length >= 2 && move.appear.length == 1) {
+      for (let i = 1; i<move.vanish.length; i++) {
+        const v = move.vanish[i];
+        // Did opponent king disappeared?
+        if (v.p == V.KING)
+          this.kingPos[this.turn] = [-1, -1];
+        // Or maybe a rook?
+        else if (v.p == V.ROOK) {
+          if (v.y < this.kingPos[v.c][1])
+            this.castleFlags[v.c][0] = 8;
+          else
+            // v.y > this.kingPos[v.c][1]
+            this.castleFlags[v.c][1] = 8;
+        }
+      }
+    }
+  }
+
+  preUndo(move) {
+    super.preUndo(move);
+    const oppCol = this.turn;
+    if (move.vanish.length >= 2 && move.appear.length == 1) {
+      // Did opponent king disappeared?
+      const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
+      if (psq)
+        this.kingPos[psq.c] = [psq.x, psq.y];
+    }
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0)
+      // King disappeared
+      return color == "w" ? "0-1" : "1-0";
+    if (this.atLeastOneMove()) return "*";
+    // Kings still there, no moves:
+    return "1/2";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 1;
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    // Add a capture mark (not describing what is captured...):
+    if (move.vanish.length > 1 && move.appear.length == 1) {
+      if (!!(notation.match(/^[a-h]x/)))
+        // Pawn capture: remove initial "b" in bxc4 for example
+        notation = notation.substr(1);
+      notation = notation.replace("x","") + "X";
+    }
+    return notation;
+  }
+
+};