diff --git a/client/src/App.jsx b/client/src/App.jsx index 836a30b..9b1257f 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -7,6 +7,7 @@ import HomeScreen from "./components/HomeScreen"; import SongSubmissionScreen from "./components/SongSubmissionScreen"; import VotingScreen from "./components/VotingScreen"; import ResultsScreen from "./components/ResultsScreen"; +import BattleResultScreen from "./components/BattleResultScreen"; import ConnectionStatus from "./components/ConnectionStatus"; const App = () => { @@ -52,6 +53,8 @@ const App = () => { return ; case 'FINISHED': return ; + case 'BATTLE': + return ; default: return ; } diff --git a/client/src/common/styles/components/battle-result-screen.sass b/client/src/common/styles/components/battle-result-screen.sass new file mode 100644 index 0000000..276851a --- /dev/null +++ b/client/src/common/styles/components/battle-result-screen.sass @@ -0,0 +1,243 @@ +// Battle Result Screen styles + +.battle-result-screen + display: flex + flex-direction: column + min-height: 100% + padding: 1.5rem + position: relative + overflow: hidden + + header + display: flex + justify-content: center + align-items: center + flex-direction: column + margin-bottom: 2rem + + h1 + margin: 0 + color: $accent + display: flex + align-items: center + gap: 0.75rem + font-size: 2.2rem + font-family: 'Press Start 2P', monospace + text-transform: uppercase + animation: winner-pulse 2s infinite alternate + + svg + color: $accent + filter: drop-shadow(2px 2px 0 #000) + + .countdown + margin-top: 1rem + font-family: 'Press Start 2P', monospace + font-size: 0.9rem + color: $text-muted + padding: 0.8rem 1.2rem + background-color: rgba(0, 0, 0, 0.3) + border-radius: 1rem + animation: pulse 1s infinite alternate + display: flex + align-items: center + gap: 0.5rem + + svg + color: $accent + margin-right: 0.5rem + + .winner-announcement + display: flex + flex-direction: column + align-items: center + margin-bottom: 2rem + + .song-cards + display: flex + flex-direction: column + width: 100% + max-width: 700px + gap: 2rem + + @media (min-width: 768px) + flex-direction: row + align-items: flex-start + + .song-card + position: relative + background-color: rgba(0, 0, 0, 0.3) + padding: 1.5rem + border-radius: 1rem + + &.winner + flex: 3 + border: 6px solid $accent + box-shadow: 0 0 20px rgba($accent, 0.3) + transform: scale(1.05) + z-index: 1 + + .victory-badge + position: absolute + top: -12px + right: 10px + background-color: $accent + color: white + font-family: 'Press Start 2P', monospace + font-size: 0.7rem + padding: 0.5rem 1rem + border-radius: 0.5rem + display: flex + align-items: center + gap: 0.5rem + box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3) + + svg + font-size: 0.8rem + + &.loser + flex: 2 + opacity: 0.7 + filter: saturate(0.7) + + .versus + position: absolute + top: 50% + left: -30px + font-family: 'Bangers', cursive + font-size: 2.5rem + color: $accent + transform: translateY(-50%) + text-shadow: 0 0 5px rgba($accent, 0.5) + + @media (max-width: 767px) + top: -25px + left: 50% + transform: translateX(-50%) + + .song-info + margin-bottom: 1.5rem + + h2, h3 + margin: 0 0 0.5rem 0 + font-family: 'Press Start 2P', monospace + + h2 + font-size: 1.4rem + color: $text + margin-right: 70px + + h3 + font-size: 1.2rem + color: $text + + .artist + color: $text-muted + font-size: 1.1rem + margin-bottom: 1rem + + .submitter + font-size: 0.9rem + color: $text-muted + font-style: italic + margin-bottom: 0.5rem + + .vote-count + display: inline-block + padding: 0.5rem 1rem + background-color: rgba(0, 0, 0, 0.3) + border-radius: 1rem + font-family: 'Press Start 2P', monospace + font-size: 0.8rem + + .votes + color: $text + + .winner-video + width: 100% + aspect-ratio: 16 / 9 + border-radius: 0.5rem + overflow: hidden + + .no-video + width: 100% + aspect-ratio: 16 / 9 + display: flex + flex-direction: column + align-items: center + justify-content: center + background-color: rgba(0, 0, 0, 0.3) + border-radius: 0.5rem + color: $text-muted + + .pulse-icon + font-size: 2rem + margin-bottom: 1rem + animation: pulse 2s infinite + + .battle-actions + display: flex + justify-content: center + margin-top: auto + padding-top: 1.5rem + + .btn + padding: 1rem 2rem + font-size: 1.1rem + display: flex + align-items: center + gap: 0.5rem + + &.pixelated + position: relative + + .pixel-corner + position: absolute + width: 8px + height: 8px + background-color: $primary + + &.tl + top: -4px + left: -4px + + &.tr + top: -4px + right: -4px + + &.bl + bottom: -4px + left: -4px + + &.br + bottom: -4px + right: -4px + +// Confetti animation +.confetti + position: absolute + width: 10px + height: 20px + transform-origin: center bottom + animation: confetti-fall 3s linear forwards + z-index: -1 + +@keyframes confetti-fall + 0% + transform: translateY(-100vh) rotate(0deg) + 100% + transform: translateY(100vh) rotate(360deg) + +@keyframes winner-pulse + 0% + transform: scale(1) + text-shadow: 2px 2px 0 #000, -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000 + 100% + transform: scale(1.05) + text-shadow: 3px 3px 0 #000, -3px -3px 0 #000, 3px -3px 0 #000, -3px 3px 0 #000 + +@keyframes pulse + 0% + opacity: 0.7 + 100% + opacity: 1 diff --git a/client/src/common/styles/main.sass b/client/src/common/styles/main.sass index 2416076..1129ac9 100644 --- a/client/src/common/styles/main.sass +++ b/client/src/common/styles/main.sass @@ -9,6 +9,7 @@ @import './components/song-submission-screen' @import './components/voting-screen' @import './components/results-screen' +@import './components/battle-result-screen' @import './components/youtube-embed' @import './components/youtube-search' @import './components/song-form-overlay' diff --git a/client/src/components/BattleResultScreen.jsx b/client/src/components/BattleResultScreen.jsx new file mode 100644 index 0000000..f94174d --- /dev/null +++ b/client/src/components/BattleResultScreen.jsx @@ -0,0 +1,198 @@ +import { useEffect, useState } from 'react'; +import { useGame } from '../context/GameContext'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrophy, faMusic, faCrown, faClock } from '@fortawesome/free-solid-svg-icons'; +import YouTubeEmbed from './YouTubeEmbed'; +import { getDisplayName } from '../utils/playerUtils'; + +const BattleResultScreen = () => { + const { lobby, currentPlayer, isHost, proceedToNextBattle } = useGame(); + const [countdown, setCountdown] = useState(10); // Auto-advance after 10 seconds + const [showConfetti, setShowConfetti] = useState(true); + + // Use previousBattle from the lobby state + const previousBattle = lobby?.previousBattle; + + // Extract winning song + const winnerSongId = previousBattle?.winner; + const winningSong = winnerSongId === previousBattle?.song1?.id + ? previousBattle?.song1 + : previousBattle?.song2; + + const losingSong = winnerSongId === previousBattle?.song1?.id + ? previousBattle?.song2 + : previousBattle?.song1; + + const winningVotes = winnerSongId === previousBattle?.song1?.id + ? previousBattle?.song1Votes + : previousBattle?.song2Votes; + + const losingVotes = winnerSongId === previousBattle?.song1?.id + ? previousBattle?.song2Votes + : previousBattle?.song1Votes; + + // Get the player who submitted the winning song + const findSubmitter = (song) => { + if (!lobby || !song) return null; + + const submitter = lobby.players.find(p => p.id === song.submittedById); + return submitter; + }; + + const winnerSubmitter = findSubmitter(winningSong); + + // YouTube video ID extraction for the winner + const getYouTubeId = (url) => { + if (!url) return null; + + const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; + const match = url.match(regExp); + + return (match && match[2].length === 11) ? match[2] : null; + }; + + const winnerVideoId = getYouTubeId(winningSong?.youtubeLink); + + // Auto-advance countdown + useEffect(() => { + if (countdown > 0) { + const timer = setInterval(() => { + setCountdown(prev => { + if (prev <= 1) { + clearInterval(timer); + // Only auto-advance if host, otherwise wait for host + if (isHost) { + proceedToNextBattle(); + } + return 0; + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(timer); + } + }, [countdown, isHost, proceedToNextBattle]); + + // Confetti effect + useEffect(() => { + if (showConfetti) { + const createConfetti = () => { + const confettiContainer = document.createElement('div'); + confettiContainer.className = 'confetti'; + + // Random position, size, and color + const left = Math.random() * 100; + const colors = ['#f94144', '#f3722c', '#f8961e', '#f9c74f', '#90be6d', '#43aa8b', '#577590']; + const color = colors[Math.floor(Math.random() * colors.length)]; + const size = Math.random() * 0.5 + 0.3; // Between 0.3 and 0.8rem + const rotation = Math.random() * 360; // Random rotation + const duration = Math.random() * 2 + 1; // Between 1 and 3 seconds + + confettiContainer.style.left = `${left}%`; + confettiContainer.style.backgroundColor = color; + confettiContainer.style.width = `${size}rem`; + confettiContainer.style.height = `${size * 0.6}rem`; + confettiContainer.style.transform = `rotate(${rotation}deg)`; + confettiContainer.style.animation = `confetti-fall ${duration}s linear forwards`; + + document.querySelector('.battle-result-screen').appendChild(confettiContainer); + + // Remove after animation + setTimeout(() => { + confettiContainer.remove(); + }, duration * 1000); + }; + + // Create confetti pieces + const confettiInterval = setInterval(() => { + for (let i = 0; i < 3; i++) { + createConfetti(); + } + }, 300); + + // Stop confetti after a few seconds + setTimeout(() => { + clearInterval(confettiInterval); + setShowConfetti(false); + }, 4000); + + return () => { + clearInterval(confettiInterval); + }; + } + }, [showConfetti]); + + if (!previousBattle || !winningSong) { + return ( +
+

Nächster Kampf wird vorbereitet...

+
+ ); + } + + return ( +
+
+

Gewinner dieser Runde

+ {countdown > 0 && ( +
+ + Nächster Kampf in {countdown}s +
+ )} +
+ +
+
+
+
+ Gewinner +
+ +
+

{winningSong.title}

+

{winningSong.artist}

+ + {!lobby?.settings?.hidePlayerNames && winnerSubmitter && ( +

+ Eingereicht von: {getDisplayName(winnerSubmitter, lobby, currentPlayer)} +

+ )} + +
+ {winningVotes} Stimmen +
+
+ + {winnerVideoId ? ( +
+ +
+ ) : ( +
+ + Kein Video verfügbar +
+ )} +
+ + {losingSong && ( +
+
VS
+
+

{losingSong.title}

+

{losingSong.artist}

+
+ {losingVotes} Stimmen +
+
+
+ )} +
+
+
+ ); +}; + +export default BattleResultScreen; diff --git a/client/src/context/GameContext.jsx b/client/src/context/GameContext.jsx index c14926e..8d4177a 100644 --- a/client/src/context/GameContext.jsx +++ b/client/src/context/GameContext.jsx @@ -439,6 +439,19 @@ export function GameProvider({ children }) { }); }; + // Handle battle ended and show result screen + const handleBattleEnded = (data) => { + console.log('Battle ended, showing result screen:', data); + setLobby(prevLobby => { + if (!prevLobby) return prevLobby; + return { + ...prevLobby, + state: 'BATTLE', + previousBattle: data.previousBattle + }; + }); + }; + // Game finished const handleGameFinished = (data) => { setLobby(prevLobby => { @@ -461,11 +474,13 @@ export function GameProvider({ children }) { socket.on('songs_updated', handleSongsUpdated); socket.on('player_status_changed', handlePlayerStatusChanged); socket.on('vote_submitted', handleVoteSubmitted); + socket.on('battle_ended', handleBattleEnded); socket.on('tournament_started', data => { console.log('Tournament started event received:', data); setLobby(prevLobby => prevLobby ? {...prevLobby, state: data.state} : prevLobby); }); socket.on('new_battle', handleNewBattle); + socket.on('battle_ended', handleBattleEnded); socket.on('game_finished', handleGameFinished); // Clean up listeners on unmount @@ -479,6 +494,7 @@ export function GameProvider({ children }) { socket.off('player_status_changed', handlePlayerStatusChanged); socket.off('vote_submitted', handleVoteSubmitted); socket.off('new_battle', handleNewBattle); + socket.off('battle_ended', handleBattleEnded); socket.off('game_finished', handleGameFinished); }; }, [socket]); @@ -706,6 +722,23 @@ export function GameProvider({ children }) { setCurrentPlayer(null); }; + // Proceed to next battle after the battle result screen + const proceedToNextBattle = () => { + if (!socket || !isConnected) { + setError('Not connected to server'); + return; + } + + safeEmit('proceed_to_next_battle', {}, (response) => { + if (response.error) { + setError(response.error); + } else if (response.lobby) { + setLobby(response.lobby); + updateSavedGameData(response.lobby); + } + }); + }; + return ( {children} diff --git a/server/game.js b/server/game.js index e945020..74bf67b 100644 --- a/server/game.js +++ b/server/game.js @@ -635,11 +635,24 @@ class GameManager { song2: lobby.currentBattle.song2, song1Votes: lobby.currentBattle.song1Votes, song2Votes: 0, - winner: winnerSongId + winner: winnerSongId, + bye: true }); - // Move to next battle or finish tournament - this._moveToNextBattle(lobby); + // Store current battle for potential future reference + lobby.previousBattle = { + ...lobby.currentBattle, + winner: winnerSongId + }; + + // Skip battle result screen for byes - go straight to the next battle + lobby.state = 'VOTING'; + const tournamentFinished = this._moveToNextBattle(lobby); + + // Check if the tournament has finished + if (tournamentFinished || lobby.state === 'FINISHED') { + console.log(`Tournament has finished after bye battle! Winner: ${lobby.finalWinner ? lobby.finalWinner.title : 'No winner'}`); + } return { lobby, lobbyId }; } @@ -666,8 +679,132 @@ class GameManager { winner: winnerSongId }); - // Move to next battle or finish tournament - this._moveToNextBattle(lobby); + // Store current battle as previousBattle and set state to BATTLE_RESULT + lobby.previousBattle = { + ...lobby.currentBattle, + winner: winnerSongId + }; + + // Change state to BATTLE to show the battle result screen + lobby.state = 'BATTLE'; + } + + return { lobby, lobbyId }; + } + + /** + * Proceed to the next battle after showing the battle result screen + * @param {string} playerId - ID of the player (should be host) + * @returns {Object} Updated lobby data or error + */ + proceedToNextBattle(playerId) { + const lobbyId = this.playerToLobby.get(playerId); + if (!lobbyId) { + return { error: 'Player not in a lobby' }; + } + + const lobby = this.lobbies.get(lobbyId); + + // Check if player is the host + if (lobby.hostId !== playerId) { + return { error: 'Only the host can proceed to the next battle' }; + } + + // Check if we're in the battle result state + // Allow proceeding from either BATTLE or VOTING state to handle race conditions + if (lobby.state !== 'BATTLE' && lobby.state !== 'VOTING') { + console.log(`Warning: Attempting to proceed to next battle from state ${lobby.state}`); + return { error: 'Not in a valid state to proceed to next battle' }; + } + + // If we're in VOTING state, ensure we have a winner before proceeding + if (lobby.state === 'VOTING' && lobby.currentBattle && !lobby.currentBattle.winner) { + console.log('Warning: Attempting to proceed without a winner determined'); + + // Check if this is a bye battle (only one song, automatic advancement) + if (lobby.currentBattle.bye === true || !lobby.currentBattle.song2) { + console.log('Processing bye battle - automatic advancement'); + // For bye battles, the winner is always song1 + const winnerSongId = lobby.currentBattle.song1.id; + + lobby.currentBattle.winner = winnerSongId; + lobby.currentBattle.song1Votes = 1; // Add a dummy vote for song1 + + // Store as previous battle for history + lobby.previousBattle = { + ...lobby.currentBattle, + winner: winnerSongId + }; + + // Add to battle history + lobby.battles.push({ + round: lobby.currentBattle.round, + song1: lobby.currentBattle.song1, + song2: lobby.currentBattle.song2, + song1Votes: 1, // Always at least one vote for the bye song + song2Votes: 0, + winner: winnerSongId, + bye: true + }); + } + // Determine winner based on votes if present for regular battles + else if (lobby.currentBattle.votes && Object.keys(lobby.currentBattle.votes).length > 0) { + const winnerSongId = (lobby.currentBattle.song1Votes || 0) > (lobby.currentBattle.song2Votes || 0) + ? lobby.currentBattle.song1.id + : lobby.currentBattle.song2.id; + + lobby.currentBattle.winner = winnerSongId; + + // Store as previous battle for history + lobby.previousBattle = { + ...lobby.currentBattle, + winner: winnerSongId + }; + + // Add to battle history + lobby.battles.push({ + round: lobby.currentBattle.round, + song1: lobby.currentBattle.song1, + song2: lobby.currentBattle.song2, + song1Votes: lobby.currentBattle.song1Votes || 0, + song2Votes: lobby.currentBattle.song2Votes || 0, + winner: winnerSongId + }); + } else { + // For regular battles with no votes, force a winner to avoid getting stuck + console.log('No votes recorded but proceeding anyway to avoid blocking the game'); + const winnerSongId = lobby.currentBattle.song1.id; // Default to song1 as winner + + lobby.currentBattle.winner = winnerSongId; + lobby.currentBattle.song1Votes = 1; // Add a dummy vote + + // Store as previous battle for history + lobby.previousBattle = { + ...lobby.currentBattle, + winner: winnerSongId + }; + + // Add to battle history + lobby.battles.push({ + round: lobby.currentBattle.round, + song1: lobby.currentBattle.song1, + song2: lobby.currentBattle.song2, + song1Votes: 1, + song2Votes: 0, + winner: winnerSongId + }); + } + } + + // Set state to VOTING for the next battle + lobby.state = 'VOTING'; + + // Move to next battle or finish tournament - capture if the tournament has finished + const tournamentFinished = this._moveToNextBattle(lobby); + + // Check if the game has ended after trying to move to the next battle + if (tournamentFinished || lobby.state === 'FINISHED') { + console.log(`Tournament has finished! Winner: ${lobby.finalWinner ? lobby.finalWinner.title : 'No winner'}`); } return { lobby, lobbyId }; @@ -854,18 +991,81 @@ class GameManager { /** * Move to next battle or finish tournament * @param {Object} lobby - Lobby object + * @returns {boolean} true if tournament has finished, false otherwise * @private */ _moveToNextBattle(lobby) { + // If coming from BATTLE state, reset to VOTING for the next battle + if (lobby.state === 'BATTLE') { + lobby.state = 'VOTING'; + } + // Check if there are more battles in current round lobby.currentBracketIndex++; if (lobby.currentBracketIndex < lobby.brackets.length) { // Move to next battle lobby.currentBattle = lobby.brackets[lobby.currentBracketIndex]; - return; + + // Ensure votes is initialized as an object + if (!lobby.currentBattle.votes) { + lobby.currentBattle.votes = {}; + } + + // Initialize vote count for UI + lobby.currentBattle.voteCount = 0; + + // Check if this is a bye battle that should be auto-advanced + if (lobby.currentBattle.bye === true) { + console.log('Auto-advancing bye battle'); + // Auto-advance logic for bye battles + const winnerSongId = lobby.currentBattle.song1.id; + + lobby.currentBattle.winner = winnerSongId; + lobby.currentBattle.song1Votes = 1; // Add a dummy vote + + // Store to history + lobby.battles.push({ + round: lobby.currentBattle.round, + song1: lobby.currentBattle.song1, + song2: null, + song1Votes: 1, + song2Votes: 0, + winner: winnerSongId, + bye: true + }); + + // Immediately advance to the next battle + return this._moveToNextBattle(lobby); + } + + return false; // Tournament continues } + // No more battles in current round, set up the next round + return this._setUpNextRound(lobby); + } + + /** + * Shuffle array in-place using Fisher-Yates algorithm + * @param {Array} array - Array to shuffle + * @private + */ + _shuffleArray(array) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + } + + /** + * Helper method to set up the next tournament round + * Similar logic to end of _moveToNextBattle but extracted for reuse + * @param {Object} lobby - Lobby object + * @returns {boolean} true if tournament has finished, false otherwise + * @private + */ + _setUpNextRound(lobby) { // Current round complete, check if tournament is finished const winners = lobby.brackets .filter(b => !b.bye) // Skip byes @@ -884,9 +1084,10 @@ class GameManager { // If only one song remains, we have a winner if (nextRoundSongs.length <= 1) { lobby.state = 'FINISHED'; - lobby.finalWinner = nextRoundSongs[0]; + lobby.finalWinner = nextRoundSongs.length > 0 ? nextRoundSongs[0] : null; lobby.currentBattle = null; - return; + console.log(`Tournament has finished! Final winner: ${lobby.finalWinner ? lobby.finalWinner.title : 'No winner'}`); + return true; // Return true to indicate tournament has finished } // Create brackets for next round @@ -906,20 +1107,22 @@ class GameManager { song2Votes: 0, votes: {} // Initialize votes as an object }); - } // Create pairs for next round - for (let i = 0; i < nextRoundSongs.length; i += 2) { - if (i + 1 < nextRoundSongs.length) { - nextRound.push({ - round, - song1: nextRoundSongs[i], - song2: nextRoundSongs[i + 1], - song1Votes: 0, - song2Votes: 0, - winner: null, - votes: {} - }); - } + } + + // Create pairs for next round + for (let i = 0; i < nextRoundSongs.length; i += 2) { + if (i + 1 < nextRoundSongs.length) { + nextRound.push({ + round, + song1: nextRoundSongs[i], + song2: nextRoundSongs[i + 1], + song1Votes: 0, + song2Votes: 0, + winner: null, + votes: {} + }); } + } // Update brackets and reset index lobby.brackets = nextRound; @@ -936,19 +1139,33 @@ class GameManager { // Initialize vote count for UI lobby.currentBattle.voteCount = 0; + + // Check if this first battle is a bye - if so, auto-advance + if (lobby.currentBattle.bye === true) { + console.log('Auto-advancing bye battle in new round'); + // Auto-advance logic similar to above + const winnerSongId = lobby.currentBattle.song1.id; + + lobby.currentBattle.winner = winnerSongId; + lobby.currentBattle.song1Votes = 1; + + // Store to history + lobby.battles.push({ + round: lobby.currentBattle.round, + song1: lobby.currentBattle.song1, + song2: null, + song1Votes: 1, + song2Votes: 0, + winner: winnerSongId, + bye: true + }); + + // Move to next battle + this._moveToNextBattle(lobby); + } } - } - - /** - * Shuffle array in-place using Fisher-Yates algorithm - * @param {Array} array - Array to shuffle - * @private - */ - _shuffleArray(array) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } + + return false; // Tournament continues } } diff --git a/server/index.js b/server/index.js index 6937a87..6b10e35 100644 --- a/server/index.js +++ b/server/index.js @@ -302,38 +302,69 @@ io.on('connection', (socket) => { // Submit a vote in a battle socket.on('submit_vote', ({ songId }, callback) => { try { + console.log(`Player ${socket.id} voting for song ${songId}`); const result = gameManager.submitVote(socket.id, songId); if (result.error) { + if (callback) callback({ error: result.error }); + } else { + // Broadcast updated game state to all players in the lobby + socket.to(result.lobbyId).emit('vote_submitted', result); + + // Check if we've entered the BATTLE state (all votes are in and battle has ended) + const lobby = result.lobby; + if (lobby.state === 'BATTLE') { + // Broadcast battle result to all players in the lobby + io.to(result.lobbyId).emit('battle_ended', { + previousBattle: lobby.previousBattle, + state: 'BATTLE' + }); + } + // Check if the game has finished (edge case - last battle with automatic winner) + else if (lobby.state === 'FINISHED') { + console.log('Game finished after vote submission'); + io.to(result.lobbyId).emit('game_finished', { + winner: lobby.finalWinner, + battles: lobby.battles + }); + } + if (callback) callback(result); - return; } - - // Notify all players about vote count - io.to(result.lobbyId).emit('vote_submitted', { - lobby: result.lobby - }); - - // If battle is finished, notify about new battle - if (result.lobby.currentBattle && result.lobby.currentBattle !== result.lobby.battles[result.lobby.battles.length - 1]) { - io.to(result.lobbyId).emit('new_battle', { - battle: result.lobby.currentBattle - }); - } - - // If game is finished, notify about the winner - if (result.lobby.state === 'FINISHED') { - io.to(result.lobbyId).emit('game_finished', { - winner: result.lobby.finalWinner, - battles: result.lobby.battles - }); - } - - // Send response to client - if (callback) callback(result); } catch (error) { console.error('Error submitting vote:', error); - if (callback) callback({ error: 'Failed to submit vote' }); + if (callback) callback({ error: 'Server error while submitting vote' }); + } + }); + + // Proceed to next battle after the battle result screen + socket.on('proceed_to_next_battle', (data, callback) => { + try { + const result = gameManager.proceedToNextBattle(socket.id); + + if (result.error) { + if (callback) callback({ error: result.error }); + } else { + // Check if the game has finished + if (result.lobby.state === 'FINISHED') { + console.log('Game finished, broadcasting final winner'); + io.to(result.lobbyId).emit('game_finished', { + winner: result.lobby.finalWinner, + battles: result.lobby.battles + }); + } else { + // Broadcast updated game state to all players in the lobby + io.to(result.lobbyId).emit('new_battle', { + battle: result.lobby.currentBattle, + state: result.lobby.state + }); + } + + if (callback) callback(result); + } + } catch (error) { + console.error('Error proceeding to next battle:', error); + if (callback) callback({ error: 'Server error while proceeding to next battle' }); } });