diff --git a/client/src/pages/Game/Game.jsx b/client/src/pages/Game/Game.jsx index 6daa3ff..9e67f10 100644 --- a/client/src/pages/Game/Game.jsx +++ b/client/src/pages/Game/Game.jsx @@ -1,6 +1,6 @@ import "./styles.sass"; import {SocketContext} from "@/common/contexts/SocketContext"; -import {useContext, useState, useEffect, useRef} from "react"; +import {useContext, useState, useEffect, useRef, useCallback} from "react"; import MusicSlider from "@/pages/Game/components/MusicSlider"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faMessage, faMusic, faHeadphones, faClock, faCrown} from "@fortawesome/free-solid-svg-icons"; @@ -8,9 +8,9 @@ import {faMessage, faMusic, faHeadphones, faClock, faCrown} from "@fortawesome/f export const Game = () => { const {send, on, socket} = useContext(SocketContext); - const [role, setRole] = useState(null); // 'composer' or 'guesser' + const [role, setRole] = useState(null); const [round, setRound] = useState(1); - const [phase, setPhase] = useState("waiting"); // waiting, composing, guessing, results + const [phase, setPhase] = useState("waiting"); const [timeLeft, setTimeLeft] = useState(30); const [frequency, setFrequency] = useState(440); const [currentSong, setCurrentSong] = useState(null); @@ -19,8 +19,7 @@ export const Game = () => { const [selectedSong, setSelectedSong] = useState(null); const [guessResult, setGuessResult] = useState(null); const [isHost, setIsHost] = useState(false); - - // Chat state + const [messages, setMessages] = useState([]); const [inputValue, setInputValue] = useState(""); const [connectedUsers, setConnectedUsers] = useState([]); @@ -29,104 +28,88 @@ export const Game = () => { const timerIntervalRef = useRef(null); useEffect(() => { - setPhase("waiting"); - - const handleRolesAssigned = (roles) => { - console.log("Roles assigned:", roles); - const myRole = roles[socket?.id]; - - if (myRole) { - setRole(myRole); - + const eventHandlers = { + "roles-assigned": (roles) => { + const myRole = roles[socket?.id]; + if (myRole) { + setRole(myRole); + setMessages(prev => [...prev, { + system: true, + text: myRole === "composer" + ? "Du bist der Komponist! Spiele den Song mit dem Tonregler." + : "Du bist ein Rater! Höre die Frequenzen und versuche, den Song zu erkennen." + }]); + } + }, + "song-selected": setCurrentSong, + "round-started": (data) => { + setRound(data.round); + setPhase("composing"); + setTimeLeft(data.timeRemaining); + }, + "guessing-phase-started": (data) => { + setPhase("guessing"); + setTimeLeft(data.timeRemaining); + setSongOptions(data.songOptions); + }, + "guess-result": (result) => { + setGuessResult(result.isCorrect); + setCurrentSong(result.correctSong); + }, + "round-results": (results) => { + setPhase("results"); + setScores(results.scores); + if (!currentSong) { + setCurrentSong(results.selectedSong); + } + }, + "room-users-update": (users) => { + setConnectedUsers(users); + const currentUser = users.find(u => u.id === socket?.id); + if (currentUser) setUsername(currentUser.name); + }, + "host-status": (status) => setIsHost(status.isHost), + "chat-message": (msg) => setMessages(prev => [...prev, msg]), + "user-connected": (userData) => { setMessages(prev => [...prev, { system: true, - text: myRole === "composer" - ? "Du bist der Komponist! Spiele den Song mit dem Tonregler." - : "Du bist ein Rater! Höre die Frequenzen und versuche, den Song zu erkennen." + text: `${userData.name} ist beigetreten` }]); - } else { - console.error("No role assigned to this player!"); + setConnectedUsers(prev => [...prev, userData]); + }, + "user-disconnected": (userId) => { + setConnectedUsers(prev => { + const user = prev.find(u => u.id === userId); + if (user) { + setMessages(prevMsgs => [...prevMsgs, { + system: true, + text: `${user.name} hat den Raum verlassen` + }]); + } + return prev.filter(u => u.id !== userId); + }); + }, + "frequency-update": (data) => { + if (phase === "composing") { + console.log("Received frequency update:", data.frequency); + setFrequency(data.frequency); + } } }; - - const handleSongSelected = (song) => { - console.log("Song selected:", song); - setCurrentSong(song); - }; - - const handleRoundStarted = (data) => { - console.log("Round started:", data); - setRound(data.round); - setPhase("composing"); - setTimeLeft(data.timeRemaining); - }; - - const handleGuessingPhaseStarted = (data) => { - console.log("Guessing phase started:", data); - setPhase("guessing"); - setTimeLeft(data.timeRemaining); - setSongOptions(data.songOptions); - }; - - const handleGuessResult = (result) => { - console.log("Guess result:", result); - setGuessResult(result.isCorrect); - setCurrentSong(result.correctSong); - }; - - const handleRoundResults = (results) => { - console.log("Round results:", results); - setPhase("results"); - setScores(results.scores); - if (!currentSong) { - setCurrentSong(results.selectedSong); - } - }; - - const handleRoomUsers = (users) => { - console.log("Room users:", users); - setConnectedUsers(users); - - const currentUser = users.find(u => u.id === socket?.id); - if (currentUser) { - setUsername(currentUser.name); - } - }; - - const handleHostStatus = (status) => { - setIsHost(status.isHost); - }; - const cleanupRolesAssigned = on("roles-assigned", handleRolesAssigned); - const cleanupSongSelected = on("song-selected", handleSongSelected); - const cleanupRoundStarted = on("round-started", handleRoundStarted); - const cleanupGuessingPhaseStarted = on("guessing-phase-started", handleGuessingPhaseStarted); - const cleanupGuessResult = on("guess-result", handleGuessResult); - const cleanupRoundResults = on("round-results", handleRoundResults); - const cleanupRoomUsers = on("room-users-update", handleRoomUsers); - const cleanupHostStatus = on("host-status", handleHostStatus); + const cleanupFunctions = Object.entries(eventHandlers).map( + ([event, handler]) => on(event, handler) + ); send("get-room-users"); - send("check-host-status"); - - return () => { - cleanupRolesAssigned(); - cleanupSongSelected(); - cleanupRoundStarted(); - cleanupGuessingPhaseStarted(); - cleanupGuessResult(); - cleanupRoundResults(); - cleanupRoomUsers(); - cleanupHostStatus(); - }; - }, [socket, on, send]); + + return () => cleanupFunctions.forEach(cleanup => cleanup()); + }, [socket, on, send, role, currentSong, phase]); useEffect(() => { - if (timerIntervalRef.current) { - clearInterval(timerIntervalRef.current); - } - + if (timerIntervalRef.current) clearInterval(timerIntervalRef.current); + if (phase !== "waiting" && phase !== "results") { timerIntervalRef.current = setInterval(() => { setTimeLeft(prev => Math.max(0, prev - 1)); @@ -134,92 +117,43 @@ export const Game = () => { } return () => { - if (timerIntervalRef.current) { - clearInterval(timerIntervalRef.current); - } + if (timerIntervalRef.current) clearInterval(timerIntervalRef.current); }; }, [phase]); - useEffect(() => { - const handleChatMessage = (messageData) => { - setMessages(prev => [...prev, messageData]); - }; - - const handleUserConnected = (userData) => { - setMessages(prev => [...prev, { - system: true, - text: `${userData.name} ist beigetreten` - }]); - setConnectedUsers(prev => [...prev, userData]); - }; - - const handleUserDisconnected = (userId) => { - setConnectedUsers(prev => { - const user = prev.find(u => u.id === userId); - if (user) { - setMessages(prevMsgs => [...prevMsgs, { - system: true, - text: `${user.name} hat den Raum verlassen` - }]); - } - return prev.filter(u => u.id !== userId); - }); - }; - - const handleFrequencyUpdate = (data) => { - if (role === "guesser") { - setFrequency(data.frequency); - } - }; - - const cleanupChatMessage = on("chat-message", handleChatMessage); - const cleanupUserConnected = on("user-connected", handleUserConnected); - const cleanupUserDisconnected = on("user-disconnected", handleUserDisconnected); - const cleanupFrequencyUpdate = on("frequency-update", handleFrequencyUpdate); - - return () => { - cleanupChatMessage(); - cleanupUserConnected(); - cleanupUserDisconnected(); - cleanupFrequencyUpdate(); - }; - }, [on, role]); - useEffect(() => { messageEndRef.current?.scrollIntoView({behavior: "smooth"}); }, [messages]); - const handleFrequencyChange = (newFrequency) => { + const handleFrequencyChange = useCallback((newFrequency) => { setFrequency(newFrequency); if (role === "composer") { send("submit-frequency", { frequency: newFrequency }); } - }; + }, [role, send]); - const handleSendMessage = () => { + const handleSendMessage = useCallback(() => { if (inputValue.trim()) { - const messageData = { - text: inputValue, - sender: username - }; + const messageData = { text: inputValue, sender: username }; send("send-message", messageData); setMessages(prev => [...prev, messageData]); setInputValue(""); } - }; + }, [inputValue, username, send]); - const handleSongSelect = (song) => { + const handleSongSelect = useCallback((song) => { setSelectedSong(song); - }; + send("submit-guess", { songId: song.id }); + }, [send]); - const handleNextRound = () => { + const handleNextRound = useCallback(() => { send("next-round"); - setSelectedSong(null); setGuessResult(null); setTimeLeft(0); - }; + }, [send]); + // Phase-specific content rendering const renderPhaseContent = () => { switch (phase) { case "waiting": @@ -273,13 +207,11 @@ export const Game = () => { - {role === "composer" && ( + {role === "composer" ? (

