]> Untitled Git - go.git/commitdiff
Added board state background (#1)
authorClifton Palmer <clifton.palmer@gmail.com>
Wed, 15 Sep 2021 18:16:59 +0000 (13:16 -0500)
committerGitHub <noreply@github.com>
Wed, 15 Sep 2021 18:16:59 +0000 (13:16 -0500)
Board state now gets polling updates from the websocket

.gitignore [new file with mode: 0644]
docker-compose.yml
htdocs/go.js
socket/Dockerfile [new file with mode: 0644]
socket/db.js [new file with mode: 0644]
socket/server.js [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..1377554
--- /dev/null
@@ -0,0 +1 @@
+*.swp
index 6c7bfd6a0aeece881dbf48e28f89963ae1842522..2db236995b5faa7efacfd1c7416c1157f393e684 100644 (file)
@@ -1,5 +1,17 @@
 version: '3'
 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
+        ports:
+        - 3000:3000
     httpd:
         image: httpd:2.4
         volumes:
index 6c69223e42dbb53f98674430dbc42d667c16c268..d02e55d25f9a007b213f2eeb534f1dc9845bb232 100644 (file)
@@ -1,7 +1,10 @@
 var canvas = document.getElementById("game-canvas");
 var context = canvas.getContext("2d");
 
+var backgroundColor = '#F5DEB3';
+var gridColor = '#8B4513';
 var boardSize = 19;
+
 var border = canvas.width / 10;
 var boardWidth = canvas.width - (border * 2);
 var boardHeight = canvas.height - (border * 2);
@@ -11,107 +14,292 @@ 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 state = [];
+for (var i = 0; i < boardSize; i++)
+{
+    state[i] = [];
+    for (var j = 0; j < boardSize; j++)
+    {
+        state[i][j] = 0;
+    }
+}
+
+// @connect
+// Connect to the websocket
+var socket;
+const connect = function() {
+    return new Promise((resolve, reject) => {
+        const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
+        const port = 3000;
+        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();
+        }
+
+        socket.onmessage = (msg) => {
+            // Any data from the server can be manipulated here.
+            let parsed = JSON.parse(msg.data);
+            switch (parsed.type) {
+                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;
+                    });
+                    drawGrid();
+                    break;
+                default:
+                    console.log(msg);
+            }
+        }
+
+        socket.onerror = (e) => {
+            // Return an error if any occurs
+            console.log(e);
+            resolve();
+            // Try to connect again
+            connect();
+        }
+    });
+}
+
+// @isOpen
+// check if a websocket is open
+const isOpen = function(ws) {
+    console.log(ws.readyState);
+    return ws.readyState === ws.OPEN
+}
 
-drawGrid();
 
+// finish grid
 function drawGrid()
