Fix memory leak of moves hash on server side. Draft Align4
[xogo.git] / base_rules.js
index 080b02a..a48bd3d 100644 (file)
@@ -534,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();
@@ -553,15 +569,16 @@ export default class ChessRules {
     // Compare window ratio width / height to aspectRatio:
     const windowRatio = window.innerWidth / window.innerHeight;
     let cbWidth, cbHeight;
-    if (windowRatio <= this.size.ratio) {
+    const vRatio = this.size.ratio || 1;
+    if (windowRatio <= vRatio) {
       // Limiting dimension is width:
       cbWidth = Math.min(window.innerWidth, 767);
-      cbHeight = cbWidth / this.size.ratio;
+      cbHeight = cbWidth / vRatio;
     }
     else {
       // Limiting dimension is height:
       cbHeight = Math.min(window.innerHeight, 767);
-      cbWidth = cbHeight * this.size.ratio;
+      cbWidth = cbHeight * vRatio;
     }
     if (this.hasReserve) {
       const sqSize = cbWidth / this.size.y;
@@ -569,7 +586,7 @@ export default class ChessRules {
       // Cannot use getReserveSquareSize() here, but sqSize is an upper bound.
       if ((window.innerHeight - cbHeight) / 2 < sqSize + 5) {
         cbHeight = window.innerHeight - 2 * (sqSize + 5);
-        cbWidth = cbHeight * this.size.ratio;
+        cbWidth = cbHeight * vRatio;
       }
     }
     chessboard.style.width = cbWidth + "px";
@@ -594,7 +611,7 @@ export default class ChessRules {
     const flipped = (this.playerColor == 'b');
     let board = `
       <svg
-        viewBox="0 0 80 80"
+        viewBox="0 0 ${10*this.size.y} ${10*this.size.x}"
         class="chessboard_SVG">`;
     for (let i=0; i < this.size.x; i++) {
       for (let j=0; j < this.size.y; j++) {
@@ -649,7 +666,8 @@ 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(color, i, j)[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";
@@ -717,8 +735,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(c, c, p)[p]["class"]);
         piece.classList.add(C.GetColorClass(c));
         piece.style.width = "100%";
         piece.style.height = "100%";
@@ -783,13 +800,14 @@ export default class ChessRules {
     const multFact = (mode == "up" ? 1.05 : 0.95);
     let [newWidth, newHeight] = [multFact * r.width, multFact * r.height];
     // Stay in window:
+    const vRatio = this.size.ratio || 1;
     if (newWidth > window.innerWidth) {
       newWidth = window.innerWidth;
-      newHeight = newWidth / this.size.ratio;
+      newHeight = newWidth / vRatio;
     }
     if (newHeight > window.innerHeight) {
       newHeight = window.innerHeight;
-      newWidth = newHeight * this.size.ratio;
+      newWidth = newHeight * vRatio;
     }
     chessboard.style.width = newWidth + "px";
     chessboard.style.height = newHeight + "px";
@@ -992,6 +1010,8 @@ export default class ChessRules {
     let chessboard = container.querySelector(".chessboard");
     let choices = document.createElement("div");
     choices.id = "choices";
+    if (!r)
+      r = chessboard.getBoundingClientRect();
     choices.style.width = r.width + "px";
     choices.style.height = r.height + "px";
     choices.style.left = r.x + "px";
@@ -1017,8 +1037,9 @@ 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(color, moves[i].end.x, moves[i].end.y)[cdisp]["class"]);
       piece.classList.add(C.GetColorClass(color));
       piece.style.width = "100%";
       piece.style.height = "100%";
@@ -1034,7 +1055,7 @@ export default class ChessRules {
     return {
       x: 8,
       y: 8,
-      ratio: 1 //for rectangular board = y / x
+      ratio: 1 //for rectangular board = y / x (optional, 1 = default)
     };
   }
 
@@ -1061,8 +1082,9 @@ export default class ChessRules {
   }
 
   // Piece type on square (i,j)
-  getPieceType(i, j) {
-    const p = this.getPiece(i, j);
+  getPieceType(i, j, p) {
+    if (!p)
+      p = this.getPiece(i, j);
     return C.CannibalKings[p] || p; //a cannibal king move as...
   }
 
@@ -1071,7 +1093,7 @@ export default class ChessRules {
     return (color == "w" ? "b" : "w");
   }
 
-  // Can thing on square1 capture (no return) thing on square2?
+  // Can thing on square1 capture (enemy) thing on square2?
   canTake([x1, y1], [x2, y2]) {
     return (this.getColor(x1, y1) !== this.getColor(x2, y2));
   }
@@ -1193,6 +1215,8 @@ export default class ChessRules {
         if (this.board[i][j] != "" && this.getColor(i, j) == color) {
           const allSpecs = this.pieces(color, i, j)
           let specs = allSpecs[this.getPieceType(i, j)];
+          if (specs.moveas)
+            specs = allSpecs[specs.moveas];
           const attacks = specs.attack || specs.moves;
           for (let a of attacks) {
             outerLoop: for (let step of a.steps) {
@@ -1461,7 +1485,10 @@ export default class ChessRules {
     const color = this.getColor(x, y);
     const oppCol = C.GetOppCol(color);
     const piece = this.getPieceType(x, y); //ok not cannibal king
-    const stepSpec = this.pieces(color, x, y)[piece];
+    const allSpecs = this.pieces(color, x, y);
+    let stepSpec = allSpecs[piece];
+    if (stepSpec.moveas)
+      stepSpec = allSpecs[stepSpec.moveas];
     const attacks = stepSpec.attack || stepSpec.moves;
     for (let a of attacks) {
       outerLoop: for (let step of a.steps) {
@@ -1485,10 +1512,19 @@ export default class ChessRules {
     return false;
   }
 
+  canStepOver(i, j, p) {
+    // 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);
-    const stepSpec = this.pieces(color, x, y)[piece];
+    const apparentPiece = this.getPiece(x, y); //how it looks
+    const allSpecs = this.pieces(color, x, y);
+    let stepSpec = allSpecs[piece];
+    if (stepSpec.moveas)
+      stepSpec = allSpecs[stepSpec.moveas];
     let moves = [];
     // Next 3 for Cylinder mode:
     let explored = {};
@@ -1513,7 +1549,7 @@ export default class ChessRules {
           let stepCounter = 0;
           while (
             this.onBoard(i, j) &&
-            (this.board[i][j] == "" || (i == x && j == y))
+            ((i == x && j == y) || this.canStepOver(i, j, apparentPiece))
           ) {
             if (
               type != "attack" &&
@@ -1584,8 +1620,14 @@ export default class ChessRules {
         ) {
           if (args.zen && this.isKing(this.getPiece(i, j)))
             continue; //king not captured in this way
-          const stepSpec =
-            this.pieces(args.oppCol, i, j)[this.getPieceType(i, j)];
+          const apparentPiece = this.getPiece(i, j);
+          // Quick check: does this potential attacker target x,y ?
+          if (this.canStepOver(x, y, apparentPiece))
+            continue;
+          const allSpecs = this.pieces(args.oppCol, i, j);
+          let stepSpec = allSpecs[this.getPieceType(i, j)];
+          if (stepSpec.moveas)
+            stepSpec = allSpecs[stepSpec.moveas];
           const attacks = stepSpec.attack || stepSpec.moves;
           for (let a of attacks) {
             for (let s of a.steps) {
@@ -1728,8 +1770,15 @@ export default class ChessRules {
       s.y == e.y &&
       Math.abs(s.x - e.x) == 2 &&
       // Next conditions for variants like Atomic or Rifle, Recycle...
-      (move.appear.length > 0 && move.appear[0].p == "p") &&
-      (move.vanish.length > 0 && move.vanish[0].p == "p")
+      (
+        move.appear.length > 0 &&
+        this.getPieceType(0, 0, move.appear[0].p) == "p"
+      )
+      &&
+      (
+        move.vanish.length > 0 &&
+        this.getPieceType(0, 0, move.vanish[0].p) == "p"
+      )
     ) {
       return {
         x: (s.x + e.x) / 2,
@@ -1886,10 +1935,12 @@ export default class ChessRules {
     return [-1, -1]; //king not found
   }
 
-  filterValid(moves) {
+  // Some variants (e.g. Refusal) may need to check opponent moves too
+  filterValid(moves, color) {
     if (moves.length == 0)
       return [];
-    const color = this.turn;
+    if (!color)
+      color = this.turn;
     const oppCol = C.GetOppCol(color);
     if (this.options["balance"] && [1, 3].includes(this.movesCount)) {
       // Forbid moves either giving check or exploding opponent's king:
@@ -1917,12 +1968,12 @@ export default class ChessRules {
         let square = kingPos,
             res = true; //a priori valid
         if (m.vanish.some(v => {
-          return C.CannibalKings[v.p] && v.c == color;
+          return this.isKing(v.p) && v.c == color;
         })) {
           // Search king in appear array:
           const newKingIdx =
             m.appear.findIndex(a => {
-              return C.CannibalKings[a.p] && a.c == color;
+              return this.isKing(a.p) && a.c == color;
             });
           if (newKingIdx >= 0)
             square = [m.appear[newKingIdx].x, m.appear[newKingIdx].y];
@@ -2164,7 +2215,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"]);
+      C.AddClass_es(this.g_pieces[a.x][a.y],
+                    this.pieces(a.c, a.x, a.y)[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";
@@ -2186,9 +2238,9 @@ export default class ChessRules {
     this.afterPlay(move); //user method
   }
 
-  getMaxDistance(rwidth) {
+  getMaxDistance(r) {
     // Works for all rectangular boards:
-    return Math.sqrt(rwidth ** 2 + (rwidth / this.size.ratio) ** 2);
+    return Math.sqrt(r.width ** 2 + r.height ** 2);
   }
 
   getDomPiece(x, y) {
@@ -2213,13 +2265,13 @@ export default class ChessRules {
       movingPiece.style.width = pieceWidth + "px";
       movingPiece.style.height = pieceWidth + "px";
     }
-    const maxDist = this.getMaxDistance(r.width);
-    const pieces = this.pieces();
+    const maxDist = this.getMaxDistance(r);
+    const apparentColor = this.getColor(move.start.x, move.start.y);
+    const pieces = this.pieces(apparentColor, move.start.x, move.start.y);
     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"]);
-      const apparentColor = this.getColor(move.start.x, move.start.y);
+      C.RemoveClass_es(movingPiece, pieces[startCode]["class"]);
+      C.AddClass_es(movingPiece, pieces[move.drag.p]["class"]);
       if (apparentColor != move.drag.c) {
         movingPiece.classList.remove(C.GetColorClass(apparentColor));
         movingPiece.classList.add(C.GetColorClass(move.drag.c));