Die Rater versuchen nun, deinen Song zu erraten...

- )} - - {role === "guesser" && ( + ) : (

Welchen Song hat der Komponist gespielt?

@@ -308,13 +240,11 @@ export const Game = () => {

Runde {round}: Ergebnisse

- {role === "composer" && ( + {role === "composer" ? (

Die Rater haben versucht, deinen Song zu erraten.

- )} - - {role === "guesser" && ( + ) : (
{currentSong && (
@@ -360,12 +290,13 @@ export const Game = () => {
- {isHost && ( + {isHost ? ( + ) : ( +

Warten auf Rundenwechsel durch Host...

)} - {!isHost &&

Warten auf Rundenwechsel durch Host...

}
); @@ -417,18 +348,23 @@ export const Game = () => { setInputValue(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} - placeholder="Gib eine Nachricht ein..." + placeholder="Gib eine Nachricht ein..." />
- + {/* Only render MusicSlider during composing phase */} + {phase === "composing" && ( + + )} ); -} \ No newline at end of file +}; + +export default Game; \ No newline at end of file diff --git a/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx b/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx index 2341be9..4c18df8 100644 --- a/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx +++ b/client/src/pages/Game/components/MusicSlider/MusicSlider.jsx @@ -9,30 +9,67 @@ export const MusicSlider = ({ isReadOnly = false, onFrequencyChange, frequency: const oscillatorRef = useRef(null); const gainNodeRef = useRef(null); const sliderRef = useRef(null); + const hasInteractedRef = useRef(false); + + useEffect(() => { + const initAudioContext = () => { + if (!audioContextRef.current && !hasInteractedRef.current) { + hasInteractedRef.current = true; + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + if (!oscillatorRef.current) { + startAudio(); + } + document.removeEventListener('click', initAudioContext); + document.removeEventListener('touchstart', initAudioContext); + document.removeEventListener('keydown', initAudioContext); + } + }; + + document.addEventListener('click', initAudioContext); + document.addEventListener('touchstart', initAudioContext); + document.addEventListener('keydown', initAudioContext); + + return () => { + document.removeEventListener('click', initAudioContext); + document.removeEventListener('touchstart', initAudioContext); + document.removeEventListener('keydown', initAudioContext); + }; + }, []); useEffect(() => { if (externalFrequency !== undefined && !dragging) { setFrequency(externalFrequency); - if (!isPlaying && !isReadOnly) { - startAudio(); + if (audioContextRef.current) { + if (!isPlaying) { + startAudio(); + } else if (oscillatorRef.current) { + oscillatorRef.current.frequency.setValueAtTime( + externalFrequency, + audioContextRef.current.currentTime + ); + } } } - }, [externalFrequency, dragging, isPlaying, isReadOnly]); + }, [externalFrequency, dragging, isPlaying]); const handleMouseDown = (e) => { if (isReadOnly) return; setDragging(true); - startAudio(); handleFrequencyChange(e); + + if (!audioContextRef.current) { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + } + + if (!isPlaying) { + startAudio(); + } }; const handleMouseUp = () => { setDragging(false); - if (!isReadOnly) { - stopAudio(); - } }; const handleFrequencyChange = (e) => { @@ -63,51 +100,89 @@ export const MusicSlider = ({ isReadOnly = false, onFrequencyChange, frequency: }, [dragging]); useEffect(() => { - if (isPlaying) { - if (oscillatorRef.current) { - oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); - } + if (isPlaying && oscillatorRef.current && audioContextRef.current) { + oscillatorRef.current.frequency.setValueAtTime( + frequency, + audioContextRef.current.currentTime + ); } }, [frequency, isPlaying]); const startAudio = () => { if (!audioContextRef.current) { - audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + try { + audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); + } catch (e) { + console.error("AudioContext could not be created:", e); + return; + } } - if (!oscillatorRef.current) { - oscillatorRef.current = audioContextRef.current.createOscillator(); - oscillatorRef.current.type = 'sine'; - oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); - gainNodeRef.current = audioContextRef.current.createGain(); - gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime); - - oscillatorRef.current.connect(gainNodeRef.current); - gainNodeRef.current.connect(audioContextRef.current.destination); - oscillatorRef.current.start(); - } else { - oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); + if (audioContextRef.current.state === 'suspended') { + audioContextRef.current.resume().catch(err => { + console.error("Could not resume AudioContext:", err); + }); + } + + try { + if (!oscillatorRef.current) { + oscillatorRef.current = audioContextRef.current.createOscillator(); + oscillatorRef.current.type = 'sine'; + oscillatorRef.current.frequency.setValueAtTime( + frequency, + audioContextRef.current.currentTime + ); + + gainNodeRef.current = audioContextRef.current.createGain(); + gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime); + + oscillatorRef.current.connect(gainNodeRef.current); + gainNodeRef.current.connect(audioContextRef.current.destination); + oscillatorRef.current.start(); + console.log("Audio started successfully"); + } else { + oscillatorRef.current.frequency.setValueAtTime( + frequency, + audioContextRef.current.currentTime + ); + } + setIsPlaying(true); + } catch (e) { + console.error("Error starting audio:", e); } - setIsPlaying(true); }; const stopAudio = () => { if (oscillatorRef.current) { - oscillatorRef.current.stop(); - oscillatorRef.current.disconnect(); - oscillatorRef.current = null; + try { + oscillatorRef.current.stop(); + oscillatorRef.current.disconnect(); + oscillatorRef.current = null; + setIsPlaying(false); + console.log("Audio stopped"); + } catch (e) { + console.error("Error stopping audio:", e); + } } - setIsPlaying(false); }; useEffect(() => { return () => { if (oscillatorRef.current) { - oscillatorRef.current.stop(); - oscillatorRef.current.disconnect(); + try { + oscillatorRef.current.stop(); + oscillatorRef.current.disconnect(); + } catch (e) { + console.error("Error cleaning up oscillator:", e); + } } + if (audioContextRef.current && audioContextRef.current.state !== 'closed') { - audioContextRef.current.close(); + try { + audioContextRef.current.close(); + } catch (e) { + console.error("Error closing AudioContext:", e); + } } }; }, []); @@ -137,5 +212,5 @@ export const MusicSlider = ({ isReadOnly = false, onFrequencyChange, frequency: - ) -} \ No newline at end of file + ); +}; \ No newline at end of file diff --git a/server/controller/game.js b/server/controller/game.js index e7cca59..f4c6f12 100644 --- a/server/controller/game.js +++ b/server/controller/game.js @@ -1,12 +1,17 @@ const roomController = require('./room'); const SONGS = [ - { id: 1, title: "Black Steam", artist: "Carrot Quest GmbH", coverUrl: "https://mir-s3-cdn-cf.behance.net/project_modules/1400/fe529a64193929.5aca8500ba9ab.jpg" }, - { id: 2, title: "Sunset Dreams", artist: "Ocean Waves", coverUrl: "https://place-hold.it/500x500/" }, - { id: 3, title: "Neon Nights", artist: "Electric Avenue", coverUrl: "https://place-hold.it/500x500/" }, - { id: 4, title: "Mountain Echo", artist: "Wild Terrain", coverUrl: "https://place-hold.it/500x500/" }, - { id: 5, title: "Urban Jungle", artist: "City Dwellers", coverUrl: "https://place-hold.it/500x500/" }, - { id: 6, title: "Cosmic Journey", artist: "Stargazers", coverUrl: "https://place-hold.it/500x500/" } + { + id: 1, + title: "Black Steam", + artist: "Carrot Quest GmbH", + coverUrl: "https://mir-s3-cdn-cf.behance.net/project_modules/1400/fe529a64193929.5aca8500ba9ab.jpg" + }, + {id: 2, title: "Sunset Dreams", artist: "Ocean Waves", coverUrl: "https://place-hold.it/500x500/"}, + {id: 3, title: "Neon Nights", artist: "Electric Avenue", coverUrl: "https://place-hold.it/500x500/"}, + {id: 4, title: "Mountain Echo", artist: "Wild Terrain", coverUrl: "https://place-hold.it/500x500/"}, + {id: 5, title: "Urban Jungle", artist: "City Dwellers", coverUrl: "https://place-hold.it/500x500/"}, + {id: 6, title: "Cosmic Journey", artist: "Stargazers", coverUrl: "https://place-hold.it/500x500/"} ]; const gameStates = {}; @@ -35,72 +40,75 @@ const initializeGameState = (roomId) => { gameStates[roomId].scores[user.id] = 0; }); } - + return gameStates[roomId]; }; const startNewRound = (roomId) => { const gameState = gameStates[roomId]; if (!gameState) return false; - + + const users = roomController.getRoomUsers(roomId); + if (users.length < 2) return false; + gameState.round += 1; gameState.phase = 'composing'; gameState.guessResults = {}; gameState.roundStartTime = Date.now(); - - const users = roomController.getRoomUsers(roomId); - if (users.length < 2) return false; - - console.log(`Starting round ${gameState.round} in room ${roomId} with ${users.length} users`); - gameState.roles = {}; - if (gameState.round === 1 || !gameState.currentComposer) { - const composerIndex = Math.floor(Math.random() * users.length); - gameState.currentComposer = users[composerIndex].id; - } else { - const currentIndex = users.findIndex(user => user.id === gameState.currentComposer); - - if (currentIndex === -1) { - const composerIndex = Math.floor(Math.random() * users.length); - gameState.currentComposer = users[composerIndex].id; - } else { - const nextIndex = (currentIndex + 1) % users.length; - gameState.currentComposer = users[nextIndex].id; - } - } + gameState.currentComposer = determineNextComposer(roomId, gameState, users); users.forEach(user => { gameState.roles[user.id] = user.id === gameState.currentComposer ? 'composer' : 'guesser'; - console.log(`User ${user.name} (${user.id}) assigned role: ${gameState.roles[user.id]}`); }); + selectSongAndOptions(gameState); + + return true; +}; + +const determineNextComposer = (roomId, gameState, users) => { + if (gameState.round === 1 || !gameState.currentComposer) { + return users[Math.floor(Math.random() * users.length)].id; + } + + const currentIndex = users.findIndex(user => user.id === gameState.currentComposer); + + if (currentIndex === -1) { + return users[Math.floor(Math.random() * users.length)].id; + } + + return users[(currentIndex + 1) % users.length].id; +}; + +const selectSongAndOptions = (gameState) => { gameState.selectedSong = SONGS[Math.floor(Math.random() * SONGS.length)]; const songOptions = [...SONGS].sort(() => Math.random() - 0.5).slice(0, 5); + if (!songOptions.includes(gameState.selectedSong)) { songOptions[Math.floor(Math.random() * songOptions.length)] = gameState.selectedSong; } + gameState.songOptions = songOptions; - - return true; }; const getTimeRemaining = (roomId) => { const gameState = gameStates[roomId]; if (!gameState || !gameState.roundStartTime) return 0; - + const phaseDuration = gameState.phaseTimeLimit[gameState.phase] * 1000; const timeElapsed = Date.now() - gameState.roundStartTime; const timeRemaining = Math.max(0, phaseDuration - timeElapsed); - + return Math.ceil(timeRemaining / 1000); }; const advancePhase = (roomId) => { const gameState = gameStates[roomId]; if (!gameState) return false; - + if (gameState.phase === 'composing') { gameState.phase = 'guessing'; gameState.roundStartTime = Date.now(); @@ -111,7 +119,7 @@ const advancePhase = (roomId) => { } else if (gameState.phase === 'results') { return startNewRound(roomId); } - + return false; }; @@ -121,38 +129,37 @@ const updateFrequency = (roomId, frequency) => { return true; }; -const getCurrentFrequency = (roomId) => { - return gameStates[roomId]?.lastFrequency || 440; -}; - const submitGuess = (roomId, userId, songId) => { const gameState = gameStates[roomId]; if (!gameState || gameState.phase !== 'guessing' || gameState.roles[userId] !== 'guesser') { return false; } - - const isCorrect = gameState.selectedSong.id === songId; - gameState.guessResults[userId] = { - songId, - isCorrect, - points: isCorrect ? 10 : 0 - }; + + const isCorrect = gameState.selectedSong.id === parseInt(songId); + const points = isCorrect ? 10 : 0; + + gameState.guessResults[userId] = {songId, isCorrect, points}; if (isCorrect) { - gameState.scores[userId] = (gameState.scores[userId] || 0) + 10; + gameState.scores[userId] = (gameState.scores[userId] || 0) + points; } - + return { isCorrect, correctSong: gameState.selectedSong, - points: isCorrect ? 10 : 0 + points }; }; +const getCurrentFrequency = (roomId) => gameStates[roomId]?.lastFrequency || 440; +const getRoles = (roomId) => gameStates[roomId]?.roles || {}; +const getUserRole = (roomId, userId) => gameStates[roomId]?.roles[userId] || null; +const getSongOptions = (roomId) => gameStates[roomId]?.songOptions || []; +const getSelectedSong = (roomId) => gameStates[roomId]?.selectedSong || null; const getRoundResults = (roomId) => { const gameState = gameStates[roomId]; - if (!gameState) return null; - + if (!gameState) return {round: 0, scores: {}, guessResults: {}, selectedSong: null}; + return { round: gameState.round, selectedSong: gameState.selectedSong, @@ -161,22 +168,6 @@ const getRoundResults = (roomId) => { }; }; -const getRoles = (roomId) => { - return gameStates[roomId]?.roles || {}; -}; - -const getUserRole = (roomId, userId) => { - return gameStates[roomId]?.roles[userId] || null; -}; - -const getSongOptions = (roomId) => { - return gameStates[roomId]?.songOptions || []; -}; - -const getSelectedSong = (roomId) => { - return gameStates[roomId]?.selectedSong || null; -}; - const cleanupGameState = (roomId) => { delete gameStates[roomId]; }; diff --git a/server/handler/connection.js b/server/handler/connection.js index 8a7ca06..96c1e32 100644 --- a/server/handler/connection.js +++ b/server/handler/connection.js @@ -4,7 +4,6 @@ const gameController = require("../controller/game"); module.exports = (io) => (socket) => { let currentRoomId = null; let currentUser = null; - let phaseTimers = {}; const clearRoomTimers = (roomId) => { @@ -18,62 +17,75 @@ module.exports = (io) => (socket) => { clearRoomTimers(roomId); const timeRemaining = gameController.getTimeRemaining(roomId) * 1000; - + phaseTimers[roomId] = setTimeout(() => { const advanced = gameController.advancePhase(roomId); - if (advanced) { + if (!advanced) return; + + if (typeof advanced === 'boolean') { + handleRoundStart(roomId); + } else { const currentPhase = advanced.phase || 'results'; - + if (currentPhase === 'guessing') { - const roles = gameController.getRoles(roomId); - const songOptions = gameController.getSongOptions(roomId); - const timeLeft = gameController.getTimeRemaining(roomId); - - Object.entries(roles).forEach(([userId, role]) => { - if (role === 'guesser') { - io.to(userId).emit('guessing-phase-started', { - timeRemaining: timeLeft, - songOptions - }); - } - }); - - startPhaseTimer(roomId); - } - else if (currentPhase === 'results') { - const results = gameController.getRoundResults(roomId); - io.to(roomId).emit('round-results', results); - } - else if (typeof advanced === 'boolean' && advanced) { - const roles = gameController.getRoles(roomId); - const selectedSong = gameController.getSelectedSong(roomId); - const timeLeft = gameController.getTimeRemaining(roomId); - - io.to(roomId).emit('roles-assigned', roles); - - Object.entries(roles).forEach(([userId, role]) => { - if (role === 'composer') { - io.to(userId).emit('song-selected', selectedSong); - } - }); - - io.to(roomId).emit('round-started', { - round: gameController.getRoundResults(roomId).round, - timeRemaining: timeLeft - }); - - startPhaseTimer(roomId); + handleGuessingPhaseStart(roomId); + } else if (currentPhase === 'results') { + handleResultsPhaseStart(roomId); } } }, timeRemaining); }; + const handleRoundStart = (roomId) => { + const roles = gameController.getRoles(roomId); + const selectedSong = gameController.getSelectedSong(roomId); + const timeLeft = gameController.getTimeRemaining(roomId); + + io.to(roomId).emit('roles-assigned', roles); + + Object.entries(roles).forEach(([userId, role]) => { + if (role === 'composer') { + io.to(userId).emit('song-selected', selectedSong); + } + }); + + io.to(roomId).emit('round-started', { + round: gameController.getRoundResults(roomId).round, + timeRemaining: timeLeft + }); + + startPhaseTimer(roomId); + }; + + const handleGuessingPhaseStart = (roomId) => { + const roles = gameController.getRoles(roomId); + const songOptions = gameController.getSongOptions(roomId); + const timeLeft = gameController.getTimeRemaining(roomId); + + 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); + if (roomId) { + socket.to(roomId).emit("user-disconnected", socket.id); + roomController.disconnectUser(socket.id); + } }); socket.on("join-room", ({roomId, name}) => { @@ -92,7 +104,6 @@ module.exports = (io) => (socket) => { const users = roomController.getRoomUsers(roomId); io.to(roomId).emit("room-users-update", users); - socket.to(roomId).emit("user-connected", currentUser); socket.emit("room-joined", roomId); @@ -104,6 +115,7 @@ module.exports = (io) => (socket) => { 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); @@ -114,51 +126,32 @@ module.exports = (io) => (socket) => { socket.on("start-game", () => { const roomId = roomController.getUserRoom(socket.id); - if (roomId && roomController.isUserHost(socket.id)) { - roomController.validateRoomMembers(io, roomId); - const roomUsers = roomController.getRoomUsers(roomId); - - if (roomController.startGame(roomId)) { - gameController.initializeGameState(roomId); - - if (gameController.startNewRound(roomId)) { - const roles = gameController.getRoles(roomId); - const selectedSong = gameController.getSelectedSong(roomId); - - io.to(roomId).emit("game-started"); - - io.to(roomId).emit("roles-assigned", roles); - - Object.entries(roles).forEach(([userId, role]) => { - if (role === 'composer') { - io.to(userId).emit("song-selected", selectedSong); - } - }); - - io.to(roomId).emit("round-started", { - round: 1, - timeRemaining: gameController.getTimeRemaining(roomId) - }); - - startPhaseTimer(roomId); - } - } else { - socket.emit("not-authorized"); - } + 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); + if (!gameController.startNewRound(roomId)) return; + + io.to(roomId).emit("game-started"); + handleRoundStart(roomId); }); socket.on("send-message", (messageData) => { const roomId = roomController.getUserRoom(socket.id); - if (roomId) { - socket.to(roomId).emit("chat-message", messageData); - } + if (roomId) socket.to(roomId).emit("chat-message", messageData); }); socket.on("get-user-info", () => { - if (currentUser) { - socket.emit("user-info", currentUser); - } + if (currentUser) socket.emit("user-info", currentUser); }); socket.on("get-room-users", () => { @@ -170,72 +163,55 @@ module.exports = (io) => (socket) => { }); socket.on("check-host-status", () => { - const isHost = roomController.isUserHost(socket.id); - socket.emit("host-status", { isHost }); + socket.emit("host-status", { isHost: roomController.isUserHost(socket.id) }); }); socket.on("get-room-code", () => { - if (currentRoomId) { - socket.emit("room-code", currentRoomId); - } + if (currentRoomId) socket.emit("room-code", currentRoomId); }); - + socket.on("submit-frequency", ({ frequency }) => { const roomId = roomController.getUserRoom(socket.id); if (!roomId) return; - + const userRole = gameController.getUserRole(roomId, socket.id); - if (userRole === 'composer') { - if (gameController.updateFrequency(roomId, frequency)) { - socket.to(roomId).emit("frequency-update", { frequency }); - } + if (userRole !== 'composer') return; + + if (gameController.updateFrequency(roomId, frequency)) { + socket.to(roomId).emit("frequency-update", { frequency }); } }); socket.on("submit-guess", ({ songId }) => { const roomId = roomController.getUserRoom(socket.id); if (!roomId) return; - + const result = gameController.submitGuess(roomId, socket.id, songId); - if (result) { - socket.emit("guess-result", result); - } + if (result) socket.emit("guess-result", result); }); socket.on("next-round", () => { const roomId = roomController.getUserRoom(socket.id); if (!roomId || !roomController.isUserHost(socket.id)) return; - + roomController.validateRoomMembers(io, roomId); - const roomUsers = roomController.getRoomUsers(roomId); - + const users = roomController.getRoomUsers(roomId); + + if (users.length < 2) { + return socket.emit("error", { message: "At least 2 players are required" }); + } + if (gameController.startNewRound(roomId)) { - const roles = gameController.getRoles(roomId); - const selectedSong = gameController.getSelectedSong(roomId); - const timeLeft = gameController.getTimeRemaining(roomId); - - io.to(roomId).emit("roles-assigned", roles); - - Object.entries(roles).forEach(([userId, role]) => { - if (role === 'composer') { - io.to(userId).emit("song-selected", selectedSong); - } - }); - - io.to(roomId).emit("round-started", { - round: gameController.getRoundResults(roomId).round, - timeRemaining: timeLeft - }); - - startPhaseTimer(roomId); + handleRoundStart(roomId); } }); socket.on("get-current-frequency", () => { const roomId = roomController.getUserRoom(socket.id); - if (!roomId) return; - - const frequency = gameController.getCurrentFrequency(roomId); - socket.emit("current-frequency", { frequency }); + if (roomId) { + socket.emit("current-frequency", { + frequency: gameController.getCurrentFrequency(roomId) + }); + } }); }; \ No newline at end of file