const WebSocket = require("ws"); const { v4: uuidv4 } = require("uuid"); const puzzles = require('./puzzles.json'); const names = require('./assignedNames.json'); const crypto = require('crypto'); const wss = new WebSocket.Server({ port: 8080 }); const rooms = {}; // Stores rooms and their clients const wheel = [ 'lose a turn',800,350,450,700,300,600,5000, 300,600,300,500,800,550,400,300,900,500,'spin again', 900,'Bankrupt',600,400,300 ] //represents wheel in wheel of fortune game. function getRandomValue(array) { const randomIndex = crypto.randomInt(0, array.length); return array[randomIndex]; } function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { // Generate a cryptographically secure random index const j = crypto.randomInt(0, i + 1); // Swap elements [array[i], array[j]] = [array[j], array[i]]; } return array; } const DefaultNames = shuffleArray(names); let NameIndex = 0; function getRandomName() { if (NameIndex >= DefaultNames.length) { NameIndex = 0; }else{ NameIndex++; } //since we've incremented the index but not returned the value, //calculate the index again return NameIndex === 0 ? DefaultNames[NameIndex] : DefaultNames[NameIndex-1]; } function loadCurrentPuzzle(gameStateObject) { console.log(gameStateObject); //calculate the puzzle for now we'll just go through however many iterations there are //for each slot let letterArray = gameStateObject.puzzles[gameStateObject.puzzleLevel].answer.split(''); const given =gameStateObject.puzzles[gameStateObject.puzzleLevel].given; console.log('letterArray',letterArray); let puzzleArray = []; for (let letter of letterArray) { if (letter == ' ') { puzzleArray.push(' '); } letterFound = false; for (let g of given ) { if(g.toUpperCase() == letter.toUpperCase()) { puzzleArray.push(g); letterFound = true; } } if (!letterFound) {puzzleArray.push('');} } console.log(puzzleArray); gameStateObject.puzzles[gameStateObject.puzzleLevel].puzzle = puzzleArray; return { 'puzzleLevel':gameStateObject.puzzleLevel, 'given':gameStateObject.puzzles[gameStateObject.puzzleLevel].given, 'category':gameStateObject.puzzles[gameStateObject.puzzleLevel].category, 'puzzle':puzzleArray, 'levelsRemaining':(gameStateObject.puzzles.length - 1 - gameStateObject.puzzleLevel) } } // server.js wss.on("connection", (ws) => { ws.on("message", (message) => { const data = JSON.parse(message); if (data.type === "change_name") { const roomCode = ws.roomCode; const room = rooms[ws.roomCode]; console.log(room); console.log(room.leader.name,room.clients[0].name); console.log(room.clients.filter((i)=> i.name == data.deadName)); console.log(ws.name,ws.identifierToken); //change names in all places. //if the leader wants to change their name account for this //note this could probably be exploited oneday and break the game but whatever //since there is no logging in of users this is bound to happen. //find the names by index using the identifier Token which i user can't easily create //a name collision. const nameToChange = room.clients.findIndex((i)=> i.identifierToken == ws.identifierToken ) console.log(nameToChange); if (nameToChange != -1) { room.clients[nameToChange].name = data.newName; } if (room.leader.identifierToken == room.clients[nameToChange].identifierToken) { room.leader.name = data.newName; } //if the user is not the leader, in theory just change the name on the clients list //not sure if it exists in other places but test and see //send name changed event to client, similar to joined room but it will work when people are in game. ws.send(JSON.stringify({ type: "changed_name_you", roomCode, isLeader: rooms[roomCode].leader === ws, you:data.newName })); room.clients.forEach((client) => { client.send(JSON.stringify({ type: "changed_name", roomCode, isLeader: room.leader === ws , clients: room.clients.map((i)=> i.name), leaderName:room.leader.name })); }); } if (data.type === "create_room") { const roomCode = uuidv4().slice(0, 5); ws.name = getRandomName(); ws.identifierToken = uuidv4().slice(0, 5); rooms[roomCode] = { clients: [ws], leader: ws, gameState: { started:false, puzzles:shuffleArray(puzzles), puzzleLevel:0, }, }; ws.roomCode = roomCode; ws.send(JSON.stringify({ type: "room_created", roomCode, isLeader: true, leaderName: ws.name, clients: rooms[roomCode].clients.map((i)=> i.name), you:ws.name })); } if (data.type === "join_room") { const { roomCode } = data; console.log(rooms); console.log(rooms[roomCode],roomCode); if (!rooms[roomCode]) { ws.send(JSON.stringify({ type: "error", message: "Room not found" })); } else if (rooms[roomCode].gameState.started) { ws.send(JSON.stringify({ type: "error", message: "Game has already Started!!!" })); } else { ws.name = getRandomName(); ws.identifierToken = uuidv4().slice(0, 5); rooms[roomCode].clients.push(ws); ws.roomCode = roomCode; const room = rooms[ws.roomCode]; ws.send(JSON.stringify({ type: "joined_room_you", roomCode, isLeader: rooms[roomCode].leader === ws, you:ws.name })); room.clients.forEach((client) => { client.send(JSON.stringify({ type: "joined_room", roomCode, isLeader: rooms[roomCode].leader === ws , clients: rooms[roomCode].clients.map((i)=> i.name), leaderName:rooms[roomCode].leader.name })); }); } //console.log('clients: ',rooms[roomCode].leader.name); } if (data.type === "start_game") { const room = rooms[ws.roomCode]; room.gameState.started = true; console.log('game started for:',room); if (room && room.leader === ws) { room.clients.forEach((client) => { client.send(JSON.stringify({ type: "game_started", roomCode: ws.roomCode, puzzle:loadCurrentPuzzle(room.gameState) })); }); } else { ws.send(JSON.stringify({ type: "error", message: "Only the leader can start the game" })); } } if (data.type === "spin_wheel" || data.type === "guess_letter") { const room = rooms[ws.roomCode]; if (room && room.clients.includes(ws)) { // Handle spin and guess events if (data.type === "spin_wheel") { // Simulate a wheel spin result and update room state const spinResult = getRandomValue(wheel); room.gameState.spinResult = spinResult; room.clients.forEach((client) => client.send(JSON.stringify({ type: "spin_result", spinResult, player: ws === room.leader ? "Leader" : "Player" })) ); } if (data.type === "guess_letter") { const { letter } = data; // Handle guess logic (e.g., check if the letter is in the puzzle) const correctGuess = Math.random() > 0.5; // Random outcome for simplicity room.gameState.lastGuess = { letter, correct: correctGuess }; room.clients.forEach((client) => client.send(JSON.stringify({ type: "guess_result", letter, correct: correctGuess, player: ws === room.leader ? "Leader" : "Player" })) ); } } else { ws.send(JSON.stringify({ type: "error", message: "You are not in this room" })); } } }); ws.on("close", () => { if (ws.roomCode && rooms[ws.roomCode]) { console.log('closing'); const room = rooms[ws.roomCode]; const roomCode = ws.roomCode; room.clients = room.clients.filter((client) => client !== ws); if (room.leader === ws && room.clients.length > 0) { console.log('closing within room leader') room.leader = room.clients[0]; room.leader.send(JSON.stringify({ type: "new_leader" })); } room.clients.forEach((client) => client.send(JSON.stringify({ type: "joined_room", roomCode, isLeader: rooms[roomCode].leader === ws , clients: rooms[roomCode].clients.map((i)=> i.name), leaderName:rooms[roomCode].leader.name })) ); if (room.clients.length === 0) delete rooms[ws.roomCode]; } }); }); console.log("WebSocket server is running on ws://localhost:8080"); module.exports = { DefaultNames,NameIndex, getRandomName,getRandomValue,shuffleArray }