]> Untitled Git - go.git/commitdiff
Added basic controls, refactored database sync (#2)
authorClifton Palmer <clifton.palmer@gmail.com>
Tue, 21 Sep 2021 02:06:48 +0000 (21:06 -0500)
committerGitHub <noreply@github.com>
Tue, 21 Sep 2021 02:06:48 +0000 (21:06 -0500)
* Updated board with better sync
* Let players choose stones
* Added a clear button that works locally
* Added nginx proxy for websocket port flattening
* Added compose files for prod and test

docker-compose-prod.yml [new file with mode: 0644]
docker-compose.yml
htdocs/go.js
htdocs/index.html
nginx.conf [new file with mode: 0644]
socket/db.js
socket/server.js

diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml
new file mode 100644 (file)
index 0000000..a3d1860
--- /dev/null
@@ -0,0 +1,26 @@
+version: '3'
+networks:
+    frontend:
+        external:
+            name: proxy
+services:
+    db:
+        image: mariadb:10.6
+        environment:
+            MARIADB_ROOT_PASSWORD: admin
+            MARIADB_DATABASE: go
+            MARIADB_USER: socket
+            MARIADB_PASSWORD: socketpw
+    socket:
+        build: ./socket
+        image: cliftonpalmer/go-socket
+        networks:
+        - default
+        - frontend
+    httpd:
+        image: httpd:2.4
+        volumes:
+        - ./htdocs:/usr/local/apache2/htdocs
+        networks:
+        - default
+        - frontend
index 2db236995b5faa7efacfd1c7416c1157f393e684..c80c306239380e7f05fd3d7b4d475d97c2e44116 100644 (file)
@@ -10,11 +10,23 @@ services:
     socket:
         build: ./socket
         image: cliftonpalmer/go-socket
-        ports:
-        - 3000:3000
     httpd:
         image: httpd:2.4
         volumes:
         - ./htdocs:/usr/local/apache2/htdocs
+    web:
+        image: nginx:1.17
+        restart: on-failure
+        deploy:
+            restart_policy:
+                condition: on-failure
+                delay: 5s
+                max_attempts: 5
+                window: 10s
+        volumes:
+        - ./nginx.conf:/etc/nginx/nginx.conf
+        environment:
+        - NGINX_HOST=purplebirdman.com
+        - NGINX_PORT=80
         ports:
-        - 8100:80
+        - 8000:80
index d02e55d25f9a007b213f2eeb534f1dc9845bb232..7c417ca3a42b1e5de7b89cafec2309161b0a10b3 100644 (file)
@@ -14,25 +14,15 @@ var cellHeight = boardHeight / (boardSize - 1);
 
 var lastX;
 var lastY;
-var playCount = 0;
 
 /* state of pieces 
     0: empty
     1: white
     2: black
 */
-function getStone(i) {
-    switch (i) {
-        case 1:
-            return 'white';
-        case 2:
-            return 'black';
-        default:
-            return 'empty';
-    }
-}
-
 var session = 0;
+var playerStone;
+
 var state = [];
 for (var i = 0; i < boardSize; i++)
 {
@@ -49,14 +39,13 @@ var socket;
 const connect = function() {
     return new Promise((resolve, reject) => {
         const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
-        const port = 3000;
+        const port = window.location.port;
         const socketUrl = `${socketProtocol}//${window.location.hostname}:${port}/ws/`
 
         // Define socket
         socket = new WebSocket(socketUrl);
 
         socket.onopen = (e) => {
-            // Send a little test data, which we can use on the server if we want
             // Resolve the promise - we are connected
             resolve();
         }
@@ -68,13 +57,17 @@ const connect = function() {
                 case "board":
                     console.log("Setting board state");
                     parsed.data.forEach( function (move, index) {
-                        state[move.x][move.y] =
-                            move.state === 'white' ? 1 :
-                            move.state === 'black' ? 2 :
-                            0;
+                        state[move.x][move.y] = move.state;
                     });
                     drawGrid();
                     break;
+                case "new":
+                    for (var i = 0; i < boardSize; i++) {
+                        for (var j = 0; j < boardSize; j++) {
+                            state[i][j] = 0;
+                        }
+                    }
+                    drawGrid();
                 default:
                     console.log(msg);
             }
@@ -225,19 +218,13 @@ canvas.addEventListener('mousedown', function(evt)
     try {
         // push state change to backend
         if(isOpen(socket)) {
-            var stone;
-            if (state[lastX][lastY] === 0) {
-                stone = playCount++ % 2 + 1;
-            } else {
-                stone = 0;
-            }
             socket.send(JSON.stringify({
                 "type":"move",
                 "data": {
                     "session":session,
                     "x":lastX,
                     "y":lastY,
-                    "state":getStone(stone)
+                    "state":state[lastX][lastY] > 0 ? 0 : playerStone
                 }
             }));
         } else {
@@ -302,4 +289,22 @@ function getGridPoint(evt)
 }
 
 // finish
+document.getElementById("new").onclick = function () {
+    // new game, new session, etc
+    socket.send(JSON.stringify({
+        "type":"new",
+        "data": {
+            "session":session
+        }
+    }));
+};
+
+const stones = document.getElementById("stones");
+playerStone = stones.selectedIndex;
+stones.onchange = function () {
+    // let player pick stone type
+    playerStone = stones.selectedIndex;
+    console.log(`Player changed stone to ${playerStone}`);
+};
+
 connect();
index 8c25bb1fc9d49f609f14ef573ce5bc0984535897..f42815d3a25cdd816beb1e4ecccaa9bdb8407953 100644 (file)
@@ -1,4 +1,13 @@
 <center>
-<canvas id = "game-canvas" width="600" height="600"></canvas>
+<canvas id="game-canvas" width="800" height="800"></canvas>
 </center>
+<div id="controls">
+<input id="new" type="button" value="New Game" />
+<label for="stones">Choose a stone:</label>
+<select name="stones" id="stones">
+  <option>Empty</option>
+  <option>White</option>
+  <option>Black</option>
+</select>
+</div>
 <script src="go.js"></script>
diff --git a/nginx.conf b/nginx.conf
new file mode 100644 (file)
index 0000000..f27608f
--- /dev/null
@@ -0,0 +1,23 @@
+worker_processes 1;
+
+events {
+    worker_connections 1024;
+}
+
+http {
+    server {
+        server_name go.purplebirdman.com;
+        location / {
+            proxy_pass http://httpd;
+            proxy_set_header Host $host;
+        }
+        location /ws {
+            proxy_pass http://socket:3000;
+            # websocket magic
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection "Upgrade";
+            proxy_set_header Host $host;
+        }
+    }
+}
index 89fbef094aa45d008f0ded95c537bf688579e142..b439ee55249a30fb2fe5b07c8c719099206dc4e6 100644 (file)
@@ -7,30 +7,76 @@ const dsn = {
 };
 const pool = mariadb.createPool(dsn);
 
-async function addMove(session_id, pos_x, pos_y, state) {
+async function getMaxUpdatedState(session_id) {
     let conn;
     try {
         conn = await pool.getConnection();
+        return await conn.query(`
+SELECT
+    count(*) AS num_rows,
+    UNIX_TIMESTAMP(max_updated_at) AS last_updated
+FROM go.state g JOIN (
+    SELECT MAX(updated_at) AS max_updated_at
+    FROM go.state
+    WHERE session_id = ?
+) x
+ON x.max_updated_at = g.updated_at
+            `, [session_id]);
+    } catch (err) {
+        console.log(err);
+    } finally {
+        if (conn) conn.end();
+    }
+}
+
+async function deleteSession(session_id) {
+    let conn;
+    try {
+        conn = await pool.getConnection();
+        return await conn.query(
+            "DELETE FROM go.state WHERE session_id = ?",
+            [session_id]
+        );
+    } catch (err) {
+        console.log(err);
+    } finally {
+        if (conn) conn.end();
+    }
+}
 
-        var res = await conn.query(`
+async function initBoard() {
+    let conn;
+    try {
+        conn = await pool.getConnection();
+        return await conn.query(`
 CREATE TABLE IF NOT EXISTS
 go.state (
     session_id INT UNSIGNED,
     x TINYINT UNSIGNED,
     y TINYINT UNSIGNED,
-    state ENUM('empty', 'white', 'black'),
+    state TINYINT UNSIGNED,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
         ON UPDATE CURRENT_TIMESTAMP,
     PRIMARY KEY(session_id, x, y)
 );
         `);
-        res = await conn.query(`
+    } catch (err) {
+        console.log(err);
+    } finally {
+        if (conn) conn.end();
+    }
+}
+
+async function addMove(session_id, pos_x, pos_y, state) {
+    let conn;
+    try {
+        conn = await pool.getConnection();
+        return await conn.query(`
 INSERT INTO go.state (session_id, x, y, state)
 values (?, ?, ?, ?)
 ON DUPLICATE KEY UPDATE
 state = VALUES(state);
         `, [session_id, pos_x, pos_y, state]);
-        return res;
     } catch (err) {
         console.log(err);
     } finally {
@@ -52,6 +98,8 @@ SELECT x, y, state from go.state where session_id = ?
     }
 }
 
-exports.pool = pool;
+exports.getMaxUpdatedState = getMaxUpdatedState;
+exports.deleteSession = deleteSession;
+exports.initBoard = initBoard;
 exports.addMove = addMove;
 exports.getBoardState = getBoardState;
index 7aeb30daa5ce08261820d80f139253ddaa6a1748..017bebdf62e6b81724eea11b640acb378e614039 100644 (file)
@@ -9,67 +9,78 @@ function sleep(ms) {
 }
 
 async function pollStatefulChange(ws, session_id) {
-    let conn;
-    try {
-        conn = await db.pool.getConnection();
-        var lastUpdated = 0;
-        while(true) {
-            var res = await conn.query(`
-select UNIX_TIMESTAMP(MAX(updated_at)) as last_updated
-from go.state
-where session_id = ?;
-            `, [session_id]);
-            console.log(res);
-            let updatedAt = res[0].last_updated;
-            if ( updatedAt > lastUpdated) {
-                console.log("websocket poll: board updated!");
+    var lastUpdated  = 0;
+    var rowCount = 0;
+    while (true) {
+        try {
+            var res = await db.getMaxUpdatedState(session_id);
+            var newRowCount = res[0].num_rows;
+            var updatedAt = res[0].last_updated;
+
+            // update board state of client if more moves
+            // have been added since max last timestamp
+            // If more moves have been added in <1 sec,
+            // use the row count for the max last updated timestamp
+            if (updatedAt > lastUpdated || rowCount < newRowCount) {
                 lastUpdated = updatedAt;
-                res = await db.getBoardState(session_id);
+                rowCount = newRowCount;
                 ws.send(JSON.stringify({
                     "type": "board",
-                    "data": res
+                    "data": await db.getBoardState(session_id)
                 }));
             }
             await sleep(1000);
+        } catch(err) {
+            console.log(`websocket poll error: ${err}`);
         }
-    } catch(err) {
-        console.log(`websocket poll error: ${err}`);
-    } finally {
-        if (conn) conn.end();
-    } 
+    }
 }
 
 app.ws('/ws', async function(ws, req) {
     // poll for stateful change and notify clients to update their boards
     var session_id = 0;
-    pollStatefulChange(ws, 0);
+    db.initBoard();
 
-    ws.on('message', async function(msg) {
-        console.log(`ws message: ${msg}`);
+    // send initial message to draw client board
+    ws.send(JSON.stringify({
+        "type": "board",
+        "data": await db.getBoardState(session_id)
+    }));
 
-        var parsed = JSON.parse(msg);
-        switch (parsed.type) {
-            case "move":
-                await db.addMove(
-                    parsed.data.session,
-                    parsed.data.x,
-                    parsed.data.y,
-                    parsed.data.state,
-                    );
-                // fall through and return new board state
-            case "board":
-                var res = await db.getBoardState(
-                    parsed.data.session
-                    );
-                ws.send(JSON.stringify({
-                    "type": "board",
-                    "data": res
-                }));
-                break;
-            default:
-                console.log("ws message: Unknown message type: " + type);
+    ws.on('message', async function(msg) {
+        let parsed;
+        try {
+            parsed = JSON.parse(msg);
+            switch (parsed.type) {
+                case "new":
+                    ws.send(JSON.stringify({
+                        "type": "new",
+                        "data": await db.deleteSession(parsed.data.session)
+                    }));
+                    break;
+                case "move":
+                    await db.addMove(
+                        parsed.data.session,
+                        parsed.data.x,
+                        parsed.data.y,
+                        parsed.data.state,
+                        );
+                    // fall through and return new board state
+                case "board":
+                    ws.send(JSON.stringify({
+                        "type": "board",
+                        "data": await db.getBoardState(parsed.data.session)
+                    }));
+                    break;
+                default:
+                    console.log("ws message: Unknown message type: " + type);
+            }
+        } catch(err) {
+            console.log(`ws message error: ${err}`);
         }
     });
+
+    pollStatefulChange(ws, session_id);
 });
 
 app.listen(3000);