const roomController = require("../controller/room"); const gameController = require("../controller/game"); const youtubeService = require('../services/youtubeService'); module.exports = (io) => (socket) => { let currentRoomId = null; let currentUser = null; let phaseTimers = {}; const clearRoomTimers = (roomId) => { if (phaseTimers[roomId]) { console.log(`Clearing timer for room ${roomId}`); clearTimeout(phaseTimers[roomId]); delete phaseTimers[roomId]; } }; const startPhaseTimer = (roomId) => { clearRoomTimers(roomId); const gameState = gameController.getGameState(roomId); if (!gameState) { console.error(`Cannot start timer: no game state for room ${roomId}`); return; } const timeRemaining = gameController.getTimeRemaining(roomId) * 1000; console.log(`Starting ${gameState.phase} phase timer for room ${roomId} with ${timeRemaining}ms`); phaseTimers[roomId] = setTimeout(() => { console.log(`Timer expired for room ${roomId}, advancing phase from ${gameState.phase}`); const advanced = gameController.advancePhase(roomId); if (!advanced) { console.log(`Failed to advance phase for room ${roomId}`); return; } if (typeof advanced === 'boolean') { handleRoundStart(roomId); } else { const newPhase = gameController.getGameState(roomId).phase; console.log(`Advanced to ${newPhase} phase in room ${roomId}`); if (newPhase === 'guessing') { handleGuessingPhaseStart(roomId); } else if (newPhase === 'results') { handleResultsPhaseStart(roomId); } } }, timeRemaining); }; const handleRoundStart = (roomId) => { const roles = gameController.getRoles(roomId); const selectedSong = gameController.getSelectedSong(roomId); const timeLeft = gameController.getTimeRemaining(roomId); const songOptions = gameController.getSongOptions(roomId); io.to(roomId).emit('roles-assigned', roles); Object.entries(roles).forEach(([userId, role]) => { if (role === 'composer') { io.to(userId).emit('song-selected', selectedSong); } else { io.to(userId).emit('song-options', { songOptions }); } }); io.to(roomId).emit('round-started', { round: gameController.getRoundResults(roomId).round, timeRemaining: timeLeft, phase: 'composing' }); startPhaseTimer(roomId); }; const handleGuessingPhaseStart = (roomId) => { const gameState = gameController.getGameState(roomId); if (!gameState) return; const roles = gameController.getRoles(roomId); const songOptions = gameController.getSongOptions(roomId); const timeLeft = gameController.getTimeRemaining(roomId); console.log(`Starting guessing phase for room ${roomId} with ${Object.keys(roles).length} players`); io.to(roomId).emit('phase-changed', { phase: 'guessing', timeRemaining: timeLeft, message: 'Time to submit your final answer!' }); Object.entries(roles).forEach(([userId, role]) => { if (role === 'guesser') { io.to(userId).emit('guessing-phase-started', { timeRemaining: timeLeft, songOptions }); } }); startPhaseTimer(roomId); }; const handleResultsPhaseStart = (roomId) => { const results = gameController.getRoundResults(roomId); io.to(roomId).emit('round-results', results); }; socket.on("disconnect", () => { const roomId = roomController.getUserRoom(socket.id); if (roomId) { socket.to(roomId).emit("user-disconnected", socket.id); roomController.disconnectUser(socket.id); } }); socket.on("join-room", async ({roomId, name}) => { if (currentRoomId) return socket.emit("already-in-room", currentRoomId); roomId = roomId.toString().toUpperCase(); if (roomController.roomExists(roomId)) { if (!roomController.isRoomOpen(roomId)) { return socket.emit("room-closed", roomId); } currentUser = {id: socket.id, name: name.toString()}; await roomController.connectUserToRoom(roomId, currentUser); socket.join(roomId); const users = roomController.getRoomUsers(roomId); const votes = roomController.getPlaylistVotes(roomId); const availablePlaylists = roomController.getAvailablePlaylists(roomId); socket.emit("room-joined", roomId); socket.emit("room-users", users); socket.emit("playlist-votes-updated", votes); try { const details = await youtubeService.getPlaylistDetails(availablePlaylists); Object.keys(details).forEach(genre => { const playlistId = details[genre].id; details[genre].votes = votes[playlistId]?.length || 0; }); socket.emit("playlist-options", details); } catch (error) { console.error("Error sending playlist details to new user:", error); socket.emit("error", { message: "Failed to load playlists", details: error.message }); } socket.to(roomId).emit("user-connected", currentUser); io.to(roomId).emit("room-users-update", users); currentRoomId = roomId; } else { socket.emit("room-not-found", roomId); } }); socket.on("create-room", ({name}) => { if (!name) return socket.emit("room-name-required"); const roomId = Math.random().toString(36).substring(7).toUpperCase(); currentUser = {id: socket.id, name: name?.toString(), creator: true}; roomController.connectUserToRoom(roomId, currentUser); socket.join(roomId); socket.emit("room-created", roomId); currentRoomId = roomId; }); socket.on("start-game", async () => { const roomId = roomController.getUserRoom(socket.id); if (!roomId || !roomController.isUserHost(socket.id)) { return socket.emit("not-authorized"); } roomController.validateRoomMembers(io, roomId); const users = roomController.getRoomUsers(roomId); if (users.length < 2) { return socket.emit("error", { message: "At least 2 players are required" }); } if (!roomController.startGame(roomId)) return; gameController.initializeGameState(roomId); try { const success = await gameController.startNewRound(roomId); if (!success) { return socket.emit("error", { message: "Failed to start game - could not load songs" }); } io.to(roomId).emit("game-started"); handleRoundStart(roomId); } catch (error) { console.error("Error starting game:", error); socket.emit("error", { message: "Failed to start game due to an error" }); } }); socket.on("send-message", (messageData) => { const roomId = roomController.getUserRoom(socket.id); if (!roomId) return; const serverUsername = roomController.getUserName(socket.id); if (!messageData.sender || messageData.sender === "Player" || messageData.sender === "Anonymous") { if (serverUsername) { console.log(`Fixing missing username for ${socket.id}: using "${serverUsername}" instead of "${messageData.sender || 'none'}"`); messageData.sender = serverUsername; } else { console.warn(`Could not find username for user ${socket.id}`); } } socket.to(roomId).emit("chat-message", messageData); socket.emit("chat-message-confirmation", { ...messageData, sender: messageData.sender }); }); socket.on("get-user-info", () => { if (currentUser) socket.emit("user-info", currentUser); }); socket.on("get-room-users", () => { const roomId = roomController.getUserRoom(socket.id); if (roomId) { const users = roomController.getRoomUsers(roomId); socket.emit("room-users", users); } }); socket.on("check-host-status", () => { socket.emit("host-status", { isHost: roomController.isUserHost(socket.id) }); }); socket.on("get-room-code", () => { if (currentRoomId) socket.emit("room-code", currentRoomId); }); socket.on("submit-frequency", ({ frequency, isPlaying }) => { const roomId = roomController.getUserRoom(socket.id); if (!roomId) return; const userRole = gameController.getUserRole(roomId, socket.id); if (userRole !== 'composer') return; if (gameController.updateFrequency(roomId, frequency)) { socket.to(roomId).emit("frequency-update", { frequency, isPlaying }); } }); socket.on("submit-guess", ({ songId }) => { const roomId = roomController.getUserRoom(socket.id); if (!roomId) return; console.log(`User ${socket.id} submitted guess: Song ID ${songId}`); const gamePhase = gameController.getGameState(roomId)?.phase; if (gamePhase !== 'guessing') { console.log(`Ignoring guess: room ${roomId} is in ${gamePhase} phase, not guessing`); return; } const result = gameController.submitGuess(roomId, socket.id, songId); if (result) { console.log(`Guess result for ${socket.id}:`, result); socket.emit("guess-result", result); const currentComposer = gameController.getCurrentComposer(roomId); if (currentComposer) { const guesserName = roomController.getUserName(socket.id) || "Someone"; io.to(currentComposer).emit("player-guessed", { guesserName, isCorrect: result.isCorrect }); } } }); socket.on("next-round", async () => { const roomId = roomController.getUserRoom(socket.id); if (!roomId || !roomController.isUserHost(socket.id)) return; roomController.validateRoomMembers(io, roomId); const users = roomController.getRoomUsers(roomId); if (users.length < 2) { return socket.emit("error", { message: "At least 2 players are required" }); } try { const success = await gameController.startNewRound(roomId); if (success) { handleRoundStart(roomId); } else { socket.emit("error", { message: "Failed to start next round" }); } } catch (error) { console.error("Error starting next round:", error); socket.emit("error", { message: "Failed to start next round due to an error" }); } }); socket.on("get-current-frequency", () => { const roomId = roomController.getUserRoom(socket.id); if (roomId) { socket.emit("current-frequency", { frequency: gameController.getCurrentFrequency(roomId) }); } }); socket.on("get-playlist-songs", async () => { try { const roomId = roomController.getUserRoom(socket.id); if (!roomId) { throw new Error("User not in a room"); } const gameState = gameController.getGameState(roomId); const playlistId = gameState?.selectedPlaylist || null; console.log(`Fetching songs for playlist ${playlistId}`); const songs = await youtubeService.fetchPlaylistSongs(playlistId); socket.emit("playlist-songs", { songs }); } catch (error) { console.error("Error sending playlist songs:", error); socket.emit("playlist-songs", { songs: [] }); } }); socket.on("get-playlist-options", async () => { try { const roomId = roomController.getUserRoom(socket.id); if (!roomId) { throw new Error("User not in a room"); } const availablePlaylists = roomController.getAvailablePlaylists(roomId); const details = await youtubeService.getPlaylistDetails(availablePlaylists); if (Object.keys(details).length === 0) { const newPlaylists = await youtubeService.getRandomPlaylists(3); const newDetails = await youtubeService.getPlaylistDetails(newPlaylists); roomController.updateAvailablePlaylists(roomId, newPlaylists); socket.emit("playlist-options", newDetails); } else { socket.emit("playlist-options", details); } } catch (error) { console.error("Error fetching playlist options:", error); socket.emit("error", { message: "Failed to load playlists", details: error.message }); } }); socket.on("vote-playlist", ({ playlistId }) => { const roomId = roomController.getUserRoom(socket.id); if (!roomId) return; if (roomController.voteForPlaylist(roomId, socket.id, playlistId)) { const votes = roomController.getPlaylistVotes(roomId); io.to(roomId).emit("playlist-votes-updated", votes); } }); };