Add Konane, (very) early drafts of Emergo/Fanorona/Yote/Gomoku, fix repetitions detec...
[vchess.git] / client / src / variants / Konane.js
diff --git a/client/src/variants/Konane.js b/client/src/variants/Konane.js
new file mode 100644 (file)
index 0000000..8f285ef
--- /dev/null
@@ -0,0 +1,206 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+// TODO: Maybe more flexible end of game messages (V.ColorsReversed ?!)
+
+export class KonaneRules extends ChessRules {
+
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get PIECES() {
+    return V.PAWN;
+  }
+
+  getPpath(b) {
+    return "Konane/" + b;
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num) || num <= 0) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    return true;
+  }
+
+  static GenRandInitFen() {
+    return (
+      "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/" +
+      "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP w 0"
+    );
+  }
+
+  setOtherVariables(fen) {
+    this.captures = []; //reinit for each move
+  }
+
+  hoverHighlight(x, y) {
+    if (this.movesCount >= 2) return false;
+    const c = this.turn;
+    if (c == 'w') return (x == y && [0, 3, 4, 7].includes(x));
+    // "Black": search for empty square and allow nearby
+    for (let i of [0, 3, 4, 7]) {
+      if (this.board[i][i] == V.EMPTY)
+        return (Math.abs(x - i) + Math.abs(y - i) == 1)
+    }
+  }
+
+  onlyClick([x, y]) {
+    return (
+      this.movesCount <= 1 ||
+      // TODO: next line theoretically shouldn't be required...
+      (this.movesCount == 2 && this.getColor(x, y) != this.turn)
+    );
+  }
+
+  doClick([x, y]) {
+    if (this.movesCount >= 2) return null;
+    const color = this.turn;
+    if (color == 'w') {
+      if (x != y || ![0, 3, 4, 7].includes(x)) return null;
+      return new Move({
+        appear: [],
+        vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ],
+        end: { x: x, y: y }
+      });
+    }
+    // "Black": search for empty square and allow nearby
+    for (let i of [0, 3, 4, 7]) {
+      if (this.board[i][i] == V.EMPTY) {
+        if (Math.abs(x - i) + Math.abs(y - i) != 1) return null;
+        return new Move({
+          appear: [],
+          vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ],
+          end: { x: x, y: y }
+        });
+      }
+    }
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.movesCount <= 1) {
+      const mv = this.doClick([x, y]);
+      return (!!mv ? [mv] : []);
+    }
+    const L = this.captures.length;
+    const c = (L > 0 ? this.captures[L-1] : null);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let step = null;
+    let moves = [];
+    if (!!c) {
+      if (x != c.end.x || y != c.end.y) return [];
+      step = [(c.end.x - c.start.x) / 2, (c.end.y - c.start.y) / 2];
+      // Add move to adjacent empty square to mark "end of capture"
+      moves.push(
+        new Move({
+          appear: [],
+          vanish: [],
+          start: { x: x, y: y },
+          end: { x: x - step[0], y: y - step[1] }
+        })
+      );
+    }
+    // Examine captures from here
+    for (let s of (!!step ? [step] : V.steps[V.ROOK])) {
+      let [i, j] = [x + 2*s[0], y + 2*s[1]];
+      if (
+        !!c || //avoid redundant checks if continuation
+        (
+          V.OnBoard(i, j) &&
+          this.board[i][j] == V.EMPTY &&
+          this.board[i - s[0]][j - s[1]] != V.EMPTY &&
+          this.getColor(i - s[0], j - s[1]) == oppCol
+        )
+      ) {
+        let mv = new Move({
+          appear: [
+            new PiPo({ x: i, y: j, c: color, p: V.PAWN })
+          ],
+          vanish: [
+            new PiPo({ x: x, y: y, c: color, p: V.PAWN }),
+            new PiPo({ x: i - s[0], y: j - s[1], c: oppCol, p: V.PAWN })
+          ]
+        });
+        // Is there another capture possible then?
+        [i, j] = [i + 2*s[0], j + 2*s[1]];
+        if (
+          V.OnBoard(i, j) &&
+          this.board[i][j] == V.EMPTY &&
+          this.board[i - s[0]][j - s[1]] != V.EMPTY &&
+          this.getColor(i - s[0], j - s[1]) == oppCol
+        ) {
+          mv.end.moreCapture = true;
+        }
+        moves.push(mv);
+      }
+    }
+    return moves;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    if (this.atLeastOneMove()) return "*";
+    return (this.turn == "w" ? "0-1" : "1-0");
+  }
+
+  play(move) {
+    V.PlayOnBoard(this.board, move);
+    if (!move.end.moreCapture) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.captures = [];
+    }
+    else {
+      this.captures.push(
+        {
+          start: move.start,
+          end: { x: move.end.x, y: move.end.y }
+        }
+      );
+    }
+  }
+
+  undo(move) {
+    V.UndoOnBoard(this.board, move);
+    if (!move.end.moreCapture) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    else this.captures.pop();
+  }
+
+  static get SEARCH_DEPTH() {
+    return 4;
+  }
+
+  getNotation(move) {
+    if (this.movesCount <= 1) return V.CoordsToSquare(move.start) + "X";
+    if (move.vanish.length == 0) return "end";
+    return V.CoordsToSquare(move.start) + "x" + V.CoordsToSquare(move.end);
+  }
+
+};