Cleaner fen generation + first draft of Apocalypse + a few fixes
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 1 Jul 2022 09:12:01 +0000 (11:12 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 1 Jul 2022 09:12:01 +0000 (11:12 +0200)
14 files changed:
TODO
base_rules.js
variants/Alapo/class.js
variants/Align4/class.js
variants/Ambiguous/class.js
variants/Antiking1/class.js
variants/Antiking2/class.js
variants/Apocalypse/class.js [new file with mode: 0644]
variants/Chakart/class.js
variants/Giveaway/class.js
variants/Hex/class.js
variants/Refusal/class.js
variants/Suction/class.js
variants/_Antiking/class.js

diff --git a/TODO b/TODO
index 1e4eda3..c2ab9af 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,3 +1,5 @@
+Animate castle by both movements (and generalize? match vanishes with appears?)
+
 add variants :
 Dark Racing Kings ? Checkered-Teleport ?
 
index b98c724..beac0bb 100644 (file)
@@ -197,8 +197,19 @@ export default class ChessRules {
     return (f.charCodeAt(0) <= 90 ? "w" + f.toLowerCase() : "b" + f);
   }
 
-  // Setup the initial random-or-not (asymmetric-or-not) position
   genRandInitFen(seed) {
+    Random.setSeed(seed); //may be unused
+    let baseFen = this.genRandInitBaseFen();
+    baseFen.o = Object.assign({init: true}, baseFen.o);
+    const parts = this.getPartFen(baseFen.o);
+    return (
+      baseFen.fen +
+      (Object.keys(parts).length > 0 ? (" " + JSON.stringify(parts)) : "")
+    );
+  }
+
+  // Setup the initial random-or-not (asymmetric-or-not) position
+  genRandInitBaseFen() {
     let fen, flags = "0707";
     if (!this.options.randomness)
       // Deterministic:
@@ -206,7 +217,6 @@ export default class ChessRules {
 
     else {
       // Randomize
-      Random.setSeed(seed);
       let pieces = {w: new Array(8), b: new Array(8)};
       flags = "";
       // Shuffle pieces on first (and last rank if randomness == 2)
@@ -216,9 +226,7 @@ export default class ChessRules {
           flags += flags;
           break;
         }
-
         let positions = ArrayFun.range(8);
-
         // Get random squares for bishops
         let randIndex = 2 * Random.randInt(4);
         const bishop1Pos = positions[randIndex];
@@ -228,7 +236,6 @@ export default class ChessRules {
         // Remove chosen squares
         positions.splice(Math.max(randIndex, randIndex_tmp), 1);
         positions.splice(Math.min(randIndex, randIndex_tmp), 1);
-
         // Get random squares for knights
         randIndex = Random.randInt(6);
         const knight1Pos = positions[randIndex];
@@ -236,18 +243,15 @@ export default class ChessRules {
         randIndex = Random.randInt(5);
         const knight2Pos = positions[randIndex];
         positions.splice(randIndex, 1);
-
         // Get random square for queen
         randIndex = Random.randInt(4);
         const queenPos = positions[randIndex];
         positions.splice(randIndex, 1);
-
         // Rooks and king positions are now fixed,
         // because of the ordering rook-king-rook
         const rook1Pos = positions[0];
         const kingPos = positions[1];
         const rook2Pos = positions[2];
-
         // Finally put the shuffled pieces in the board array
         pieces[c][rook1Pos] = "r";
         pieces[c][knight1Pos] = "n";
@@ -266,19 +270,7 @@ export default class ChessRules {
         " w 0"
       );
     }
-    // Add turn + flags + enpassant (+ reserve)
-    let parts = [];
-    if (this.hasFlags)
-      parts.push(`"flags":"${flags}"`);
-    if (this.hasEnpassant)
-      parts.push('"enpassant":"-"');
-    if (this.hasReserveFen)
-      parts.push('"reserve":"000000000000"');
-    if (this.options["crazyhouse"])
-      parts.push('"ispawn":"-"');
-    if (parts.length >= 1)
-      fen += " {" + parts.join(",") + "}";
-    return fen;
+    return { fen: fen, o: {flags: flags} };
   }
 
   // "Parse" FEN: just return untransformed string data
@@ -296,23 +288,28 @@ export default class ChessRules {
 
   // Return current fen (game state)
   getFen() {
-    let fen = (
-      this.getPosition() + " " +
-      this.getTurnFen() + " " +
-      this.movesCount
+    const parts = this.getPartFen({});
+    return (
+      this.getBaseFen() +
+      (Object.keys(parts).length > 0 ? (" " + JSON.stringify(parts)) : "")
     );
-    let parts = [];
+  }
+
+  getBaseFen() {
+    return this.getPosition() + " " + this.turn + " " + this.movesCount;
+  }
+
+  getPartFen(o) {
+    let parts = {};
     if (this.hasFlags)
-      parts.push(`"flags":"${this.getFlagsFen()}"`);
+      parts["flags"] = o.init ? o.flags : this.getFlagsFen();
     if (this.hasEnpassant)
-      parts.push(`"enpassant":"${this.getEnpassantFen()}"`);
+      parts["enpassant"] = o.init ? "-" : this.getEnpassantFen();
     if (this.hasReserveFen)
-      parts.push(`"reserve":"${this.getReserveFen()}"`);
+      parts["reserve"] = this.getReserveFen(o);
     if (this.options["crazyhouse"])
-      parts.push(`"ispawn":"${this.getIspawnFen()}"`);
-    if (parts.length >= 1)
-      fen += " {" + parts.join(",") + "}";
-    return fen;
+      parts["ispawn"] = this.getIspawnFen(o);
+    return parts;
   }
 
   static FenEmptySquares(count) {
@@ -353,10 +350,6 @@ export default class ChessRules {
     return position;
   }
 
-  getTurnFen() {
-    return this.turn;
-  }
-
   // Flags part of the FEN string
   getFlagsFen() {
     return ["w", "b"].map(c => {
@@ -367,17 +360,22 @@ export default class ChessRules {
   // Enpassant part of the FEN string
   getEnpassantFen() {
     if (!this.epSquare)
-      return "-"; //no en-passant
+      return "-";
     return C.CoordsToSquare(this.epSquare);
   }
 
-  getReserveFen() {
+  getReserveFen(o) {
+    if (o.init)
+      return "000000000000";
     return (
       ["w","b"].map(c => Object.values(this.reserve[c]).join("")).join("")
     );
   }
 
-  getIspawnFen() {
+  getIspawnFen(o) {
+    if (o.init)
+      // NOTE: cannot merge because this.ispawn doesn't exist yet
+      return "-";
     const squares = Object.keys(this.ispawn);
     if (squares.length == 0)
       return "-";
@@ -405,18 +403,18 @@ export default class ChessRules {
     if (o.genFenOnly)
       // This object will be used only for initial FEN generation
       return;
+
+    // Some variables
     this.playerColor = o.color;
     this.afterPlay = o.afterPlay; //trigger some actions after playing a move
+    this.containerId = o.element;
+    this.isDiagram = o.diagram;
+    this.marks = o.marks;
 
-    // Fen string fully describes the game state
+    // Initializations
     if (!o.fen)
       o.fen = this.genRandInitFen(o.seed);
     this.re_initFromFen(o.fen);
-
-    // Graphical (can use variables defined above)
-    this.containerId = o.element;
-    this.isDiagram = o.diagram;
-    this.marks = o.marks;
     this.graphicalInit();
   }
 
index 4836413..1c7c048 100644 (file)
@@ -28,64 +28,58 @@ export default class AlapoRules extends ChessRules {
     return board;
   }
 
-  genRandInitFen(seed) {
+  genRandInitBaseFen() {
+    let fen = "";
     if (this.options["randomness"] == 0)
-      return "rbqqbr/tcssct/6/6/TCSSCT/RBQQBR w 0";
-
-    Random.setSeed(seed);
-
-    const piece2pawn = {
-      r: 't',
-      q: 's',
-      b: 'c'
-    };
-
-    let pieces = { w: new Array(6), b: new Array(6) };
-    // Shuffle pieces on first (and last rank if randomness == 2)
-    for (let c of ["w", "b"]) {
-      if (c == 'b' && this.options["randomness"] == 1) {
-        pieces['b'] = pieces['w'];
-        break;
+      fen = "rbqqbr/tcssct/6/6/TCSSCT/RBQQBR w 0";
+    else {
+      const piece2pawn = {
+        r: 't',
+        q: 's',
+        b: 'c'
+      };
+      let pieces = { w: new Array(6), b: new Array(6) };
+      // Shuffle pieces on first (and last rank if randomness == 2)
+      for (let c of ["w", "b"]) {
+        if (c == 'b' && this.options["randomness"] == 1) {
+          pieces['b'] = pieces['w'];
+          break;
+        }
+        let positions = ArrayFun.range(6);
+        // Get random squares for bishops
+        let randIndex = 2 * Random.randInt(3);
+        const bishop1Pos = positions[randIndex];
+        let randIndex_tmp = 2 * Random.randInt(3) + 1;
+        const bishop2Pos = positions[randIndex_tmp];
+        positions.splice(Math.max(randIndex, randIndex_tmp), 1);
+        positions.splice(Math.min(randIndex, randIndex_tmp), 1);
+        // Get random square for queens
+        randIndex = Random.randInt(4);
+        const queen1Pos = positions[randIndex];
+        positions.splice(randIndex, 1);
+        randIndex = Random.randInt(3);
+        const queen2Pos = positions[randIndex];
+        positions.splice(randIndex, 1);
+        // Rooks positions are now fixed,
+        const rook1Pos = positions[0];
+        const rook2Pos = positions[1];
+        pieces[c][rook1Pos] = "r";
+        pieces[c][bishop1Pos] = "b";
+        pieces[c][queen1Pos] = "q";
+        pieces[c][queen2Pos] = "q";
+        pieces[c][bishop2Pos] = "b";
+        pieces[c][rook2Pos] = "r";
       }
-
-      let positions = ArrayFun.range(6);
-
-      // Get random squares for bishops
-      let randIndex = 2 * Random.randInt(3);
-      const bishop1Pos = positions[randIndex];
-      let randIndex_tmp = 2 * Random.randInt(3) + 1;
-      const bishop2Pos = positions[randIndex_tmp];
-      positions.splice(Math.max(randIndex, randIndex_tmp), 1);
-      positions.splice(Math.min(randIndex, randIndex_tmp), 1);
-
-      // Get random square for queens
-      randIndex = Random.randInt(4);
-      const queen1Pos = positions[randIndex];
-      positions.splice(randIndex, 1);
-      randIndex = Random.randInt(3);
-      const queen2Pos = positions[randIndex];
-      positions.splice(randIndex, 1);
-
-      // Rooks positions are now fixed,
-      const rook1Pos = positions[0];
-      const rook2Pos = positions[1];
-
-      pieces[c][rook1Pos] = "r";
-      pieces[c][bishop1Pos] = "b";
-      pieces[c][queen1Pos] = "q";
-      pieces[c][queen2Pos] = "q";
-      pieces[c][bishop2Pos] = "b";
-      pieces[c][rook2Pos] = "r";
+      fen = (
+        pieces["b"].join("") + "/" +
+        pieces["b"].map(p => piece2pawn[p]).join("") +
+        "/6/6/" +
+        pieces["w"].map(p => piece2pawn[p].toUpperCase()).join("") + "/" +
+        pieces["w"].join("").toUpperCase() +
+        " w 0"
+      );
     }
-
-    return (
-      pieces["b"].join("") + "/" +
-      pieces["b"].map(p => piece2pawn[p]).join("") +
-      "/6/6/" +
-      pieces["w"].map(p => piece2pawn[p].toUpperCase()).join("") + "/" +
-      pieces["w"].join("").toUpperCase() +
-      " w 0"
-    );
+    return { fen: fen, o: {} };
   }
 
   // Triangles are rotated from opponent viewpoint (=> suffix "_inv")
index f603c51..4c191db 100644 (file)
@@ -24,13 +24,15 @@ export default class Align4Rules extends ChessRules {
     return false;
   }
 
-  genRandInitFen(seed) {
-    const baseFen = super.genRandInitFen(seed);
-    const fen = baseFen.replace("rnbqkbnr/pppppppp", "4k3/8");
-    const fenParts = fen.split(" ");
-    let others = JSON.parse(fenParts[3]);
-    others["flags"] = others["flags"].substr(0, 2) + "88";
-    return fenParts.slice(0, 3).join(" ") + " " + JSON.stringify(others);
+  genRandInitBaseFen() {
+    let baseFen = super.genRandInitBaseFen();
+    return { fen: baseFen.fen.replace("rnbqkbnr/pppppppp", "4k3/8"), o: {} };
+  }
+
+  getPartFen(o) {
+    let parts = super.getPartFen(o);
+    parts["flags"] = parts["flags"].substr(0, 2) + "88";
+    return parts;
   }
 
   initReserves() {
index db24d0e..4311c4d 100644 (file)
@@ -22,10 +22,10 @@ export default class AmbiguousRules extends ChessRules {
       this.subTurn = 1;
   }
 
-  genRandInitFen(seed) {
+  genRandInitBaseFen() {
     const options = Object.assign({mode: "suicide"}, this.options);
     const gr = new GiveawayRules({options: options, genFenOnly: true});
-    return gr.genRandInitFen(seed);
+    return gr.genRandInitBaseFen();
   }
 
   canStepOver(x, y) {
@@ -57,15 +57,17 @@ export default class AmbiguousRules extends ChessRules {
         .map(m => {
           if (m.vanish.length == 1)
             m.appear[0].p = V.GOAL;
-          else
+          else {
             m.appear[0].p = V.TARGET_CODE[m.vanish[1].p];
+            m.appear[0].c = m.vanish[1].c;
+          }
           m.vanish.shift();
           return m;
         })
       );
     }
     // At subTurn == 1, play a targeted move for the opponent.
-    // Search for target (we could also have it in a stack...)
+    // Search for target:
     let target = {x: -1, y: -1};
     outerLoop: for (let i = 0; i < this.size.x; i++) {
       for (let j = 0; j < this.size.y; j++) {
@@ -142,8 +144,10 @@ export default class AmbiguousRules extends ChessRules {
     return moves;
   }
 
-  isKing(symbol) {
-    return ['k', 'l'].includes(symbol);
+  isKing(x, y, p) {
+    if (!p)
+      p = this.getPiece(x, y);
+    return ['k', 'l'].includes(p);
   }
 
   getCurrentScore() {
index 9b8af78..9d981e8 100644 (file)
@@ -28,20 +28,20 @@ export default class Antiking1Rules extends AbstractAntikingRules {
     return res;
   }
 
-  genRandInitFen() {
+  genRandInitBaseFen() {
     // Always deterministic setup
-    return (
-      '2prbkqA/2p1nnbr/2pppppp/8/8/PPPPPP2/RBNN1P2/aQKBRP2 w 0 ' +
-      '{"flags":"KAka"}'
-    );
+    return {
+      fen: "2prbkqA/2p1nnbr/2pppppp/8/8/PPPPPP2/RBNN1P2/aQKBRP2 w 0",
+      o: {"flags": "KAka"}
+    };
   }
 
   // (Anti)King flags at 1 (true) if they can knight-jump
-  setFlags(fenFlags) {
+  setFlags(fenflags) {
     this.kingFlags = { w: {}, b: {} };
-    for (let i=0; i<fenFlags.length; i++) {
-      const white = fenFlags.charCodeAt(i) <= 90;
-      const curChar = fenFlags.charAt(i).toLowerCase();
+    for (let i=0; i<fenflags.length; i++) {
+      const white = fenflags.charCodeAt(i) <= 90;
+      const curChar = fenflags.charAt(i).toLowerCase();
       this.kingFlags[white ? 'w' : 'b'][curChar] = true;
     }
   }
@@ -49,7 +49,10 @@ export default class Antiking1Rules extends AbstractAntikingRules {
   getFlagsFen() {
     return (
       Array.prototype.concat.apply(
-        ['w', 'b'].map(c => Object.keys(this.kingFlags[c]))
+        ['w', 'b'].map(c => {
+          const res = Object.keys(this.kingFlags[c]).join("");
+          return (c == 'w' ? res.toUpperCase() : res);
+        })
       ).join("")
     );
   }
index ee1030a..55c9229 100644 (file)
@@ -11,8 +11,8 @@ export default class Antiking2Rules extends AbstractAntikingRules {
     };
   }
 
-  genRandInitFen(seed) {
-    const baseFen = super.genRandInitFen(seed);
+  genRandInitBaseFen() {
+    const baseFen = super.genRandInitBaseFen();
     // Just add an antiking on 3rd ranks
     let akPos = [3, 3];
     if (this.options.randomness >= 1) {
@@ -31,10 +31,23 @@ export default class Antiking2Rules extends AbstractAntikingRules {
           : "")
       );
     };
-    return (
-      baseFen.replace("p/8", "p/" + antikingLine('b'))
-             .replace("8/P", antikingLine('w') + "/P")
-    );
+    return {
+      fen: baseFen.fen.replace("p/8", "p/" + antikingLine('b'))
+                      .replace("8/P", antikingLine('w') + "/P"),
+      o: baseFen.o
+    };
+  }
+
+  getCastleMoves([x, y]) {
+    if (this.getPiece(x, y) == 'a')
+      return [];
+    return super.getCastleMoves([x, y]);
+  }
+
+  updateCastleFlags(move) {
+    if (move.vanish.length > 0 && move.vanish[0].p == 'a')
+      return;
+    super.updateCastleFlags(move);
   }
 
 };
diff --git a/variants/Apocalypse/class.js b/variants/Apocalypse/class.js
new file mode 100644 (file)
index 0000000..e91cf81
--- /dev/null
@@ -0,0 +1,199 @@
+import ChessRules from "/base_rules.js";
+
+export class ApocalypseRules extends ChessRules {
+
+  static get Options() {
+    return {};
+  }
+
+  get pawnPromotions() {
+    return ['n', 'p'];
+  }
+
+  get size() {
+    return {x: 5, y: 5};
+  }
+
+  genRandInitBaseFen() {
+    return {
+      fen: "npppn/p3p/5/P3P/NPPPN w 0",
+      o: {"flags":"00"}
+    };
+  }
+
+  getPartFen(o) {
+    let parts = super.getPartFen(o);
+    parts["whiteMove"] = this.whiteMove || "-";
+    return parts;
+  }
+
+  getFlagsFen() {
+    return (
+      this.penaltyFlags['w'].toString() + this.penaltyFlags['b'].toString()
+    );
+  }
+
+  setOtherVariables(fen) {
+    const parsedFen = V.ParseFen(fen);
+    this.setFlags(parsedFen.flags);
+    // Also init whiteMove
+    this.whiteMove =
+      parsedFen.whiteMove != "-"
+        ? JSON.parse(parsedFen.whiteMove)
+        : null;
+  }
+
+  setFlags(fenflags) {
+    this.penaltyFlags = {
+      'w': parseInt(fenflags[0], 10),
+      'b': parseInt(fenflags[1], 10)
+    };
+  }
+
+  // TODO: could be a stack of 2 (pawn promote + relocation)
+  getWhitemoveFen() {
+    if (!this.whiteMove) return "-";
+    return JSON.stringify({
+      start: this.whiteMove.start,
+      end: this.whiteMove.end,
+      appear: this.whiteMove.appear,
+      vanish: this.whiteMove.vanish
+    });
+  }
+
+  // allow pawns to move diagonally and capture vertically --> only purpose illegal
+  // allow capture self --> same purpose
+  // ---> MARK such moves : move.illegal = true
+
+  // simpler: allow all moves, including "capturing nothing"
+  // flag every pawn capture as "illegal" (potentially)
+
+  pieces(color, x, y) {
+    const pawnShift = (color == "w" ? -1 : 1);
+    return {
+      'p': {
+        "class": "pawn",
+        moves: [
+          {
+            steps: [[pawnShift, 0], [pawnShift, -1], [pawnShift, 1]],
+            range: 1
+          }
+        ],
+      },
+      'n': super.pieces(color, x, y)['n']
+    };
+  }
+
+  canTake() {
+    return true;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = [];
+    if (this.subTurn == 2) {
+      const start = this.moveStack[0].end;
+      if (x == start.x && y == start.y) {
+        // Move the pawn to any empty square not on last rank (== x)
+        for (let i=0; i<this.size.x; i++) {
+          if (i == x)
+            continue;
+          for (let j=0; j<this.size.y; j++) {
+            if (this.board[i][j] == "")
+              moves.push(this.getBasicMove([x, y], [i, j]));
+          }
+        }
+      }
+    }
+    else {
+      moves = super.getPotentialMovesFrom([x, y])
+      // Flag a priori illegal moves
+      moves.forEach(m => {
+        if (
+          // Self-capture test:
+          (m.vanish.length == 2 && m.vanish[1].c == m.vanish[0].c) ||
+          // Pawn going diagonaly to empty square, or vertically to occupied
+          (
+            m.vanish[0].p == 'p' &&
+            (
+              (m.end.y == m.start.y && m.vanish.length == 2) ||
+              (m.end.y != m.start.y && m.vanish.length == 1)
+            )
+          )
+        ) {
+          m.illegal = true;
+        }
+      });
+    }
+    return moves;
+  }
+
+  filterValid(moves) {
+    // No checks:
+    return moves;
+  }
+
+  // White and black (partial) moves were played: merge
+  // + animate both at the same time !
+  resolveSynchroneMove(move) {
+    // TODO
+  }
+
+  play(move) {
+    // Do not play on board (would reveal the move...)
+    this.turn = V.GetOppCol(this.turn);
+    this.movesCount++;
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    if (pawn promotion into pawn) {
+      this.curMove move; //TODO: animate both move at same time + effects AFTER !
+      this.subTurn = 2
+    }
+    else if (this.turn == 'b')
+      // NOTE: whiteMove is used read-only, so no need to copy
+      this.whiteMove = move;
+    }
+    else {
+      // A full turn just ended:
+      const smove = this.resolveSynchroneMove(move);
+      V.PlayOnBoard(this.board, smove); //----> ici : animate both !
+      this.whiteMove = null;
+    }
+  }
+
+  atLeastOneLegalMove(...) {
+    // TODO
+  }
+
+  getCurrentScore() {
+    if (this.turn == 'b')
+      // Turn (white + black) not over yet. Could be stalemate if black cannot move (legally)
+      // TODO: check. If so, return "1/2".
+      return "*";
+    // Count footmen: if a side has none, it loses
+    let fmCount = { 'w': 0, 'b': 0 };
+    for (let i=0; i<5; i++) {
+      for (let j=0; j<5; j++) {
+        if (this.board[i][j] != V.EMPTY && this.getPiece(i, j) == V.PAWN)
+          fmCount[this.getColor(i, j)]++;
+      }
+    }
+    if (Object.values(fmCount).some(v => v == 0)) {
+      if (fmCount['w'] == 0 && fmCount['b'] == 0)
+        // Everyone died
+        return "1/2";
+      if (fmCount['w'] == 0) return "0-1";
+      return "1-0"; //fmCount['b'] == 0
+    }
+    // Check penaltyFlags: if a side has 2 or more, it loses
+    if (Object.values(this.penaltyFlags).every(v => v == 2)) return "1/2";
+    if (this.penaltyFlags['w'] == 2) return "0-1";
+    if (this.penaltyFlags['b'] == 2) return "1-0";
+    if (!this.atLeastOneLegalMove('w') || !this.atLeastOneLegalMove('b'))
+      // Stalemate (should be very rare)
+      return "1/2";
+    return "*";
+  }
+
+};
index 02dd16e..815be74 100644 (file)
@@ -131,15 +131,12 @@ export default class ChakartRules extends ChessRules {
     );
   }
 
-  genRandInitFen(seed) {
+  genRandInitBaseFen() {
     const options = Object.assign({mode: "suicide"}, this.options);
     const gr = new GiveawayRules({options: options, genFenOnly: true});
-    const baseFen = gr.genRandInitFen(seed);
-    const fenParts = baseFen.split(" ");
-    let others = JSON.parse(fenParts[3]);
-    delete others["enpassant"];
-    others["flags"] = "1111"; //Peach + Mario flags
-    return fenParts.slice(0, 3).join(" ") + " " + JSON.stringify(others);
+    let res = gr.genRandInitBaseFen();
+    res.o["flags"] = "1111"; //Peach + Mario flags
+    return res;
   }
 
   fen2board(f) {
index 69addf2..15a8998 100644 (file)
@@ -36,47 +36,44 @@ export default class GiveawayRules extends ChessRules {
     return res;
   }
 
-  genRandInitFen(seed) {
+  genRandInitBaseFen() {
     if (this.options["mode"] == "losers")
-      return super.genRandInitFen(seed);
+      return super.genRandInitBaseFen();
 
-    if (this.options["randomness"] == 0) {
-      return (
-        'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 {"enpassant":"-"}'
-      );
-    }
-
-    Random.setSeed(seed);
-    let pieces = { w: new Array(8), b: new Array(8) };
-    for (let c of ["w", "b"]) {
-      if (c == 'b' && this.options["randomness"] == 1) {
-        pieces['b'] = pieces['w'];
-        break;
-      }
-
-      // Get random squares for every piece, totally freely
-      let positions = Random.shuffle(ArrayFun.range(8));
-      const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
-      const rem2 = positions[0] % 2;
-      if (rem2 == positions[1] % 2) {
-        // Fix bishops (on different colors)
-        for (let i=2; i<8; i++) {
-          if (positions[i] % 2 != rem2) {
-            [positions[1], positions[i]] = [positions[i], positions[1]];
-            break;
+    let fen = "";
+    if (this.options["randomness"] == 0)
+      fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0";
+    else {
+      let pieces = { w: new Array(8), b: new Array(8) };
+      for (let c of ["w", "b"]) {
+        if (c == 'b' && this.options["randomness"] == 1) {
+          pieces['b'] = pieces['w'];
+          break;
+        }
+        // Get random squares for every piece, totally freely
+        let positions = Random.shuffle(ArrayFun.range(8));
+        const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
+        const rem2 = positions[0] % 2;
+        if (rem2 == positions[1] % 2) {
+          // Fix bishops (on different colors)
+          for (let i=2; i<8; i++) {
+            if (positions[i] % 2 != rem2) {
+              [positions[1], positions[i]] = [positions[i], positions[1]];
+              break;
+            }
           }
         }
+        for (let i = 0; i < 8; i++)
+          pieces[c][positions[i]] = composition[i];
       }
-      for (let i = 0; i < 8; i++)
-        pieces[c][positions[i]] = composition[i];
+      fen = (
+        pieces["b"].join("") +
+        "/pppppppp/8/8/8/8/PPPPPPPP/" +
+        pieces["w"].join("").toUpperCase() +
+        " w 0"
+      );
     }
-    return (
-      pieces["b"].join("") +
-      "/pppppppp/8/8/8/8/PPPPPPPP/" +
-      pieces["w"].join("").toUpperCase() +
-      // En-passant allowed, but no flags
-      ' w 0 {"enpassant":"-"}'
-    );
+    return { fen: fen, o: {} };
   }
 
   constructor(o) {
@@ -91,7 +88,8 @@ export default class GiveawayRules extends ChessRules {
   }
 
   getCurrentScore() {
-    if (this.atLeastOneMove()) return "*";
+    if (this.atLeastOneMove(this.turn))
+      return "*";
     // No valid move: the side who cannot move wins
     return (this.turn == "w" ? "1-0" : "0-1");
   }
index 80d4501..0797c91 100644 (file)
@@ -74,10 +74,13 @@ export default class HexRules extends ChessRules {
     return res;
   }
 
-  genRandInitFen() {
+  genRandInitBaseFen() {
     // NOTE: size.x == size.y (square boards)
     const emptyCount = C.FenEmptySquares(this.size.x);
-    return (emptyCount + "/").repeat(this.size.x).slice(0, -1) + " w 0";
+    return {
+      fen: (emptyCount + "/").repeat(this.size.x).slice(0, -1) + " w 0",
+      o: {}
+    };
   }
 
   getSvgChessboard() {
@@ -166,7 +169,7 @@ export default class HexRules extends ChessRules {
   get size() {
     const baseRatio = 1.6191907514450865; //2801.2 / 1730, "widescreen"
     const rc =
-      document.getElementById(this.containerid).getBoundingClientRect();
+      document.getElementById(this.containerId).getBoundingClientRect();
     const rotate = rc.width < rc.height; //"vertical screen"
     return {
       x: this.options["bsize"],
index 2cd9ca6..04301d7 100644 (file)
@@ -21,14 +21,10 @@ export default class RefusalRules extends ChessRules {
     return false;
   }
 
-  genRandInitFen(seed) {
-    return super.genRandInitFen(seed).slice(0, -1)  + ',"lastmove":"null"}';
-  }
-
-  getFen() {
-    return (
-      super.getFen().slice(0, -1) + ',"lastmove":"' +
-        JSON.stringify(this.lastMove) + '"}');
+  getPartFen(o) {
+    let parts = super.getPartFen(o);
+    parts["lastmove"] = o.init ? null : this.lastMove;
+    return parts;
   }
 
   setOtherVariables(fenParsed) {
@@ -119,10 +115,10 @@ export default class RefusalRules extends ChessRules {
     super.postPlay(move);
   }
 
-  atLeastOneMove() {
+  atLeastOneMove(color) {
     if (!this.lastMove.noRef)
       return true;
-    return super.atLeastOneMove();
+    return super.atLeastOneMove(color);
   }
 
 };
index cf5ae16..889a37f 100644 (file)
@@ -41,22 +41,19 @@ export default class SuctionRules extends ChessRules {
     }
   }
 
-  genRandInitFen(seed) {
+  genRandInitBaseFen() {
     const options = Object.assign({mode: "suicide"}, this.options);
     const gr = new GiveawayRules({options: options, genFenOnly: true});
-    const baseFen = gr.genRandInitFen(seed);
-    // Add empty cmove:
-    const fenParts = baseFen.split(" ");
-    let others = JSON.parse(fenParts[3]);
-    others["cmove"] = "-";
-    return fenParts.slice(0, 3).join(" ") + " " + JSON.stringify(others);
+    return gr.genRandInitBaseFen();
   }
 
-  getFen() {
-    const cmoveFen = !this.cmove
+  getPartFen(o) {
+    let parts = super.getPartFen(o);
+    const cmoveFen = o.init || !this.cmove
       ? "-"
       : C.CoordsToSquare(this.cmove.start) + C.CoordsToSquare(this.cmove.end);
-    return super.getFen().slice(0, -1) + ',"cmove":"' + cmoveFen + '"}';
+    parts["cmove"] = cmoveFen;
+    return parts;
   }
 
   getBasicMove([sx, sy], [ex, ey]) {
index 8e2173a..941bd82 100644 (file)
@@ -55,9 +55,11 @@ export default class AbstractAntikingRules extends ChessRules {
       m.vanish.length == 1 || m.vanish[1].p != 'a');
   }
 
-  underCheck(squares, color) {
+  underCheck(square_s, color) {
     let res = false;
-    squares.forEach(sq => {
+    if (!Array.isArray(square_s[0]))
+      square_s = [square_s];
+    square_s.forEach(sq => {
       switch (this.getPiece(sq[0], sq[1])) {
         case 'k':
           res ||= super.underAttack(sq, color);