From d621e620e7b568df94c53611f6c71ab318f4ffe3 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 31 May 2022 19:09:18 +0200
Subject: [PATCH] First draft of Hex game

---
 app.js                 |  29 ++++++--
 base_rules.js          |  51 +++++++------
 common.css             |   2 +
 variants/Hex/class.js  | 158 +++++++++++++++++++++++++++++++++++++++--
 variants/Hex/style.css |   3 +
 5 files changed, 208 insertions(+), 35 deletions(-)
 create mode 100644 variants/Hex/style.css

diff --git a/app.js b/app.js
index 6036d49..17fe770 100644
--- a/app.js
+++ b/app.js
@@ -130,7 +130,9 @@ function toggleStyle(event, obj) {
 let options;
 function prepareOptions() {
   options = {};
-  let optHtml = V.Options.select.map(select => { return `
+  let optHtml = "";
+  if (V.Options.select) {
+    optHtml += V.Options.select.map(select => { return `
       <div class="option-select">
         <label for="var_${select.variable}">${select.label}</label>
         <div class="select">
@@ -147,9 +149,10 @@ function prepareOptions() {
           <span class="focus"></span>
         </div>
       </div>`;
-  }).join("");
-  optHtml += V.Options.check.map(check => {
-    return `
+    }).join("");
+  }
+  if (V.Options.check) {
+    optHtml += V.Options.check.map(check => { return `
       <div class="option-check">
         <label class="checkbox">
           <input id="var_${check.variable}"
@@ -158,8 +161,22 @@ function prepareOptions() {
           <span>${check.label}</span>
         </label>
       </div>`;
-  }).join("");
-  if (V.Options.styles.length >= 1) {
+    }).join("");
+  }
+  if (V.Options.input) {
+    optHtml += V.Options.input.map(input => { return `
+      <div class="option-input">
+        <label class="input">
+          <input id="var_${input.variable}"
+                 type="${input.type}"
+                 content="${input.defaut}"/>
+          <span class="spacer"></span>
+          <span>${input.label}</span>
+        </label>
+      </div>`;
+    }).join("");
+  }
+  if (V.Options.styles) {
     optHtml += '<div class="words">';
     let i = 0;
     const stylesLength = V.Options.styles.length;
diff --git a/base_rules.js b/base_rules.js
index 75545c2..20ff25e 100644
--- a/base_rules.js
+++ b/base_rules.js
@@ -290,19 +290,20 @@ export default class ChessRules {
     return fen;
   }
 
+  static FenEmptySquares(count) {
+    // if more than 9 consecutive free spaces, break the integer,
+    // otherwise FEN parsing will fail.
+    if (count <= 9)
+      return count;
+    // Most boards of size < 18:
+    if (count <= 18)
+      return "9" + (count - 9);
+    // Except Gomoku:
+    return "99" + (count - 18);
+  }
+
   // Position part of the FEN string
   getPosition() {
-    const format = (count) => {
-      // if more than 9 consecutive free spaces, break the integer,
-      // otherwise FEN parsing will fail.
-      if (count <= 9)
-        return count;
-      // 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 < this.size.y; i++) {
       let emptyCount = 0;
@@ -312,7 +313,7 @@ export default class ChessRules {
         else {
           if (emptyCount > 0) {
             // Add empty squares in-between
-            position += format(emptyCount);
+            position += C.FenEmptySquares(emptyCount);
             emptyCount = 0;
           }
           position += this.board2fen(this.board[i][j]);
@@ -320,7 +321,7 @@ export default class ChessRules {
       }
       if (emptyCount > 0)
         // "Flush remainder"
-        position += format(emptyCount);
+        position += C.FenEmptySquares(emptyCount);
       if (i < this.size.y - 1)
         position += "/"; //separate rows
     }
@@ -789,16 +790,20 @@ export default class ChessRules {
     chessboard.style.top = newY + "px";
     const newR = {x: newX, y: newY, width: newWidth, height: newHeight};
     const pieceWidth = this.getPieceWidth(newWidth);
-    for (let i=0; i < this.size.x; i++) {
-      for (let j=0; j < this.size.y; j++) {
-        if (this.g_pieces[i][j]) {
-          // NOTE: could also use CSS transform "scale"
-          this.g_pieces[i][j].style.width = pieceWidth + "px";
-          this.g_pieces[i][j].style.height = pieceWidth + "px";
-          const [ip, jp] = this.getPixelPosition(i, j, newR);
-          // Translate coordinates to use chessboard as reference:
-          this.g_pieces[i][j].style.transform =
-            `translate(${ip - newX}px,${jp - newY}px)`;
+    // NOTE: next "if" for variants which use squares filling
+    // instead of "physical", moving pieces
+    if (this.g_pieces) {
+      for (let i=0; i < this.size.x; i++) {
+        for (let j=0; j < this.size.y; j++) {
+          if (this.g_pieces[i][j]) {
+            // NOTE: could also use CSS transform "scale"
+            this.g_pieces[i][j].style.width = pieceWidth + "px";
+            this.g_pieces[i][j].style.height = pieceWidth + "px";
+            const [ip, jp] = this.getPixelPosition(i, j, newR);
+            // Translate coordinates to use chessboard as reference:
+            this.g_pieces[i][j].style.transform =
+              `translate(${ip - newX}px,${jp - newY}px)`;
+          }
         }
       }
     }
diff --git a/common.css b/common.css
index 6f8472c..7096ff8 100644
--- a/common.css
+++ b/common.css
@@ -303,12 +303,14 @@ piece.hidden {
   height: 100%;
 }
 
+/* Default squares colors (can be overriden or unused) */
 .dark-square {
   fill: #b58863;
 }
 .light-square {
   fill: #f0d9b5;
 }
+
 .in-shadow {
   filter: brightness(50%);
 }
diff --git a/variants/Hex/class.js b/variants/Hex/class.js
index f1e3a83..ec14a06 100644
--- a/variants/Hex/class.js
+++ b/variants/Hex/class.js
@@ -1,5 +1,78 @@
+// https://www.boardspace.net/hex/english/Rules%20-%20HexWiki.htm
+export default class HexRules extends ChessRules {
+
+  static get Options() {
+    return {
+      input: [
+        {
+          label: "Board size",
+          type: "number",
+          defaut: 11,
+          variable: "bsize"
+        }
+      ],
+      check: [
+        {
+          label: "Swap",
+          defaut: true,
+          variable: "swap"
+        }
+      ]
+    };
+  }
+
+  get hasReserve() {
+    return false;
+  }
+
+  get noAnimate() {
+    return true;
+  }
+
+  doClick(coords) {
+    if (
+      this.board[coords.x][coords.y] != "" &&
+      (!this.swap || this.movesCount >= 2)
+    ) {
+      return null;
+    }
+    let res = new Move({
+      start: {x: coords.x, y: coords.y},
+      appear: [
+        new PiPo({
+          x: coords.x,
+          y: coords.y,
+          c: this.turn,
+          p: 'p'
+        })
+      ],
+      vanish: []
+    });
+    if (this.board[coords.x][coords.y] != "") {
+      res.vanish.push(
+        new PiPo({
+          x: coords.x,
+          y: coords.y,
+          c: C.GetOppCol(this.turn),
+          p: 'p'
+        })
+      );
+    }
+    return res;
+  }
+
+  genRandInitFen() {
+    // NOTE: size.x == size.y (square boards)
+    const emptyCount = C.FenEmptySquares(this.size.x.repeat);
+    return (emptyCount + "/").repeat(this.size.x).slice(0, -1);
+  }
+
+  getPieceWidth(rwidth) {
+    return (rwidth / this.size.y); //TODO
+  }
+
+  // TODO
   getSvgChessboard() {
-    const flipped = (this.playerColor == 'b');
     let board = `
       <svg
         width="2771.2px" height="1700px"
@@ -16,7 +89,7 @@
       for (let j=0; j < this.size.y; j++) {
         let classes = this.getSquareColorClass(i, j);
         board += `<rect
-          class="${classes}"
+          class="neutral-square"
           id="${this.coordsToId([i, j])}"
           width="10"
           height="10"
@@ -28,9 +101,82 @@
     return board;
   }
 
-// neutral-light neutral-dark --> specify per variant in CSS file
-  getSquareColorClass(i, j) {
-    return ((i+j) % 2 == 0 ? "light-square": "dark-square");
+  setupPieces() {
+    // TODO: just scan board and get IDs, and addClass "bg-white" or "bg-black"
+  }
+
+  // TODO (NOTE: no flip here, always same view)
+  getPixelPosition(i, j, r) {
+    if (i < 0 || j < 0)
+      return [0, 0]; //piece vanishes
+    let x, y;
+    const sqSize = r.width / this.size.y;
+    const flipped = (this.playerColor == 'b');
+    const x = (flipped ? this.size.y - 1 - j : j) * sqSize,
+          y = (flipped ? this.size.x - 1 - i : i) * sqSize;
+    return [r.x + x, r.y + y];
+  }
+
+  initMouseEvents() {
+    const mousedown = (e) => {
+      if (e.touches && e.touches.length > 1)
+        e.preventDefault();
+      const cd = this.idToCoords(e.target.id);
+      if (cd) {
+        const move = this.doClick(cd);
+        if (move)
+          this.playPlusVisual(move);
+      }
+    };
+
+    if ('onmousedown' in window)
+      document.addEventListener("mousedown", mousedown);
+    if ('ontouchstart' in window)
+      document.addEventListener("touchstart", mousedown, {passive: false});
+  }
+
+  get size() {
+    return {
+      x: this.bsize,
+      y: this.bsize,
+      ratio: 1.630118
+    };
+  }
+
+  pieces() {
+    return {
+      'p': {
+        "class": "pawn",
+      }
+    };
+  }
+
+  play(move) {
+    super.playOnBoard(move);
+  }
+
+  // TODO:
+  getCurrentScore(move) {
+    const oppCol = C.GetOppCol(this.turn);
+    // Search for connecting path of opp color: TODO
+    // ...
+    if (path found)
+      return (oppCol == "w" ? "1-0" : "0-1");
+    return "*";
+  }
+
+  playVisual(move) {
+    move.vanish.forEach(v => {
+// TODO: just get ID, and remClass "bg-white" or "bg-black" (in CSS: TODO)
+    });
+    move.appear.forEach(a => {
+// TODO: just get ID, and addClass "bg-white" or "bg-black" (in CSS: TODO)
+//      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");
+//      this.g_pieces[a.x][a.y].style.width = pieceWidth + "px";
+//      this.g_pieces[a.x][a.y].style.height = pieceWidth + "px";
+    });
   }
 
-// TODO: generalize base_rules.js to not assume size.x == width and size.y == height (not true here).
+};
diff --git a/variants/Hex/style.css b/variants/Hex/style.css
new file mode 100644
index 0000000..91ea8fd
--- /dev/null
+++ b/variants/Hex/style.css
@@ -0,0 +1,3 @@
+.neutral-square {
+  fill: #eaeded;
+}
-- 
2.44.0