Experimental change: options replacing randomness (more general)
[vchess.git] / client / src / base_rules.js
index 2c2b705..43f9f26 100644 (file)
@@ -22,8 +22,8 @@ export const Move = class Move {
   constructor(o) {
     this.appear = o.appear;
     this.vanish = o.vanish;
-    this.start = o.start ? o.start : { x: o.vanish[0].x, y: o.vanish[0].y };
-    this.end = o.end ? o.end : { x: o.appear[0].x, y: o.appear[0].y };
+    this.start = o.start || { x: o.vanish[0].x, y: o.vanish[0].y };
+    this.end = o.end || { x: o.appear[0].x, y: o.appear[0].y };
   }
 };
 
@@ -34,6 +34,30 @@ export const ChessRules = class ChessRules {
   //////////////
   // MISC UTILS
 
+  static get Options() {
+    return {
+      select: [
+        {
+          label: "Randomness",
+          variable: "randomness",
+          defaut: 2,
+          options: [
+            { label: "Deterministic", value: 0 },
+            { label: "Symmetric random", value: 1 },
+            { label: "Asymmetric random", value: 2 }
+          ]
+        }
+      ],
+      check: []
+    };
+  }
+
+  static AbbreviateOptions(opts) {
+    return "";
+    // Randomness is a special option: (TODO?)
+    //return "R" + opts.randomness;
+  }
+
   // Some variants don't have flags:
   static get HasFlags() {
     return true;
@@ -112,6 +136,11 @@ export const ChessRules = class ChessRules {
     return false;
   }
 
+  // Some games are drawn unusually (bottom right corner is black)
+  static get DarkBottomRight() {
+    return false;
+  }
+
   // Some variants require lines drawing
   static get Lines() {
     if (V.Monochrome) {
@@ -130,6 +159,19 @@ export const ChessRules = class ChessRules {
   static get LoseOnRepetition() {
     return false;
   }
+  // And in some others (Iceage), repetitions should be ignored:
+  static get IgnoreRepetition() {
+    return false;
+  }
+  loseOnRepetition() {
+    // In some variants, result depends on the position:
+    return V.LoseOnRepetition;
+  }
+
+  // At some stages, some games could wait clicks only:
+  onlyClick() {
+    return false;
+  }
 
   // Some variants use click infos:
   doClick() {
@@ -153,7 +195,7 @@ export const ChessRules = class ChessRules {
 
   // Turn "p" into "bp" (for board)
   static fen2board(f) {
-    return f.charCodeAt() <= 90 ? "w" + f.toLowerCase() : "b" + f;
+    return f.charCodeAt(0) <= 90 ? "w" + f.toLowerCase() : "b" + f;
   }
 
   // Check if FEN describes a board situation correctly
@@ -192,7 +234,7 @@ export const ChessRules = class ChessRules {
         if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
         else {
           const num = parseInt(row[i], 10);
-          if (isNaN(num)) return false;
+          if (isNaN(num) || num <= 0) return false;
           sumElts += num;
         }
       }
@@ -270,7 +312,7 @@ export const ChessRules = class ChessRules {
 
   // En-passant square, if any
   getEpSquare(moveOrSquare) {
-    if (!moveOrSquare) return undefined;
+    if (!moveOrSquare) return undefined; //TODO: necessary line?!
     if (typeof moveOrSquare === "string") {
       const square = moveOrSquare;
       if (square == "-") return undefined;
@@ -325,8 +367,8 @@ export const ChessRules = class ChessRules {
   // FEN UTILS
 
   // Setup the initial random (asymmetric) position
-  static GenRandInitFen(randomness) {
-    if (randomness == 0)
+  static GenRandInitFen(options) {
+    if (!options.randomness || options.randomness == 0)
       // Deterministic:
       return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -";
 
@@ -334,7 +376,7 @@ export const ChessRules = class ChessRules {
     let flags = "";
     // Shuffle pieces on first (and last rank if randomness == 2)
     for (let c of ["w", "b"]) {
-      if (c == 'b' && randomness == 1) {
+      if (c == 'b' && options.randomness == 1) {
         pieces['b'] = pieces['w'];
         flags += flags;
         break;
@@ -432,8 +474,10 @@ export const ChessRules = class ChessRules {
       // if more than 9 consecutive free spaces, break the integer,
       // otherwise FEN parsing will fail.
       if (count <= 9) return count;
-      // Currently only boards of size up to 11 or 12:
-      return "9" + (count - 9);
+      // Most boards of size < 18:
+      if (count <= 18) return "9" + (count - 9);
+      // Except Gomoku:
+      return "99" + (count - 18);
     };
     let position = "";
     for (let i = 0; i < V.size.x; i++) {
@@ -523,11 +567,11 @@ export const ChessRules = class ChessRules {
   }
 
   // Scan board for kings positions
+  // TODO: should be done from board, no need for the complete FEN
   scanKings(fen) {
     // Squares of white and black king:
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
-    const startRow = { 'w': V.size.x - 1, 'b': 0 };
     for (let i = 0; i < fenRows.length; i++) {
       let k = 0; //column index on board
       for (let j = 0; j < fenRows[i].length; j++) {
@@ -648,22 +692,16 @@ export const ChessRules = class ChessRules {
   // MOVES GENERATION
 
   // All possible moves from selected square
-  getPotentialMovesFrom([x, y]) {
-    switch (this.getPiece(x, y)) {
-      case V.PAWN:
-        return this.getPotentialPawnMoves([x, y]);
-      case V.ROOK:
-        return this.getPotentialRookMoves([x, y]);
-      case V.KNIGHT:
-        return this.getPotentialKnightMoves([x, y]);
-      case V.BISHOP:
-        return this.getPotentialBishopMoves([x, y]);
-      case V.QUEEN:
-        return this.getPotentialQueenMoves([x, y]);
-      case V.KING:
-        return this.getPotentialKingMoves([x, y]);
+  getPotentialMovesFrom(sq) {
+    switch (this.getPiece(sq[0], sq[1])) {
+      case V.PAWN: return this.getPotentialPawnMoves(sq);
+      case V.ROOK: return this.getPotentialRookMoves(sq);
+      case V.KNIGHT: return this.getPotentialKnightMoves(sq);
+      case V.BISHOP: return this.getPotentialBishopMoves(sq);
+      case V.QUEEN: return this.getPotentialQueenMoves(sq);
+      case V.KING: return this.getPotentialKingMoves(sq);
     }
-    return []; //never reached
+    return []; //never reached (but some variants may use it: Bario...)
   }
 
   // Build a regular move from its initial and destination squares.
@@ -676,8 +714,8 @@ export const ChessRules = class ChessRules {
         new PiPo({
           x: ex,
           y: ey,
-          c: tr ? tr.c : initColor,
-          p: tr ? tr.p : initPiece
+          c: !!tr ? tr.c : initColor,
+          p: !!tr ? tr.p : initPiece
         })
       ],
       vanish: [
@@ -714,7 +752,7 @@ export const ChessRules = class ChessRules {
       let j = y + step[1];
       while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
         moves.push(this.getBasicMove([x, y], [i, j]));
-        if (oneStep) continue outerLoop;
+        if (!!oneStep) continue outerLoop;
         i += step[0];
         j += step[1];
       }
@@ -755,9 +793,8 @@ export const ChessRules = class ChessRules {
       if (!!promotions) finalPieces = promotions;
       else if (!!V.PawnSpecs.promotions) finalPieces = V.PawnSpecs.promotions;
     }
-    let tr = null;
     for (let piece of finalPieces) {
-      tr = (piece != V.PAWN ? { c: color, p: piece } : null);
+      const tr = (piece != V.PAWN ? { c: color, p: piece } : null);
       moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
     }
   }
@@ -893,7 +930,6 @@ export const ChessRules = class ChessRules {
     // Castling ?
     const oppCol = V.GetOppCol(c);
     let moves = [];
-    let i = 0;
     // King, then rook:
     finalSquares = finalSquares || [ [2, 3], [V.size.y - 2, V.size.y - 3] ];
     const castlingKing = this.board[x][y].charAt(1);
@@ -920,7 +956,7 @@ export const ChessRules = class ChessRules {
       // Nothing on the path of the king ? (and no checks)
       const finDist = finalSquares[castleSide][0] - y;
       let step = finDist / Math.max(1, Math.abs(finDist));
-      i = y;
+      let i = y;
       do {
         if (
           (!castleInCheck && this.isAttacked([x, i], oppCol)) ||
@@ -1032,6 +1068,9 @@ export const ChessRules = class ChessRules {
 
   // Stop at the first move found
   // TODO: not really, it explores all moves from a square (one is enough).
+  // Possible fix: add extra arg "oneMove" to getPotentialMovesFrom,
+  // and then return only boolean true at first move found
+  // (in all getPotentialXXXMoves() ... for all variants ...)
   atLeastOneMove() {
     const color = this.turn;
     for (let i = 0; i < V.size.x; i++) {
@@ -1072,6 +1111,7 @@ export const ChessRules = class ChessRules {
       }
       if (
         V.OnBoard(rx, ry) &&
+        this.board[rx][ry] != V.EMPTY &&
         this.getPiece(rx, ry) == piece &&
         this.getColor(rx, ry) == color
       ) {
@@ -1173,6 +1213,7 @@ export const ChessRules = class ChessRules {
   }
 
   updateCastleFlags(move, piece, color) {
+    // TODO: check flags. If already off, no need to always re-evaluate
     const c = color || V.GetOppCol(this.turn);
     const firstRank = (c == "w" ? V.size.x - 1 : 0);
     // Update castling flags if rooks are moved