Fix Atarigo + Gomoku, prepare Emergo
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 19 Jan 2021 20:35:55 +0000 (21:35 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 19 Jan 2021 20:35:55 +0000 (21:35 +0100)
client/README.md
client/public/images/pieces/Emergo/.gitignore [new file with mode: 0644]
client/public/images/pieces/Emergo/generateSVG_composite.py [new file with mode: 0755]
client/public/images/pieces/Emergo/generateSVG_simple.py [new file with mode: 0755]
client/src/variants/Atarigo.js
client/src/variants/Gomoku.js

index 6c657db..2c1caab 100644 (file)
@@ -6,6 +6,9 @@ Rename and edit src/parameters.js.dist into parameters.js. Then,
 download the folders and files listed in download\_objects file.
 (This is optional, most variants will work without it).
 
+For Emergo and Avalam, run generateSVG\*.py scripts in folder
+public/images/pieces/Emergo and Avalam (pieces SVGs).
+
 Finally install dependencies.
 ```
 npm install
diff --git a/client/public/images/pieces/Emergo/.gitignore b/client/public/images/pieces/Emergo/.gitignore
new file mode 100644 (file)
index 0000000..5e3b89f
--- /dev/null
@@ -0,0 +1,3 @@
+*
+!generate*.py
+!.gitignore
diff --git a/client/public/images/pieces/Emergo/generateSVG_composite.py b/client/public/images/pieces/Emergo/generateSVG_composite.py
new file mode 100755 (executable)
index 0000000..87eb55f
--- /dev/null
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+# Compose each piece SVG with numbers
+# https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
+# https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths
+
+preamble = """<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">
+<defs>
+  <mask id="stripe">
+    <rect x="0" y="0" width="230" height="230" fill="white"/>
+    <rect x="130" y="0" width="90" height="230"/>
+  </mask>
+</defs>"""
+
+black_left = '<circle cx="115" cy="115" r="100" fill="black" stroke="orange" mask="url(#stripe)"/>'
+white_right = '<circle cx="115" cy="115" r="100" fill="whitesmoke"/>'
+white_left = '<circle cx="115" cy="115" r="100" fill="whitesmoke" stroke="orange" mask="url(#stripe)"/>'
+black_right = '<circle cx="115" cy="115" r="100" fill="black"/>'
+
+digits = {
+    "left": [
+        # 1
+        '<path d="M90,95 v40" stroke="red" fill="none" stroke-width="2"/>',
+        # 2
+        '<path d="M70,95 h20 v20 h-20 v20 h20" stroke="red" fill="none" stroke-width="2"/>',
+        # 3
+        '<path d="M70,95 h20 v20 h-20 M90,115 v20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+        # 4
+        '<path d="M70,95 v20 h20 v20 M90,95 v20" stroke="red" fill="none" stroke-width="2"/>',
+        # 5
+        '<path d="M90,95 h-20 v20 h20 v20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+        # 6
+        '<path d="M90,95 h-20 v40 h20 v-20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+        # 7
+        '<path d="M70,95 h20 v40" stroke="red" fill="none" stroke-width="2"/>',
+        # 8
+        '<path d="M70,95 h20 v40 h-20 z M70,115 h20" stroke="red" fill="none" stroke-width="2"/>',
+        # 9
+        '<path d="M70,135 h20 v-40 h-20 v20 h20" stroke="red" fill="none" stroke-width="2"/>',
+        # 10
+        '<path d="M60,95 v40 M70,95 h20 v40 h-20 v-40" stroke="red" fill="none" stroke-width="2"/>',
+        # 11
+        '<path d="M60,95 v40 M90,95 v40" stroke="red" fill="none" stroke-width="2"/>',
+        # 12
+        '<path d="M60,95 v40 M70,95 h20 v20 h-20 M90,115 v20 h-20" stroke="red" fill="none" stroke-width="2"/>'
+    ],
+    "right": [
+        # 1
+        '<path d="M180,95 v40" stroke="red" fill="none" stroke-width="2"/>',
+        # 2
+        '<path d="M160,95 h20 v20 h-20 v20 h20" stroke="red" fill="none" stroke-width="2"/>',
+        # 3
+        '<path d="M160,95 h20 v20 h-20 M180,115 v20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+        # 4
+        '<path d="M160,95 v20 h20 v20 M180,95 v20" stroke="red" fill="none" stroke-width="2"/>',
+        # 5
+        '<path d="M180,95 h-20 v20 h20 v20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+        # 6
+        '<path d="M180,95 h-20 v40 h20 v-20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+        # 7
+        '<path d="M160,95 h20 v40" stroke="red" fill="none" stroke-width="2"/>',
+        # 8
+        '<path d="M160,95 h20 v40 h-20 z M160,115 h20" stroke="red" fill="none" stroke-width="2"/>',
+        # 9
+        '<path d="M160,135 h20 v-40 h-20 v20 h20" stroke="red" fill="none" stroke-width="2"/>',
+        # 10
+        '<path d="M150,95 v40 M160,95 h20 v40 h-20 v-40" stroke="red" fill="none" stroke-width="2"/>',
+        # 11
+        '<path d="M150,95 v40 M180,95 v40" stroke="red" fill="none" stroke-width="2"/>',
+        # 12
+        '<path d="M150,95 v40 M160,95 h20 v20 h-20 M180,115 v20 h-20" stroke="red" fill="none" stroke-width="2"/>'
+    ]
+}
+
+final = "</svg>"
+
+for colorLeft in ["white", "black"]:
+    chrShift = 0 if colorLeft == "white" else 32
+    for left in range(12):
+        for right in range(12):
+            filename = chr(65 + left + chrShift) + chr(65 + right + chrShift) + ".svg"
+            f = open(filename, "w")
+            f.write(preamble)
+            f.write("\n");
+            f.write(black_right if colorLeft == "white" else white_right)
+            f.write("\n");
+            f.write(white_left if colorLeft == "white" else black_left)
+            f.write("\n");
+            f.write(digits["left"][left])
+            f.write("\n");
+            f.write(digits["right"][right])
+            f.write("\n");
+            f.write(final)
+            f.close()
diff --git a/client/public/images/pieces/Emergo/generateSVG_simple.py b/client/public/images/pieces/Emergo/generateSVG_simple.py
new file mode 100755 (executable)
index 0000000..1437626
--- /dev/null
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+# Compose each piece SVG with numbers
+# https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649
+# https://developer.mozilla.org/fr/docs/Web/SVG/Tutoriel/Paths
+
+preamble = """<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="230" height="230">"""
+
+black = '<circle cx="115" cy="115" r="100" fill="black" stroke="orange"/>'
+white = '<circle cx="115" cy="115" r="100" fill="whitesmoke" stroke="orange"/>'
+
+digits = [
+    # 1
+    '<path d="M125,95 v40" stroke="red" fill="none" stroke-width="2"/>',
+    # 2
+    '<path d="M105,95 h20 v20 h-20 v20 h20" stroke="red" fill="none" stroke-width="2"/>',
+    # 3
+    '<path d="M105,95 h20 v20 h-20 M125,115 v20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+    # 4
+    '<path d="M105,95 v20 h20 v20 M125,95 v20" stroke="red" fill="none" stroke-width="2"/>',
+    # 5
+    '<path d="M125,95 h-20 v20 h20 v20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+    # 6
+    '<path d="M125,95 h-20 v40 h20 v-20 h-20" stroke="red" fill="none" stroke-width="2"/>',
+    # 7
+    '<path d="M105,95 h20 v40" stroke="red" fill="none" stroke-width="2"/>',
+    # 8
+    '<path d="M105,95 h20 v40 h-20 z M105,115 h20" stroke="red" fill="none" stroke-width="2"/>',
+    # 9
+    '<path d="M105,135 h20 v-40 h-20 v20 h20" stroke="red" fill="none" stroke-width="2"/>',
+    # 10
+    '<path d="M100,95 v40 M110,95 h20 v40 h-20 v-40" stroke="red" fill="none" stroke-width="2"/>',
+    # 11
+    '<path d="M100,95 v40 M130,95 v40" stroke="red" fill="none" stroke-width="2"/>',
+    # 12
+    '<path d="M100,95 v40 M110,95 h20 v20 h-20 M130,115 v20 h-20" stroke="red" fill="none" stroke-width="2"/>'
+]
+
+final = "</svg>"
+
+for color in ["white", "black"]:
+    chrShift = 0 if color == "white" else 32
+    for number in range(12):
+        filename = chr(65 + number + chrShift) + "_.svg"
+        f = open(filename, "w")
+        f.write(preamble)
+        f.write("\n");
+        f.write(white if color == "white" else black)
+        f.write("\n");
+        f.write(digits[number])
+        f.write("\n");
+        f.write(final)
+        f.close()
index 2e50922..f2c84a8 100644 (file)
@@ -182,7 +182,7 @@ export class AtarigoRules extends ChessRules {
     for (let i = 0; i < V.size.x; i++) {
       for (let j=0; j < V.size.y; j++) {
         if (this.board[i][j] == V.EMPTY) {
-          const mv = this.doClick(i, j);
+          const mv = this.doClick([i, j]);
           if (!!mv) moves.push(mv);
         }
       }
@@ -211,10 +211,73 @@ export class AtarigoRules extends ChessRules {
     return "*";
   }
 
+  // Modified version to count liberties + find locations
+  countEmptySpaces([x, y], color, explored) {
+    if (explored[x][y]) return [];
+    explored[x][y] = true;
+    let res = [];
+    for (let s of V.steps[V.ROOK]) {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j)) {
+        if (!explored[i][j] && this.board[i][j] == V.EMPTY) {
+          res.push([i, j]);
+          explored[i][j] = true; //not counting liberties twice!
+        }
+        else if (this.getColor(i, j) == color)
+          res = res.concat(this.countEmptySpaces([i, j], color, explored));
+      }
+    }
+    return res;
+  }
+
   getComputerMove() {
     const moves = super.getAllValidMoves();
     if (moves.length == 0) return null;
-    // Just random mover for now... writing a good bot is far out of scope
+    // Any capture?
+    const captures = moves.filter(m => m.vanish.length >= 1);
+    if (captures.length > 0) return captures[randInt(captures.length)];
+    // Any group in immediate danger?
+    const color = this.turn;
+    let explored = ArrayFun.init(V.size.x, V.size.y, false);
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i, j) == color &&
+          !explored[i][j]
+        ) {
+          // Before this search, reset liberties,
+          // because two groups might share them.
+          for (let ii = 0; ii < V.size.x; ii++) {
+            for (let jj = 0; jj < V.size.y; jj++) {
+              if (explored[ii][jj] && this.board[ii][jj] == V.EMPTY)
+                explored[ii][jj] = false;
+            }
+          }
+          const liberties = this.countEmptySpaces([i, j], color, explored);
+          if (liberties.length == 1) {
+            const L = liberties[0];
+            const toPlay = moves.find(m => m.end.x == L[0] && m.end.y == L[1]);
+            if (!!toPlay) return toPlay;
+          }
+        }
+      }
+    }
+    // At this point, pick a random move not far from current stones (TODO)
+    const candidates = moves.filter(m => {
+      const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+      return (
+        steps.some(s => {
+          const [i, j] = [m.end.x + s[0], m.end.y + s[1]];
+          return (
+            V.OnBoard(i, j) &&
+            this.board[i][j] != V.EMPTY &&
+            this.getColor(i, j) == color
+          );
+        })
+      );
+    });
+    if (candidates.length > 0) return candidates[randInt(candidates.length)];
     return moves[randInt(moves.length)];
   }
 
index f003294..51d0bed 100644 (file)
@@ -103,7 +103,7 @@ export class GomokuRules extends ChessRules {
     let moves = [];
     for (let i = 0; i < 19; i++) {
       for (let j=0; j < 19; j++) {
-        if (this.board[i][j] == V.EMPTY) moves.push(this.doClick(i, j));
+        if (this.board[i][j] == V.EMPTY) moves.push(this.doClick([i, j]));
       }
     }
     return moves;