Add Gomoku + Atarigo
[vchess.git] / client / src / variants / Atarigo.js
diff --git a/client/src/variants/Atarigo.js b/client/src/variants/Atarigo.js
new file mode 100644 (file)
index 0000000..2e50922
--- /dev/null
@@ -0,0 +1,225 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+import { ArrayFun } from "@/utils/array";
+
+export class AtarigoRules extends ChessRules {
+
+  static get Monochrome() {
+    return true;
+  }
+
+  static get Notoodark() {
+    return true;
+  }
+
+  static get Lines() {
+    let lines = [];
+    // Draw all inter-squares lines, shifted:
+    for (let i = 0; i < V.size.x; i++)
+      lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]);
+    for (let j = 0; j < V.size.y; j++)
+      lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]);
+    return lines;
+  }
+
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get ReverseColors() {
+    return true;
+  }
+
+  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 (row[i].toLowerCase() == V.PAWN) 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 IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 3) Check capture "flag"
+    if (!fenParsed.capture || !fenParsed.capture.match(/^[01]$/))
+      return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      // Capture field allows to compute the score cleanly.
+      { capture: fenParts[3] }
+    );
+  }
+
+  static get size() {
+    return { x: 12, y: 12 };
+  }
+
+  static GenRandInitFen() {
+    return "93/93/93/93/93/5Pp5/5pP5/93/93/93/93/93 w 0 0";
+  }
+
+  getFen() {
+    return super.getFen() + " " + (this.capture ? 1 : 0);
+  }
+
+  setOtherVariables(fen) {
+    this.capture = parseInt(V.ParseFen(fen).capture, 10);
+  }
+
+  getPiece() {
+    return V.PAWN;
+  }
+
+  getPpath(b) {
+    return "Gomoku/" + b;
+  }
+
+  onlyClick() {
+    return true;
+  }
+
+  canIplay(side, [x, y]) {
+    return (side == this.turn && this.board[x][y] == V.EMPTY);
+  }
+
+  hoverHighlight([x, y], side) {
+    if (!!side && side != this.turn) return false;
+    return (this.board[x][y] == V.EMPTY);
+  }
+
+  searchForEmptySpace([x, y], color, explored) {
+    if (explored[x][y]) return false; //didn't find empty space
+    explored[x][y] = true;
+    let res = false;
+    for (let s of V.steps[V.ROOK]) {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j)) {
+        if (this.board[i][j] == V.EMPTY) res = true;
+        else if (this.getColor(i, j) == color)
+          res = this.searchForEmptySpace([i, j], color, explored) || res;
+      }
+    }
+    return res;
+  }
+
+  doClick([x, y]) {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let move = new Move({
+      appear: [
+        new PiPo({ x: x, y: y, c: color, p: V.PAWN })
+      ],
+      vanish: [],
+      start: { x: -1, y: -1 }
+    });
+    V.PlayOnBoard(this.board, move); //put the stone
+    let noSuicide = false;
+    let captures = [];
+    for (let s of V.steps[V.ROOK]) {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j)) {
+        if (this.board[i][j] == V.EMPTY) noSuicide = true; //clearly
+        else if (this.getColor(i, j) == color) {
+          // Free space for us = not a suicide
+          if (!noSuicide) {
+            let explored = ArrayFun.init(V.size.x, V.size.y, false);
+            noSuicide = this.searchForEmptySpace([i, j], color, explored);
+          }
+        }
+        else {
+          // Free space for opponent = not a capture
+          let explored = ArrayFun.init(V.size.x, V.size.y, false);
+          const captureSomething =
+            !this.searchForEmptySpace([i, j], oppCol, explored);
+          if (captureSomething) {
+            for (let ii = 0; ii < V.size.x; ii++) {
+              for (let jj = 0; jj < V.size.y; jj++) {
+                if (explored[ii][jj]) {
+                  captures.push(
+                    new PiPo({ x: ii, y: jj, c: oppCol, p: V.PAWN })
+                  );
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    V.UndoOnBoard(this.board, move); //remove the stone
+    if (!noSuicide && captures.length == 0) return null;
+    Array.prototype.push.apply(move.vanish, captures);
+    return move;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const move = this.doClick([x, y]);
+    return (!move ? [] : [move]);
+  }
+
+  getAllPotentialMoves() {
+    let moves = [];
+    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) {
+          const mv = this.doClick(i, j);
+          if (!!mv) moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  postPlay(move) {
+    if (move.vanish.length >= 1) this.capture = true;
+  }
+
+  postUndo() {
+    this.capture = false;
+  }
+
+  getCurrentScore() {
+    if (this.capture) return (this.turn == 'w' ? "0-1" : "1-0");
+    return "*";
+  }
+
+  getComputerMove() {
+    const moves = super.getAllValidMoves();
+    if (moves.length == 0) return null;
+    // Just random mover for now... writing a good bot is far out of scope
+    return moves[randInt(moves.length)];
+  }
+
+  getNotation(move) {
+    return V.CoordsToSquare(move.end);
+  }
+
+};