dcrey7 commited on
Commit
5b97c22
·
verified ·
1 Parent(s): 093737e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +347 -400
app.py CHANGED
@@ -1,401 +1,348 @@
1
- from flask import Flask, render_template, request, jsonify
2
- from flask_socketio import SocketIO, emit
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
- }
56
- return game_id
57
-
58
- def cleanup_inactive_games(self):
59
- """Remove inactive game sessions older than 2 hours."""
60
- current_time = datetime.now()
61
- inactive_threshold = 7200 # 2 hours in seconds
62
-
63
- for game_id, game in list(self.games.items()):
64
- start_time = datetime.fromisoformat(game['start_time'])
65
- if (current_time - start_time).total_seconds() > inactive_threshold:
66
- del self.games[game_id]
67
-
68
- # Initialize global game state
69
- game_state = GameState()
70
-
71
- async def generate_question():
72
- """Generate an engaging question using Mistral AI."""
73
- try:
74
- headers = {
75
- 'Authorization': f'Bearer {MISTRAL_API_KEY}',
76
- 'Content-Type': 'application/json'
77
- }
78
-
79
- # Craft a prompt that encourages interesting, personal questions
80
- payload = {
81
- 'messages': [{
82
- 'role': 'user',
83
- 'content': '''Generate an engaging personal question for a social game.
84
- The question should:
85
- 1. Encourage creative and unique responses
86
- 2. Be open-ended but not too philosophical
87
- 3. Be answerable in 15-30 seconds
88
- 4. Be appropriate for all ages
89
- 5. Spark interesting conversation
90
-
91
- Generate only the question, without any additional text.'''
92
- }]
93
- }
94
-
95
- response = requests.post(
96
- 'https://api.mistral.ai/v1/chat/completions',
97
- headers=headers,
98
- json=payload,
99
- timeout=10 # Set timeout to handle slow responses
100
- )
101
-
102
- if response.status_code == 200:
103
- question = response.json()['choices'][0]['message']['content'].strip()
104
- logger.info(f"Generated question: {question}")
105
- return question
106
- else:
107
- logger.error(f"Mistral API error: {response.status_code}")
108
- # Fallback questions if API fails
109
- fallback_questions = [
110
- "What is your favorite childhood memory?",
111
- "What's the most interesting place you've ever visited?",
112
- "What's a skill you'd love to master and why?",
113
- "What's the best piece of advice you've ever received?"
114
- ]
115
- return random.choice(fallback_questions)
116
-
117
- except Exception as e:
118
- logger.error(f"Error generating question: {str(e)}")
119
- return "What is your favorite memory from your childhood?"
120
-
121
- async def generate_impostor_answer(question):
122
- """Generate a convincing impostor response using Mistral AI."""
123
- try:
124
- headers = {
125
- 'Authorization': f'Bearer {MISTRAL_API_KEY}',
126
- 'Content-Type': 'application/json'
127
- }
128
-
129
- # Craft a detailed prompt for generating a natural response
130
- prompt = f'''Given the question "{question}", generate a detailed and convincing personal response.
131
- The response should:
132
- 1. Sound natural and conversational
133
- 2. Be 2-3 sentences long
134
- 3. Include specific details to sound authentic
135
- 4. Be suitable for text-to-speech conversion
136
- 5. Avoid complex words or punctuation that might affect voice synthesis
137
-
138
- Generate only the response, without any additional context.'''
139
-
140
- payload = {
141
- 'messages': [{
142
- 'role': 'user',
143
- 'content': prompt
144
- }]
145
- }
146
-
147
- response = requests.post(
148
- 'https://api.mistral.ai/v1/chat/completions',
149
- headers=headers,
150
- json=payload,
151
- timeout=10
152
- )
153
-
154
- if response.status_code == 200:
155
- answer = response.json()['choices'][0]['message']['content'].strip()
156
- logger.info(f"Generated impostor answer: {answer}")
157
- return answer
158
- else:
159
- logger.error(f"Mistral API error generating answer: {response.status_code}")
160
- return "I have an interesting story about that, but I'd need more time to explain it properly."
161
-
162
- except Exception as e:
163
- logger.error(f"Error generating impostor answer: {str(e)}")
164
- return "I have an interesting story about that, but I'd need more time to explain it properly."
165
-
166
- async def clone_voice(audio_file):
167
- """Clone a voice using ElevenLabs API."""
168
- try:
169
- headers = {
170
- 'xi-api-key': ELEVENLABS_API_KEY
171
- }
172
-
173
- with open(audio_file, 'rb') as f:
174
- files = {
175
- 'files': ('recording.wav', f, 'audio/wav')
176
- }
177
-
178
- response = requests.post(
179
- 'https://api.elevenlabs.io/v1/voices/add',
180
- headers=headers,
181
- files=files,
182
- timeout=30
183
- )
184
-
185
- if response.status_code == 200:
186
- voice_id = response.json().get('voice_id')
187
- logger.info(f"Successfully cloned voice: {voice_id}")
188
- return voice_id
189
- else:
190
- logger.error(f"ElevenLabs voice cloning error: {response.status_code}")
191
- return None
192
-
193
- except Exception as e:
194
- logger.error(f"Error cloning voice: {str(e)}")
195
- return None
196
-
197
- async def generate_cloned_speech(voice_id, text):
198
- """Generate speech using ElevenLabs with a cloned voice."""
199
- try:
200
- headers = {
201
- 'xi-api-key': ELEVENLABS_API_KEY,
202
- 'Content-Type': 'application/json'
203
- }
204
-
205
- payload = {
206
- 'text': text,
207
- 'voice_settings': {
208
- 'stability': 0.75,
209
- 'similarity_boost': 0.75
210
- }
211
- }
212
-
213
- response = requests.post(
214
- f'https://api.elevenlabs.io/v1/text-to-speech/{voice_id}',
215
- headers=headers,
216
- json=payload,
217
- timeout=30
218
- )
219
-
220
- if response.status_code == 200:
221
- filename = f"temp/impostor_audio_{uuid.uuid4()}.mp3"
222
- with open(filename, 'wb') as f:
223
- f.write(response.content)
224
- logger.info(f"Generated cloned speech: {filename}")
225
- return filename
226
- else:
227
- logger.error(f"ElevenLabs speech generation error: {response.status_code}")
228
- return None
229
-
230
- except Exception as e:
231
- logger.error(f"Error generating cloned speech: {str(e)}")
232
- return None
233
-
234
- @app.route('/')
235
- def home():
236
- """Serve the main game page."""
237
- return render_template('index.html')
238
-
239
- @app.route('/api/start_game', methods=['POST'])
240
- async def start_game():
241
- """Initialize a new game session."""
242
- try:
243
- data = request.get_json()
244
- game_id = data.get('game_id')
245
-
246
- if game_id not in game_state.games:
247
- return jsonify({'error': 'Game not found'}), 404
248
-
249
- game = game_state.games[game_id]
250
-
251
- # Generate question for the round
252
- question = await generate_question()
253
- game['question'] = question
254
- game['current_phase'] = 'recording'
255
-
256
- return jsonify({
257
- 'status': 'success',
258
- 'question': question
259
- })
260
-
261
- except Exception as e:
262
- logger.error(f"Error starting game: {str(e)}")
263
- return jsonify({'error': 'Internal server error'}), 500
264
-
265
- @app.route('/api/submit_recording', methods=['POST'])
266
- async def submit_recording():
267
- """Handle voice recording submissions."""
268
- try:
269
- game_id = request.form.get('game_id')
270
- player_id = request.form.get('player_id')
271
- audio_file = request.files.get('audio')
272
-
273
- if not all([game_id, player_id, audio_file]):
274
- return jsonify({'error': 'Missing required data'}), 400
275
-
276
- if game_id not in game_state.games:
277
- return jsonify({'error': 'Game not found'}), 404
278
-
279
- # Save the recording
280
- filename = secure_filename(f"recording_{game_id}_{player_id}.wav")
281
- filepath = os.path.join('temp', filename)
282
- audio_file.save(filepath)
283
-
284
- game_state.games[game_id]['recordings'][player_id] = filepath
285
-
286
- return jsonify({'status': 'success'})
287
-
288
- except Exception as e:
289
- logger.error(f"Error submitting recording: {str(e)}")
290
- return jsonify({'error': 'Internal server error'}), 500
291
-
292
- @app.route('/api/process_impostor', methods=['POST'])
293
- async def process_impostor():
294
- """Handle the complete impostor voice generation process."""
295
- try:
296
- data = request.get_json()
297
- game_id = data.get('game_id')
298
- impostor_id = data.get('impostor_id')
299
-
300
- if game_id not in game_state.games:
301
- return jsonify({'error': 'Game not found'}), 404
302
-
303
- game = game_state.games[game_id]
304
-
305
- # Generate impostor's answer
306
- impostor_answer = await generate_impostor_answer(game['question'])
307
- game['impostor_answer'] = impostor_answer
308
-
309
- # Get impostor's original recording
310
- original_recording = game['recordings'].get(impostor_id)
311
- if not original_recording:
312
- return jsonify({'error': 'Original recording not found'}), 404
313
-
314
- # Clone voice
315
- voice_id = await clone_voice(original_recording)
316
- if not voice_id:
317
- return jsonify({'error': 'Voice cloning failed'}), 500
318
-
319
- # Generate modified speech
320
- audio_file = await generate_cloned_speech(voice_id, impostor_answer)
321
- if not audio_file:
322
- return jsonify({'error': 'Speech generation failed'}), 500
323
-
324
- game['modified_recording'] = audio_file
325
-
326
- return jsonify({
327
- 'status': 'success',
328
- 'audio_url': audio_file
329
- })
330
-
331
- except Exception as e:
332
- logger.error(f"Error processing impostor: {str(e)}")
333
- return jsonify({'error': 'Internal server error'}), 500
334
-
335
- @socketio.on('join_game')
336
- def handle_join_game(data):
337
- """Handle a player joining the game."""
338
- game_id = data.get('game_id')
339
- player_name = data.get('player_name')
340
-
341
- if game_id in game_state.games:
342
- game = game_state.games[game_id]
343
- if len(game['players']) < 5:
344
- player_id = len(game['players']) + 1
345
- game['players'].append({
346
- 'id': player_id,
347
- 'name': player_name
348
- })
349
- emit('player_joined', {
350
- 'player_id': player_id,
351
- 'player_name': player_name
352
- }, broadcast=True, room=game_id)
353
-
354
- @socketio.on('submit_vote')
355
- def handle_vote(data):
356
- """Process player votes and determine round outcome."""
357
- game_id = data.get('game_id')
358
- voter_id = data.get('voter_id')
359
- vote_for = data.get('vote_for')
360
-
361
- if game_id in game_state.games:
362
- game = game_state.games[game_id]
363
- game['votes'][voter_id] = vote_for
364
-
365
- # Check if all players have voted
366
- if len(game['votes']) == len(game['players']):
367
- # Calculate results
368
- votes_count = {}
369
- for vote in game['votes'].values():
370
- votes_count[vote] = votes_count.get(vote, 0) + 1
371
-
372
- most_voted = max(votes_count.items(), key=lambda x: x[1])[0]
373
-
374
- # Update scores
375
- if most_voted == game['impostor']:
376
- game['score']['player_wins'] += 1
377
- else:
378
- game['score']['impostor_wins'] += 1
379
-
380
- # Store round results
381
- game['completed_rounds'].append({
382
- 'round_number': game['round_number'],
383
- 'impostor': game['impostor'],
384
- 'votes': game['votes'].copy(),
385
- 'most_voted': most_voted
386
- })
387
-
388
- # Emit results
389
- emit('round_result', {
390
- 'impostor': game['impostor'],
391
- 'most_voted': most_voted,
392
- 'votes': game['votes'],
393
- 'score': game['score']
394
- }, broadcast=True, room=game_id)
395
-
396
- if __name__ == '__main__':
397
- # Create temporary directory for recordings if it doesn't exist
398
- os.makedirs('temp', exist_ok=True)
399
-
400
- # Start the server
401
  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, leave_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)
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)