dcrey7 commited on
Commit
058bc84
·
verified ·
1 Parent(s): ff3579e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -265
app.py CHANGED
@@ -1,5 +1,5 @@
1
  from flask import Flask, render_template, request, jsonify
2
- from flask_socketio import SocketIO, emit, join_room, leave_room
3
  import os
4
  import requests
5
  import json
@@ -13,336 +13,185 @@ import asyncio
13
 
14
  # Initialize Flask and configure core settings
15
  app = Flask(__name__)
16
- app.config['SECRET_KEY'] = os.urandom(24)
17
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
18
 
19
- # Initialize SocketIO with CORS support and logging
20
- socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True, async_mode='eventlet')
21
 
22
- # Load environment variables
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 with more detailed format
28
- logging.basicConfig(
29
- level=logging.INFO,
30
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31
- )
32
  logger = logging.getLogger(__name__)
33
 
34
  class GameState:
35
  """Manages the state of all active game sessions."""
36
 
37
  def __init__(self):
38
- self.games = {}
39
- self.cleanup_interval = 3600
 
40
 
41
  def create_game(self):
42
- """Creates a new game session with proper initialization."""
43
- try:
44
- game_id = str(uuid.uuid4())
45
- self.games[game_id] = {
46
- 'players': [],
47
- 'current_phase': 'setup',
48
- 'recordings': {},
49
- 'impostor': None,
50
- 'votes': {},
51
- 'question': None,
52
- 'impostor_answer': None,
53
- 'modified_recording': None,
54
- 'round_number': 1,
55
- 'start_time': datetime.now().isoformat(),
56
- 'completed_rounds': [],
57
- 'score': {'impostor_wins': 0, 'player_wins': 0},
58
- 'room': game_id # Add room for socket management
59
- }
60
- logger.info(f"Successfully created game with ID: {game_id}")
61
- return game_id
62
- except Exception as e:
63
- logger.error(f"Error creating game: {str(e)}")
64
- raise
65
-
66
- def get_game(self, game_id):
67
- """Safely retrieves a game by ID."""
68
- game = self.games.get(game_id)
69
- if not game:
70
- logger.error(f"Game not found: {game_id}")
71
- raise ValueError("Game not found")
72
- return game
73
 
74
  def cleanup_inactive_games(self):
75
- """Removes inactive game sessions."""
76
  current_time = datetime.now()
 
 
77
  for game_id, game in list(self.games.items()):
78
  start_time = datetime.fromisoformat(game['start_time'])
79
- if (current_time - start_time).total_seconds() > 7200: # 2 hours
80
  del self.games[game_id]
81
- logger.info(f"Cleaned up inactive game: {game_id}")
82
 
83
  # Initialize global game state
84
  game_state = GameState()
85
 
86
- @app.route('/')
87
- def home():
88
- """Serves the main game page."""
89
- return render_template('index.html')
90
-
91
- @socketio.on('connect')
92
- def handle_connect():
93
- """Handles client connection."""
94
- logger.info(f"Client connected: {request.sid}")
95
- emit('connection_success', {'status': 'connected'})
96
-
97
- @socketio.on('disconnect')
98
- def handle_disconnect():
99
- """Handles client disconnection."""
100
- logger.info(f"Client disconnected: {request.sid}")
101
-
102
  @socketio.on('create_game')
103
  def handle_create_game():
104
- """Handles game creation request."""
105
  try:
106
  game_id = game_state.create_game()
107
- join_room(game_id) # Create socket room for the game
108
- logger.info(f"Created and joined game room: {game_id}")
109
  emit('game_created', {
110
- 'gameId': game_id,
111
- 'status': 'success'
112
  })
 
113
  except Exception as e:
114
- logger.error(f"Error in game creation: {str(e)}")
115
- emit('game_error', {
116
- 'error': 'Failed to create game',
117
- 'details': str(e)
118
  })
119
 
120
  @socketio.on('join_game')
121
  def handle_join_game(data):
122
- """Handles player joining a game."""
123
- try:
124
- game_id = data.get('gameId')
125
- player_name = data.get('playerName')
 
 
 
 
 
 
126
 
127
- if not game_id or not player_name:
128
- raise ValueError("Missing game ID or player name")
 
 
 
 
129
 
130
- game = game_state.get_game(game_id)
131
-
132
- # Validate player count
133
- if len(game['players']) >= 5:
134
- raise ValueError("Game is full")
 
 
135
 
136
- # Add player to game
137
  player_id = len(game['players']) + 1
138
- player = {
139
  'id': player_id,
140
  'name': player_name,
141
  'socket_id': request.sid
142
  }
143
- game['players'].append(player)
 
 
 
144
 
145
- # Join socket room
146
- join_room(game_id)
147
- logger.info(f"Player {player_name} (ID: {player_id}) joined game {game_id}")
148
-
149
- # Broadcast to all players in the game
150
  emit('player_joined', {
151
- 'playerId': player_id,
152
- 'playerName': player_name,
153
- 'status': 'success'
154
- }, room=game_id)
155
-
 
156
  except Exception as e:
157
- error_msg = str(e)
158
- logger.error(f"Error in handle_join_game: {error_msg}")
159
- emit('game_error', {'error': error_msg})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  @app.route('/api/start_game', methods=['POST'])
162
  async def start_game():
163
- """Initializes a new game round."""
164
  try:
165
  data = request.get_json()
166
- game_id = data.get('gameId')
167
-
168
- if not game_id:
169
- raise ValueError("Missing game ID")
170
-
171
- game = game_state.get_game(game_id)
172
 
173
- # Validate player count
174
- if len(game['players']) < 3:
175
- raise ValueError("Need at least 3 players to start")
176
-
177
- # Generate question using Mistral AI
 
