Fix audio not playing

This commit is contained in:
2025-03-01 00:32:03 +01:00
parent 7d7dd263fe
commit 87fc9a2f39
4 changed files with 372 additions and 394 deletions

View File

@ -1,6 +1,6 @@
import "./styles.sass"; import "./styles.sass";
import {SocketContext} from "@/common/contexts/SocketContext"; 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 MusicSlider from "@/pages/Game/components/MusicSlider";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faMessage, faMusic, faHeadphones, faClock, faCrown} from "@fortawesome/free-solid-svg-icons"; 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 = () => { export const Game = () => {
const {send, on, socket} = useContext(SocketContext); 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 [round, setRound] = useState(1);
const [phase, setPhase] = useState("waiting"); // waiting, composing, guessing, results const [phase, setPhase] = useState("waiting");
const [timeLeft, setTimeLeft] = useState(30); const [timeLeft, setTimeLeft] = useState(30);
const [frequency, setFrequency] = useState(440); const [frequency, setFrequency] = useState(440);
const [currentSong, setCurrentSong] = useState(null); const [currentSong, setCurrentSong] = useState(null);
@ -19,8 +19,7 @@ export const Game = () => {
const [selectedSong, setSelectedSong] = useState(null); const [selectedSong, setSelectedSong] = useState(null);
const [guessResult, setGuessResult] = useState(null); const [guessResult, setGuessResult] = useState(null);
const [isHost, setIsHost] = useState(false); const [isHost, setIsHost] = useState(false);
// Chat state
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const [connectedUsers, setConnectedUsers] = useState([]); const [connectedUsers, setConnectedUsers] = useState([]);
@ -29,104 +28,88 @@ export const Game = () => {
const timerIntervalRef = useRef(null); const timerIntervalRef = useRef(null);
useEffect(() => { useEffect(() => {
setPhase("waiting"); const eventHandlers = {
"roles-assigned": (roles) => {
const handleRolesAssigned = (roles) => { const myRole = roles[socket?.id];
console.log("Roles assigned:", roles); if (myRole) {
const myRole = roles[socket?.id]; setRole(myRole);
setMessages(prev => [...prev, {
if (myRole) { system: true,
setRole(myRole); 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, { setMessages(prev => [...prev, {
system: true, system: true,
text: myRole === "composer" text: `${userData.name} ist beigetreten`
? "Du bist der Komponist! Spiele den Song mit dem Tonregler."
: "Du bist ein Rater! Höre die Frequenzen und versuche, den Song zu erkennen."
}]); }]);
} else { setConnectedUsers(prev => [...prev, userData]);
console.error("No role assigned to this player!"); },
"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 cleanupFunctions = Object.entries(eventHandlers).map(
const cleanupSongSelected = on("song-selected", handleSongSelected); ([event, handler]) => on(event, handler)
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);
send("get-room-users"); send("get-room-users");
send("check-host-status"); send("check-host-status");
return () => { return () => cleanupFunctions.forEach(cleanup => cleanup());
cleanupRolesAssigned(); }, [socket, on, send, role, currentSong, phase]);
cleanupSongSelected();
cleanupRoundStarted();
cleanupGuessingPhaseStarted();
cleanupGuessResult();
cleanupRoundResults();
cleanupRoomUsers();
cleanupHostStatus();
};
}, [socket, on, send]);
useEffect(() => { useEffect(() => {
if (timerIntervalRef.current) { if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
clearInterval(timerIntervalRef.current);
}
if (phase !== "waiting" && phase !== "results") { if (phase !== "waiting" && phase !== "results") {
timerIntervalRef.current = setInterval(() => { timerIntervalRef.current = setInterval(() => {
setTimeLeft(prev => Math.max(0, prev - 1)); setTimeLeft(prev => Math.max(0, prev - 1));
@ -134,92 +117,43 @@ export const Game = () => {
} }
return () => { return () => {
if (timerIntervalRef.current) { if (timerIntervalRef.current) clearInterval(timerIntervalRef.current);
clearInterval(timerIntervalRef.current);
}
}; };
}, [phase]); }, [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(() => { useEffect(() => {
messageEndRef.current?.scrollIntoView({behavior: "smooth"}); messageEndRef.current?.scrollIntoView({behavior: "smooth"});
}, [messages]); }, [messages]);
const handleFrequencyChange = (newFrequency) => { const handleFrequencyChange = useCallback((newFrequency) => {
setFrequency(newFrequency); setFrequency(newFrequency);
if (role === "composer") { if (role === "composer") {
send("submit-frequency", { frequency: newFrequency }); send("submit-frequency", { frequency: newFrequency });
} }
}; }, [role, send]);
const handleSendMessage = () => { const handleSendMessage = useCallback(() => {
if (inputValue.trim()) { if (inputValue.trim()) {
const messageData = { const messageData = { text: inputValue, sender: username };
text: inputValue,
sender: username
};
send("send-message", messageData); send("send-message", messageData);
setMessages(prev => [...prev, messageData]); setMessages(prev => [...prev, messageData]);
setInputValue(""); setInputValue("");
} }
}; }, [inputValue, username, send]);
const handleSongSelect = (song) => { const handleSongSelect = useCallback((song) => {
setSelectedSong(song); setSelectedSong(song);
}; send("submit-guess", { songId: song.id });
}, [send]);
const handleNextRound = () => { const handleNextRound = useCallback(() => {
send("next-round"); send("next-round");
setSelectedSong(null); setSelectedSong(null);
setGuessResult(null); setGuessResult(null);
setTimeLeft(0); setTimeLeft(0);
}; }, [send]);
// Phase-specific content rendering
const renderPhaseContent = () => { const renderPhaseContent = () => {
switch (phase) { switch (phase) {
case "waiting": case "waiting":
@ -273,13 +207,11 @@ export const Game = () => {
</div> </div>
</div> </div>
{role === "composer" && ( {role === "composer" ? (
<div className="waiting-for-guessers"> <div className="waiting-for-guessers">
<p>Die Rater versuchen nun, deinen Song zu erraten...</p> <p>Die Rater versuchen nun, deinen Song zu erraten...</p>
</div> </div>
)} ) : (
{role === "guesser" && (
<div className="song-options"> <div className="song-options">
<p className="instruction">Welchen Song hat der Komponist gespielt?</p> <p className="instruction">Welchen Song hat der Komponist gespielt?</p>
<div className="song-grid"> <div className="song-grid">
@ -308,13 +240,11 @@ export const Game = () => {
<h3>Runde {round}: Ergebnisse</h3> <h3>Runde {round}: Ergebnisse</h3>
<div className="round-results"> <div className="round-results">
{role === "composer" && ( {role === "composer" ? (
<div className="composer-results"> <div className="composer-results">
<p>Die Rater haben versucht, deinen Song zu erraten.</p> <p>Die Rater haben versucht, deinen Song zu erraten.</p>
</div> </div>
)} ) : (
{role === "guesser" && (
<div className="guesser-results"> <div className="guesser-results">
{currentSong && ( {currentSong && (
<div className="correct-song"> <div className="correct-song">
@ -360,12 +290,13 @@ export const Game = () => {
</div> </div>
</div> </div>
{isHost && ( {isHost ? (
<button className="next-round-button" onClick={handleNextRound}> <button className="next-round-button" onClick={handleNextRound}>
Nächste Runde Nächste Runde
</button> </button>
) : (
<p className="waiting-message">Warten auf Rundenwechsel durch Host...</p>
)} )}
{!isHost && <p className="waiting-message">Warten auf Rundenwechsel durch Host...</p>}
</div> </div>
</div> </div>
); );
@ -417,18 +348,23 @@ export const Game = () => {
<input type="text" value={inputValue} <input type="text" value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
placeholder="Gib eine Nachricht ein..." placeholder="Gib eine Nachricht ein..."
/> />
<button onClick={handleSendMessage}>Send</button> <button onClick={handleSendMessage}>Send</button>
</div> </div>
</div> </div>
</div> </div>
<MusicSlider {/* Only render MusicSlider during composing phase */}
isReadOnly={role !== "composer" || phase !== "composing"} {phase === "composing" && (
onFrequencyChange={handleFrequencyChange} <MusicSlider
frequency={frequency} isReadOnly={role !== "composer"}
/> onFrequencyChange={handleFrequencyChange}
frequency={frequency}
/>
)}
</div> </div>
); );
} };
export default Game;

View File

@ -9,30 +9,67 @@ export const MusicSlider = ({ isReadOnly = false, onFrequencyChange, frequency:
const oscillatorRef = useRef(null); const oscillatorRef = useRef(null);
const gainNodeRef = useRef(null); const gainNodeRef = useRef(null);
const sliderRef = 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(() => { useEffect(() => {
if (externalFrequency !== undefined && !dragging) { if (externalFrequency !== undefined && !dragging) {
setFrequency(externalFrequency); setFrequency(externalFrequency);
if (!isPlaying && !isReadOnly) { if (audioContextRef.current) {
startAudio(); 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) => { const handleMouseDown = (e) => {
if (isReadOnly) return; if (isReadOnly) return;
setDragging(true); setDragging(true);
startAudio();
handleFrequencyChange(e); handleFrequencyChange(e);
if (!audioContextRef.current) {
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)();
}
if (!isPlaying) {
startAudio();
}
}; };
const handleMouseUp = () => { const handleMouseUp = () => {
setDragging(false); setDragging(false);
if (!isReadOnly) {
stopAudio();
}
}; };
const handleFrequencyChange = (e) => { const handleFrequencyChange = (e) => {
@ -63,51 +100,89 @@ export const MusicSlider = ({ isReadOnly = false, onFrequencyChange, frequency:
}, [dragging]); }, [dragging]);
useEffect(() => { useEffect(() => {
if (isPlaying) { if (isPlaying && oscillatorRef.current && audioContextRef.current) {
if (oscillatorRef.current) { oscillatorRef.current.frequency.setValueAtTime(
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); frequency,
} audioContextRef.current.currentTime
);
} }
}, [frequency, isPlaying]); }, [frequency, isPlaying]);
const startAudio = () => { const startAudio = () => {
if (!audioContextRef.current) { 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(); if (audioContextRef.current.state === 'suspended') {
gainNodeRef.current.gain.setValueAtTime(0.5, audioContextRef.current.currentTime); audioContextRef.current.resume().catch(err => {
console.error("Could not resume AudioContext:", err);
oscillatorRef.current.connect(gainNodeRef.current); });
gainNodeRef.current.connect(audioContextRef.current.destination); }
oscillatorRef.current.start();
} else { try {
oscillatorRef.current.frequency.setValueAtTime(frequency, audioContextRef.current.currentTime); 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 = () => { const stopAudio = () => {
if (oscillatorRef.current) { if (oscillatorRef.current) {
oscillatorRef.current.stop(); try {
oscillatorRef.current.disconnect(); oscillatorRef.current.stop();
oscillatorRef.current = null; oscillatorRef.current.disconnect();
oscillatorRef.current = null;
setIsPlaying(false);
console.log("Audio stopped");
} catch (e) {
console.error("Error stopping audio:", e);
}
} }
setIsPlaying(false);
}; };
useEffect(() => { useEffect(() => {
return () => { return () => {
if (oscillatorRef.current) { if (oscillatorRef.current) {
oscillatorRef.current.stop(); try {
oscillatorRef.current.disconnect(); oscillatorRef.current.stop();
oscillatorRef.current.disconnect();
} catch (e) {
console.error("Error cleaning up oscillator:", e);
}
} }
if (audioContextRef.current && audioContextRef.current.state !== 'closed') { 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:
</div> </div>
</div> </div>
</div> </div>
) );
} };

View File

@ -1,12 +1,17 @@
const roomController = require('./room'); const roomController = require('./room');
const SONGS = [ 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: 1,
{ id: 3, title: "Neon Nights", artist: "Electric Avenue", coverUrl: "https://place-hold.it/500x500/" }, title: "Black Steam",
{ id: 4, title: "Mountain Echo", artist: "Wild Terrain", coverUrl: "https://place-hold.it/500x500/" }, artist: "Carrot Quest GmbH",
{ id: 5, title: "Urban Jungle", artist: "City Dwellers", coverUrl: "https://place-hold.it/500x500/" }, coverUrl: "https://mir-s3-cdn-cf.behance.net/project_modules/1400/fe529a64193929.5aca8500ba9ab.jpg"
{ id: 6, title: "Cosmic Journey", artist: "Stargazers", coverUrl: "https://place-hold.it/500x500/" } },
{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 = {}; const gameStates = {};
@ -35,72 +40,75 @@ const initializeGameState = (roomId) => {
gameStates[roomId].scores[user.id] = 0; gameStates[roomId].scores[user.id] = 0;
}); });
} }
return gameStates[roomId]; return gameStates[roomId];
}; };
const startNewRound = (roomId) => { const startNewRound = (roomId) => {
const gameState = gameStates[roomId]; const gameState = gameStates[roomId];
if (!gameState) return false; if (!gameState) return false;
const users = roomController.getRoomUsers(roomId);
if (users.length < 2) return false;
gameState.round += 1; gameState.round += 1;
gameState.phase = 'composing'; gameState.phase = 'composing';
gameState.guessResults = {}; gameState.guessResults = {};
gameState.roundStartTime = Date.now(); 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 = {}; gameState.roles = {};
if (gameState.round === 1 || !gameState.currentComposer) { gameState.currentComposer = determineNextComposer(roomId, gameState, users);
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;
}
}
users.forEach(user => { users.forEach(user => {
gameState.roles[user.id] = user.id === gameState.currentComposer ? 'composer' : 'guesser'; 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)]; gameState.selectedSong = SONGS[Math.floor(Math.random() * SONGS.length)];
const songOptions = [...SONGS].sort(() => Math.random() - 0.5).slice(0, 5); const songOptions = [...SONGS].sort(() => Math.random() - 0.5).slice(0, 5);
if (!songOptions.includes(gameState.selectedSong)) { if (!songOptions.includes(gameState.selectedSong)) {
songOptions[Math.floor(Math.random() * songOptions.length)] = gameState.selectedSong; songOptions[Math.floor(Math.random() * songOptions.length)] = gameState.selectedSong;
} }
gameState.songOptions = songOptions; gameState.songOptions = songOptions;
return true;
}; };
const getTimeRemaining = (roomId) => { const getTimeRemaining = (roomId) => {
const gameState = gameStates[roomId]; const gameState = gameStates[roomId];
if (!gameState || !gameState.roundStartTime) return 0; if (!gameState || !gameState.roundStartTime) return 0;
const phaseDuration = gameState.phaseTimeLimit[gameState.phase] * 1000; const phaseDuration = gameState.phaseTimeLimit[gameState.phase] * 1000;
const timeElapsed = Date.now() - gameState.roundStartTime; const timeElapsed = Date.now() - gameState.roundStartTime;
const timeRemaining = Math.max(0, phaseDuration - timeElapsed); const timeRemaining = Math.max(0, phaseDuration - timeElapsed);
return Math.ceil(timeRemaining / 1000); return Math.ceil(timeRemaining / 1000);
}; };
const advancePhase = (roomId) => { const advancePhase = (roomId) => {
const gameState = gameStates[roomId]; const gameState = gameStates[roomId];
if (!gameState) return false; if (!gameState) return false;
if (gameState.phase === 'composing') { if (gameState.phase === 'composing') {
gameState.phase = 'guessing'; gameState.phase = 'guessing';
gameState.roundStartTime = Date.now(); gameState.roundStartTime = Date.now();
@ -111,7 +119,7 @@ const advancePhase = (roomId) => {
} else if (gameState.phase === 'results') { } else if (gameState.phase === 'results') {
return startNewRound(roomId); return startNewRound(roomId);
} }
return false; return false;
}; };
@ -121,38 +129,37 @@ const updateFrequency = (roomId, frequency) => {
return true; return true;
}; };
const getCurrentFrequency = (roomId) => {
return gameStates[roomId]?.lastFrequency || 440;
};
const submitGuess = (roomId, userId, songId) => { const submitGuess = (roomId, userId, songId) => {
const gameState = gameStates[roomId]; const gameState = gameStates[roomId];
if (!gameState || gameState.phase !== 'guessing' || gameState.roles[userId] !== 'guesser') { if (!gameState || gameState.phase !== 'guessing' || gameState.roles[userId] !== 'guesser') {
return false; return false;
} }
const isCorrect = gameState.selectedSong.id === songId; const isCorrect = gameState.selectedSong.id === parseInt(songId);
gameState.guessResults[userId] = { const points = isCorrect ? 10 : 0;
songId,
isCorrect, gameState.guessResults[userId] = {songId, isCorrect, points};
points: isCorrect ? 10 : 0
};
if (isCorrect) { if (isCorrect) {
gameState.scores[userId] = (gameState.scores[userId] || 0) + 10; gameState.scores[userId] = (gameState.scores[userId] || 0) + points;
} }
return { return {
isCorrect, isCorrect,
correctSong: gameState.selectedSong, 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 getRoundResults = (roomId) => {
const gameState = gameStates[roomId]; const gameState = gameStates[roomId];
if (!gameState) return null; if (!gameState) return {round: 0, scores: {}, guessResults: {}, selectedSong: null};
return { return {
round: gameState.round, round: gameState.round,
selectedSong: gameState.selectedSong, 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) => { const cleanupGameState = (roomId) => {
delete gameStates[roomId]; delete gameStates[roomId];
}; };

View File

@ -4,7 +4,6 @@ const gameController = require("../controller/game");
module.exports = (io) => (socket) => { module.exports = (io) => (socket) => {
let currentRoomId = null; let currentRoomId = null;
let currentUser = null; let currentUser = null;
let phaseTimers = {}; let phaseTimers = {};
const clearRoomTimers = (roomId) => { const clearRoomTimers = (roomId) => {
@ -18,62 +17,75 @@ module.exports = (io) => (socket) => {
clearRoomTimers(roomId); clearRoomTimers(roomId);
const timeRemaining = gameController.getTimeRemaining(roomId) * 1000; const timeRemaining = gameController.getTimeRemaining(roomId) * 1000;
phaseTimers[roomId] = setTimeout(() => { phaseTimers[roomId] = setTimeout(() => {
const advanced = gameController.advancePhase(roomId); const advanced = gameController.advancePhase(roomId);
if (advanced) { if (!advanced) return;
if (typeof advanced === 'boolean') {
handleRoundStart(roomId);
} else {
const currentPhase = advanced.phase || 'results'; const currentPhase = advanced.phase || 'results';
if (currentPhase === 'guessing') { if (currentPhase === 'guessing') {
const roles = gameController.getRoles(roomId); handleGuessingPhaseStart(roomId);
const songOptions = gameController.getSongOptions(roomId); } else if (currentPhase === 'results') {
const timeLeft = gameController.getTimeRemaining(roomId); handleResultsPhaseStart(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);
} }
} }
}, timeRemaining); }, 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", () => { socket.on("disconnect", () => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (roomId) socket.to(roomId).emit("user-disconnected", socket.id); if (roomId) {
socket.to(roomId).emit("user-disconnected", socket.id);
roomController.disconnectUser(socket.id); roomController.disconnectUser(socket.id);
}
}); });
socket.on("join-room", ({roomId, name}) => { socket.on("join-room", ({roomId, name}) => {
@ -92,7 +104,6 @@ module.exports = (io) => (socket) => {
const users = roomController.getRoomUsers(roomId); const users = roomController.getRoomUsers(roomId);
io.to(roomId).emit("room-users-update", users); io.to(roomId).emit("room-users-update", users);
socket.to(roomId).emit("user-connected", currentUser); socket.to(roomId).emit("user-connected", currentUser);
socket.emit("room-joined", roomId); socket.emit("room-joined", roomId);
@ -104,6 +115,7 @@ module.exports = (io) => (socket) => {
socket.on("create-room", ({name}) => { socket.on("create-room", ({name}) => {
if (!name) return socket.emit("room-name-required"); if (!name) return socket.emit("room-name-required");
const roomId = Math.random().toString(36).substring(7).toUpperCase(); const roomId = Math.random().toString(36).substring(7).toUpperCase();
currentUser = {id: socket.id, name: name?.toString(), creator: true}; currentUser = {id: socket.id, name: name?.toString(), creator: true};
roomController.connectUserToRoom(roomId, currentUser); roomController.connectUserToRoom(roomId, currentUser);
@ -114,51 +126,32 @@ module.exports = (io) => (socket) => {
socket.on("start-game", () => { socket.on("start-game", () => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (roomId && roomController.isUserHost(socket.id)) { if (!roomId || !roomController.isUserHost(socket.id)) {
roomController.validateRoomMembers(io, roomId); return socket.emit("not-authorized");
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");
}
} }
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) => { socket.on("send-message", (messageData) => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (roomId) { if (roomId) socket.to(roomId).emit("chat-message", messageData);
socket.to(roomId).emit("chat-message", messageData);
}
}); });
socket.on("get-user-info", () => { socket.on("get-user-info", () => {
if (currentUser) { if (currentUser) socket.emit("user-info", currentUser);
socket.emit("user-info", currentUser);
}
}); });
socket.on("get-room-users", () => { socket.on("get-room-users", () => {
@ -170,72 +163,55 @@ module.exports = (io) => (socket) => {
}); });
socket.on("check-host-status", () => { socket.on("check-host-status", () => {
const isHost = roomController.isUserHost(socket.id); socket.emit("host-status", { isHost: roomController.isUserHost(socket.id) });
socket.emit("host-status", { isHost });
}); });
socket.on("get-room-code", () => { socket.on("get-room-code", () => {
if (currentRoomId) { if (currentRoomId) socket.emit("room-code", currentRoomId);
socket.emit("room-code", currentRoomId);
}
}); });
socket.on("submit-frequency", ({ frequency }) => { socket.on("submit-frequency", ({ frequency }) => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (!roomId) return; if (!roomId) return;
const userRole = gameController.getUserRole(roomId, socket.id); const userRole = gameController.getUserRole(roomId, socket.id);
if (userRole === 'composer') { if (userRole !== 'composer') return;
if (gameController.updateFrequency(roomId, frequency)) {
socket.to(roomId).emit("frequency-update", { frequency }); if (gameController.updateFrequency(roomId, frequency)) {
} socket.to(roomId).emit("frequency-update", { frequency });
} }
}); });
socket.on("submit-guess", ({ songId }) => { socket.on("submit-guess", ({ songId }) => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (!roomId) return; if (!roomId) return;
const result = gameController.submitGuess(roomId, socket.id, songId); const result = gameController.submitGuess(roomId, socket.id, songId);
if (result) { if (result) socket.emit("guess-result", result);
socket.emit("guess-result", result);
}
}); });
socket.on("next-round", () => { socket.on("next-round", () => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (!roomId || !roomController.isUserHost(socket.id)) return; if (!roomId || !roomController.isUserHost(socket.id)) return;
roomController.validateRoomMembers(io, roomId); 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)) { if (gameController.startNewRound(roomId)) {
const roles = gameController.getRoles(roomId); handleRoundStart(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);
} }
}); });
socket.on("get-current-frequency", () => { socket.on("get-current-frequency", () => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (!roomId) return; if (roomId) {
socket.emit("current-frequency", {
const frequency = gameController.getCurrentFrequency(roomId); frequency: gameController.getCurrentFrequency(roomId)
socket.emit("current-frequency", { frequency }); });
}
}); });
}; };