dcrey7 commited on
Commit
a24fc4c
·
verified ·
1 Parent(s): f5365fb

Update static/js/game.js

Browse files
Files changed (1) hide show
  1. static/js/game.js +323 -177
static/js/game.js CHANGED
@@ -1,55 +1,53 @@
 
 
 
 
 
 
 
1
 
2
  class Game {
3
  constructor() {
4
- // Enhanced game state with connection tracking
5
  this.state = {
6
  gameId: null,
7
  currentPhase: 'landing',
8
  players: [],
9
  currentQuestion: null,
10
  isRecording: false,
 
 
 
11
  recordings: new Map(),
12
  votes: new Map(),
13
- connectionStatus: 'disconnected',
14
- connectionRetries: 0,
15
- socket: null,
16
- uiElements: null
 
17
  };
18
 
19
- this.initializeGame();
 
 
 
 
 
20
  }
21
 
22
- initializeGame() {
23
- this.initializeSocket();
24
- this.initializeUI();
25
  this.initializeEventListeners();
 
 
26
  }
27
 
28
- initializeSocket() {
29
- console.log('Initializing WebSocket connection...');
30
- this.state.socket = io({
31
- reconnection: true,
32
- reconnectionAttempts: 5,
33
- reconnectionDelay: 3000,
34
- transports: ['websocket'],
35
- autoConnect: false
36
- });
37
-
38
- // Socket event handlers
39
- this.state.socket.on('connect', () => this.handleSocketConnect());
40
- this.state.socket.on('disconnect', () => this.handleSocketDisconnect());
41
- this.state.socket.on('connect_error', (error) => this.handleSocketError(error));
42
- this.state.socket.on('game_created', (data) => this.handleGameCreated(data));
43
- this.state.socket.on('player_joined', (data) => this.handlePlayerJoined(data));
44
- this.state.socket.on('round_start', (data) => this.handleRoundStart(data));
45
- this.state.socket.on('round_result', (data) => this.handleRoundResult(data));
46
-
47
- // Manually connect after setting up handlers
48
- this.state.socket.connect();
49
- }
50
-
51
- initializeUI() {
52
- this.state.uiElements = {
53
  pages: {
54
  landing: document.getElementById('landing-page'),
55
  setup: document.getElementById('setup-page'),
@@ -67,129 +65,241 @@ class Game {
67
  displays: {
68
  timer: document.getElementById('timer-display'),
69
  question: document.getElementById('question-display'),
70
- playerList: document.getElementById('player-list'),
71
- connectionStatus: document.getElementById('connection-status')
72
  }
73
  };
 
 
 
 
 
 
 
74
  }
75
 
76
  initializeEventListeners() {
 
 
77
  // Button click handlers
78
- this.state.uiElements.buttons.play?.addEventListener('click', () => this.handlePlay());
79
- this.state.uiElements.buttons.addPlayer?.addEventListener('click', () => this.addPlayer());
80
- this.state.uiElements.buttons.start?.addEventListener('click', () => this.startGame());
81
- this.state.uiElements.buttons.record?.addEventListener('click', () => this.toggleRecording());
82
 
83
- // Window resize handler
 
 
 
 
 
 
 
 
 
 
 
 
84
  window.addEventListener('resize', () => this.handleResize());
85
  }
86
 
87
- handleSocketConnect() {
88
- console.log('WebSocket connected:', this.state.socket.id);
89
- this.state.connectionStatus = 'connected';
90
- this.updateConnectionStatus();
91
- }
 
 
92
 
93
- handleSocketDisconnect() {
94
- console.log('WebSocket disconnected');
95
- this.state.connectionStatus = 'disconnected';
96
- this.updateConnectionStatus();
97
- this.showError('Disconnected from server. Reconnecting...');
98
- }
99
 
100
- handleSocketError(error) {
101
- console.error('WebSocket error:', error);
102
- this.state.connectionStatus = 'error';
103
- this.updateConnectionStatus();
104
- this.showError(`Connection error: ${error.message}`);
105
- }
106
 
107
- updateConnectionStatus() {
108
- if (!this.state.uiElements.displays.connectionStatus) return;
109
-
110
- const statusMap = {
111
- connected: '🟢 Connected',
112
- disconnected: '🔴 Disconnected',
113
- error: '⚠️ Connection Error'
114
- };
115
-
116
- this.state.uiElements.displays.connectionStatus.textContent =
117
- statusMap[this.state.connectionStatus] || '❓ Unknown';
118
- }
119
 
120
- async handlePlay() {
121
- if (this.state.connectionStatus !== 'connected') {
122
- this.showError('Not connected to server');
123
- return;
124
- }
 
 
 
 
 
 
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  try {
127
- await this.createNewGame();
128
- this.showPage('setup');
129
- this.addDefaultPlayers();
 
 
130
  } catch (error) {
131
- this.showError(error.message);
 
132
  }
133
  }
134
 
135
- createNewGame() {
136
  return new Promise((resolve, reject) => {
137
- this.state.socket.emit('create_game', (response) => {
138
- if (response.status === 'success') {
139
- this.state.gameId = response.gameId;
140
- resolve(response);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  } else {
142
- reject(new Error(response.error || 'Game creation failed'));
143
  }
144
  });
145
 
146
- setTimeout(() => {
147
- reject(new Error('Game creation timed out'));
148
- }, 10000);
 
 
149
  });
150
  }
151
 
152
  addDefaultPlayers() {
 
 
 
 
 
 
 
153
  this.addPlayer('Player 1');
154
  this.addPlayer('Player 2');
155
  }
156
 
157
  addPlayer(defaultName = null) {
158
- if (this.state.players.length >= 5) {
159
- this.showError('Maximum players reached (5)');
160
  return;
161
  }
162
 
163
  const playerName = defaultName || `Player ${this.state.players.length + 1}`;
164
-
165
- this.state.socket.emit('join_game', {
 
166
  gameId: this.state.gameId,
167
  playerName: playerName
168
- }, (response) => {
169
- if (response.status === 'success') {
170
- this.state.players.push({
171
- id: response.player.id,
172
- name: response.player.name
173
- });
174
- this.updatePlayerList();
175
- this.updateGameControls();
 
 
 
 
 
 
 
 
 
 
 
176
  } else {
177
- this.showError(response.error || 'Failed to add player');
178
  }
179
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  }
181
 
182
  updatePlayerList() {
183
- const container = this.state.uiElements.displays.playerList;
184
- if (!container) return;
185
 
186
- container.innerHTML = '';
187
- const grid = document.createElement('div');
188
- grid.className = 'player-grid';
 
 
189
 
190
  this.state.players.forEach(player => {
191
- const card = document.createElement('div');
192
- card.className = 'player-card';
193
 
194
  const circle = document.createElement('div');
195
  circle.className = 'player-circle';
@@ -199,106 +309,142 @@ class Game {
199
  name.className = 'player-name';
200
  name.textContent = player.name;
201
 
202
- card.append(circle, name);
203
- grid.appendChild(card);
 
204
  });
205
 
206
- container.appendChild(grid);
207
  }
208
 
209
- updateGameControls() {
210
- // Update add player button
211
- this.state.uiElements.buttons.addPlayer.disabled =
212
- this.state.players.length >= 5;
 
213
 
214
- // Update start game button
215
- this.state.uiElements.buttons.start.disabled =
216
- this.state.players.length < 3;
 
217
  }
218
 
219
- async startGame() {
220
- try {
221
- const response = await fetch('/api/start_game', {
222
- method: 'POST',
223
- headers: {'Content-Type': 'application/json'},
224
- body: JSON.stringify({
225
- gameId: this.state.gameId,
226
- players: this.state.players.map(p => p.id)
227
- })
228
- });
229
-
230
- const data = await response.json();
231
- if (data.status === 'success') {
232
- this.handleRoundStart(data);
233
- } else {
234
- throw new Error(data.error || 'Failed to start game');
235
- }
236
- } catch (error) {
237
- this.showError(error.message);
238
  }
239
  }
240
 
241
- handleRoundStart(data) {
242
- this.state.currentQuestion = data.question;
243
- this.state.currentPhase = 'recording';
244
- this.showPage('recording');
245
- this.updateQuestionDisplay();
246
- this.startTimer(30);
247
  }
248
 
249
  showPage(pageName) {
250
- Object.values(this.state.uiElements.pages).forEach(page => {
251
- page?.classList.remove('active');
 
 
 
 
252
  });
253
- this.state.uiElements.pages[pageName]?.classList.add('active');
254
- }
255
 
256
- updateQuestionDisplay() {
257
- if (this.state.uiElements.displays.question && this.state.currentQuestion) {
258
- this.state.uiElements.displays.question.textContent = this.state.currentQuestion;
 
 
 
259
  }
260
  }
261
 
262
  startTimer(duration) {
263
- let remaining = duration;
264
- this.updateTimerDisplay(remaining);
265
-
266
- const timer = setInterval(() => {
267
- remaining--;
268
- this.updateTimerDisplay(remaining);
269
-
270
- if (remaining <= 0) {
271
- clearInterval(timer);
272
  this.handlePhaseEnd();
273
  }
274
  }, 1000);
275
  }
276
 
277
- updateTimerDisplay(seconds) {
278
- if (!this.state.uiElements.displays.timer) return;
279
- const mins = Math.floor(seconds / 60);
280
- const secs = seconds % 60;
281
- this.state.uiElements.displays.timer.textContent =
282
- `${mins}:${secs.toString().padStart(2, '0')}`;
 
283
  }
284
 
285
- showError(message, duration = 5000) {
286
- const errorElement = document.createElement('div');
287
- errorElement.className = 'error-message';
288
- errorElement.textContent = message;
289
- document.body.appendChild(errorElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
- setTimeout(() => {
292
- errorElement.remove();
293
- }, duration);
 
 
 
 
 
 
 
294
  }
295
 
296
  handleResize() {
297
- // Add responsive layout handling if needed
 
 
 
 
298
  }
299
  }
300
 
301
- // Initialize game when DOM loads
302
  document.addEventListener('DOMContentLoaded', () => {
 
303
  window.game = new Game();
304
- });
 
1
+ // Initialize Socket.IO connection with proper configuration
2
+ const socket = io({
3
+ reconnection: true,
4
+ reconnectionAttempts: 5,
5
+ reconnectionDelay: 1000,
6
+ transports: ['websocket', 'polling'] // Try WebSocket first, fallback to polling
7
+ });
8
 
9
  class Game {
10
  constructor() {
11
+ // Initialize core game state
12
  this.state = {
13
  gameId: null,
14
  currentPhase: 'landing',
15
  players: [],
16
  currentQuestion: null,
17
  isRecording: false,
18
+ recordingTime: 30,
19
+ listeningTime: 60,
20
+ votingTime: 60,
21
  recordings: new Map(),
22
  votes: new Map(),
23
+ impostor: null,
24
+ maxPlayers: 5,
25
+ minPlayers: 3,
26
+ isConnected: false,
27
+ errorDisplayed: false
28
  };
29
 
30
+ // Initialize game when DOM is ready
31
+ if (document.readyState === 'loading') {
32
+ document.addEventListener('DOMContentLoaded', () => this.initialize());
33
+ } else {
34
+ this.initialize();
35
+ }
36
  }
37
 
38
+ initialize() {
39
+ console.log('Initializing game components...');
40
+ this.bindUIElements();
41
  this.initializeEventListeners();
42
+ this.initializeSocketHandlers();
43
+ this.showPage('landing');
44
  }
45
 
46
+ bindUIElements() {
47
+ console.log('Binding UI elements...');
48
+ // Cache all UI elements for quick access
49
+ this.ui = {
50
+ gameContainer: document.getElementById('game-container'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  pages: {
52
  landing: document.getElementById('landing-page'),
53
  setup: document.getElementById('setup-page'),
 
65
  displays: {
66
  timer: document.getElementById('timer-display'),
67
  question: document.getElementById('question-display'),
68
+ playerList: document.getElementById('player-list')
 
69
  }
70
  };
71
+
72
+ // Log UI element bindings for debugging
73
+ console.log('UI Elements bound:', {
74
+ landing: !!this.ui.pages.landing,
75
+ setup: !!this.ui.pages.setup,
76
+ playButton: !!this.ui.buttons.play
77
+ });
78
  }
79
 
80
  initializeEventListeners() {
81
+ console.log('Setting up event listeners...');
82
+
83
  // Button click handlers
84
+ if (this.ui.buttons.play) {
85
+ this.ui.buttons.play.addEventListener('click', () => this.handlePlayButton());
86
+ }
 
87
 
88
+ if (this.ui.buttons.addPlayer) {
89
+ this.ui.buttons.addPlayer.addEventListener('click', () => this.addPlayer());
90
+ }
91
+
92
+ if (this.ui.buttons.start) {
93
+ this.ui.buttons.start.addEventListener('click', () => this.startGame());
94
+ }
95
+
96
+ if (this.ui.buttons.record) {
97
+ this.ui.buttons.record.addEventListener('click', () => this.toggleRecording());
98
+ }
99
+
100
+ // Window resize handler for responsive layout
101
  window.addEventListener('resize', () => this.handleResize());
102
  }
103
 
104
+ initializeSocketHandlers() {
105
+ // Connection events
106
+ socket.on('connect', () => {
107
+ console.log('Connected to server');
108
+ this.state.isConnected = true;
109
+ this.clearError();
110
+ });
111
 
112
+ socket.on('disconnect', () => {
113
+ console.log('Disconnected from server');
114
+ this.state.isConnected = false;
115
+ this.handleError('Connection lost. Attempting to reconnect...');
116
+ });
 
117
 
118
+ socket.on('connect_error', (error) => {
119
+ console.error('Connection error:', error);
120
+ this.handleError('Unable to connect to server. Please check your connection.');
121
+ });
 
 
122
 
123
+ // Game events
124
+ socket.on('connection_success', (data) => {
125
+ console.log('Connection successful:', data);
126
+ });
 
 
 
 
 
 
 
 
127
 
128
+ socket.on('game_created', (data) => {
129
+ console.log('Game created:', data);
130
+ if (data.status === 'success') {
131
+ this.state.gameId = data.gameId;
132
+ this.clearError();
133
+ this.showPage('setup');
134
+ this.addDefaultPlayers();
135
+ } else {
136
+ this.handleError('Failed to create game');
137
+ }
138
+ });
139
 
140
+ socket.on('game_error', (data) => {
141
+ console.error('Game error:', data);
142
+ this.handleError(data.error || 'An error occurred');
143
+ });
144
+
145
+ socket.on('player_joined', (data) => {
146
+ console.log('Player joined:', data);
147
+ if (data.status === 'success') {
148
+ // Update player list and UI
149
+ const newPlayer = {
150
+ id: data.playerId,
151
+ name: data.playerName
152
+ };
153
+ this.state.players.push(newPlayer);
154
+ this.updatePlayerList();
155
+ this.updatePlayerControls();
156
+ }
157
+ });
158
+
159
+ socket.on('round_started', (data) => {
160
+ console.log('Round started:', data);
161
+ this.handleRoundStart(data);
162
+ });
163
+
164
+ socket.on('recording_submitted', (data) => {
165
+ console.log('Recording submitted:', data);
166
+ this.updateRecordingStatus(data);
167
+ });
168
+
169
+ socket.on('round_result', (data) => {
170
+ console.log('Round result:', data);
171
+ this.handleRoundResult(data);
172
+ });
173
+ }
174
+
175
+ async handlePlayButton() {
176
+ console.log('Play button clicked');
177
  try {
178
+ if (!this.state.isConnected) {
179
+ throw new Error('Not connected to server');
180
+ }
181
+ this.clearError();
182
+ await this.createGame();
183
  } catch (error) {
184
+ console.error('Error handling play button:', error);
185
+ this.handleError('Failed to start game. Please try again.');
186
  }
187
  }
188
 
189
+ createGame() {
190
  return new Promise((resolve, reject) => {
191
+ console.log('Creating new game...');
192
+
193
+ // Reset game state
194
+ this.state.gameId = null;
195
+ this.state.players = [];
196
+
197
+ // Emit create game event
198
+ socket.emit('create_game');
199
+
200
+ // Set timeout for response
201
+ const timeout = setTimeout(() => {
202
+ reject(new Error('Game creation timeout'));
203
+ }, 5000);
204
+
205
+ // Handle response
206
+ socket.once('game_created', (data) => {
207
+ clearTimeout(timeout);
208
+ if (data.status === 'success') {
209
+ console.log('Game created successfully:', data);
210
+ resolve(data);
211
  } else {
212
+ reject(new Error(data.error || 'Failed to create game'));
213
  }
214
  });
215
 
216
+ // Handle error
217
+ socket.once('game_error', (data) => {
218
+ clearTimeout(timeout);
219
+ reject(new Error(data.error || 'Failed to create game'));
220
+ });
221
  });
222
  }
223
 
224
  addDefaultPlayers() {
225
+ console.log('Adding default players');
226
+ if (!this.state.gameId) {
227
+ console.error('No game ID available');
228
+ return;
229
+ }
230
+
231
+ // Add two default players
232
  this.addPlayer('Player 1');
233
  this.addPlayer('Player 2');
234
  }
235
 
236
  addPlayer(defaultName = null) {
237
+ if (this.state.players.length >= this.state.maxPlayers) {
238
+ this.handleError('Maximum players reached');
239
  return;
240
  }
241
 
242
  const playerName = defaultName || `Player ${this.state.players.length + 1}`;
243
+ console.log('Adding player:', playerName);
244
+
245
+ socket.emit('join_game', {
246
  gameId: this.state.gameId,
247
  playerName: playerName
248
+ });
249
+ }
250
+
251
+ async startGame() {
252
+ console.log('Starting game...');
253
+ try {
254
+ const response = await fetch('/api/start_game', {
255
+ method: 'POST',
256
+ headers: {
257
+ 'Content-Type': 'application/json'
258
+ },
259
+ body: JSON.stringify({
260
+ gameId: this.state.gameId
261
+ })
262
+ });
263
+
264
+ const data = await response.json();
265
+ if (data.status === 'success') {
266
+ this.handleRoundStart(data);
267
  } else {
268
+ throw new Error(data.error || 'Failed to start game');
269
  }
270
+ } catch (error) {
271
+ console.error('Error starting game:', error);
272
+ this.handleError('Failed to start game');
273
+ }
274
+ }
275
+
276
+ handleRoundStart(data) {
277
+ console.log('Round starting:', data);
278
+ this.state.currentQuestion = data.question;
279
+ this.state.currentPhase = 'recording';
280
+ this.showPage('recording');
281
+ this.updateQuestionDisplay();
282
+ this.startTimer(this.state.recordingTime);
283
+ }
284
+
285
+ updateQuestionDisplay() {
286
+ if (this.ui.displays.question && this.state.currentQuestion) {
287
+ this.ui.displays.question.textContent = this.state.currentQuestion;
288
+ }
289
  }
290
 
291
  updatePlayerList() {
292
+ if (!this.ui.displays.playerList) return;
 
293
 
294
+ const playerList = this.ui.displays.playerList;
295
+ playerList.innerHTML = '';
296
+
297
+ const gridContainer = document.createElement('div');
298
+ gridContainer.className = 'player-grid';
299
 
300
  this.state.players.forEach(player => {
301
+ const playerCard = document.createElement('div');
302
+ playerCard.className = 'player-card';
303
 
304
  const circle = document.createElement('div');
305
  circle.className = 'player-circle';
 
309
  name.className = 'player-name';
310
  name.textContent = player.name;
311
 
312
+ playerCard.appendChild(circle);
313
+ playerCard.appendChild(name);
314
+ gridContainer.appendChild(playerCard);
315
  });
316
 
317
+ playerList.appendChild(gridContainer);
318
  }
319
 
320
+ updatePlayerControls() {
321
+ if (this.ui.buttons.addPlayer) {
322
+ this.ui.buttons.addPlayer.disabled =
323
+ this.state.players.length >= this.state.maxPlayers;
324
+ }
325
 
326
+ if (this.ui.buttons.start) {
327
+ this.ui.buttons.start.disabled =
328
+ this.state.players.length < this.state.minPlayers;
329
+ }
330
  }
331
 
332
+ handleError(message) {
333
+ console.error('Error:', message);
334
+
335
+ // Prevent multiple error messages
336
+ if (this.state.errorDisplayed) {
337
+ return;
338
+ }
339
+
340
+ this.state.errorDisplayed = true;
341
+ const errorElement = document.createElement('div');
342
+ errorElement.className = 'error-message';
343
+ errorElement.textContent = message;
344
+
345
+ if (this.ui.gameContainer) {
346
+ this.ui.gameContainer.appendChild(errorElement);
347
+ setTimeout(() => {
348
+ errorElement.remove();
349
+ this.state.errorDisplayed = false;
350
+ }, 3000);
351
  }
352
  }
353
 
354
+ clearError() {
355
+ const errorMessages = document.querySelectorAll('.error-message');
356
+ errorMessages.forEach(msg => msg.remove());
357
+ this.state.errorDisplayed = false;
 
 
358
  }
359
 
360
  showPage(pageName) {
361
+ console.log('Showing page:', pageName);
362
+
363
+ Object.values(this.ui.pages).forEach(page => {
364
+ if (page) {
365
+ page.classList.remove('active');
366
+ }
367
  });
 
 
368
 
369
+ const pageToShow = this.ui.pages[pageName];
370
+ if (pageToShow) {
371
+ pageToShow.classList.add('active');
372
+ this.state.currentPhase = pageName;
373
+ } else {
374
+ console.error(`Page ${pageName} not found`);
375
  }
376
  }
377
 
378
  startTimer(duration) {
379
+ let timeLeft = duration;
380
+ this.updateTimerDisplay(timeLeft);
381
+
382
+ this.timer = setInterval(() => {
383
+ timeLeft--;
384
+ this.updateTimerDisplay(timeLeft);
385
+
386
+ if (timeLeft <= 0) {
387
+ clearInterval(this.timer);
388
  this.handlePhaseEnd();
389
  }
390
  }, 1000);
391
  }
392
 
393
+ updateTimerDisplay(timeLeft) {
394
+ if (this.ui.displays.timer) {
395
+ const minutes = Math.floor(timeLeft / 60);
396
+ const seconds = timeLeft % 60;
397
+ this.ui.displays.timer.textContent =
398
+ `${minutes}:${seconds.toString().padStart(2, '0')}`;
399
+ }
400
  }
401
 
402
+ handlePhaseEnd() {
403
+ switch (this.state.currentPhase) {
404
+ case 'recording':
405
+ this.showPage('listening');
406
+ this.startTimer(this.state.listeningTime);
407
+ break;
408
+ case 'listening':
409
+ this.showPage('voting');
410
+ this.startTimer(this.state.votingTime);
411
+ break;
412
+ case 'voting':
413
+ this.showPage('results');
414
+ break;
415
+ }
416
+ }
417
+
418
+ cleanup() {
419
+ // Clear timers
420
+ if (this.timer) clearInterval(this.timer);
421
+
422
+ // Remove event listeners
423
+ window.removeEventListener('resize', this.handleResize);
424
 
425
+ // Reset game state
426
+ this.state = {
427
+ gameId: null,
428
+ currentPhase: 'landing',
429
+ players: [],
430
+ currentQuestion: null,
431
+ isRecording: false,
432
+ isConnected: false,
433
+ errorDisplayed: false
434
+ };
435
  }
436
 
437
  handleResize() {
438
+ if (window.innerWidth < 768) {
439
+ this.ui.gameContainer.classList.add('mobile');
440
+ } else {
441
+ this.ui.gameContainer.classList.remove('mobile');
442
+ }
443
  }
444
  }
445
 
446
+ // Initialize the game when the DOM is loaded
447
  document.addEventListener('DOMContentLoaded', () => {
448
+ console.log('Initializing game...');
449
  window.game = new Game();
450
+ });