-{      
-       var backgroundColor = '#F5DEB3';
-       var gridColor = '#8B4513';
-       
-       context.fillStyle = backgroundColor;
-       context.fillRect(0, 0, canvas.width, canvas.height);
-       
+{    
+    
+    /* draw board square */
+    context.fillStyle = backgroundColor;
+    context.fillRect(0, 0, canvas.width, canvas.height);
+    
+    /* draw board grid */
     context.fillStyle = gridColor;
-
-       for (var i = 0; i < boardSize - 1; i++)
-       {
-               for (var j = 0; j < boardSize - 1; j++)
-               {
-                       context.fillRect(i * cellWidth + border, j * cellHeight + border, cellWidth - 1, cellHeight - 1);
-               }
-       }
-       
-       var quater = Math.floor((boardSize - 1) / 4);
-       var markerSize = 8;
-       var markerMargin = (markerSize / 2) + 0.5;
-       
-       context.fillStyle = backgroundColor;
-       
-       if (!!(boardSize % 2))
-       {
-               context.fillRect((((boardSize - 1) / 2) * cellWidth) + border - markerMargin, (((boardSize - 1) / 2) * cellWidth) + border - markerMargin, markerSize, markerSize);
-       }
-       
-       context.fillRect((quater * cellWidth) + border - markerMargin, (quater * cellWidth) + border - markerMargin, markerSize, markerSize);
-       context.fillRect((((boardSize - 1) - quater) * cellWidth) + border - markerMargin, (quater * cellWidth) + border - markerMargin, markerSize, markerSize);
-       
-       context.fillRect((((boardSize - 1) - quater) * cellWidth) + border - markerMargin, (((boardSize - 1) - quater) * cellWidth) + border - markerMargin, markerSize, markerSize);
-       context.fillRect((quater * cellWidth) + border - markerMargin, (((boardSize - 1) - quater) * cellWidth) + border - markerMargin, markerSize, markerSize);
-       
-       var size = canvas.width / 40;
-       var textSpacing = 10;
-       context.fillStyle = '#000000';
+    for (var i = 0; i < boardSize - 1; i++)
+    {
+        for (var j = 0; j < boardSize - 1; j++)
+        {
+            context.fillRect(
+                i * cellWidth + border, 
+                j * cellHeight + border, 
+                cellWidth - 1, 
+                cellHeight - 1
+            );
+        }
+    }
+    
+    /* draw quarter marks and center mark on grid */
+    var quarter = Math.floor((boardSize - 1) / 4);
+    var markerSize = 8;
+    var markerMargin = (markerSize / 2) + 0.5;
+    
+    context.fillStyle = backgroundColor;
+    if (!!(boardSize % 2))
+    {
+        context.fillRect(
+            (((boardSize - 1) / 2) * cellWidth) + border - markerMargin, 
+            (((boardSize - 1) / 2) * cellWidth) + border - markerMargin,
+            markerSize, 
+            markerSize
+        );
+    }
+    context.fillRect(
+        (quarter * cellWidth) + border - markerMargin, 
+        (quarter * cellWidth) + border - markerMargin, 
+        markerSize, 
+        markerSize
+    );
+    context.fillRect(
+        (((boardSize - 1) - quarter) * cellWidth) + border - markerMargin, 
+        (quarter * cellWidth) + border - markerMargin, 
+        markerSize, 
+        markerSize
+    );
+    context.fillRect(
+        (((boardSize - 1) - quarter) * cellWidth) + border - markerMargin, 
+        (((boardSize - 1) - quarter) * cellWidth) + border - markerMargin, 
+        markerSize, 
+        markerSize
+    );
+    context.fillRect(
+        (quarter * cellWidth) + border - markerMargin,
+        (((boardSize - 1) - quarter) * cellWidth) + border - markerMargin,
+        markerSize, 
+        markerSize
+    );
+    
+    /* draw text labels for grid */
+    var size = canvas.width / 40;
+    var textSpacing = 10;
+    context.fillStyle = '#000000';
     context.font = size + "px Arial";
-       
-       for (i = 0; i < boardSize; i++)
-       {
-               context.fillText((i + 1), textSpacing, ((canvas.height - border) - (i * cellHeight)) + (size / 3));
-               context.fillText((i + 1), canvas.width - (size + textSpacing), ((canvas.height - border) - (i * cellHeight)) + (size / 3));     
-
-               context.fillText((String.fromCharCode(97 + i)), (border + (i * cellHeight) + (size / 3)) - (size / 1.5), textSpacing + size);           
-               context.fillText((String.fromCharCode(97 + i)), (border + (i * cellHeight) + (size / 3)) - (size / 1.5), canvas.height - (textSpacing * 2));
-       }
+    
+    for (i = 0; i < boardSize; i++)
+    {
+        context.fillText(
+            (i + 1), textSpacing, 
+            ((canvas.height - border) - (i * cellHeight)) + (size / 3)
+        );
+        context.fillText(
+            (i + 1), canvas.width - (size + textSpacing), 
+            ((canvas.height - border) - (i * cellHeight)) + (size / 3)
+        );
+
+        context.fillText(
+            (String.fromCharCode(97 + i)),
+            (border + (i * cellHeight) + (size / 3)) - (size / 1.5), 
+            textSpacing + size
+        );
+        context.fillText(
+            (String.fromCharCode(97 + i)), 
+            (border + (i * cellHeight) + (size / 3)) - (size / 1.5), 
+            canvas.height - (textSpacing * 2)
+        );
+    }
+
+    drawPieces();
+}
+
+function drawPieces() {
+    /* draw pieces */
+    for (var i = 0; i < boardSize; i++)
+    {
+        for (var j = 0; j < boardSize; j++)
+        {
+            switch(state[i][j]) {
+                case 1:
+                    placeStone(
+                        (i * cellWidth) + border, 
+                        (j * cellWidth) + border, 
+                        'rgba(255, 255, 255, 1)'
+                    );
+                    break;
+                case 2:
+                    placeStone(
+                        (i * cellWidth) + border, 
+                        (j * cellWidth) + border, 
+                        'rgba(0, 0, 0, 1)'
+                    );
+                    break;
+                default:
+            }
+        }
+    }
 }
 
 canvas.addEventListener('mousedown', function(evt) 
 {
-    if (lastX && lastY) {
-        placeStone((lastX * cellWidth) + border, (lastY * cellWidth) + border, 'rgba(0, 0, 0, 1)');
+    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)
+                }
+            }));
+        } else {
+            console.log("Websocket is closed");
+        }
+    } catch(e) {
+        console.log(e);
     }
 });
 
 canvas.addEventListener('mousemove', function(evt) 
 {
-       var position = getGridPoint(evt);
-       
-       if ((position.x != lastX) || (position.y != lastY))
-       {
-               drawGrid();
-
-               if (((position.x >=0) && (position.x < boardSize)) && ((position.y >=0) && (position.y < boardSize)))
-               {
-                                       placeStone((position.x * cellWidth) + border, (position.y * cellWidth) + border, 'rgba(0, 0, 0, 0.2)');                                 
-               }
-       }
-       
-       lastX = position.x;
-       lastY = position.y;             
+    var position = getGridPoint(evt);
+
+    if ((position.x != lastX) || (position.y != lastY))
+    {
+        drawGrid();
+        if (
+            ((position.x >=0) && (position.x < boardSize)) && 
+            ((position.y >=0) && (position.y < boardSize))
+        ) {
+            placeStone(
+                (position.x * cellWidth) + border, 
+                (position.y * cellWidth) + border, 
+                'rgba(0, 0, 0, 0.2)'
+            );
+        }
+    }
+    lastX = position.x;
+    lastY = position.y;        
 });
 
 function placeStone(x, y, color)
 {
-       var radius = cellWidth / 2;
-       
-       context.beginPath();
-       context.arc(x, y, radius, 0, 2 * Math.PI, false);
-       context.fillStyle = color;      
-       context.fill();
-       context.lineWidth = 5;
+    var radius = cellWidth / 2;
+    
+    context.beginPath();
+    context.arc(x, y, radius, 0, 2 * Math.PI, false);
+    context.fillStyle = color;    
+    context.fill();
+    context.lineWidth = 5;
 }
 
 function getGridPoint(evt)
 {
-       var rect = canvas.getBoundingClientRect();
-               
-       var x = Math.round((evt.clientX-border-rect.left)/(rect.right-2*border-rect.left)* boardWidth);
-       var y = Math.round((evt.clientY-border-rect.top)/(rect.bottom-2*border-rect.top)* boardHeight);
-       
-       var roundX = Math.round(x / cellWidth);
-       var roundY = Math.round(y / cellHeight);
-       
-       return {
-         x: roundX,
-         y: roundY
-       };
+    var rect = canvas.getBoundingClientRect();
+        
+    var x = Math.round(
+        (evt.clientX-border-rect.left) / (rect.right-2*border-rect.left) * boardWidth
+    );
+    var y = Math.round(
+        (evt.clientY-border-rect.top) / (rect.bottom-2*border-rect.top) * boardHeight
+    );
+    
+    var roundX = Math.round(x / cellWidth);
+    var roundY = Math.round(y / cellHeight);
+    
+    return {
+        x: roundX,
+        y: roundY
+    };
 }
