Fix (a lot but maybe not all of) Chakart
[xogo.git] / base_rules.js
index 20ff25e..8ad0894 100644 (file)
@@ -29,16 +29,18 @@ export default class ChessRules {
           {label: "Asymmetric random", value: 2}
         ]
       }],
-      check: [
+      input: [
         {
           label: "Capture king",
-          defaut: false,
-          variable: "taking"
+          variable: "taking",
+          type: "checkbox",
+          defaut: false
         },
         {
           label: "Falling pawn",
-          defaut: false,
-          variable: "pawnfall"
+          variable: "pawnfall",
+          type: "checkbox",
+          defaut: false
         }
       ],
       // Game modifiers (using "elementary variants"). Default: false
@@ -85,6 +87,10 @@ export default class ChessRules {
       (!!this.options["recycle"] && !this.options["teleport"])
     );
   }
+  // Some variants do not store reserve state (Align4, Chakart...)
+  get hasReserveFen() {
+    return this.hasReserve;
+  }
 
   get noAnimate() {
     return !!this.options["dark"];
@@ -173,8 +179,6 @@ export default class ChessRules {
 
   // Setup the initial random-or-not (asymmetric-or-not) position
   genRandInitFen(seed) {
-    Random.setSeed(seed);
-
     let fen, flags = "0707";
     if (!this.options.randomness)
       // Deterministic:
@@ -182,7 +186,8 @@ export default class ChessRules {
 
     else {
       // Randomize
-      let pieces = { w: new Array(8), b: new Array(8) };
+      Random.setSeed(seed);
+      let pieces = {w: new Array(8), b: new Array(8)};
       flags = "";
       // Shuffle pieces on first (and last rank if randomness == 2)
       for (let c of ["w", "b"]) {
@@ -281,7 +286,7 @@ export default class ChessRules {
       parts.push(`"flags":"${this.getFlagsFen()}"`);
     if (this.hasEnpassant)
       parts.push(`"enpassant":"${this.getEnpassantFen()}"`);
-    if (this.hasReserve)
+    if (this.hasReserveFen)
       parts.push(`"reserve":"${this.getReserveFen()}"`);
     if (this.options["crazyhouse"])
       parts.push(`"ispawn":"${this.getIspawnFen()}"`);
@@ -372,6 +377,14 @@ export default class ChessRules {
 
   constructor(o) {
     this.options = o.options;
+    // Fill missing options (always the case if random challenge)
+    (V.Options.select || []).concat(V.Options.input || []).forEach(opt => {
+      if (this.options[opt.variable] === undefined)
+        this.options[opt.variable] = opt.defaut;
+    });
+    if (o.genFenOnly)
+      // This object will be used only for initial FEN generation
+      return;
     this.playerColor = o.color;
     this.afterPlay = o.afterPlay; //trigger some actions after playing a move
 
@@ -521,6 +534,22 @@ export default class ChessRules {
     return `${this.containerId}|rnum-${color}${piece}`;
   }
 
+  static AddClass_es(piece, class_es) {
+    if (!Array.isArray(class_es))
+      class_es = [class_es];
+    class_es.forEach(cl => {
+      piece.classList.add(cl);
+    });
+  }
+
+  static RemoveClass_es(piece, class_es) {
+    if (!Array.isArray(class_es))
+      class_es = [class_es];
+    class_es.forEach(cl => {
+      piece.classList.remove(cl);
+    });
+  }
+
   graphicalInit() {
     // NOTE: not window.onresize = this.re_drawBoardElts because scope (this)
     window.onresize = () => this.re_drawBoardElements();
@@ -528,7 +557,6 @@ export default class ChessRules {
     this.initMouseEvents();
     const chessboard =
       document.getElementById(this.containerId).querySelector(".chessboard");
-    new ResizeObserver(this.rescale).observe(chessboard);
   }
 
   re_drawBoardElements() {
@@ -583,8 +611,7 @@ export default class ChessRules {
     let board = `
       <svg
         viewBox="0 0 80 80"
-        class="chessboard_SVG">
-      <g>`;
+        class="chessboard_SVG">`;
     for (let i=0; i < this.size.x; i++) {
       for (let j=0; j < this.size.y; j++) {
         const ii = (flipped ? this.size.x - 1 - i : i);
@@ -593,16 +620,18 @@ export default class ChessRules {
         if (this.enlightened && !this.enlightened[ii][jj])
           classes += " in-shadow";
         // NOTE: x / y reversed because coordinates system is reversed.
-        board += `<rect
-          class="${classes}"
-          id="${this.coordsToId({x: ii, y: jj})}"
-          width="10"
-          height="10"
-          x="${10*j}"
-          y="${10*i}" />`;
+        board += `
+          <rect
+            class="${classes}"
+            id="${this.coordsToId({x: ii, y: jj})}"
+            width="10"
+            height="10"
+            x="${10*j}"
+            y="${10*i}"
+          />`;
       }
     }
-    board += "</g></svg>";
+    board += "</svg>";
     return board;
   }
 
@@ -636,7 +665,7 @@ export default class ChessRules {
           const color = this.getColor(i, j);
           const piece = this.getPiece(i, j);
           this.g_pieces[i][j] = document.createElement("piece");
-          this.g_pieces[i][j].classList.add(this.pieces()[piece]["class"]);
+          C.AddClass_es(this.g_pieces[i][j], this.pieces()[piece]["class"]);
           this.g_pieces[i][j].classList.add(C.GetColorClass(color));
           this.g_pieces[i][j].style.width = pieceWidth + "px";
           this.g_pieces[i][j].style.height = pieceWidth + "px";
@@ -654,24 +683,17 @@ export default class ChessRules {
       this.re_drawReserve(['w', 'b'], r);
   }
 
-  // NOTE: assume !!this.reserve
+  // NOTE: assume this.reserve != null
   re_drawReserve(colors, r) {
     if (this.r_pieces) {
       // Remove (old) reserve pieces
       for (let c of colors) {
-        if (!this.reserve[c])
-          continue;
-        Object.keys(this.reserve[c]).forEach(p => {
-          if (this.r_pieces[c][p]) {
-            this.r_pieces[c][p].remove();
-            delete this.r_pieces[c][p];
-            const numId = this.getReserveNumId(c, p);
-            document.getElementById(numId).remove();
-          }
+        Object.keys(this.r_pieces[c]).forEach(p => {
+          this.r_pieces[c][p].remove();
+          delete this.r_pieces[c][p];
+          const numId = this.getReserveNumId(c, p);
+          document.getElementById(numId).remove();
         });
-        let reservesDiv = document.getElementById("reserves_" + c);
-        if (reservesDiv)
-          reservesDiv.remove();
       }
     }
     else
@@ -680,6 +702,9 @@ export default class ChessRules {
     if (!r)
       r = container.querySelector(".chessboard").getBoundingClientRect();
     for (let c of colors) {
+      let reservesDiv = document.getElementById("reserves_" + c);
+      if (reservesDiv)
+        reservesDiv.remove();
       if (!this.reserve[c])
         continue;
       const nbR = this.getNbReservePieces(c);
@@ -708,8 +733,7 @@ export default class ChessRules {
         r_cell.style.height = sqResSize + "px";
         rcontainer.appendChild(r_cell);
         let piece = document.createElement("piece");
-        const pieceSpec = this.pieces()[p];
-        piece.classList.add(pieceSpec["class"]);
+        C.AddClass_es(piece, this.pieces()[p]["class"]);
         piece.classList.add(C.GetColorClass(c));
         piece.style.width = "100%";
         piece.style.height = "100%";
@@ -766,24 +790,24 @@ export default class ChessRules {
     }
   }
 
-  // After resize event: no need to destroy/recreate pieces
-  rescale() {
-    const container = document.getElementById(this.containerId);
-    if (!container)
-      return; //useful at initial loading
-    let chessboard = container.querySelector(".chessboard");
+  // Resize board: no need to destroy/recreate pieces
+  rescale(mode) {
+    let chessboard =
+      document.getElementById(this.containerId).querySelector(".chessboard");
     const r = chessboard.getBoundingClientRect();
-    const newRatio = r.width / r.height;
-    let newWidth = r.width,
-        newHeight = r.height;
-    if (newRatio > this.size.ratio) {
-      newWidth = r.height * this.size.ratio;
-      chessboard.style.width = newWidth + "px";
-    }
-    else if (newRatio < this.size.ratio) {
-      newHeight = r.width / this.size.ratio;
-      chessboard.style.height = newHeight + "px";
-    }
+    const multFact = (mode == "up" ? 1.05 : 0.95);
+    let [newWidth, newHeight] = [multFact * r.width, multFact * r.height];
+    // Stay in window:
+    if (newWidth > window.innerWidth) {
+      newWidth = window.innerWidth;
+      newHeight = newWidth / this.size.ratio;
+    }
+    if (newHeight > window.innerHeight) {
+      newHeight = window.innerHeight;
+      newWidth = newHeight * this.size.ratio;
+    }
+    chessboard.style.width = newWidth + "px";
+    chessboard.style.height = newHeight + "px";
     const newX = (window.innerWidth - newWidth) / 2;
     chessboard.style.left = newX + "px";
     const newY = (window.innerHeight - newHeight) / 2;
@@ -938,11 +962,6 @@ export default class ChessRules {
     };
 
     const mouseup = (e) => {
-      const newR = chessboard.getBoundingClientRect();
-      if (newR.width != r.width || newR.height != r.height) {
-        this.rescale();
-        return;
-      }
       if (!start)
         return;
       const [x, y] = [start.x, start.y];
@@ -971,6 +990,8 @@ export default class ChessRules {
       document.addEventListener("mousedown", mousedown);
       document.addEventListener("mousemove", mousemove);
       document.addEventListener("mouseup", mouseup);
+      document.addEventListener("wheel",
+        (e) => this.rescale(e.deltaY < 0 ? "up" : "down"));
     }
     if ('ontouchstart' in window) {
       // https://stackoverflow.com/a/42509310/12660887
@@ -1011,8 +1032,8 @@ export default class ChessRules {
       choice.style.backgroundColor = "lightyellow";
       choice.onclick = () => callback(moves[i]);
       const piece = document.createElement("piece");
-      const pieceSpec = this.pieces()[moves[i].appear[0].p];
-      piece.classList.add(pieceSpec["class"]);
+      const cdisp = moves[i].choice || moves[i].appear[0].p;
+      C.AddClass_es(piece, this.pieces()[cdisp]["class"]);
       piece.classList.add(C.GetColorClass(color));
       piece.style.width = "100%";
       piece.style.height = "100%";
@@ -1040,7 +1061,11 @@ export default class ChessRules {
   }
 
   static GetColorClass(c) {
-    return (c == 'w' ? "white" : "black");
+    if (c == 'w')
+      return "white";
+    if (c == 'b')
+      return "black";
+    return "other-color"; //unidentified color
   }
 
   // Assume square i,j isn't empty
@@ -1475,6 +1500,11 @@ export default class ChessRules {
     return false;
   }
 
+  canStepOver(i, j) {
+    // In some variants, objects on boards don't stop movement (Chakart)
+    return this.board[i][j] == "";
+  }
+
   // Generic method to find possible moves of "sliding or jumping" pieces
   getPotentialMovesOf(piece, [x, y]) {
     const color = this.getColor(x, y);
@@ -1503,7 +1533,7 @@ export default class ChessRules {
           let stepCounter = 0;
           while (
             this.onBoard(i, j) &&
-            (this.board[i][j] == "" || (i == x && j == y))
+            (this.canStepOver(i, j) || (i == x && j == y))
           ) {
             if (
               type != "attack" &&
@@ -2144,9 +2174,7 @@ export default class ChessRules {
 
   playVisual(move, r) {
     move.vanish.forEach(v => {
-      // TODO: next "if" shouldn't be required
-      if (this.g_pieces[v.x][v.y])
-        this.g_pieces[v.x][v.y].remove();
+      this.g_pieces[v.x][v.y].remove();
       this.g_pieces[v.x][v.y] = null;
     });
     let chessboard =
@@ -2156,8 +2184,8 @@ export default class ChessRules {
     const pieceWidth = this.getPieceWidth(r.width);
     move.appear.forEach(a => {
       this.g_pieces[a.x][a.y] = document.createElement("piece");
-      this.g_pieces[a.x][a.y].classList.add(this.pieces()[a.p]["class"]);
-      this.g_pieces[a.x][a.y].classList.add(a.c == "w" ? "white" : "black");
+      C.AddClass_es(this.g_pieces[a.x][a.y], this.pieces()[a.p]["class"]);
+      this.g_pieces[a.x][a.y].classList.add(C.GetColorClass(a.c));
       this.g_pieces[a.x][a.y].style.width = pieceWidth + "px";
       this.g_pieces[a.x][a.y].style.height = pieceWidth + "px";
       const [ip, jp] = this.getPixelPosition(a.x, a.y, r);
@@ -2193,10 +2221,6 @@ export default class ChessRules {
       return;
     }
     let initPiece = this.getDomPiece(move.start.x, move.start.y);
-    if (!initPiece) { //TODO this shouldn't be required
-      callback();
-      return;
-    }
     // NOTE: cloning generally not required, but light enough, and simpler
     let movingPiece = initPiece.cloneNode();
     initPiece.style.opacity = "0";
@@ -2213,8 +2237,8 @@ export default class ChessRules {
     const pieces = this.pieces();
     if (move.drag) {
       const startCode = this.getPiece(move.start.x, move.start.y);
-      movingPiece.classList.remove(pieces[startCode]["class"]);
-      movingPiece.classList.add(pieces[move.drag.p]["class"]);
+      C.RemoveClass_es(movingPiece, pieces[startCode]["class"]);
+      C.AddClass_es(movingPiece, pieces[move.drag.p]["class"]);
       const apparentColor = this.getColor(move.start.x, move.start.y);
       if (apparentColor != move.drag.c) {
         movingPiece.classList.remove(C.GetColorClass(apparentColor));