diff --git a/app.js b/app.js index e4614fab940b64093697c895737c8fa350cf20dd..8514f46d6d72bf2d3e3c94269bac0622c7c2608a 100644 --- a/app.js +++ b/app.js @@ -1,28 +1,5 @@ const express = require("express"); -const { WebSocketServer } = require("ws"); -const gameModule = require("./src/game/game.js"); - -// Maintain a list of connections -// This will eventually map connections to lobby or game instances -let connections = []; - -let games = {}; - -// TO BE REWRITTEN -games["game1"] = { - gameBoard: gameModule.createGameBoard(10, 10), - players: [], -}; - -games["game2"] = { - gameBoard: gameModule.createGameBoard(10, 10), - players: [], -}; - -games["game3"] = { - gameBoard: gameModule.createGameBoard(10, 10), - players: [], -}; +const sockServer = require("./src/models/WebSocketModel.js"); let webserver = express(); @@ -39,7 +16,7 @@ webserver.get("/public_games", (_, res) => { webserver.get("/join_game/:gameId", (req, res) => { let { gameId } = req.params; - if (!games.hasOwnProperty(gameId)) { + if (!sockServer.hasGame(gameId)) { return res.status(404).send(); } console.log("Sending room", gameId); @@ -51,118 +28,16 @@ webserver.get("/join_game/:gameId", (req, res) => { res.sendFile("public/game.html", { root: __dirname }); }); -webserver.listen(3000, () => console.log("Web server started.")); - -const sockserver = new WebSocketServer({ port: 3001 }); - -let gameBoard = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 2, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], -]; - -let moveHandler = (connection, message) => { - console.log("Received move: " + message.data); - let movement = message.data; - let pid = message.pid; - let gameBoard = games[connection.protocol].gameBoard; - console.log("pid: ", pid); - - for (var i = 0; i < gameBoard.length; i++) { - for (var j = 0; j < gameBoard[0].length; j++) { - if (gameBoard[i][j].type === 1 && gameBoard[i][j].pid === pid) { - console.log("Updating direction to: " + movement); - switch (movement) { - case "up": - gameBoard[i][j].direction = 0; - break; - case "right": - console.log("1"); - console.log(gameBoard[i][j]); - gameBoard[i][j].direction = 1; - cell.direction = 1; - break; - case "down": - gameBoard[i][j].direction = 2; - break; - case "left": - gameBoard[i][j].direction = 3; - break; - } - console.log("New direction set to: " + gameBoard[i][j].direction); - return; - } - } - } -}; - -sockserver.on("connection", (connection) => { - console.log("New client connected!"); - - console.log(connection.protocol); - - let roomId = connection.protocol; - - games[roomId].players.push(connection); - games[roomId].gameBoard = gameModule.addPlayer( - games[roomId].gameBoard, - games[roomId].players.length, - ); - - connection.send( - JSON.stringify({ type: "newUser", data: games[roomId].players.length }), - ); - - connection.on("close", () => { - games[roomId].players = games[roomId].players.filter( - (curr) => curr !== games[roomId].players, - ); - console.log("Client has disconnected!"); - }); - - connection.on("message", (event) => { - try { - let message = JSON.parse(event); - // All messages are expected to have a type - if (message.type === "move") { - console.log("Received move: " + message.data); - moveHandler(connection, message); - } else if (message.type === "chat") { - console.log("Received chat: " + message.data); - } else if (message.type === "join") { - console.log("Received join: " + message.data); - } else if (message.type === "leave") { - console.log("Received leave: " + message.data); - } else { - console.log("Received: " + message); - } - } catch (e) { - console.log("Error parsing JSON: " + e.message); - } - }); - - connection.onerror = function () { - console.log("websocket error"); - }; +webserver.get("/public/gameClient.js", (_, res) => { + res.sendFile("gameClient.js", { root: `${__dirname}/public` }); }); +webserver.listen(3000, () => console.log("Web server started.")); + console.log("Server started."); // Game loop -for (let game in games) { - setInterval(() => { - games[game].gameBoard = gameModule.moveOneStep(games[game].gameBoard); - for (let conn of games[game].players) { - conn.send( - JSON.stringify({ type: "gameBoard", data: games[game].gameBoard }), - ); - } - }, 1000); -} - //setInterval(() => { // console.log('Broadcasting message to clients, connections.length: ', connections.length); // connections.forEach(connection => { diff --git a/public/game.html b/public/game.html index 80e94909daa1c6ed8265db5c03645fb382ff4436..c156280708127f100ac9a64e6eddc9e24599a067 100644 --- a/public/game.html +++ b/public/game.html @@ -1,151 +1,30 @@ <!doctype html> <html lang="en"> <head> - <title>WebSocket Example</title> + <title>GBS</title> </head> - <body> - <h1>WebSocket Client</h1> - <input type="text" id="messageInput" placeholder="Type a message..." /> - <button id="sendButton">Send</button> - <div id="messages"></div> + <style> + #chatOutput { + height: 200px; + overflow-y: scroll; + border: 1px solid black; + } + </style> - <div id="game"> - <canvas id="gameCanvas" width="800" height="600"></canvas> + <body> + <div id="game"></div> + <div id="Chat"> + <div id="chatOutput"></div> + <input type="text" id="chatInput" /> + <button id="chatSend">Send</button> + </div> + <div id="start"> + <div id="numPlayers">Number of Players: 0</div> + <div id="playersStarted">Players Started: 0</div> + <button id="startButton">Ready</button> </div> - <script> - let pathParts = window.location.pathname.split("/"); - let roomId = pathParts[pathParts.length - 1]; - - const joinMessage = { type: "join", data: `${roomId}` }; - let moveMessage = { type: "move", data: "up", pid: 0 }; - - const socket = new WebSocket("ws://127.0.0.1:3001", `${roomId}`); - - const canvas = document.getElementById("gameCanvas"); - const width = canvas.width; - const height = canvas.height; - - let gameBoardHandler = (event) => { - let ctx = canvas.getContext("2d"); - ctx.clearRect(0, 0, width, height); - ctx.fillStyle = "black"; - ctx.fillRect(0, 0, width, height); - ctx.fillStyle = "white"; - - let board = event.data; - console.log(board); - - widthStep = width / board[0].length; - heightStep = height / board.length; - - for (let i = 0; i < board.length; i++) { - for (let j = 0; j < board[i].length; j++) { - if (board[i][j].type === 0) { - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - } - if (board[i][j].type === 1) { - ctx.fillStyle = "red"; - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - ctx.fillStyle = "white"; - } - if (board[i][j].type === 2) { - ctx.fillStyle = "red"; - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - ctx.fillStyle = "white"; - } - if (board[i][j].type === 3) { - ctx.fillStyle = "blue"; - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - ctx.fillStyle = "white"; - } - if (board[i][j].type === 4) { - ctx.fillStyle = "yellow"; - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - ctx.fillStyle = "white"; - } - } - } - }; - - let newUserHandler = (event) => { - moveMessage.pid = event.data; - }; - - socket.addEventListener("open", () => { - console.log("Connected to server"); - socket.send(JSON.stringify(joinMessage)); - }); - - socket.addEventListener("message", (event) => { - try { - let msg = JSON.parse(event.data); - if (msg.type === "gameBoard") { - gameBoardHandler(msg); - } else if (msg.type === "newUser") { - newUserHandler(msg); - } else { - const messages = document.getElementById("messages"); - const messageElement = document.createElement("div"); - messageElement.innerText = event.data; - messages.appendChild(messageElement); - } - } catch { - console.log(event.data); - } - }); - - document.addEventListener("keydown", (event) => { - if (event.key === "ArrowUp") { - moveMessage.data = "up"; - socket.send(JSON.stringify(moveMessage)); - } - if (event.key === "ArrowDown") { - moveMessage.data = "down"; - socket.send(JSON.stringify(moveMessage)); - } - if (event.key === "ArrowLeft") { - moveMessage.data = "left"; - socket.send(JSON.stringify(moveMessage)); - } - if (event.key === "ArrowRight") { - moveMessage.data = "right"; - socket.send(JSON.stringify(moveMessage)); - } - }); - - document.getElementById("sendButton").addEventListener("click", () => { - const messageInput = document.getElementById("messageInput"); - const message = messageInput.value; - socket.send(message); - messageInput.value = ""; - }); - </script> + <script src="/public/gameClient.js"></script> </body> </html> diff --git a/public/gameClient.js b/public/gameClient.js new file mode 100644 index 0000000000000000000000000000000000000000..e8060c75318cc29bb86b5b5830812697e148135e --- /dev/null +++ b/public/gameClient.js @@ -0,0 +1,195 @@ +let pathParts = window.location.pathname.split("/"); +let roomId = pathParts[pathParts.length - 1]; + +const joinMessage = { type: "join", data: `${roomId}`, pid: 0 }; +let moveMessage = { type: "move", data: "up", pid: 0 }; +const startMessage = { type: "start", data: `${roomId}`, pid: 0 }; +const chatMessage = { type: "chat", data: "", pid: 0 }; + +const socket = new WebSocket("ws://127.0.0.1:3001", `${roomId}`); + +let canvas = null; +let width = 800; +let height = 600; + +let gameStarted = false; + +let startButton = document.getElementById("startButton"); +let chatSend = document.getElementById("chatSend"); +let chatInput = document.getElementById("chatInput"); + +let playersReady = 0; + +let gameBoardHandler = (event) => { + canvas = document.getElementById("gameCanvas"); + let ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, width, height); + ctx.fillStyle = "white"; + + let board = event.data; + widthStep = width / board[0].length; + heightStep = height / board.length; + + for (let i = 0; i < board.length; i++) { + for (let j = 0; j < board[i].length; j++) { + if (board[i][j].type === 0) { + ctx.fillRect( + j * widthStep + 1, + i * heightStep + 1, + widthStep - 2, + heightStep - 2, + ); + } + if (board[i][j].type === 1) { + ctx.fillStyle = "red"; + ctx.fillRect( + j * widthStep + 1, + i * heightStep + 1, + widthStep - 2, + heightStep - 2, + ); + ctx.fillStyle = "white"; + } + if (board[i][j].type === 2) { + ctx.fillStyle = "red"; + ctx.fillRect( + j * widthStep + 1, + i * heightStep + 1, + widthStep - 2, + heightStep - 2, + ); + ctx.fillStyle = "white"; + } + if (board[i][j].type === 3) { + ctx.fillStyle = "blue"; + ctx.fillRect( + j * widthStep + 1, + i * heightStep + 1, + widthStep - 2, + heightStep - 2, + ); + ctx.fillStyle = "white"; + } + if (board[i][j].type === 4) { + ctx.fillStyle = "yellow"; + ctx.fillRect( + j * widthStep + 1, + i * heightStep + 1, + widthStep - 2, + heightStep - 2, + ); + ctx.fillStyle = "white"; + } + } + } +}; + +let newUserHandler = (event) => { + moveMessage.pid = event.data; + startMessage.pid = event.data; + chatMessage.pid = event.data; + joinMessage.pid = event.data; + socket.send(JSON.stringify(joinMessage)); +}; + +socket.addEventListener("open", () => { + console.log("Connected to server"); +}); + +socket.addEventListener("message", (event) => { + try { + let msg = JSON.parse(event.data); + if (msg.type === "gameBoard") { + gameBoardHandler(msg); + } else if (msg.type === "newUser") { + newUserHandler(msg); + } else if (msg.type === "chat") { + const messages = document.getElementById("chatOutput"); + const messageElement = document.createElement("div"); + messageElement.innerText = `${msg.pid}: ${msg.data}`; + messages.appendChild(messageElement); + } else if (msg.type === "start") { + gameStart(); + gameStarted = true; + } else if (msg.type === "playerReady") { + const messages = document.getElementById("chatOutput"); + const messageElement = document.createElement("div"); + messageElement.innerText = `Player ${msg.pid} is ready!`; + messages.appendChild(messageElement); + + updatePlayerCount(1); + } else if (msg.type === "playerJoined") { + const messages = document.getElementById("chatOutput"); + const messageElement = document.createElement("div"); + messageElement.innerText = `Player ${msg.pid} has joined the game!`; + messages.appendChild(messageElement); + + updateNumberPlayers(msg.numPlayers); + } + } catch (e) { + console.log(e.message); + } +}); + +document.addEventListener("keydown", (event) => { + console.log(event.key); + if (event.key === "ArrowUp") { + moveMessage.data = "up"; + socket.send(JSON.stringify(moveMessage)); + } + if (event.key === "ArrowDown") { + moveMessage.data = "down"; + socket.send(JSON.stringify(moveMessage)); + } + if (event.key === "ArrowLeft") { + moveMessage.data = "left"; + socket.send(JSON.stringify(moveMessage)); + } + if (event.key === "ArrowRight") { + moveMessage.data = "right"; + socket.send(JSON.stringify(moveMessage)); + } + if (event.key === "Enter") { + if (!gameStarted) { + sendChat(); + } + } +}); + +startButton.addEventListener("click", () => { + socket.send(JSON.stringify(startMessage)); +}); + +chatSend.addEventListener("click", () => { + sendChat(); +}); + +let sendChat = () => { + chatMessage.data = chatInput.value; + socket.send(JSON.stringify(chatMessage)); + chatInput.value = ""; +}; + +let gameStart = () => { + let gameDiv = document.getElementById("game"); + canvas = document.createElement("canvas"); + canvas.id = "gameCanvas"; + canvas.width = width; + canvas.height = height; + gameDiv.appendChild(canvas); + + document.getElementById("chatInput").disabled = false; +}; + +let updatePlayerCount = (count) => { + let playerCount = document.getElementById("playersStarted"); + playersReady += count; + playerCount.innerText = `Players: ${playersReady}`; +}; + +let updateNumberPlayers = (count) => { + let countDiv = document.getElementById("numPlayers"); + countDiv.innerText = `Number of players: ${count}`; +}; diff --git a/src/game/game.js b/src/game/game.js index 1087a46df079a33437644b2fbccf65e4c9ce60ce..7f7fb14b90aefcf375d810a01c65de91fdc2ebfd 100644 --- a/src/game/game.js +++ b/src/game/game.js @@ -3,7 +3,7 @@ const cellTypes = { PLAYERHEAD: 1, PLAYERBODY: 2, FOOD: 3, - BANANA: 4 + BANANA: 4, }; function createCell(x, y, type) { @@ -32,7 +32,9 @@ function addFood(gameBoard) { } } - let bananaExists = gameBoard.some(row => row.some(cell => cell.type === cellTypes.BANANA)); + let bananaExists = gameBoard.some((row) => + row.some((cell) => cell.type === cellTypes.BANANA), + ); //Create food cells randomly if (emptySpaces.length > 0) { @@ -76,9 +78,9 @@ module.exports = { if (gameBoard[y][x].type === cellTypes.EMPTY) { gameBoard[y][x].type = cellTypes.PLAYERHEAD; gameBoard[y][x].pid = pid; - - let bodyPlaced = false; - + + let bodyPlaced = false; + if (y - 1 >= 0 && gameBoard[y - 1][x].type === cellTypes.EMPTY) { // Place body segment gameBoard[y - 1][x] = createCell(x, y - 1, cellTypes.PLAYERBODY); @@ -86,20 +88,20 @@ module.exports = { gameBoard[y][x].next = gameBoard[y - 1][x]; bodyPlaced = true; } - if (bodyPlaced) { + if (bodyPlaced) { placedCell = true; - } else { + } else { gameBoard[y][x] = createCell(x, y, cellTypes.EMPTY); + } } } - } //console.log(gameBoard); return gameBoard; }, moveOneStep: (gameBoard) => { // Save board state to allow multiple snake movements - let updatedBoard = gameBoard.map(row => row.map(cell => ({ ...cell }))); + let updatedBoard = gameBoard.map((row) => row.map((cell) => ({ ...cell }))); // Loop through board until we find a PLAYERHEAD for (var i = 0; i < gameBoard.length; i++) { @@ -137,8 +139,10 @@ module.exports = { ) { console.log(`Player ${cell.pid} has collided with the wall!`); // Remove the player from the game (indicating death) - updatedBoard = updatedBoard.map(row => - row.map(c => (c.pid === cell.pid ? createCell(c.x, c.y, cellTypes.EMPTY) : c)) + updatedBoard = updatedBoard.map((row) => + row.map((c) => + c.pid === cell.pid ? createCell(c.x, c.y, cellTypes.EMPTY) : c, + ), ); continue; } @@ -157,14 +161,16 @@ module.exports = { // Handle collision with banana if (gameBoard[newY][newX].type == cellTypes.BANANA) { - console.log('Slip'); - updatedBoard[newY][newX] = createCell(newX, newY, cellTypes.EMPTY); - updatedBoard = updatedBoard.map(row => - row.map(c => (c.pid === cell.pid ? createCell(c.x, c.y, cellTypes.EMPTY) : c)) + console.log("Slip"); + updatedBoard[newY][newX] = createCell(newX, newY, cellTypes.EMPTY); + updatedBoard = updatedBoard.map((row) => + row.map((c) => + c.pid === cell.pid ? createCell(c.x, c.y, cellTypes.EMPTY) : c, + ), ); - updatedBoard = addFood(updatedBoard); - continue; - } + updatedBoard = addFood(updatedBoard); + continue; + } // Snake head to new position by updating cell object let newHeadCell = createCell(newX, newY, cellTypes.PLAYERHEAD); diff --git a/src/models/WebSocketModel.js b/src/models/WebSocketModel.js new file mode 100644 index 0000000000000000000000000000000000000000..7408c97f53695e2514432d2c7b6997a2f0868ce1 --- /dev/null +++ b/src/models/WebSocketModel.js @@ -0,0 +1,182 @@ +const { WebSocketServer } = require("ws"); +const gameModule = require("../game/game.js"); + +class WebSocketModel { + constructor() { + this.connections = []; + this.games = {}; + this.games["game1"] = { + gameBoard: gameModule.createGameBoard(10, 10), + players: [], + started: false, + readyPlayers: 0, + }; + this.games["game2"] = { + gameBoard: gameModule.createGameBoard(10, 10), + players: [], + started: false, + readyPlayers: 0, + }; + this.games["game3"] = { + gameBoard: gameModule.createGameBoard(10, 10), + players: [], + started: false, + readyPlayers: 0, + }; + this.sockserver = new WebSocketServer({ port: 3001 }); + + this.onConnection(); + + for (let game in this.games) { + setInterval(() => { + if (!this.games[game].started) return; + + this.games[game].gameBoard = gameModule.moveOneStep( + this.games[game].gameBoard, + ); + for (let conn of this.games[game].players) { + conn.send( + JSON.stringify({ + type: "gameBoard", + data: this.games[game].gameBoard, + }), + ); + } + }, 1000); + } + } + + hasGame(gameId) { + return this.games.hasOwnProperty(gameId); + } + + moveHandler(connection, message) { + console.log("Received move: " + message.data); + let movement = message.data; + let pid = message.pid; + let gameBoard = this.games[connection.protocol].gameBoard; + console.log("pid: ", pid); + + for (var i = 0; i < gameBoard.length; i++) { + for (var j = 0; j < gameBoard[0].length; j++) { + if (gameBoard[i][j].type === 1 && gameBoard[i][j].pid === pid) { + console.log("Updating direction to: " + movement); + switch (movement) { + case "up": + gameBoard[i][j].direction = 0; + break; + case "right": + gameBoard[i][j].direction = 1; + cell.direction = 1; + break; + case "down": + gameBoard[i][j].direction = 2; + break; + case "left": + gameBoard[i][j].direction = 3; + break; + } + console.log("New direction set to: " + gameBoard[i][j].direction); + return; + } + } + } + } + + onConnection() { + this.sockserver.on("connection", (connection) => { + console.log("New client connected!"); + + console.log(connection.protocol); + + let roomId = connection.protocol; + + this.games[roomId].players.push(connection); + this.games[roomId].gameBoard = gameModule.addPlayer( + this.games[roomId].gameBoard, + this.games[roomId].players.length, + ); + + connection.send( + JSON.stringify({ + type: "newUser", + data: this.games[roomId].players.length, + }), + ); + + connection.on("close", () => { + this.games[roomId].players = this.games[roomId].players.filter( + (curr) => curr !== this.games[roomId].players, + ); + console.log("Client has disconnected!"); + }); + + connection.on("message", (event) => { + try { + let message = JSON.parse(event); + console.log("Received message: " + message); + // All messages are expected to have a type + if (message.type === "move") { + console.log("Received move: " + message.data); + this.moveHandler(connection, message); + } else if (message.type === "chat") { + console.log("Received chat: " + message.data); + for (let conn of this.games[roomId].players) { + conn.send( + JSON.stringify({ + type: "chat", + data: message.data, + pid: message.pid, + }), + ); + } + } else if (message.type === "join") { + console.log("Received join: " + message.data); + for (let conn of this.games[roomId].players) { + conn.send( + JSON.stringify({ + type: "playerJoined", + data: message.data, + numPlayers: this.games[roomId].players.length, + pid: message.pid, + }), + ); + } + } else if (message.type === "leave") { + console.log("Received leave: " + message.data); + } else if (message.type === "start") { + console.log("Received start: " + message.data); + for (let conn of this.games[roomId].players) { + if (conn !== connection) + conn.send( + JSON.stringify({ type: "playerReady", data: message.data }), + ); + } + this.games[roomId].readyPlayers++; + if ( + this.games[roomId].players.length === + this.games[roomId].readyPlayers + ) { + for (let conn of this.games[roomId].players) { + conn.send( + JSON.stringify({ type: "start", data: message.data }), + ); + } + this.games[roomId].started = true; + } + } else { + console.log("Received: " + message); + } + } catch (e) { + console.log("Error parsing JSON: " + e.message); + } + }); + + connection.onerror = function () { + console.log("websocket error"); + }; + }); + } +} + +module.exports = new WebSocketModel(); diff --git a/tests/TEMPFILE b/tests/TEMPFILE new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/websocket.test.js b/tests/websocket.test.js deleted file mode 100644 index 1dfa7434ee9075b61494750f740bb01ec536ade9..0000000000000000000000000000000000000000 --- a/tests/websocket.test.js +++ /dev/null @@ -1,50 +0,0 @@ -let WebSocketClient = require("websocket").client; - -let createClient = (port, hostname, protocol) => { - let WebSocketClient = require("websocket").client; - let client = new WebSocketClient(); - - client.on("connectFailed", (error) => { - console.log("Connect Error: " + error.toString()); - }); - - client.on("connect", (connection) => { - console.log("WebSocket Client Connected"); - connection.on("error", (error) => { - console.log("Connection Error: " + error.toString()); - }); - - connection.on("close", () => { - console.log(`${protocol} Connection Closed`); - }); - - connection.on("message", (message) => { - if (message.type === "utf8") { - console.log("Received: '" + message.utf8Data + "'"); - } - }); - }); - - client.connect(`ws://${hostname}:${port}/`, protocol); -}; - -test("start websocket server", () => { - let ConnectionController = require("../src/route/connectionController"); - let wsServer = ConnectionController.startServer(8080, "localhost"); - expect(wsServer).toBeDefined(); - expect(wsServer.server).toBeDefined(); - expect(wsServer.app).toBeDefined(); - expect(wsServer.connections).toBeDefined(); - - console.log(wsServer.server); - - let shutdown = () => { - wsServer.server.closeAllConnections(); - wsServer.server.shutDown(); - wsServer.app.close(); - }; - - let client = createClient(8080, "localhost", "echo-protocol"); - - shutdown(); -});