+
+// finish
+connect();
diff --git a/socket/Dockerfile b/socket/Dockerfile
new file mode 100644 (file)
index 0000000..052fda9
--- /dev/null
@@ -0,0 +1,9 @@
+FROM node:16.9-alpine
+
+WORKDIR /usr/src/app
+
+RUN npm install mariadb express express-ws
+COPY server.js db.js ./
+
+EXPOSE 3000
+CMD [ "node", "server.js" ]
diff --git a/socket/db.js b/socket/db.js
new file mode 100644 (file)
index 0000000..89fbef0
--- /dev/null
@@ -0,0 +1,57 @@
+const mariadb = require('mariadb');
+const dsn = {
+    host: 'db', 
+    user: 'socket',
+    password: 'socketpw',
+    connectionLimit: 10
+};
+const pool = mariadb.createPool(dsn);
+
+async function addMove(session_id, pos_x, pos_y, state) {
+    let conn;
+    try {
+        conn = await pool.getConnection();
+
+        var res = 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'),
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+        ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY(session_id, x, y)
+);
+        `);
+        res = 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 {
+        if (conn) conn.end();
+    }
+}
+
+async function getBoardState(session_id) {
+    let conn;
+    try {
+        conn = await pool.getConnection();
+        return await conn.query(`
+SELECT x, y, state from go.state where session_id = ?
+        `, [session_id]);
+    } catch (err) {
+        console.log(err);
+    } finally {
+        if (conn) conn.end();
+    }
+}
+
+exports.pool = pool;
+exports.addMove = addMove;
+exports.getBoardState = getBoardState;
diff --git a/socket/server.js b/socket/server.js
new file mode 100644 (file)
index 0000000..7aeb30d
--- /dev/null
@@ -0,0 +1,75 @@
+var express = require('express');
+var app = express();
+var expressWs = require('express-ws')(app);
+
+var db = require('./db');
+
+function sleep(ms) {
+  return new Promise(resolve => setTimeout(resolve, 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!");
+                lastUpdated = updatedAt;
+                res = await db.getBoardState(session_id);
+                ws.send(JSON.stringify({
+                    "type": "board",
+                    "data": res
+                }));
+            }
+            await sleep(1000);
+        }
+    } 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);
+
+    ws.on('message', async function(msg) {
+        console.log(`ws message: ${msg}`);
+
+        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);
+        }
+    });
+});
+
+app.listen(3000);