Add Orda variant. Some advances in Dynamo draft. Display error message when loading...
[vchess.git] / client / src / variants / Orda.js
diff --git a/client/src/variants/Orda.js b/client/src/variants/Orda.js
new file mode 100644 (file)
index 0000000..4d911f4
--- /dev/null
@@ -0,0 +1,323 @@
+import { ChessRules } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt } from "@/utils/alea";
+
+export class OrdaRules extends ChessRules {
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      { promotions: [V.QUEEN, V.KHESHIG] }
+    );
+  }
+
+  static IsGoodFlags(flags) {
+    // Only white can castle
+    return !!flags.match(/^[a-z]{2,2}$/);
+  }
+
+  getPpath(b) {
+    if (b[0] == 'b' || b[1] == 'h')
+      // Horde piece or white promoted pawn in kheshig
+      return "Orda/" + b;
+    return b;
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w 0 ah -";
+
+    // Mapping kingdom --> horde:
+    const piecesMap = {
+      'r': 'l',
+      'n': 'h',
+      'b': 'a',
+      'q': 'y',
+      'k': 'k'
+    };
+
+    let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
+    // Shuffle pieces on first (and last rank if randomness == 2)
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'].map(p => piecesMap[p]);
+        break;
+      }
+
+      // TODO: same code as in base_rules. Should extract and factorize?
+
+      let positions = ArrayFun.range(8);
+
+      let randIndex = 2 * randInt(4);
+      const bishop1Pos = positions[randIndex];
+      let randIndex_tmp = 2 * randInt(4) + 1;
+      const bishop2Pos = positions[randIndex_tmp];
+      positions.splice(Math.max(randIndex, randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex, randIndex_tmp), 1);
+
+      randIndex = randInt(6);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(4);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      const rook1Pos = positions[0];
+      const kingPos = positions[1];
+      const rook2Pos = positions[2];
+
+      pieces[c][rook1Pos] = "r";
+      pieces[c][knight1Pos] = "n";
+      pieces[c][bishop1Pos] = "b";
+      pieces[c][queenPos] = "q";
+      pieces[c][kingPos] = "k";
+      pieces[c][bishop2Pos] = "b";
+      pieces[c][knight2Pos] = "n";
+      pieces[c][rook2Pos] = "r";
+      if (c == 'b') pieces[c] = pieces[c].map(p => piecesMap[p]);
+      else flags = V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
+    }
+    // Add turn + flags + enpassant
+    return (
+      pieces["b"].join("") +
+      "/8/pppppppp/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 " + flags + " -"
+    );
+  }
+
+  getFlagsFen() {
+    return this.castleFlags['w'].map(V.CoordToColumn).join("");
+  }
+
+  setFlags(fenflags) {
+    this.castleFlags = { 'w': [-1, -1] };
+    for (let i = 0; i < 2; i++)
+      this.castleFlags['w'][i] = V.ColumnToCoord(fenflags.charAt(i));
+  }
+
+  static get LANCER() {
+    return 'l';
+  }
+  static get ARCHER() {
+    return 'a';
+  }
+  static get KHESHIG() {
+    return 'h';
+  }
+  static get YURT() {
+    return 'y';
+  }
+  // Khan is technically a King, so let's keep things simple.
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.LANCER, V.ARCHER, V.KHESHIG, V.YURT]);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    switch (this.getPiece(x, y)) {
+      case V.LANCER:
+        return this.getPotentialLancerMoves([x, y]);
+      case V.ARCHER:
+        return this.getPotentialArcherMoves([x, y]);
+      case V.KHESHIG:
+        return this.getPotentialKheshigMoves([x, y]);
+      case V.YURT:
+        return this.getPotentialYurtMoves([x, y]);
+      default:
+        return super.getPotentialMovesFrom([x, y]);
+    }
+    return [];
+  }
+
+  getSlideNJumpMoves([x, y], steps, oneStep, options) {
+    options = options || {};
+    let moves = [];
+    outerLoop: for (let step of steps) {
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        if (!options.onlyTake) moves.push(this.getBasicMove([x, y], [i, j]));
+        if (oneStep) continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !options.onlyMove)
+        moves.push(this.getBasicMove([x, y], [i, j]));
+    }
+    return moves;
+  }
+
+  getPotentialLancerMoves(sq) {
+    const onlyMoves = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.KNIGHT],
+      "oneStep",
+      { onlyMove: true }
+    );
+    const onlyTakes = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK],
+      null,
+      { onlyTake: true }
+    );
+    return onlyMoves.concat(onlyTakes);
+  }
+
+  getPotentialArcherMoves(sq) {
+    const onlyMoves = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.KNIGHT],
+      "oneStep",
+      { onlyMove: true }
+    );
+    const onlyTakes = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.BISHOP],
+      null,
+      { onlyTake: true }
+    );
+    return onlyMoves.concat(onlyTakes);
+  }
+
+  getPotentialLancerMoves(sq) {
+    const onlyMoves = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.KNIGHT],
+      "oneStep",
+      { onlyMove: true }
+    );
+    const onlyTakes = this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.ROOK],
+      null,
+      { onlyTake: true }
+    );
+    return onlyMoves.concat(onlyTakes);
+  }
+
+  getPotentialKheshigMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  getPotentialYurtMoves(sq) {
+    return this.getSlideNJumpMoves(
+      sq,
+      V.steps[V.BISHOP].concat([1, 0]),
+      "oneSTep"
+    );
+  }
+
+  getPotentialKingMoves([x, y]) {
+    if (this.getColor(x, y) == 'w') return super.getPotentialKingMoves([x, y]);
+    // Horde doesn't castle:
+    return this.getSlideNJumpMoves(
+      [x, y],
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  isAttacked(sq, color) {
+    if (color == 'w') {
+      return (
+        super.isAttacked(sq, color) ||
+        this.isAttackedByKheshig(sq, color)
+      );
+    }
+    // Horde: only pawn and queen (if promotions) in common:
+    return (
+      super.isAttackedByPawn(sq, color) ||
+      this.isAttackedByLancer(sq, color) ||
+      this.isAttackedByKheshig(sq, color) ||
+      this.isAttackedByArcher(sq, color) ||
+      this.isAttackedByYurt(sq, color) ||
+      super.isAttackedByQueen(sq, color)
+    );
+  }
+
+  isAttackedByLancer(sq, color) {
+    return this.isAttackedBySlideNJump(sq, color, V.LANCER, V.steps[V.ROOK]);
+  }
+
+  isAttackedByArcher(sq, color) {
+    return this.isAttackedBySlideNJump(sq, color, V.ARCHER, V.steps[V.BISHOP]);
+  }
+
+  isAttackedByKheshig(sq, color) {
+    return super.isAttackedBySlideNJump(
+      sq,
+      color,
+      V.KHESHIG,
+      V.steps[V.KNIGHT].concat(V.steps[V.ROOK]).concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  isAttackedByYurt(sq, color) {
+    return super.isAttackedBySlideNJump(
+      sq,
+      color,
+      V.YURT,
+      V.steps[V.BISHOP].concat([1, 0]),
+      "oneStep"
+    );
+  }
+
+  updateCastleFlags(move, piece) {
+    // Only white can castle:
+    const firstRank = 7;
+    if (piece == V.KING && move.appear[0].c == 'w')
+      this.castleFlags['w'] = [8, 8];
+    else if (
+      move.start.x == firstRank &&
+      this.castleFlags['w'].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags['w'][0] ? 0 : 1);
+      this.castleFlags['w'][flagIdx] = 8;
+    }
+    else if (
+      move.end.x == firstRank &&
+      this.castleFlags['w'].includes(move.end.y)
+    ) {
+      const flagIdx = (move.end.y == this.castleFlags['w'][0] ? 0 : 1);
+      this.castleFlags['w'][flagIdx] = 8;
+    }
+  }
+
+  getCurrentScore() {
+    // Turn has changed:
+    const color = V.GetOppCol(this.turn);
+    const lastRank = (color == 'w' ? 0 : 7);
+    if (this.kingPos[color][0] == lastRank)
+      // The opposing edge is reached!
+      return color == "w" ? "1-0" : "0-1";
+    if (this.atLeastOneMove()) return "*";
+    // Game over
+    const oppCol = this.turn;
+    if (!this.underCheck(oppCol)) return "1/2";
+    return (oppCol == "w" ? "0-1" : "1-0");
+  }
+
+  static get VALUES() {
+    return Object.assign(
+      {},
+      ChessRules.VALUES,
+      {
+        y: 2,
+        a: 4,
+        h: 7,
+        l: 4
+      }
+    );
+  }
+};