178
  question = await generate_question()
179
  game['question'] = question
180
  game['current_phase'] = 'recording'
181
 
182
- logger.info(f"Started game {game_id} with question: {question}")
183
-
184
- # Notify all players in the game room
185
- socketio.emit('round_started', {
186
- 'question': question
187
- }, room=game_id)
188
-
189
  return jsonify({
190
  'status': 'success',
191
  'question': question
192
  })
193
-
194
- except Exception as e:
195
- error_msg = str(e)
196
- logger.error(f"Error starting game: {error_msg}")
197
- return jsonify({
198
- 'status': 'error',
199
- 'error': error_msg
200
- }), 500
201
-
202
- async def generate_question():
203
- """Generates an engaging question using Mistral AI."""
204
- try:
205
- headers = {
206
- 'Authorization': f'Bearer {MISTRAL_API_KEY}',
207
- 'Content-Type': 'application/json'
208
- }
209
-
210
- payload = {
211
- 'messages': [{
212
- 'role': 'user',
213
- 'content': '''Generate an engaging personal question for a social game.
214
- The question should:
215
- 1. Encourage creative and unique responses
216
- 2. Be open-ended but not too philosophical
217
- 3. Be answerable in 15-30 seconds
218
- 4. Be appropriate for all ages
219
- 5. Spark interesting conversation
220
-
221
- Generate only the question, without any additional text.'''
222
- }]
223
- }
224
-
225
- response = requests.post(
226
- 'https://api.mistral.ai/v1/chat/completions',
227
- headers=headers,
228
- json=payload,
229
- timeout=10
230
- )
231
-
232
- if response.status_code == 200:
233
- question = response.json()['choices'][0]['message']['content'].strip()
234
- logger.info(f"Generated question: {question}")
235
- return question
236
-
237
- logger.error(f"Mistral API error: {response.status_code}")
238
- return random.choice([
239
- "What's your favorite childhood memory?",
240
- "What's the most interesting place you've ever visited?",
241
- "What's a skill you'd love to master and why?",
242
- "What's the best piece of advice you've ever received?"
243
- ])
244
-
245
- except Exception as e:
246
- logger.error(f"Error generating question: {str(e)}")
247
- return "What is your favorite memory from your childhood?"
248
-
249
- @app.route('/api/submit_recording', methods=['POST'])
250
- async def submit_recording():
251
- """Handles voice recording submissions."""
252
- try:
253
- game_id = request.form.get('gameId')
254
- player_id = request.form.get('playerId')
255
- audio_file = request.files.get('audio')
256
-
257
- if not all([game_id, player_id, audio_file]):
258
- raise ValueError("Missing required data")
259
-
260
- game = game_state.get_game(game_id)
261
-
262
- # Save the recording
263
- filename = secure_filename(f"recording_{game_id}_{player_id}.wav")
264
- filepath = os.path.join('temp', filename)
265
- audio_file.save(filepath)
266
-
267
- game['recordings'][player_id] = filepath
268
- logger.info(f"Saved recording for player {player_id} in game {game_id}")
269
-
270
- # Notify all players about the new recording
271
- socketio.emit('recording_submitted', {
272
- 'playerId': player_id,
273
- 'status': 'success'
274
- }, room=game_id)
275
-
276
- return jsonify({'status': 'success'})
277
 
278
  except Exception as e:
279
- error_msg = str(e)
280
- logger.error(f"Error submitting recording: {error_msg}")
281
- return jsonify({
282
- 'status': 'error',
283
- 'error': error_msg
284
- }), 500
285
 
286
- @socketio.on('submit_vote')
287
- def handle_vote(data):
288
- """Processes player votes and determines round outcome."""
289
- try:
290
- game_id = data.get('gameId')
291
- voter_id = data.get('voterId')
292
- vote_for = data.get('voteFor')
293
-
294
- if not all([game_id, voter_id, vote_for]):
295
- raise ValueError("Missing vote data")
296
-
297
- game = game_state.get_game(game_id)
298
- game['votes'][voter_id] = vote_for
299
-
300
- # Check if all players have voted
301
- if len(game['votes']) == len(game['players']):
302
- # Calculate results
303
- votes_count = {}
304
- for vote in game['votes'].values():
305
- votes_count[vote] = votes_count.get(vote, 0) + 1
306
-
307
- most_voted = max(votes_count.items(), key=lambda x: x[1])[0]
308
-
309
- # Update scores
310
- if most_voted == game['impostor']:
311
- game['score']['player_wins'] += 1
312
- result = 'players_win'
313
- else:
314
- game['score']['impostor_wins'] += 1
315
- result = 'impostor_wins'
316
-
317
- # Store round results
318
- game['completed_rounds'].append({
319
- 'round_number': game['round_number'],
320
- 'impostor': game['impostor'],
321
- 'votes': game['votes'].copy(),
322
- 'most_voted': most_voted,
323
- 'result': result
324
- })
325
-
326
- # Emit results to all players
327
- emit('round_result', {
328
- 'impostor': game['impostor'],
329
- 'most_voted': most_voted,
330
- 'votes': game['votes'],
331
- 'score': game['score'],
332
- 'result': result
333
- }, room=game_id)
334
-
335
- logger.info(f"Round completed for game {game_id}. Result: {result}")
336
-
337
- except Exception as e:
338
- error_msg = str(e)
339
- logger.error(f"Error processing vote: {error_msg}")
340
- emit('game_error', {'error': error_msg})
341
 
342
  if __name__ == '__main__':
343
- # Create temporary directory for recordings
344
  os.makedirs('temp', exist_ok=True)
345
 
346
  # Start the server
347
- logger.info("Starting server...")
348
  socketio.run(app, host='0.0.0.0', port=7860, debug=True)
 
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
 
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)