// Initialize Socket.IO connection with automatic reconnection const socket = io({ reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000 }); class Game { constructor() { // Core game state this.state = { gameId: null, currentPhase: 'landing', playerId: null, players: [], currentQuestion: null, isRecording: false, recordingTime: 30, // seconds listeningTime: 60, // seconds votingTime: 60, // seconds recordings: new Map(), votes: new Map(), impostor: null }; // Audio recording configuration this.audioConfig = { mediaRecorder: null, audioChunks: [], stream: null }; // Initialize the game this.initializeEventListeners(); this.bindUIElements(); } // Initialize all event listeners for the game initializeEventListeners() { // Socket event listeners socket.on('connect', () => this.handleConnection()); socket.on('game_created', (data) => this.handleGameCreated(data)); socket.on('player_joined', (data) => this.handlePlayerJoined(data)); socket.on('round_start', (data) => this.handleRoundStart(data)); socket.on('round_result', (data) => this.handleRoundResult(data)); // UI event listeners document.getElementById('start-button')?.addEventListener('click', () => this.startGame()); document.getElementById('add-player-button')?.addEventListener('click', () => this.addPlayer()); } // Bind UI elements and initialize their event handlers bindUIElements() { // Bind all necessary UI elements const uiElements = { gameContainer: document.getElementById('game-container'), landingPage: document.getElementById('landing-page'), setupPage: document.getElementById('setup-page'), gamePage: document.getElementById('game-page'), questionDisplay: document.getElementById('question-display'), timerDisplay: document.getElementById('timer-display'), recordButton: document.getElementById('record-button'), playerList: document.getElementById('player-list'), voteButtons: document.querySelectorAll('.vote-button') }; // Store UI elements in the class this.ui = uiElements; } // Handle initial connection to the server handleConnection() { console.log('Connected to server'); this.showPage('landing'); } // Create a new game session async createGame() { try { socket.emit('create_game'); } catch (error) { this.handleError('Failed to create game'); } } // Handle successful game creation handleGameCreated(data) { this.state.gameId = data.gameId; this.showPage('setup'); } // Add a new player to the game async addPlayer() { if (this.state.players.length >= 5) { this.handleError('Maximum player limit reached'); return; } const playerName = prompt('Enter player name:'); if (!playerName) return; socket.emit('join_game', { gameId: this.state.gameId, playerName: playerName }); } // Handle new player joining the game handlePlayerJoined(data) { this.state.players.push({ id: data.playerId, name: data.playerName }); this.updatePlayerList(); } // Update the player list in the UI updatePlayerList() { if (!this.ui.playerList) return; this.ui.playerList.innerHTML = ''; this.state.players.forEach(player => { const playerElement = document.createElement('div'); playerElement.className = 'player-avatar'; playerElement.textContent = player.id; this.ui.playerList.appendChild(playerElement); }); } // Start the game async startGame() { if (this.state.players.length < 3) { this.handleError('Need at least 3 players to start'); return; } try { const response = await fetch('/api/start_game', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ gameId: this.state.gameId }) }); const data = await response.json(); if (data.status === 'success') { this.handleRoundStart(data); } } catch (error) { this.handleError('Failed to start game'); } } // Handle the start of a new round handleRoundStart(data) { this.state.currentQuestion = data.question; this.state.currentPhase = 'recording'; this.showPage('game'); this.updateQuestionDisplay(); this.startTimer(this.state.recordingTime, () => this.endRecordingPhase()); } // Start audio recording async startRecording() { try { this.audioConfig.stream = await navigator.mediaDevices.getUserMedia({ audio: true }); this.audioConfig.mediaRecorder = new MediaRecorder(this.audioConfig.stream); this.audioConfig.audioChunks = []; this.audioConfig.mediaRecorder.ondataavailable = (event) => { this.audioConfig.audioChunks.push(event.data); }; this.audioConfig.mediaRecorder.onstop = () => { this.submitRecording(); }; this.audioConfig.mediaRecorder.start(); this.state.isRecording = true; this.updateRecordButton(); } catch (error) { this.handleError('Failed to start recording'); } } // Stop audio recording stopRecording() { if (this.audioConfig.mediaRecorder && this.state.isRecording) { this.audioConfig.mediaRecorder.stop(); this.audioConfig.stream.getTracks().forEach(track => track.stop()); this.state.isRecording = false; this.updateRecordButton(); } } // Submit recording to server async submitRecording() { const audioBlob = new Blob(this.audioConfig.audioChunks, { type: 'audio/wav' }); const formData = new FormData(); formData.append('audio', audioBlob); formData.append('gameId', this.state.gameId); formData.append('playerId', this.state.playerId); try { const response = await fetch('/api/submit_recording', { method: 'POST', body: formData }); const data = await response.json(); if (data.status === 'success') { this.state.recordings.set(this.state.playerId, data.recordingUrl); } } catch (error) { this.handleError('Failed to submit recording'); } } // Start the timer for a game phase startTimer(duration, callback) { let timeLeft = duration; this.updateTimerDisplay(timeLeft); this.timer = setInterval(() => { timeLeft--; this.updateTimerDisplay(timeLeft); if (timeLeft <= 0) { clearInterval(this.timer); if (callback) callback(); } }, 1000); } // Update the timer display updateTimerDisplay(timeLeft) { if (this.ui.timerDisplay) { const minutes = Math.floor(timeLeft / 60); const seconds = timeLeft % 60; this.ui.timerDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } } // End the recording phase and move to listening phase endRecordingPhase() { if (this.state.isRecording) { this.stopRecording(); } this.state.currentPhase = 'listening'; this.startListeningPhase(); } // Start the listening phase startListeningPhase() { this.showPage('listening'); this.loadRecordings(); this.startTimer(this.state.listeningTime, () => this.startVotingPhase()); } // Load all player recordings async loadRecordings() { // Implementation for loading and playing recordings // This would integrate with the audio playback UI } // Start the voting phase startVotingPhase() { this.state.currentPhase = 'voting'; this.showPage('voting'); this.startTimer(this.state.votingTime, () => this.endVotingPhase()); } // Submit a vote submitVote(votedPlayerId) { socket.emit('submit_vote', { gameId: this.state.gameId, voterId: this.state.playerId, votedPlayerId: votedPlayerId }); } // Handle the round results handleRoundResult(data) { this.showResults(data); // Trigger dramatic background effect window.gameBackground?.addDramaticEffect('impostor_reveal'); } // Show the results page showResults(data) { this.showPage('results'); // Implementation for displaying round results } // Switch between game pages showPage(pageName) { const pages = document.querySelectorAll('.game-page'); pages.forEach(page => { page.classList.remove('active'); if (page.id === `${pageName}-page`) { page.classList.add('active'); } }); } // Handle errors handleError(message) { const errorElement = document.createElement('div'); errorElement.className = 'error-message'; errorElement.textContent = message; this.ui.gameContainer.appendChild(errorElement); setTimeout(() => errorElement.remove(), 3000); } } // Initialize the game when the DOM is loaded document.addEventListener('DOMContentLoaded', () => { const game = new Game(); // Expose game instance for debugging window.game = game; }); export default Game;