dcrey7 commited on
Commit
ff3579e
·
verified ·
1 Parent(s): 45c744a

Update static/js/game.js

Browse files
Files changed (1) hide show
  1. static/js/game.js +352 -188
static/js/game.js CHANGED
@@ -1,197 +1,361 @@
1
- from flask import Flask, render_template, request, jsonify
2
- from flask_socketio import SocketIO, emit, join_room
3
- import os
4
- import requests
5
- import json
6
- import uuid
7
- from datetime import datetime
8
- from dotenv import load_dotenv
9
- import logging
10
- from werkzeug.utils import secure_filename
11
- import random
12
- import asyncio
13
-
14
- # Initialize Flask and configure core settings
15
- app = Flask(__name__)
16
- app.config['SECRET_KEY'] = os.urandom(24) # Generate a random secret key for security
17
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Limit file uploads to 16MB
18
-
19
- # Initialize SocketIO with CORS support for development
20
- socketio = SocketIO(app, cors_allowed_origins="*")
21
-
22
- # Load environment variables from .env file
23
- load_dotenv()
24
- MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY')
25
- ELEVENLABS_API_KEY = os.getenv('ELEVENLABS_API_KEY')
26
-
27
- # Configure logging to track application behavior
28
- logging.basicConfig(level=logging.INFO)
29
- logger = logging.getLogger(__name__)
30
-
31
- class GameState:
32
- """Manages the state of all active game sessions."""
33
-
34
- def __init__(self):
35
- """Initialize the game state manager."""
36
- self.games = {} # Dictionary to store all active games
37
- self.cleanup_interval = 3600 # Cleanup inactive games every hour
38
-
39
- def create_game(self):
40
- """Create a new game session with a unique identifier."""
41
- game_id = str(uuid.uuid4())
42
- self.games[game_id] = {
43
- 'players': [],
44
- 'current_phase': 'setup',
45
- 'recordings': {},
46
- 'impostor': None,
47
- 'votes': {},
48
- 'question': None,
49
- 'impostor_answer': None,
50
- 'modified_recording': None,
51
- 'round_number': 1,
52
- 'start_time': datetime.now().isoformat(),
53
- 'completed_rounds': [],
54
- 'score': {'impostor_wins': 0, 'player_wins': 0},
55
- 'socket_room': f'game_{game_id}' # Add dedicated socket room
56
  }
57
- return game_id
58
 
59
- def cleanup_inactive_games(self):
60
- """Remove inactive game sessions older than 2 hours."""
61
- current_time = datetime.now()
62
- inactive_threshold = 7200 # 2 hours in seconds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- for game_id, game in list(self.games.items()):
65
- start_time = datetime.fromisoformat(game['start_time'])
66
- if (current_time - start_time).total_seconds() > inactive_threshold:
67
- del self.games[game_id]
68
-
69
- # Initialize global game state
70
- game_state = GameState()
71
-
72
- # Socket.IO event handlers
73
- @socketio.on('create_game')
74
- def handle_create_game():
75
- """Handle new game creation requests."""
76
- try:
77
- game_id = game_state.create_game()
78
- emit('game_created', {
79
- 'status': 'success',
80
- 'gameId': game_id
81
- })
82
- logger.info(f"New game created: {game_id}")
83
- except Exception as e:
84
- logger.error(f"Error creating game: {str(e)}")
85
- emit('game_creation_failed', {
86
- 'status': 'error',
87
- 'error': 'Could not create game'
88
- })
89
-
90
- @socketio.on('join_game')
91
- def handle_join_game(data):
92
- """Handle a player joining the game."""
93
- game_id = data.get('game_id')
94
- player_name = data.get('player_name')
95
-
96
- if not game_id or not player_name:
97
- emit('join_failed', {
98
- 'status': 'error',
99
- 'error': 'Missing game ID or player name'
100
- })
101
- return
102
-
103
- if game_id not in game_state.games:
104
- emit('join_failed', {
105
- 'status': 'error',
106
- 'error': 'Game not found'
107
- })
108
- return
109
-
110
- game = game_state.games[game_id]
111
- if len(game['players']) >= 5:
112
- emit('join_failed', {
113
- 'status': 'error',
114
- 'error': 'Game is full'
115
- })
116
- return
117
-
118
- try:
119
- player_id = len(game['players']) + 1
120
- new_player = {
121
- 'id': player_id,
122
- 'name': player_name,
123
- 'socket_id': request.sid
124
  }
