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