From 3e72fcf5c0b53fa0ad16114434c7ddd527892297 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 24 Jul 2023 11:47:11 +0200
Subject: [PATCH] Start working on Convert variant

---
 variants.js               |   2 +-
 variants/Convert/class.js | 224 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 225 insertions(+), 1 deletion(-)
 create mode 100644 variants/Convert/class.js

diff --git a/variants.js b/variants.js
index 1b398aa..f3565dd 100644
--- a/variants.js
+++ b/variants.js
@@ -31,7 +31,7 @@ const variants = [
   {name: 'Chess960', disp: "Chess 960", desc: "Standard rules"},
   {name: 'Circular', desc: 'Run forward'},
   {name: 'Clorange', desc: 'A Clockwork Orange', disp: 'Clockwork Orange'},
-//  {name: 'Convert', desc: 'Convert enemy pieces'},
+  {name: 'Convert', desc: 'Convert enemy pieces'},
 //  {name: 'Copycat', desc: 'Borrow powers'},
 //  {name: 'Coregal', desc: 'Two royal pieces'},
 //  {name: 'Coronation', desc: 'Long live the Queen'},
diff --git a/variants/Convert/class.js b/variants/Convert/class.js
new file mode 100644
index 0000000..1a006e2
--- /dev/null
+++ b/variants/Convert/class.js
@@ -0,0 +1,224 @@
+import ChessRules from "/base_rules.js";
+import PiPo from "/utils/PiPo.js";
+import Move from "/utils/Move.js";
+
+export default class ConvertRules extends ChessRules {
+
+  // TODO
+  static get Options() {
+    return {
+      select: C.Options.select,
+      input: C.Options.input,
+      styles: [
+        "atomic", "cannibal", "capture", "cylinder",
+        "dark", "madrasi", "rifle", "teleport"
+      ]
+    };
+  }
+
+  get hasEnpassant() {
+    return false;
+  }
+
+  setOtherVariables(fenParsed, pieceArray) {
+    super.setOtherVariables(fenParsed, pieceArray);
+    // Stack of "last move" only for intermediate chaining
+    this.lastMoveEnd = [null];
+  }
+
+  genRandInitBaseFen() {
+    const baseFen = super.genRandInitBaseFen();
+    return {
+      fen: baseFen.fen.replace("pppppppp/8", "8/pppppppp")
+                      .replace("8/PPPPPPPP", "PPPPPPPP/8"),
+      o: baseFen.o
+    };
+  }
+
+  getBasicMove([sx, sy], [ex, ey], tr) {
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    const piece = (!!lm ? lm.p : null);
+    const c = this.turn;
+    if (this.board[ex][ey] == "") {
+      if (piece && !tr)
+        tr = {c: c, p: piece};
+      let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
+      if (piece)
+        mv.vanish.pop();
+      return mv;
+    }
+    // Capture: initial, or inside a chain
+    const initPiece = (piece || this.getPiece(sx, sy));
+    const oppCol = C.GetOppTurn(c);
+    const oppPiece = this.getPiece(ex, ey);
+    let mv = new Move({
+      start: {x: sx, y: sy},
+      end: {x: ex, y: ey},
+      appear: [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: c,
+          p: (!!tr ? tr.p : initPiece)
+        })
+      ],
+      vanish: [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: oppCol,
+          p: oppPiece
+        })
+      ]
+    });
+    if (!piece) {
+      // Initial capture
+      mv.vanish.unshift(
+        new PiPo({
+          x: sx,
+          y: sy,
+          c: c,
+          p: initPiece
+        })
+      );
+    }
+    return mv;
+  }
+
+// TODO from here
+
+  getPotentialMovesFrom([x, y], asA) {
+    const L = this.lastMoveEnd.length;
+    if (!!this.lastMoveEnd[L-1]) {
+      if (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y)
+        // A capture was played: wrong square
+        return [];
+      asA = this.lastMoveEnd[L-1].p;
+    }
+    switch (asA || this.getPiece(x, y)) {
+      case V.PAWN: return super.getPotentialPawnMoves([x, y]);
+      case V.ROOK: return super.getPotentialRookMoves([x, y]);
+      case V.KNIGHT: return super.getPotentialKnightMoves([x, y]);
+      case V.BISHOP: return super.getPotentialBishopMoves([x, y]);
+      case V.QUEEN: return super.getPotentialQueenMoves([x, y]);
+      case V.KING: return super.getPotentialKingMoves([x, y]);
+    }
+    return [];
+  }
+
+  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));
+  }
+
+  isAttacked_aux([x, y], color, explored) {
+    if (explored.some(sq => sq[0] == x && sq[1] == y))
+      // Start of an infinite loop: exit
+      return false;
+    explored.push([x, y]);
+    if (super.isAttacked([x, y], color)) return true;
+    // Maybe indirect "chaining" attack:
+    const myColor = this.turn
+    let res = false;
+    let toCheck = []; //check all but king (no need)
+    // Pawns:
+    const shiftToPawn = (myColor == 'w' ? -1 : 1);
+    for (let yShift of [-1, 1]) {
+      const [i, j] = [x + shiftToPawn, y + yShift];
+      if (
+        V.OnBoard(i, j) &&
+        this.board[i][j] != V.EMPTY &&
+        // NOTE: no need to check color (no enemy pawn can take directly)
+        this.getPiece(i, j) == V.PAWN
+      ) {
+        toCheck.push([i, j]);
+      }
+    }
+    // Knights:
+    V.steps[V.KNIGHT].forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (
+        V.OnBoard(i, j) &&
+        this.board[i][j] != V.EMPTY &&
+        this.getPiece(i, j) == V.KNIGHT
+      ) {
+        toCheck.push([i, j]);
+      }
+    });
+    // Sliders:
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      let [i, j] = [x + s[0], y + s[1]];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        i += s[0];
+        j += s[1];
+      }
+      if (!V.OnBoard(i, j)) return;
+      const piece = this.getPiece(i, j);
+      if (
+        piece == V.QUEEN ||
+        (piece == V.ROOK && (s[0] == 0 || s[1] == 0)) ||
+        (piece == V.BISHOP && (s[0] != 0 && s[1] != 0))
+      ) {
+        toCheck.push([i, j]);
+      }
+    });
+    for (let ij of toCheck) {
+      if (this.isAttacked_aux(ij, color, explored)) return true;
+    }
+    return false;
+  }
+
+  isAttacked([x, y], color) {
+    let explored = [];
+    return this.isAttacked_aux([x, y], color, explored);
+  }
+
+  filterValid(moves) {
+    // No "checks" (except to forbid castle)
+    return moves;
+  }
+
+  prePlay(move) {
+    const c = this.turn;
+    // Extra conditions to avoid tracking converted kings:
+    if (
+      move.appear[0].p == V.KING &&
+      move.vanish.length >= 1 &&
+      move.vanish[0].p == V.KING
+    ) {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+    }
+  }
+
+  play(move) {
+    this.prePlay(move);
+    const c = this.turn;
+    move.flags = JSON.stringify(this.aggregateFlags());
+    V.PlayOnBoard(this.board, move);
+    if (!move.end.converted) {
+      // 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.end.converted })
+      );
+    }
+    super.updateCastleFlags(move, move.appear[0].p, c);
+  }
+
+};
-- 
2.44.0