125
- game['players'].append(new_player)
126
-
127
- # Join the game room
128
- join_room(game['socket_room'])
129
-
130
- emit('player_joined', {
131
- 'status': 'success',
132
- 'player': new_player
133
- }, room=game['socket_room'])
134
-
135
- logger.info(f"Player {player_name} joined game {game_id}")
136
-
137
- except Exception as e:
138
- logger.error(f"Error joining game: {str(e)}")
139
- emit('join_failed', {
140
- 'status': 'error',
141
- 'error': 'Internal server error'
142
- })
143
-
144
- @socketio.on('round_start')
145
- def handle_round_start(data):
146
- """Handle the start of a new round."""
147
- game_id = data.get('game_id')
148
- if game_id not in game_state.games:
149
- return
150
-
151
- game = game_state.games[game_id]
152
- emit('round_start', {
153
- 'question': game['question'],
154
- 'phase': 'recording',
155
- 'duration': 30
156
- }, room=game['socket_room'])
157
-
158
- # API endpoints
159
- @app.route('/')
160
- def home():
161
- """Serve the main game page."""
162
- return render_template('index.html')
163
-
164
- @app.route('/api/start_game', methods=['POST'])
165
- async def start_game():
166
- """Initialize a new game session."""
167
- try:
168
- data = request.get_json()
169
- game_id = data.get('game_id')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- if game_id not in game_state.games:
172
- return jsonify({'error': 'Game not found'}), 404
 
 
 
 
173
 
174
- game = game_state.games[game_id]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- # Generate question for the round
177
- question = await generate_question()
178
- game['question'] = question
179
- game['current_phase'] = 'recording'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- return jsonify({
182
- 'status': 'success',
183
- 'question': question
184
- })
185
 
186
- except Exception as e:
187
- logger.error(f"Error starting game: {str(e)}")
188
- return jsonify({'error': 'Internal server error'}), 500
189
-
190
- # ... (rest of the API endpoints remain the same)
191
-
192
- if __name__ == '__main__':
193
- # Create temporary directory for recordings if it doesn't exist
194
- os.makedirs('temp', exist_ok=True)
195
-
196
- # Start the server
197
- socketio.run(app, host='0.0.0.0', port=7860, debug=True)
 
 
 
 
 
