|
|
|
const socket = io({ |
|
reconnection: true, |
|
reconnectionAttempts: 5, |
|
reconnectionDelay: 1000, |
|
transports: ['websocket', 'polling'] |
|
}); |
|
|
|
class Game { |
|
constructor() { |
|
|
|
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 |
|
}; |
|
|
|
|
|
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...'); |
|
|
|
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') |
|
} |
|
}; |
|
|
|
|
|
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...'); |
|
|
|
|
|
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.addEventListener('resize', () => this.handleResize()); |
|
} |
|
|
|
initializeSocketHandlers() { |
|
|
|
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.'); |
|
}); |
|
|
|
|
|
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') { |
|
|
|
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...'); |
|
|
|
|
|
this.state.gameId = null; |
|
this.state.players = []; |
|
|
|
|
|
socket.emit('create_game'); |
|
|
|
|
|
const timeout = setTimeout(() => { |
|
reject(new Error('Game creation timeout')); |
|
}, 5000); |
|
|
|
|
|
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')); |
|
} |
|
}); |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
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() { |
|
|
|
if (this.timer) clearInterval(this.timer); |
|
|
|
|
|
window.removeEventListener('resize', this.handleResize); |
|
|
|
|
|
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'); |
|
} |
|
} |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
console.log('Initializing game...'); |
|
window.game = new Game(); |
|
}); |