diff --git a/public/gameClient.js b/public/gameClient.js index 2f37c9492da3ed383aa303c638ba68281a587536..63805ad7d6c35dba4741e0317cb35720b38cde3b 100644 --- a/public/gameClient.js +++ b/public/gameClient.js @@ -82,6 +82,16 @@ let gameBoardHandler = (event) => { ); ctx.fillStyle = "white"; } + if (board[i][j].type === 5) { + ctx.fillStyle = "black"; + ctx.fillRect( + j * widthStep + 1, + i * heightStep + 1, + widthStep - 2, + heightStep - 2, + ); + ctx.fillStyle = "white"; + } } } }; diff --git a/src/game/game.js b/src/game/game.js index 7f7fb14b90aefcf375d810a01c65de91fdc2ebfd..b06b15aa6f86ccb314e0ec8b6f53a26165e8abe0 100644 --- a/src/game/game.js +++ b/src/game/game.js @@ -4,6 +4,15 @@ const cellTypes = { PLAYERBODY: 2, FOOD: 3, BANANA: 4, + BORDER: 5, +}; + +const COLLISIONTYPES = { + NONE: 0, + FOOD: 1, + BANANA: 2, + BORDER: 3, + SNAKE: 4, }; function createCell(x, y, type) { @@ -50,8 +59,43 @@ function addFood(gameBoard) { return gameBoard; } +function checkCollisions(newY, newX, gameBoard) { + // returns a list of players that have collided, + // and a new gameBoard with the players removed + if ( + newX < 0 || + newX >= gameBoard[0].length || + newY < 0 || + newY >= gameBoard.length + ) { + console.log(`Player has collided with the wall!`); + return COLLISIONTYPES.BORDER; + } + //Handle collision with food + if (gameBoard[newY][newX].type === cellTypes.FOOD) { + console.log(`Player has eaten the food!`); + return COLLISIONTYPES.FOOD; + } + if (gameBoard[newY][newX].type === cellTypes.PLAYERHEAD) { + console.log(`Player has collided with another player!`); + return COLLISIONTYPES.SNAKE; + } + //Handle collision with banana + if (gameBoard[newY][newX].type == cellTypes.BANANA) { + console.log("Slip"); + return COLLISIONTYPES.BANANA; + } + //Handle collision with border + if (gameBoard[newY][newX].type == cellTypes.BORDER) { + console.log("Void"); + return COLLISIONTYPES.BORDER; + } + return COLLISIONTYPES.NONE; +} + module.exports = { cellTypes, + COLLISIONTYPES, createGameBoard: (width, height) => { let gameBoard = []; @@ -82,7 +126,7 @@ module.exports = { let bodyPlaced = false; if (y - 1 >= 0 && gameBoard[y - 1][x].type === cellTypes.EMPTY) { - // Place body segment + //Place body segment gameBoard[y - 1][x] = createCell(x, y - 1, cellTypes.PLAYERBODY); gameBoard[y - 1][x].pid = pid; gameBoard[y][x].next = gameBoard[y - 1][x]; @@ -100,10 +144,11 @@ module.exports = { }, moveOneStep: (gameBoard) => { - // Save board state to allow multiple snake movements + //Save board state to allow multiple snake movements let updatedBoard = gameBoard.map((row) => row.map((cell) => ({ ...cell }))); + let deadPlayers = []; - // Loop through board until we find a PLAYERHEAD + //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]; @@ -115,74 +160,73 @@ module.exports = { let tempVar = cell.direction; - // New position based on direction + //New position based on direction if (cell.direction === 0) { - // Up + //Up newY -= 1; } else if (cell.direction === 1) { - // Right + //Right newX += 1; } else if (cell.direction === 2) { - // Down + //Down newY += 1; } else if (cell.direction === 3) { - // Left + //Left newX -= 1; } - // Check for collisions with walls + let collision = checkCollisions(newY, newX, gameBoard); + if ( - newX < 0 || - newX >= gameBoard[0].length || - newY < 0 || - newY >= gameBoard.length + collision === COLLISIONTYPES.BANANA || + collision === COLLISIONTYPES.FOOD ) { - console.log(`Player ${cell.pid} has collided with the wall!`); - // Remove the player from the game (indicating death) + updatedBoard = addFood(updatedBoard); + } + if (collision === COLLISIONTYPES.SNAKE) { updatedBoard = updatedBoard.map((row) => row.map((c) => c.pid === cell.pid ? createCell(c.x, c.y, cellTypes.EMPTY) : c, ), ); + deadPlayers.push(cell.pid); + updatedBoard = updatedBoard.map((row) => + row.map((c) => + c.pid === gameBoard[newY][newX].pid + ? createCell(c.x, c.y, cellTypes.EMPTY) + : c, + ), + ); + deadPlayers.push(gameBoard[newY][newX].pid); continue; } - - // Handle collision with food - if (gameBoard[newY][newX].type === cellTypes.FOOD) { - console.log(`Player ${cell.pid} has eaten the food!`); - // Add new body segment at the current head's position - let newBodySegment = createCell(oldX, oldY, cellTypes.PLAYERBODY); - newBodySegment.pid = cell.pid; - newBodySegment.next = cell.next; - cell.next = newBodySegment; - // Place new food somewhere else - updatedBoard = addFood(updatedBoard); - } - - // Handle collision with banana - if (gameBoard[newY][newX].type == cellTypes.BANANA) { - console.log("Slip"); - updatedBoard[newY][newX] = createCell(newX, newY, cellTypes.EMPTY); + if (collision > COLLISIONTYPES.FOOD) { updatedBoard = updatedBoard.map((row) => row.map((c) => c.pid === cell.pid ? createCell(c.x, c.y, cellTypes.EMPTY) : c, ), ); - updatedBoard = addFood(updatedBoard); + deadPlayers.push(cell.pid); continue; } + if (collision === COLLISIONTYPES.FOOD) { + let newBodySegment = createCell(oldX, oldY, cellTypes.PLAYERBODY); + newBodySegment.pid = cell.pid; + newBodySegment.next = cell.next; + cell.next = newBodySegment; + } - // Snake head to new position by updating cell object + //Snake head to new position by updating cell object let newHeadCell = createCell(newX, newY, cellTypes.PLAYERHEAD); newHeadCell.pid = cell.pid; newHeadCell.direction = tempVar; newHeadCell.next = cell.next; updatedBoard[newY][newX] = newHeadCell; - // Remove previous head position + //Remove previous head position updatedBoard[oldY][oldX] = createCell(oldX, oldY, cellTypes.EMPTY); - // Move the body segments + //Move the body segments let currentBody = newHeadCell.next; while (currentBody) { let bodyOldX = currentBody.x; @@ -190,7 +234,7 @@ module.exports = { currentBody.x = oldX; currentBody.y = oldY; - // Update cell body position + //Update cell body position updatedBoard[oldY][oldX] = { ...currentBody, type: cellTypes.PLAYERBODY, @@ -200,18 +244,105 @@ module.exports = { currentBody = currentBody.next; } - // Remove previous body position if it's no longer part of the snake + //Remove previous body position if it's no longer part of the snake if (oldX !== newHeadCell.x || oldY !== newHeadCell.y) { updatedBoard[oldY][oldX] = createCell(oldX, oldY, cellTypes.EMPTY); } } } } - return updatedBoard; + return [updatedBoard, deadPlayers]; + }, + + applyBorders: (gameBoard, borderCounter) => { + let height = gameBoard.length; + let width = gameBoard[0].length; + + //console.log(borderCounter); + + //Return gameBoard when it is 2x2 + if (borderCounter * 2 >= Math.min(height, width) - 2) { + return gameBoard; + } + + //Top + for (let x = 0; x < width; x++) { + gameBoard[borderCounter][x].type = cellTypes.BORDER; + gameBoard[borderCounter][x].pid = 0; + gameBoard[borderCounter][x].direction = 0; + gameBoard[borderCounter][x].next = null; + } + + //Bottom + for (let x = 0; x < width; x++) { + gameBoard[height - borderCounter - 1][x].type = cellTypes.BORDER; + gameBoard[height - borderCounter - 1][x].pid = 0; + gameBoard[height - borderCounter - 1][x].direction = 0; + gameBoard[height - borderCounter - 1][x].next = null; + } + + //Left + for (let y = 0; y < height; y++) { + gameBoard[y][borderCounter].type = cellTypes.BORDER; + gameBoard[y][borderCounter].pid = 0; + gameBoard[y][borderCounter].direction = 0; + gameBoard[y][borderCounter].next = null; + } + + //Right + for (let y = 0; y < height; y++) { + gameBoard[y][width - borderCounter - 1].type = cellTypes.BORDER; + gameBoard[y][width - borderCounter - 1].pid = 0; + gameBoard[y][width - borderCounter - 1].direction = 0; + gameBoard[y][width - borderCounter - 1].next = null; + } + + return gameBoard; }, - checkCollisions: (gameBoard) => { - // returns a list of players that have collided, - // and a new gameBoard with the players removed + getPlayerView: (gameBoard, pid) => { + let updatedBoard = gameBoard.map((row) => row.map((cell) => ({ ...cell }))); + + let width = 11; + let height = 11; + + let head = null; + for (var i = 0; i < updatedBoard.length; i++) { + for (var j = 0; j < updatedBoard[i].length; j++) { + let cell = updatedBoard[i][j]; + if (cell.type === cellTypes.PLAYERHEAD && cell.pid === pid) { + console.log("MY Player head found at: ", cell.x, cell.y); + head = cell; + } + } + } + + if (head === null) { + return []; + } + + let playerView = []; + + for (var i = 0; i <= height; i++) { + let row = []; + for (var j = 0; j <= width; j++) { + let x = head.x + j - Math.floor(width / 2); + let y = head.y + i - Math.floor(height / 2); + + if ( + x < 0 || + x >= updatedBoard[0].length || + y < 0 || + y >= updatedBoard.length + ) { + row.push(cellTypes.BORDER); + } else { + row.push(updatedBoard[y][x]); + } + } + playerView.push(row); + } + + return playerView; }, }; diff --git a/src/models/WebSocketModel.js b/src/models/WebSocketModel.js index 348144b15315e2c7307b30f214f5130452b81c31..d157cc4c34f7736a19e78cf3f20d63f4e6551c48 100644 --- a/src/models/WebSocketModel.js +++ b/src/models/WebSocketModel.js @@ -2,50 +2,87 @@ const { WebSocketServer } = require("ws"); const gameModule = require("../game/game.js"); class WebSocketModel { + createInterval(game) { + setInterval(() => { + if (!this.games[game].started) return; + let deadPlayers = []; + + [this.games[game].gameBoard, deadPlayers] = gameModule.moveOneStep( + this.games[game].gameBoard, + ); + + if (!this.games[game].elapsedTime) { + this.games[game].elapsedTime = 0; + } + this.games[game].elapsedTime++; + + //Every 10 seconds, generate borders + if (this.games[game].elapsedTime % 10 === 0) { + this.games[game].gameBoard = gameModule.applyBorders( + this.games[game].gameBoard, + this.games[game].borderCounter, + ); + this.games[game].borderCounter++; + } + + if (deadPlayers.length > 0) { + for (let conn of this.games[game].players) { + conn.connection.send( + JSON.stringify({ + type: "deadPlayers", + data: deadPlayers, + }), + ); + } + } + + for (let conn of this.games[game].players) { + let playerView = gameModule.getPlayerView( + this.games[game].gameBoard, + conn.pid, + ); + conn.connection.send( + JSON.stringify({ + type: "gameBoard", + data: playerView, // YANG: gameModule.getPlayerView()... + }), + ); + } + }, 500); + } constructor() { this.connections = []; this.games = {}; this.games["game1"] = { - gameBoard: gameModule.createGameBoard(10, 10), + gameBoard: gameModule.createGameBoard(100, 100), players: [], type: "public", started: false, readyPlayers: 0, + borderCounter: 0, }; this.games["game2"] = { - gameBoard: gameModule.createGameBoard(10, 10), + gameBoard: gameModule.createGameBoard(100, 100), players: [], type: "public", started: false, readyPlayers: 0, + borderCounter: 0, }; this.games["game3"] = { - gameBoard: gameModule.createGameBoard(10, 10), + gameBoard: gameModule.createGameBoard(100, 100), players: [], type: "public", started: false, readyPlayers: 0, + borderCounter: 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); + this.createInterval(game); } } @@ -63,12 +100,14 @@ class WebSocketModel { createGame(gameId, gameType) { this.games[gameId] = { - gameBoard: gameModule.createGameBoard(10, 10), + gameBoard: gameModule.createGameBoard(100, 100), players: [], type: gameType, started: false, readyPlayers: 0, + borderCounter: 0, }; + this.createInterval(gameId); } moveHandler(connection, message) { @@ -113,13 +152,19 @@ class WebSocketModel { let roomId = connection.protocol; if (!this.games[roomId].started) { - this.games[roomId].players.push(connection); + this.games[roomId].players.push({ + connection, + pid: this.games[roomId].players.length + 1, + }); this.games[roomId].gameBoard = gameModule.addPlayer( this.games[roomId].gameBoard, this.games[roomId].players.length, ); } else { - this.games[roomId].players.push(connection); + this.games[roomId].players.push({ + connection, + pid: this.games[roomId].players.length + 1, + }); } connection.send( @@ -130,6 +175,7 @@ class WebSocketModel { ); connection.on("close", () => { + // Need to fix this this.games[roomId].players = this.games[roomId].players.filter( (curr) => curr !== this.games[roomId].players, ); @@ -140,14 +186,14 @@ class WebSocketModel { try { let message = JSON.parse(event); console.log("Received message: " + message); - // All messages are expected to have a type + //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( + conn.connection.send( JSON.stringify({ type: "chat", data: message.data, @@ -158,7 +204,7 @@ class WebSocketModel { } else if (message.type === "join") { console.log("Received join: " + message.data); for (let conn of this.games[roomId].players) { - conn.send( + conn.connection.send( JSON.stringify({ type: "playerJoined", data: message.data, @@ -172,8 +218,8 @@ class WebSocketModel { } else if (message.type === "start") { console.log("Received start: " + message.data); for (let conn of this.games[roomId].players) { - if (conn !== connection) - conn.send( + if (conn.connection !== connection) + conn.connection.send( JSON.stringify({ type: "playerReady", data: message.data }), ); } @@ -183,7 +229,7 @@ class WebSocketModel { this.games[roomId].readyPlayers ) { for (let conn of this.games[roomId].players) { - conn.send( + conn.connection.send( JSON.stringify({ type: "start", data: message.data }), ); }