From 8edaa9cccffe5455e6097fc5571ac13771369d8e Mon Sep 17 00:00:00 2001 From: Jake Dreher <jdreherschool@gmail.com> Date: Sat, 17 Aug 2024 15:49:09 -0400 Subject: [PATCH] 5 - Create basic rooms. Only handles public rooms created on startup. Private rooms tbd --- app.js | 83 +++++++++++++++---- public/game.html | 141 +++++++++++++++++++++++++++++++++ public/index.html | 198 +++++++++++++++++++++++----------------------- src/game/game.js | 178 +++++++++++++++++++++-------------------- 4 files changed, 399 insertions(+), 201 deletions(-) create mode 100644 public/game.html diff --git a/app.js b/app.js index 4d2cfa6..43e8e94 100644 --- a/app.js +++ b/app.js @@ -2,12 +2,6 @@ const express = require("express"); const { WebSocketServer } = require("ws"); const gameModule = require("./src/game/game.js"); -const webserver = express() - .use((_, res) => res.sendFile("/index.html", { root: `${__dirname}/public` })) - .listen(3000, () => console.log(`Listening on ${3000}`)); - -const sockserver = new WebSocketServer({ port: 3001 }); - // Maintain a list of connections // This will eventually map connections to lobby or game instances let connections = []; @@ -16,10 +10,51 @@ let games = {}; // TO BE REWRITTEN games["game1"] = { - gameBoard: gameModule.createGameBoard(100, 100), + gameBoard: gameModule.createGameBoard(10, 10), + players: [], +}; + +games["game2"] = { + gameBoard: gameModule.createGameBoard(10, 10), + players: [], +}; + +games["game3"] = { + gameBoard: gameModule.createGameBoard(10, 10), players: [], }; +let webserver = express(); + +webserver.use(express.json()); + +webserver.get("/", (_, res) => { + res.sendFile("index.html", { root: `${__dirname}/public` }); +}); + +webserver.get("/public_games", (_, res) => { + let ids = Object.keys(games); + res.json({ ids: ids }); +}); + +webserver.get("/join_game/:gameId", (req, res) => { + let { gameId } = req.params; + if (!games.hasOwnProperty(gameId)) { + return res.status(404).send(); + } + console.log("Sending room", gameId); + // could also use server-side rendering to create the HTML + // that way, we could embed the room code + // and existing chat messages in the generated HTML + // but the client can also get the roomId from the URL + // and use Ajax to request the messages on load + 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], @@ -74,13 +109,24 @@ let moveHandler = (connection, message) => { sockserver.on("connection", (connection) => { console.log("New client connected!"); - connections.push(connection); + 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: connections.length }), + JSON.stringify({ type: "newUser", data: games[roomId].players.length }), ); connection.on("close", () => { - connections = connections.filter((curr) => curr !== connection); + games[roomId].players = games[roomId].players.filter( + (curr) => curr !== games[roomId].players, + ); console.log("Client has disconnected!"); }); @@ -90,15 +136,10 @@ sockserver.on("connection", (connection) => { // All messages are expected to have a type if (message.type === "move") { console.log("Received move: " + message.data); - moveHandler(connection, message); - for (let conn of connections) { - conn.send(JSON.stringify({ type: "gameBoard", data: gameBoard })); - } } else if (message.type === "chat") { console.log("Received chat: " + message.data); } else if (message.type === "join") { console.log("Received join: " + message.data); - connection.send(JSON.stringify({ type: "gameBoard", data: gameBoard })); } else if (message.type === "leave") { console.log("Received leave: " + message.data); } else { @@ -117,6 +158,18 @@ sockserver.on("connection", (connection) => { console.log("Server started."); // Game loop + +for (let game in games) { + setInterval(() => { + for (let conn of games[game].players) { + games[game].gameBoard = gameModule.moveOneStep(games[game].gameBoard); + 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 new file mode 100644 index 0000000..397b0c8 --- /dev/null +++ b/public/game.html @@ -0,0 +1,141 @@ +<!doctype html> +<html lang="en"> + <head> + <title>WebSocket Example</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> + + <div id="game"> + <canvas id="gameCanvas" width="800" height="600"></canvas> + </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"; + } + } + } + }; + + 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> + </body> +</html> diff --git a/public/index.html b/public/index.html index 89f1981..155b83b 100644 --- a/public/index.html +++ b/public/index.html @@ -1,117 +1,113 @@ <!doctype html> <html lang="en"> <head> - <title>WebSocket Example</title> + <title>GBS</title> + + <!-- Bootstrap --> + <link + rel="stylesheet" + href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" + integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" + crossorigin="anonymous" + /> + <script + src="https://code.jquery.com/jquery-3.2.1.slim.min.js" + integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" + crossorigin="anonymous" + ></script> + <script + src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" + integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" + crossorigin="anonymous" + ></script> + <script + src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" + integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" + crossorigin="anonymous" + ></script> </head> <body> - <h1>WebSocket Client</h1> - <input type="text" id="messageInput" placeholder="Type a message..." /> - <button id="sendButton">Send</button> - <div id="messages"></div> + <h1>GBS</h1> + + <div id="accordion"> + <div class="card"> + <div class="card-header" id="headingOne"> + <h5 class="mb-0"> + <button + class="btn btn-link" + data-toggle="collapse" + data-target="#collapseOne" + aria-expanded="true" + aria-controls="collapseOne" + > + Public Games + </button> + </h5> + </div> - <div id="game"> - <canvas id="gameCanvas" width="800" height="600"></canvas> + <div + id="collapseOne" + class="collapse show" + aria-labelledby="headingOne" + data-parent="#accordion" + > + <div class="card-body"></div> + </div> + </div> + <div class="card"> + <div class="card-header" id="headingTwo"> + <h5 class="mb-0"> + <button + class="btn btn-link collapsed" + data-toggle="collapse" + data-target="#collapseTwo" + aria-expanded="false" + aria-controls="collapseTwo" + > + Private Games + </button> + </h5> + </div> + <div + id="collapseTwo" + class="collapse" + aria-labelledby="headingTwo" + data-parent="#accordion" + > + <div class="card-body">Test2</div> + </div> + </div> </div> <script> - const joinMessage = { type: "join", data: "Hello, server" }; - const socket = new WebSocket("ws://127.0.0.1:3001"); - - const canvas = document.getElementById("gameCanvas"); - const width = canvas.width; - const height = canvas.height; - - let moveMessage = { type: "move", data: "up", pid: 0 }; - - 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; + let publicBody = document + .getElementById("collapseOne") + .getElementsByClassName("card-body")[0]; - widthStep = width / board[0].length; - heightStep = height / board.length; + publicBody.textContent = "Loading..."; - for (let i = 0; i < board.length; i++) { - for (let j = 0; j < board[i].length; j++) { - if (board[i][j] === 0) { - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - } - if (board[i][j] === 1) { - ctx.fillStyle = "red"; - ctx.fillRect( - j * widthStep + 1, - i * heightStep + 1, - widthStep - 2, - heightStep - 2, - ); - ctx.fillStyle = "white"; - } + fetch("/public_games") + .then((response) => { + return response.json(); + }) + .then((data) => { + console.log(data); + publicBody.textContent = ""; + for (let i = 0; i < data.ids.length; i++) { + console.log("----------"); + console.log(data.ids[i]); + let button = document.createElement("button"); + button.textContent = data.ids[i]; + button.className = "btn btn-primary"; + button.addEventListener("click", () => { + // will redirect to new chatroom immediately + console.log("Joining game " + data.ids[i]); + window.location = `/join_game/${data.ids[i]}`; + }); + publicBody.appendChild(button); } - } - }; - - 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> </body> </html> diff --git a/src/game/game.js b/src/game/game.js index d33f57a..cfca5ff 100644 --- a/src/game/game.js +++ b/src/game/game.js @@ -30,7 +30,7 @@ function addFood(gameBoard) { } } } - + //Create food cells randomly if (emptySpaces.length > 0) { let randomCell = emptySpaces[getRandomInt(emptySpaces.length)]; @@ -55,104 +55,112 @@ module.exports = { for (var i = 0; i < 10; i++) { gameBoard = addFood(gameBoard); } - //console.log(gameBoard); + //console.log(gameBoard); return gameBoard; }, addPlayer: (gameBoard, pid) => { - let placedCell = false; - while (!placedCell) { - let x = getRandomInt(gameBoard[0].length); - let y = getRandomInt(gameBoard.length); - - if (gameBoard[y][x].type === cellTypes.EMPTY) { - gameBoard[y][x].type = cellTypes.PLAYERHEAD; - gameBoard[y][x].pid = pid; - gameBoard[y][x].direction = 0; - - let y2 = y - 1; - if (y2 >= 0 && gameBoard[y2][x].type === cellTypes.EMPTY) { - gameBoard[y2][x].type = cellTypes.PLAYERBODY; - gameBoard[y2][x].pid = pid; - gameBoard[y2][x].direction = 0; - gameBoard[y][x].next = gameBoard[y2][x]; - } - placedCell = true; - } - } - //console.log(gameBoard); + let placedCell = false; + while (!placedCell) { + let x = getRandomInt(gameBoard[0].length); + let y = getRandomInt(gameBoard.length); + + if (gameBoard[y][x].type === cellTypes.EMPTY) { + gameBoard[y][x].type = cellTypes.PLAYERHEAD; + gameBoard[y][x].pid = pid; + gameBoard[y][x].direction = 0; + + let y2 = y - 1; + if (y2 >= 0 && gameBoard[y2][x].type === cellTypes.EMPTY) { + gameBoard[y2][x].type = cellTypes.PLAYERBODY; + gameBoard[y2][x].pid = pid; + gameBoard[y2][x].direction = 0; + gameBoard[y][x].next = gameBoard[y2][x]; + } + placedCell = true; + } + } + //console.log(gameBoard); return gameBoard; }, moveOneStep: (gameBoard) => { - //Loop through board until we find a PLAYERHEAD - for (var i = 0; i < gameBoard.length; i++) { - for (var j = 0; j < gameBoard[i].length; j++) { - let cell = gameBoard[i][j]; - if (cell.type === cellTypes.PLAYERHEAD) { - let oldX = cell.x; - let oldY = cell.y; - let newX = oldX; - let newY = oldY; - - //New position based on direction - if (cell.direction === 0) { // Up - newY -= 1; - } else if (cell.direction === 1) { // Right - newX += 1; - } else if (cell.direction === 2) { // Down - newY += 1; - } else if (cell.direction === 3) { // Left - newX -= 1; - } - - //Continue if within bounds - if (newX >= 0 && newX < gameBoard[0].length && newY >= 0 && newY < gameBoard.length) { - - //Snake head to new position by updating cell object - gameBoard[newY][newX] = { - ...cell, - x: newX, - y: newY, - }; - - //Remove previous head position - gameBoard[oldY][oldX].type = cellTypes.EMPTY; - gameBoard[oldY][oldX].pid = 0; - gameBoard[oldY][oldX].direction = 0; - gameBoard[oldY][oldX].next = null; - - //Snake body to previous locations - let currentBody = gameBoard[newY][newX].next; - while (currentBody) { - let bodyOldX = currentBody.x; - let bodyOldY = currentBody.y; - currentBody.x = oldX; - currentBody.y = oldY; - - //Update cell body position - gameBoard[oldY][oldX] = { - ...currentBody, - type: cellTypes.PLAYERBODY, - }; - oldX = bodyOldX; - oldY = bodyOldY; - currentBody = currentBody.next; + //Loop through board until we find a PLAYERHEAD + for (var i = 0; i < gameBoard.length; i++) { + for (var j = 0; j < gameBoard[i].length; j++) { + let cell = gameBoard[i][j]; + if (cell.type === cellTypes.PLAYERHEAD) { + let oldX = cell.x; + let oldY = cell.y; + let newX = oldX; + let newY = oldY; + + //New position based on direction + if (cell.direction === 0) { + // Up + newY -= 1; + } else if (cell.direction === 1) { + // Right + newX += 1; + } else if (cell.direction === 2) { + // Down + newY += 1; + } else if (cell.direction === 3) { + // Left + newX -= 1; } - //Remove previous body position - gameBoard[oldY][oldX].type = cellTypes.EMPTY; - gameBoard[oldY][oldX].pid = 0; - gameBoard[oldY][oldX].direction = 0; - gameBoard[oldY][oldX].next = null; + //Continue if within bounds + if ( + newX >= 0 && + newX < gameBoard[0].length && + newY >= 0 && + newY < gameBoard.length + ) { + //Snake head to new position by updating cell object + gameBoard[newY][newX] = { + ...cell, + x: newX, + y: newY, + }; - //console.log(gameBoard); - return gameBoard; + //Remove previous head position + gameBoard[oldY][oldX].type = cellTypes.EMPTY; + gameBoard[oldY][oldX].pid = 0; + gameBoard[oldY][oldX].direction = 0; + gameBoard[oldY][oldX].next = null; + + //Snake body to previous locations + let currentBody = gameBoard[newY][newX].next; + while (currentBody) { + let bodyOldX = currentBody.x; + let bodyOldY = currentBody.y; + currentBody.x = oldX; + currentBody.y = oldY; + + //Update cell body position + gameBoard[oldY][oldX] = { + ...currentBody, + type: cellTypes.PLAYERBODY, + }; + oldX = bodyOldX; + oldY = bodyOldY; + currentBody = currentBody.next; + } + + //Remove previous body position + gameBoard[oldY][oldX].type = cellTypes.EMPTY; + gameBoard[oldY][oldX].pid = 0; + gameBoard[oldY][oldX].direction = 0; + gameBoard[oldY][oldX].next = null; + + //console.log(gameBoard); + return gameBoard; + } } } } - } - return gameBoard; + return gameBoard; }, checkCollisions: (gameBoard) => { -- GitLab