// Initialize Socket.IO connection with proper configuration const socket = io({ reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, transports: ['websocket', 'polling'] // Try WebSocket first, fallback to polling }); class Game { constructor() { // Initialize core game state this.state = { gameId: null, currentPhase: 'landing', players: [], currentQuestion: null, isRecording: false, recordingTime: 30, listeningTime: 60, votingTime: 60, recordings: new Map(), votes: new Map(), impostor: null, maxPlayers: 5, minPlayers: 3, isConnected: false, errorDisplayed: false }; // Initialize game when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.initialize()); } else { this.initialize(); } } initialize() { console.log('Initializing game components...'); this.bindUIElements(); this.initializeEventListeners(); this.initializeSocketHandlers(); this.showPage('landing'); } bindUIElements() { console.log('Binding UI elements...'); // Cache all UI elements for quick access this.ui = { gameContainer: document.getElementById('game-container'), pages: { landing: document.getElementById('landing-page'), setup: document.getElementById('setup-page'), recording: document.getElementById('recording-page'), listening: document.getElementById('listening-page'), voting: document.getElementById('voting-page'), results: document.getElementById('results-page') }, buttons: { play: document.getElementById('play-button'), addPlayer: document.getElementById('add-player-button'), start: document.getElementById('start-button'), record: document.getElementById('record-button') }, displays: { timer: document.getElementById('timer-display'), question: document.getElementById('question-display'), playerList: document.getElementById('player-list') } }; // Log UI element bindings for debugging console.log('UI Elements bound:', { landing: !!this.ui.pages.landing, setup: !!this.ui.pages.setup, playButton: !!this.ui.buttons.play }); } initializeEventListeners() { console.log('Setting up event listeners...'); // Button click handlers if (this.ui.buttons.play) { this.ui.buttons.play.addEventListener('click', () => this.handlePlayButton()); } if (this.ui.buttons.addPlayer) { this.ui.buttons.addPlayer.addEventListener('click', () => this.addPlayer()); } if (this.ui.buttons.start) { this.ui.buttons.start.addEventListener('click', () => this.startGame()); } if (this.ui.buttons.record) { this.ui.buttons.record.addEventListener('click', () => this.toggleRecording()); } // Window resize handler for responsive layout window.addEventListener('resize', () => this.handleResize()); } initializeSocketHandlers() { // Connection events socket.on('connect', () => { console.log('Connected to server'); this.state.isConnected = true; this.clearError(); }); socket.on('disconnect', () => { console.log('Disconnected from server'); this.state.isConnected = false; this.handleError('Connection lost. Attempting to reconnect...'); }); socket.on('connect_error', (error) => { console.error('Connection error:', error); this.handleError('Unable to connect to server. Please check your connection.'); }); // Game events socket.on('connection_success', (data) => { console.log('Connection successful:', data); }); socket.on('game_created', (data) => { console.log('Game created:', data); if (data.status === 'success') { this.state.gameId = data.gameId; this.clearError(); this.showPage('setup'); this.addDefaultPlayers(); } else { this.handleError('Failed to create game'); } }); socket.on('game_error', (data) => { console.error('Game error:', data); this.handleError(data.error || 'An error occurred'); }); socket.on('player_joined', (data) => { console.log('Player joined:', data); if (data.status === 'success') { // Update player list and UI const newPlayer = { id: data.playerId, name: data.playerName }; this.state.players.push(newPlayer); this.updatePlayerList(); this.updatePlayerControls(); } }); socket.on('round_started', (data) => { console.log('Round started:', data); this.handleRoundStart(data); }); socket.on('recording_submitted', (data) => { console.log('Recording submitted:', data); this.updateRecordingStatus(data); }); socket.on('round_result', (data) => { console.log('Round result:', data); this.handleRoundResult(data); }); } async handlePlayButton() { console.log('Play button clicked'); try { if (!this.state.isConnected) { throw new Error('Not connected to server'); } this.clearError(); await this.createGame(); } catch (error) { console.error('Error handling play button:', error); this.handleError('Failed to start game. Please try again.'); } } createGame() { return new Promise((resolve, reject) => { console.log('Creating new game...'); // Reset game state this.state.gameId = null; this.state.players = []; // Emit create game event socket.emit('create_game'); // Set timeout for response const timeout = setTimeout(() => { reject(new Error('Game creation timeout')); }, 5000); // Handle response socket.once('game_created', (data) => { clearTimeout(timeout); if (data.status === 'success') { console.log('Game created successfully:', data); resolve(data); } else { reject(new Error(data.error || 'Failed to create game')); } }); // Handle error socket.once('game_error', (data) => { clearTimeout(timeout); reject(new Error(data.error || 'Failed to create game')); }); }); } addDefaultPlayers() { console.log('Adding default players'); if (!this.state.gameId) { console.error('No game ID available'); return; } // Add two default players this.addPlayer('Player 1'); this.addPlayer('Player 2'); } addPlayer(defaultName = null) { if (this.state.players.length >= this.state.maxPlayers) { this.handleError('Maximum players reached'); return; } const playerName = defaultName || `Player ${this.state.players.length + 1}`; console.log('Adding player:', playerName); socket.emit('join_game', { gameId: this.state.gameId, playerName: playerName }); } async startGame() { console.log('Starting game...'); 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); } else { throw new Error(data.error || 'Failed to start game'); } } catch (error) { console.error('Error starting game:', error); this.handleError('Failed to start game'); } } handleRoundStart(data) { console.log('Round starting:', data); this.state.currentQuestion = data.question; this.state.currentPhase = 'recording'; this.showPage('recording'); this.updateQuestionDisplay(); this.startTimer(this.state.recordingTime); } updateQuestionDisplay() { if (this.ui.displays.question && this.state.currentQuestion) { this.ui.displays.question.textContent = this.state.currentQuestion; } } updatePlayerList() { if (!this.ui.displays.playerList) return; const playerList = this.ui.displays.playerList; playerList.innerHTML = ''; const gridContainer = document.createElement('div'); gridContainer.className = 'player-grid'; this.state.players.forEach(player => { const playerCard = document.createElement('div'); playerCard.className = 'player-card'; const circle = document.createElement('div'); circle.className = 'player-circle'; circle.textContent = player.id; const name = document.createElement('div'); name.className = 'player-name'; name.textContent = player.name; playerCard.appendChild(circle); playerCard.appendChild(name); gridContainer.appendChild(playerCard); }); playerList.appendChild(gridContainer); } updatePlayerControls() { if (this.ui.buttons.addPlayer) { this.ui.buttons.addPlayer.disabled = this.state.players.length >= this.state.maxPlayers; } if (this.ui.buttons.start) { this.ui.buttons.start.disabled = this.state.players.length < this.state.minPlayers; } } handleError(message) { console.error('Error:', message); // Prevent multiple error messages if (this.state.errorDisplayed) { return; } this.state.errorDisplayed = true; const errorElement = document.createElement('div'); errorElement.className = 'error-message'; errorElement.textContent = message; if (this.ui.gameContainer) { this.ui.gameContainer.appendChild(errorElement); setTimeout(() => { errorElement.remove(); this.state.errorDisplayed = false; }, 3000); } } clearError() { const errorMessages = document.querySelectorAll('.error-message'); errorMessages.forEach(msg => msg.remove()); this.state.errorDisplayed = false; } showPage(pageName) { console.log('Showing page:', pageName); Object.values(this.ui.pages).forEach(page => { if (page) { page.classList.remove('active'); } }); const pageToShow = this.ui.pages[pageName]; if (pageToShow) { pageToShow.classList.add('active'); this.state.currentPhase = pageName; } else { console.error(`Page ${pageName} not found`); } } startTimer(duration) { let timeLeft = duration; this.updateTimerDisplay(timeLeft); this.timer = setInterval(() => { timeLeft--; this.updateTimerDisplay(timeLeft); if (timeLeft <= 0) { clearInterval(this.timer); this.handlePhaseEnd(); } }, 1000); } updateTimerDisplay(timeLeft) { if (this.ui.displays.timer) { const minutes = Math.floor(timeLeft / 60); const seconds = timeLeft % 60; this.ui.displays.timer.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } } handlePhaseEnd() { switch (this.state.currentPhase) { case 'recording': this.showPage('listening'); this.startTimer(this.state.listeningTime); break; case 'listening': this.showPage('voting'); this.startTimer(this.state.votingTime); break; case 'voting': this.showPage('results'); break; } } cleanup() { // Clear timers if (this.timer) clearInterval(this.timer); // Remove event listeners window.removeEventListener('resize', this.handleResize); // Reset game state this.state = { gameId: null, currentPhase: 'landing', players: [], currentQuestion: null, isRecording: false, isConnected: false, errorDisplayed: false }; } handleResize() { if (window.innerWidth < 768) { this.ui.gameContainer.classList.add('mobile'); } else { this.ui.gameContainer.classList.remove('mobile'); } } } // Initialize the game when the DOM is loaded document.addEventListener('DOMContentLoaded', () => { console.log('Initializing game...'); window.game = new Game(); });