/**
* This class handles socket initialization and listening events
* @class SocketServerHandler
*/
// IMPORTING
const { Server } = require('socket.io'); // Server class from socket.io
const GameData = require("./GameData"); // Manages game data for each room
class SocketServerHandler{
/**
* Initialize new Socket.io server instance. Return new server to caller, returns null if fails
* @param {object} httpServer HTTP server created using express application
* @returns new server instance on success, 0 on failure
*/
createServerInstance(httpServer) {
try {
const newServerInstance = new Server(httpServer, {
cors: {
origin: "*", // Allows connections from any origin (use restrictive policies in production)
methods: ['GET', "POST"] // Specifies allowed HTTP methods
}
});
return newServerInstance;
} catch (error) {
console.log(error);
return 0;
}
}
/**
* Start the requested server on the requested port
* @param {object} httpServer HTTP server created using express application
* @param {number} port port number to be exposed for client listeners
*/
startServer(httpServer, port) {
try {
httpServer.listen(port, () => {
console.log(`Scribblers server is running on port ${port}`);
});
} catch (error) {
console.log(error);
}
}
/**
* Initialize Socket.io server listeners to manage client requests
* @param {object} server Socket.io server instance
* @param {object} client Socket.io client connecting to server
* @param {Map} gameDataMap Server-side map of game data
*/
initializeServerListeners(server, client, gameDataMap) {
// Listen for start of new game
client.on('start-game', (room) => {
console.log(`starting game in room ${room}`)
client.to(room).emit('start-game');
gameDataMap.get(room).startNewRound(server, room, gameDataMap);
})
// Listen to play game again
client.on('play-again', (room) => {
console.log(`Playing game again in room ${room}`)
client.to(room).emit('play-again');
})
// Listen for request to create new lobby
client.on("create-new-lobby", (numRounds, maxPlayers, players, user) => {
// Generate new lobby code
let randomCodeDigits = [];
let uniqueCodeFound = false;
while (!uniqueCodeFound) {
// Generate random values in array
randomCodeDigits.value = Array.from({ length: 5 }, () =>
Math.floor(Math.random() * 9) + 1
);
// Check if value is in mapped array
const newCodeString = randomCodeDigits.value.join('');
if (server.sockets.adapter.rooms.has(newCodeString)) {
// Do nothing and cycle through while loop to try again
}
else{
uniqueCodeFound = true;
server.to(client.id).emit("update-lobby-code", newCodeString);
// Add host to player list if playing
let playerData = new Map();
if (user) {
playerData.set(user, {currentGuess: "", score: 0})
}
const gameData = new GameData(numRounds, 0, maxPlayers, players, null, "", 0, 0, playerData);
gameDataMap.set(newCodeString, gameData);
console.log(gameDataMap);
}
}
});
// Listens for user joining room
client.on('join-room', (room, user, isHost) => {
if (!gameDataMap.get(room).playerData.has(user) || isHost) {
// Prevent users from joining non-existent rooms, joining full rooms, or duplicate avatars
if (server.sockets.adapter.rooms.has(room) && gameDataMap.get(room).playerData.size < gameDataMap.get(room).maxPlayers) {
//console.log(`room ${room} exists!`)
client.join(room);
gameDataMap.get(room).players.push(user)
gameDataMap.get(room).playerData.set(user, {currentGuess: "", score: 0})
gameDataMap.get(room).playerData.forEach((value, key) => {
server.to(client.id).emit("add-player", key, value);
})
server.to(room).emit("update-player-list", gameDataMap.get(room).players);
client.to(room).emit("add-player", user, {currentGuess: "", score: 0});
server.to(client.id).emit("update-max-players", gameDataMap.get(room).maxPlayers);
server.to(client.id).emit("update-round", gameDataMap.get(room).currentRound);
server.to(client.id).emit("update-num-rounds", gameDataMap.get(room).numberRounds);
console.log(gameDataMap);
}
else{
//console.log(`room ${room} does not exist!`)
client.join(room);
if (!isHost)
server.to(client.id).emit("update-user", '');
}
console.log(`User ${client.id} is connected to room ${room}`); // Logs when a new user connects
//avatar is taken
} else {
server.to(client.id).emit("return-to-join-screen")
}
})
// Listens for users leaving the room
client.on('leave-room', (room, user) => {
console.log(`User ${client.id} with avatar ${user} has disconnected from room ${room}`);
client.leave(room);
if (!gameDataMap.has(room) || !gameDataMap.get(room).playerData.has(user)) return;
// Loop through array to find and remove player from array
let index = 0;
let arrLength = gameDataMap.get(room).players.length
for (index = 0; index < arrLength; index++) {
if (gameDataMap.get(room).players[index] == user) {
gameDataMap.get(room).players.splice(index, 1);
index = arrLength;
//console.log(gameDataMap.get(room).players);
}
}
client.to(room).emit("update-player-list", gameDataMap.get(room).players);
gameDataMap.get(room).playerData.delete(user)
client.to(room).emit('remove-player', user)
console.log(gameDataMap.get(room))
// Check if room deleted
if (!server.of("/").adapter.rooms.get(room)) {
clearInterval(gameDataMap.get(room).timerID);
console.log(`room ${room} has been deleted`);
// Remove game data from deleted room. Delete() function returns false if key does not exist.
gameDataMap.delete(room);
console.log(gameDataMap);
}
})
// Reset the scores of all players
client.on("reset-scores", (room) => {
// Get the room map
let roomDataMap = gameDataMap.get(room)
// For each user player map in the room data map, get the user's data
for (const [user, data] of roomDataMap.playerData.entries()) {
// Set their score to zero
data.score = 0;
// Broadcast that all scores are being reset
server.to(room).to(room).emit("reset-scores", {
user,
score: 0
});
}
});
client.on("update-user-guess", (room, user, guess, imagePath, score) => {
// update player's guess in game data map and emit to all players in room except sender
gameDataMap.get(room).playerData.get(user).currentGuess = guess
gameDataMap.get(room).playerData.get(user).score = score
client.to(room).to(room).emit("update-user-guess", {
user,
guess,
imagePath,
score
});
// handle all users guessing correctly
if (gameDataMap.get(room).allGuessesCorrect()) {
clearInterval(gameDataMap.get(room).timerID);
//send websocket message for "all guessed correctly message"
const message = "Everyone Guessed Correctly!"
server.to(room).emit("all-guessed-correct" , {message}); //emit to everyone including guesser
gameDataMap.get(room).startNewRound(server, room, gameDataMap);
}
});
client.on("draw-init", (room, x, y, draw_color, draw_width, context) => {
client.to(room).emit("cast-draw-init", x, y, draw_color, draw_width, context);
});
client.on("draw", (room, x, y) => {
client.to(room).emit("cast-draw", x, y);
});
client.on("draw-end", (room) => {
client.to(room).emit("cast-draw-end");
});
client.on("draw-clear", (room) => {
client.to(room).emit("cast-draw-clear");
});
client.on("draw-undo", (room) => {
client.to(room).emit("cast-draw-undo");
});
client.on('disconnect', () => {
console.log(`(2)User ${client.id} disconnected`); // Logs when a user disconnects
});
}
}
module.exports = SocketServerHandler;