A few fixes + draft Interweave and Takenmake. Only 1 1/2 variant to go now :)
[vchess.git] / client / src / variants / Takenmake.js
diff --git a/client/src/variants/Takenmake.js b/client/src/variants/Takenmake.js
new file mode 100644 (file)
index 0000000..9a09b35
--- /dev/null
@@ -0,0 +1,147 @@
+import { ChessRules } from "@/base_rules";
+
+export class TakenmakeRules extends ChessRules {
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    // Stack of "last move" only for intermediate captures
+    this.lastMoveEnd = [null];
+  }
+
+  getPotentialMovesFrom([x, y], asA) {
+    const L = this.lastMoveEnd.length;
+    if (!asA && !!this.lastMoveEnd[L-1]) {
+      asA = this.lastMoveEnd[L-1].p;
+      if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y) {
+        // A capture was played: wrong square
+        return [];
+      }
+    }
+    let moves = [];
+    const piece = this.getPiece(x, y);
+    switch (asA || piece) {
+      case V.PAWN:
+        if (!asA || piece == V.PAWN)
+          moves = this.getPotentialPawnMoves([x, y]);
+        else {
+          // Special case: we don't want promotion, since just moving like
+          // a pawn, but I'm in fact not a pawn :)
+          const shiftX = (this.turn == 'w' ? -1 : 1);
+          if (this.board[x + shiftX][y] == V.EMPTY)
+            moves = [this.getBasicMove([x, y], [x + shiftX, y])];
+        }
+        break;
+      case V.ROOK:
+        moves = this.getPotentialRookMoves([x, y]);
+        break;
+      case V.KNIGHT:
+        moves = this.getPotentialKnightMoves([x, y]);
+        break;
+      case V.BISHOP:
+        moves = this.getPotentialBishopMoves([x, y]);
+        break;
+      case V.KING:
+        moves = this.getPotentialKingMoves([x, y]);
+        break;
+      case V.QUEEN:
+        moves = this.getPotentialQueenMoves([x, y]);
+        break;
+    }
+    // Post-process: if capture,
+    // can a move "as-capturer" be achieved with the same piece?
+    if (!asA) {
+      const color = this.turn;
+      return moves.filter(m => {
+        if (m.vanish.length == 2 && m.appear.length == 1) {
+          this.play(m);
+          let moveOk = true;
+          const makeMoves =
+            this.getPotentialMovesFrom([m.end.x, m.end.y], m.vanish[1].p);
+          if (
+            makeMoves.every(mm => {
+              // Cannot castle after a capturing move
+              // (with the capturing piece):
+              if (mm.vanish.length == 2) return true;
+              this.play(mm);
+              const res = this.underCheck(color);
+              this.undo(mm);
+              return res;
+            })
+          ) {
+            moveOk = false;
+          }
+          this.undo(m);
+          return moveOk;
+        }
+        return true;
+      });
+    }
+    // Moving "as a": filter out captures (no castles here)
+    return moves.filter(m => m.vanish.length == 1);
+  }
+
+  getPossibleMovesFrom(sq) {
+    const L = this.lastMoveEnd.length;
+    let asA = undefined;
+    if (!!this.lastMoveEnd[L-1]) {
+      if (
+        sq[0] != this.lastMoveEnd[L-1].x ||
+        sq[1] != this.lastMoveEnd[L-1].y
+      ) {
+        return [];
+      }
+      asA = this.lastMoveEnd[L-1].p;
+    }
+    return this.filterValid(this.getPotentialMovesFrom(sq, asA));
+  }
+
+  filterValid(moves) {
+    let noCaptureMoves = [];
+    let captureMoves = [];
+    moves.forEach(m => {
+      if (m.vanish.length == 1 || m.appear.length == 2) noCaptureMoves.push(m);
+      else captureMoves.push(m);
+    });
+    // Capturing moves were already checked in getPotentialMovesFrom()
+    return super.filterValid(noCaptureMoves).concat(captureMoves);
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    this.epSquares.push(this.getEpSquare(move));
+    V.PlayOnBoard(this.board, move);
+    if (move.vanish.length == 1 || move.appear.length == 2) {
+      // Not a capture: change turn
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.lastMoveEnd.push(null);
+    }
+    else {
+      this.lastMoveEnd.push(
+        Object.assign({}, move.end, { p: move.vanish[1].p })
+      );
+    }
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    const c = move.vanish[0].c;
+    const piece = move.vanish[0].p;
+    if (piece == V.KING && move.appear.length > 0) {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+    }
+    super.updateCastleFlags(move, piece);
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    this.epSquares.pop();
+    this.lastMoveEnd.pop();
+    V.UndoOnBoard(this.board, move);
+    if (move.vanish.length == 1 || move.appear.length == 2) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    super.postUndo(move);
+  }
+};