Fix guessing phase

This commit is contained in:
Mathias Wagner 2025-03-01 00:40:01 +01:00
parent 87fc9a2f39
commit a3daab2f84
6 changed files with 285 additions and 44 deletions

View File

@ -19,6 +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);
const [hasGuessed, setHasGuessed] = useState(false);
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
@ -48,9 +49,10 @@ export const Game = () => {
setTimeLeft(data.timeRemaining); setTimeLeft(data.timeRemaining);
}, },
"guessing-phase-started": (data) => { "guessing-phase-started": (data) => {
console.log("Guessing phase started:", data);
setPhase("guessing"); setPhase("guessing");
setTimeLeft(data.timeRemaining); setTimeLeft(data.timeRemaining);
setSongOptions(data.songOptions); setSongOptions(data.songOptions || []);
}, },
"guess-result": (result) => { "guess-result": (result) => {
setGuessResult(result.isCorrect); setGuessResult(result.isCorrect);
@ -94,6 +96,13 @@ export const Game = () => {
console.log("Received frequency update:", data.frequency); console.log("Received frequency update:", data.frequency);
setFrequency(data.frequency); setFrequency(data.frequency);
} }
},
"phase-changed": (data) => {
console.log("Phase changed:", data);
setPhase(data.phase);
if (data.timeRemaining) {
setTimeLeft(data.timeRemaining);
}
} }
}; };
@ -143,8 +152,21 @@ export const Game = () => {
const handleSongSelect = useCallback((song) => { const handleSongSelect = useCallback((song) => {
setSelectedSong(song); setSelectedSong(song);
send("submit-guess", { songId: song.id }); }, []);
}, [send]);
const handleSubmitGuess = useCallback(() => {
if (!selectedSong || phase !== 'guessing') return;
console.log("Submitting guess:", selectedSong.id);
send("submit-guess", { songId: selectedSong.id });
setMessages(prev => [...prev, {
system: true,
text: `Du hast "${selectedSong.title}" von ${selectedSong.artist} gewählt.`
}]);
setHasGuessed(true);
}, [selectedSong, send, phase]);
const handleNextRound = useCallback(() => { const handleNextRound = useCallback(() => {
send("next-round"); send("next-round");
@ -212,23 +234,54 @@ export const Game = () => {
<p>Die Rater versuchen nun, deinen Song zu erraten...</p> <p>Die Rater versuchen nun, deinen Song zu erraten...</p>
</div> </div>
) : ( ) : (
<div className="song-options"> <div className="song-selection">
<p className="instruction">Welchen Song hat der Komponist gespielt?</p> <p className="instruction">Welchen Song hat der Komponist gespielt?</p>
<div className="song-grid">
{songOptions.map(song => ( {songOptions.length === 0 ? (
<div <div className="loading-songs">
key={song.id} <p>Lade Songoptionen...</p>
className={`song-option ${selectedSong?.id === song.id ? 'selected' : ''}`} </div>
onClick={() => handleSongSelect(song)} ) : (
> <>
<img src={song.coverUrl} alt={song.title} /> <div className="song-grid">
<div className="song-details"> {songOptions.map(song => (
<div className="song-title">{song.title}</div> <div
<div className="song-artist">{song.artist}</div> key={song.id}
</div> className={`song-option ${selectedSong?.id === song.id ? 'selected' : ''} ${hasGuessed ? 'disabled' : ''}`}
onClick={() => !hasGuessed && handleSongSelect(song)}
>
<div className="song-image">
<img src={song.coverUrl} alt={song.title} />
{selectedSong?.id === song.id && (
<div className="selection-indicator">
<FontAwesomeIcon icon="fa-solid fa-check-circle" />
</div>
)}
</div>
<div className="song-details">
<div className="song-title">{song.title}</div>
<div className="song-artist">{song.artist}</div>
</div>
</div>
))}
</div> </div>
))}
</div> <div className="guess-actions">
{hasGuessed ? (
<p className="guess-submitted">Deine Antwort wurde eingereicht!</p>
) : (
<button
className={`submit-guess-button ${!selectedSong ? 'disabled' : ''}`}
onClick={handleSubmitGuess}
disabled={!selectedSong}
>
<FontAwesomeIcon icon="fa-solid fa-paper-plane" />
Antwort einreichen
</button>
)}
</div>
</>
)}
</div> </div>
)} )}
</div> </div>

View File

@ -187,6 +187,14 @@ export const MusicSlider = ({ isReadOnly = false, onFrequencyChange, frequency:
}; };
}, []); }, []);
useEffect(() => {
return () => {
if (isPlaying) {
stopAudio();
}
};
}, [isPlaying]);
return ( return (
<div className={`otamatone-container ${isReadOnly ? 'read-only' : ''}`}> <div className={`otamatone-container ${isReadOnly ? 'read-only' : ''}`}>
<div <div

View File

@ -332,34 +332,124 @@
grid-template-columns: repeat(3, 1fr) grid-template-columns: repeat(3, 1fr)
gap: 20px gap: 20px
margin-top: 20px margin-top: 20px
animation: fade-in 0.5s ease-out
@media (max-width: 768px)
grid-template-columns: repeat(2, 1fr)
@media (max-width: 500px)
grid-template-columns: 1fr
.song-option .song-option
background: rgba(255, 255, 255, 0.1) background: rgba(255, 255, 255, 0.1)
border-radius: 15px border-radius: 15px
overflow: hidden overflow: hidden
cursor: pointer cursor: pointer
transition: all 0.3s ease transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)
border: 2px solid transparent border: 2px solid transparent
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2)
&:hover &:hover
transform: translateY(-5px) transform: translateY(-5px)
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3) box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3), 0 0 20px rgba(255, 255, 255, 0.1)
background: rgba(255, 255, 255, 0.15)
&.selected &.selected
border: 2px solid $yellow border: 2px solid $yellow
box-shadow: 0 0 20px rgba(255, 255, 0, 0.5) box-shadow: 0 0 20px rgba(255, 255, 0, 0.5), 0 10px 30px rgba(0, 0, 0, 0.3)
background: rgba(255, 255, 255, 0.2)
img .song-image
width: 100% position: relative
height: 120px overflow: hidden
object-fit: cover
img
width: 100%
height: 150px
object-fit: cover
transition: transform 0.3s ease
.selected &
transform: scale(1.05)
.selection-indicator
position: absolute
top: 10px
right: 10px
background-color: $yellow
color: #000
width: 30px
height: 30px
border-radius: 50%
display: flex
align-items: center
justify-content: center
box-shadow: 0 0 15px rgba(255, 255, 0, 0.7)
animation: pulse 1.5s infinite ease-in-out
.song-details .song-details
padding: 10px padding: 15px
.song-title .song-title
font-weight: bold font-weight: bold
font-size: 16px font-size: 18px
color: #fff
margin-bottom: 5px
.song-artist
font-size: 14px
color: rgba(255, 255, 255, 0.8)
.guess-actions
margin-top: 30px
text-align: center
animation: fade-in 0.5s ease-out 0.2s both
.submit-guess-button
background: linear-gradient(135deg, $purple, $blue)
color: #fff
border: none
border-radius: 10px
padding: 12px 25px
font-size: 16px
font-weight: bold
cursor: pointer
transition: all 0.3s ease
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3)
display: inline-flex
align-items: center
justify-content: center
svg
margin-right: 8px
&:hover:not(.disabled)
transform: translateY(-3px)
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4), 0 0 20px rgba(102, 204, 255, 0.4)
&:active:not(.disabled)
transform: translateY(-1px)
&.disabled
opacity: 0.5
cursor: not-allowed
background: linear-gradient(135deg, #999, #666)
.selection-hint
margin-top: 10px
color: rgba(255, 255, 255, 0.7)
font-size: 14px
font-style: italic
.loading-songs
text-align: center
padding: 40px
color: $white
p
font-size: 18px
opacity: 0.8
animation: pulse 1.5s infinite ease-in-out
@keyframes subtle-text-glow @keyframes subtle-text-glow
0%, 100% 0%, 100%
@ -418,3 +508,19 @@
50% 50%
transform: translateY(-10px) scale(1.05) rotate(1deg) transform: translateY(-10px) scale(1.05) rotate(1deg)
filter: drop-shadow(0 0 25px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 50px rgba(102, 204, 255, 0.6)) filter: drop-shadow(0 0 25px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 50px rgba(102, 204, 255, 0.6))
@keyframes fade-in
0%
opacity: 0
transform: translateY(20px)
100%
opacity: 1
transform: translateY(0)
@keyframes pulse
0%, 100%
opacity: 0.8
transform: scale(1)
50%
opacity: 1
transform: scale(1.05)

View File

@ -107,19 +107,31 @@ const getTimeRemaining = (roomId) => {
const advancePhase = (roomId) => { const advancePhase = (roomId) => {
const gameState = gameStates[roomId]; const gameState = gameStates[roomId];
if (!gameState) return false; if (!gameState) {
console.error(`Cannot advance phase: no game state for room ${roomId}`);
return false;
}
if (gameState.phase === 'composing') { const currentPhase = gameState.phase;
console.log(`Advancing phase for room ${roomId} from ${currentPhase}`);
if (currentPhase === 'composing') {
gameState.phase = 'guessing'; gameState.phase = 'guessing';
gameState.roundStartTime = Date.now(); gameState.roundStartTime = Date.now();
return true; console.log(`Room ${roomId} advanced to guessing phase`);
} else if (gameState.phase === 'guessing') { return { phase: 'guessing' };
}
else if (currentPhase === 'guessing') {
gameState.phase = 'results'; gameState.phase = 'results';
return true; console.log(`Room ${roomId} advanced to results phase`);
} else if (gameState.phase === 'results') { return { phase: 'results' };
}
else if (currentPhase === 'results') {
console.log(`Room ${roomId} starting new round from results phase`);
return startNewRound(roomId); return startNewRound(roomId);
} }
console.warn(`Cannot advance from unknown phase "${currentPhase}" in room ${roomId}`);
return false; return false;
}; };
@ -172,6 +184,12 @@ const cleanupGameState = (roomId) => {
delete gameStates[roomId]; delete gameStates[roomId];
}; };
const getCurrentComposer = (roomId) => gameStates[roomId]?.currentComposer || null;
const getGameState = (roomId) => {
return gameStates[roomId] || null;
};
module.exports = { module.exports = {
initializeGameState, initializeGameState,
startNewRound, startNewRound,
@ -185,5 +203,7 @@ module.exports = {
getUserRole, getUserRole,
getSongOptions, getSongOptions,
getSelectedSong, getSelectedSong,
cleanupGameState cleanupGameState,
getCurrentComposer,
getGameState
}; };

View File

@ -122,3 +122,14 @@ module.exports.validateRoomMembers = (io, roomId) => {
} }
module.exports.setCleanupGameState = setCleanupGameState; module.exports.setCleanupGameState = setCleanupGameState;
module.exports.getUserName = (userId) => {
for (const roomId in rooms) {
const room = rooms[roomId];
const member = room.members.find(m => m.id === userId);
if (member) {
return member.name;
}
}
return null;
};

View File

@ -8,6 +8,7 @@ module.exports = (io) => (socket) => {
const clearRoomTimers = (roomId) => { const clearRoomTimers = (roomId) => {
if (phaseTimers[roomId]) { if (phaseTimers[roomId]) {
console.log(`Clearing timer for room ${roomId}`);
clearTimeout(phaseTimers[roomId]); clearTimeout(phaseTimers[roomId]);
delete phaseTimers[roomId]; delete phaseTimers[roomId];
} }
@ -16,21 +17,33 @@ module.exports = (io) => (socket) => {
const startPhaseTimer = (roomId) => { const startPhaseTimer = (roomId) => {
clearRoomTimers(roomId); clearRoomTimers(roomId);
const gameState = gameController.getGameState(roomId);
if (!gameState) {
console.error(`Cannot start timer: no game state for room ${roomId}`);
return;
}
const timeRemaining = gameController.getTimeRemaining(roomId) * 1000; const timeRemaining = gameController.getTimeRemaining(roomId) * 1000;
console.log(`Starting ${gameState.phase} phase timer for room ${roomId} with ${timeRemaining}ms`);
phaseTimers[roomId] = setTimeout(() => { phaseTimers[roomId] = setTimeout(() => {
console.log(`Timer expired for room ${roomId}, advancing phase from ${gameState.phase}`);
const advanced = gameController.advancePhase(roomId); const advanced = gameController.advancePhase(roomId);
if (!advanced) return; if (!advanced) {
console.log(`Failed to advance phase for room ${roomId}`);
return;
}
if (typeof advanced === 'boolean') { if (typeof advanced === 'boolean') {
handleRoundStart(roomId); handleRoundStart(roomId);
} else { } else {
const currentPhase = advanced.phase || 'results'; const newPhase = gameController.getGameState(roomId).phase;
console.log(`Advanced to ${newPhase} phase in room ${roomId}`);
if (currentPhase === 'guessing') { if (newPhase === 'guessing') {
handleGuessingPhaseStart(roomId); handleGuessingPhaseStart(roomId);
} else if (currentPhase === 'results') { } else if (newPhase === 'results') {
handleResultsPhaseStart(roomId); handleResultsPhaseStart(roomId);
} }
} }
@ -59,10 +72,20 @@ module.exports = (io) => (socket) => {
}; };
const handleGuessingPhaseStart = (roomId) => { const handleGuessingPhaseStart = (roomId) => {
const gameState = gameController.getGameState(roomId);
if (!gameState) return;
const roles = gameController.getRoles(roomId); const roles = gameController.getRoles(roomId);
const songOptions = gameController.getSongOptions(roomId); const songOptions = gameController.getSongOptions(roomId);
const timeLeft = gameController.getTimeRemaining(roomId); const timeLeft = gameController.getTimeRemaining(roomId);
console.log(`Starting guessing phase for room ${roomId} with ${Object.keys(roles).length} players`);
io.to(roomId).emit('phase-changed', {
phase: 'guessing',
timeRemaining: timeLeft
});
Object.entries(roles).forEach(([userId, role]) => { Object.entries(roles).forEach(([userId, role]) => {
if (role === 'guesser') { if (role === 'guesser') {
io.to(userId).emit('guessing-phase-started', { io.to(userId).emit('guessing-phase-started', {
@ -186,8 +209,28 @@ module.exports = (io) => (socket) => {
const roomId = roomController.getUserRoom(socket.id); const roomId = roomController.getUserRoom(socket.id);
if (!roomId) return; if (!roomId) return;
console.log(`User ${socket.id} submitted guess: Song ID ${songId}`);
const gamePhase = gameController.getGameState(roomId)?.phase;
if (gamePhase !== 'guessing') {
console.log(`Ignoring guess: room ${roomId} is in ${gamePhase} phase, not guessing`);
return;
}
const result = gameController.submitGuess(roomId, socket.id, songId); const result = gameController.submitGuess(roomId, socket.id, songId);
if (result) socket.emit("guess-result", result); if (result) {
console.log(`Guess result for ${socket.id}:`, result);
socket.emit("guess-result", result);
const currentComposer = gameController.getCurrentComposer(roomId);
if (currentComposer) {
const guesserName = roomController.getUserName(socket.id) || "Someone";
io.to(currentComposer).emit("player-guessed", {
guesserName,
isCorrect: result.isCorrect
});
}
}
}); });
socket.on("next-round", () => { socket.on("next-round", () => {