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