1
+
2
+ // Initialize Socket.IO connection with automatic reconnection
3
+ const socket = io({
4
+ reconnection: true,
5
+ reconnectionAttempts: 5,
6
+ reconnectionDelay: 1000
7
+ });
8
+
9
+ class Game {
10
+ constructor() {
11
+ // Core game state with all necessary properties
12
+ this.state = {
13
+ gameId: null,
14
+ currentPhase: 'landing',
15
+ players: [],
16
+ currentQuestion: null,
17
+ isRecording: false,
18
+ recordingTime: 30, // seconds
19
+ listeningTime: 60, // seconds
20
+ votingTime: 60, // seconds
21
+ recordings: new Map(),
22
+ votes: new Map(),
23
+ impostor: null,
24
+ maxPlayers: 5,
25
+ minPlayers: 3,
26
+ socketConnected: false // Track socket connection status
27
+ };
28
+
29
+ // Initialize if DOM is ready, otherwise wait for it
30
+ if (document.readyState === 'loading') {
31
+ document.addEventListener('DOMContentLoaded', () => this.initialize());
32
+ } else {
33
+ this.initialize();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
+ }
36
 
37
+ initialize() {
38
+ console.log('Initializing game components...');
39
+ this.bindUIElements();
40
+ this.initializeEventListeners();
41
+ this.initializeSocketHandlers();
42
+ this.initializeAudioManager();
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'),
54
+ recording: document.getElementById('recording-page'),
55
+ listening: document.getElementById('listening-page'),
56
+ voting: document.getElementById('voting-page'),
57
+ results: document.getElementById('results-page')
58
+ },
59
+ buttons: {
60
+ play: document.getElementById('play-button'),
61
+ addPlayer: document.getElementById('add-player-button'),
62
+ start: document.getElementById('start-button'),
63
+ record: document.getElementById('record-button')
64
+ },
65
+ displays: {
66
+ timer: document.getElementById('timer-display'),
67
+ question: document.getElementById('question-display'),
68
+ playerList: document.getElementById('player-list')
69
+ }
70
+ };
71
+ }
72
+
73
+ initializeEventListeners() {
74
+ console.log('Setting up event listeners...');
75
 
76
+ // Button event listeners
77
+ if (this.ui.buttons.play) {
78
+ this.ui.buttons.play.addEventListener('click', () => this.handlePlayButton());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
+
81
+ if (this.ui.buttons.addPlayer) {
82
+ this.ui.buttons.addPlayer.addEventListener('click', () => this.addPlayer());
83
+ }
84
+
85
+ if (this.ui.buttons.start) {
86
+ this.ui.buttons.start.addEventListener('click', () => this.startGame());
87
+ }
88
+
89
+ if (this.ui.buttons.record) {
90
+ this.ui.buttons.record.addEventListener('click', () => this.toggleRecording());
91
+ }
92
+
93
+ // Handle window resize for responsive layout
94
+ window.addEventListener('resize', () => this.handleResize());
95
+ }
96
+
97
+ initializeSocketHandlers() {
98
+ // Server connection events
99
+ socket.on('connect', () => {
100
+ this.state.socketConnected = true;
101
+ console.log('Connected to server');
102
+ });
103
+
104
+ socket.on('disconnect', () => {
105
+ this.state.socketConnected = false;
106
+ console.log('Disconnected from server');
107
+ this.handleError('Connection lost. Please refresh the page.');
108
+ });
109
+
110
+ // Game events
111
+ socket.on('game_created', (data) => {
112
+ if (data.status === 'success') {
113
+ this.state.gameId = data.gameId;
114
+ console.log('Game created:', data);
115
+ this.showPage('setup');
116
+ this.addDefaultPlayers();
117
+ } else {
118
+ this.handleError(data.error || 'Failed to create game');
119
+ }
120
+ });
121
+
122
+ socket.on('player_joined', (data) => {
123
+ if (data.status === 'success') {
124
+ this.state.players.push({
125
+ id: data.player.id,
126
+ name: data.player.name
127
+ });
128
+ this.updatePlayerList();
129
+ this.updatePlayerControls();
130
+ }
131
+ });
132
+
133
+ socket.on('round_start', (data) => this.handleRoundStart(data));
134
+ socket.on('round_result', (data) => this.handleRoundResult(data));
135
+ }
136
+
137
+ initializeAudioManager() {
138
+ // Initialize audio context for recording
139
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
140
+ }
141
+
142
+ async handlePlayButton() {
143
+ console.log('Play button clicked');
144
+ try {
145
+ await this.createGame();
146
+ } catch (error) {
147
+ console.error('Error starting game:', error);
148
+ this.handleError('Failed to start game');
149
+ }
150
+ }
151
+
152
+ createGame() {
153
+ return new Promise((resolve, reject) => {
154
+ console.log('Creating new game...');
155
+ socket.emit('create_game', {}, (response) => {
156
+ if (response.status === 'success') {
157
+ resolve(response);
158
+ } else {
159
+ reject(new Error(response.error || 'Game creation failed'));
160
+ }
161
+ });
162
+
163
+ setTimeout(() => reject(new Error('Game creation timeout')), 5000);
164
+ });
165
+ }
166
+
167
+ addDefaultPlayers() {
168
+ // Add two default players to start
169
+ this.addPlayer('Player 1');
170
+ this.addPlayer('Player 2');
171
+ this.updatePlayerControls();
172
+ }
173
+
174
+ addPlayer(defaultName = null) {
175
+ if (this.state.players.length >= this.state.maxPlayers) {
176
+ this.handleError('Maximum players reached');
177
+ return;
178
+ }
179
+
180
+ const playerName = defaultName || `Player ${this.state.players.length + 1}`;
181
+ socket.emit('join_game', {
182
+ gameId: this.state.gameId,
183
+ playerName: playerName
184
+ }, (response) => {
185
+ if (response.status === 'success') {
186
+ this.state.players.push({
187
+ id: response.player.id,
188
+ name: response.player.name
189
+ });
190
+ this.updatePlayerList();
191
+ this.updatePlayerControls();
192
+ } else {
193
+ this.handleError(response.error || 'Failed to add player');
194
+ }
195
+ });
196
+ }
197
+
198
+ updatePlayerList() {
199
+ if (!this.ui.displays.playerList) return;
200
+
201
+ const playerList = this.ui.displays.playerList;
202
+ playerList.innerHTML = '';
203
 
204
+ const gridContainer = document.createElement('div');
205
+ gridContainer.className = 'player-grid';
206
+
207
+ this.state.players.forEach(player => {
208
+ const playerCard = document.createElement('div');
209
+ playerCard.className = 'player-card';
210
 
211
+ const circle = document.createElement('div');
212
+ circle.className = 'player-circle';
213
+ circle.textContent = player.id;
214
+
215
+ const name = document.createElement('div');
216
+ name.className = 'player-name';
217
+ name.textContent = player.name;
218
+
219
+ playerCard.appendChild(circle);
220
+ playerCard.appendChild(name);
221
+ gridContainer.appendChild(playerCard);
222
+ });
223
+
224
+ playerList.appendChild(gridContainer);
225
+ }
226
+
227
+ updatePlayerControls() {
228
+ if (this.ui.buttons.addPlayer) {
229
+ this.ui.buttons.addPlayer.disabled =
230
+ this.state.players.length >= this.state.maxPlayers;
231
+ }
232
+
233
+ if (this.ui.buttons.start) {
234
+ this.ui.buttons.start.disabled =
235
+ this.state.players.length < this.state.minPlayers;
236
+ }
237
+ }
238
+
239
+ async startGame() {
240
+ console.log('Starting game...');
241
+ try {
242
+ const response = await fetch('/api/start_game', {
243
+ method: 'POST',
244
+ headers: {
245
+ 'Content-Type': 'application/json'
246
+ },
247
+ body: JSON.stringify({
248
+ gameId: this.state.gameId
249
+ })
250
+ });
251
+
252
+ const data = await response.json();
253
+ if (data.status === 'success') {
254
+ this.handleRoundStart(data);
255
+ } else {
256
+ throw new Error(data.error || 'Failed to start game');
257
+ }
258
+ } catch (error) {
259
+ console.error('Error starting game:', error);
260
+ this.handleError('Failed to start game');
261
+ }
262
+ }
263
+
264
+ handleRoundStart(data) {
265
+ console.log('Round starting:', data);
266
+ this.state.currentQuestion = data.question;
267
+ this.state.currentPhase = 'recording';
268
+ this.showPage('recording');
269
+ this.updateQuestionDisplay();
270
+ this.startTimer(this.state.recordingTime);
271
+ }
272
+
273
+ updateQuestionDisplay() {
274
+ if (this.ui.displays.question && this.state.currentQuestion) {
275
+ this.ui.displays.question.textContent = this.state.currentQuestion;
276
+ }
277
+ }
278
+
279
+ startTimer(duration) {
280
+ let timeLeft = duration;
281
+ this.updateTimerDisplay(timeLeft);
282
+
283
+ this.timer = setInterval(() => {
284
+ timeLeft--;
285
+ this.updateTimerDisplay(timeLeft);
286
+
287
+ if (timeLeft <= 0) {
288
+ clearInterval(this.timer);
289
+ this.handlePhaseEnd();
290
+ }
291
+ }, 1000);
292
+ }
293
+
294
+ updateTimerDisplay(timeLeft) {
295
+ if (this.ui.displays.timer) {
296
+ const minutes = Math.floor(timeLeft / 60);
297
+ const seconds = timeLeft % 60;
298
+ this.ui.displays.timer.textContent =
299
+ `${minutes}:${seconds.toString().padStart(2, '0')}`;
300
+ }
301
+ }
302
+
303
+ showPage(pageName) {
304
+ console.log('Showing page:', pageName);
305
 
306
+ Object.values(this.ui.pages).forEach(page => {
307
+ if (page) {
308
+ page.classList.remove('active');
309
+ }
310
+ });
311
+
312
+ const pageToShow = this.ui.pages[pageName];
313
+ if (pageToShow) {
314
+ pageToShow.classList.add('active');
315
+ this.state.currentPhase = pageName;
316
+ } else {
317
+ console.error(`Page ${pageName} not found`);
318
+ }
319
+ }
320
+
321
+ handleError(message) {
322
+ console.error('Error:', message);
323
+ const errorElement = document.createElement('div');
324
+ errorElement.className = 'error-message';
325
+ errorElement.textContent = message;
326
+ this.ui.gameContainer.appendChild(errorElement);
327
+ setTimeout(() => errorElement.remove(), 3000);
328
+ }
329
+
330
+ handleResize() {
331
+ // Update any size-dependent UI elements
332
+ if (window.innerWidth < 768) {
333
+ this.ui.gameContainer.classList.add('mobile');
334
+ } else {
335
+ this.ui.gameContainer.classList.remove('mobile');
336
+ }
337
+ }
338
+
339
+ cleanup() {
340
+ // Clear all intervals and timeouts
341
+ if (this.timer) clearInterval(this.timer);
342
 
343
+ // Remove event listeners
344
+ window.removeEventListener('resize', this.handleResize);
 
 
345
 
346
+ // Reset game state
347
+ this.state = {
348
+ gameId: null,
349
+ currentPhase: 'landing',
350
+ players: [],
351
+ currentQuestion: null,
352
+ isRecording: false
353
+ };
354
+ }
355
+ }
356
+
357
+ // Initialize the game when the DOM is loaded
358
+ document.addEventListener('DOMContentLoaded', () => {
359
+ console.log('Initializing game...');
360
+ window.game = new Game();
361
+ });