clementpep commited on
Commit
59ee354
·
1 Parent(s): e335712

feat: improve game UI: disabled dice button and victory popup

Browse files
backend/ai_service.py CHANGED
@@ -160,6 +160,55 @@ Rends-le incisif et mémorable."""
160
  print(f"Error generating comment: {e}")
161
  return None
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  def _generate_text(self, prompt: str) -> str:
164
  """
165
  Internal method to generate text using OpenAI API.
 
160
  print(f"Error generating comment: {e}")
161
  return None
162
 
163
+ async def generate_victory_comment(
164
+ self,
165
+ player_name: str,
166
+ character: str,
167
+ weapon: str,
168
+ room: str,
169
+ narrative_tone: str = "🕵️ Sérieuse",
170
+ ) -> Optional[str]:
171
+ """
172
+ Generate a skeptical victory comment from Desland.
173
+ Returns None if AI is disabled or if generation fails.
174
+ """
175
+ print(f"[AI Service] generate_victory_comment called for {player_name}")
176
+
177
+ if not self.enabled or not self.client:
178
+ print(f"[AI Service] AI disabled or client not initialized")
179
+ return None
180
+
181
+ try:
182
+ prompt = f"""Desland commente la victoire (1-2 phrases max):
183
+
184
+ Gagnant: {player_name}
185
+ Solution: {character} avec {weapon} dans {room}
186
+
187
+ IMPORTANT: Desland est SCEPTIQUE et JALOUX. Il minimise la victoire en suggérant que c'était de la chance, pas du talent. Ton:
188
+ - "C'était sûrement de la chance, je ne crois pas en son talent à celui-là..."
189
+ - "Pff, n'importe qui aurait pu trouver ça. Même un péchailloux masqué..."
190
+ - "Bon, arrête de te vanter {player_name}, on sait tous que c'était armankaboul et que t'as eu du bol."
191
+
192
+ Ton narratif: {narrative_tone}
193
+ Sois sarcastique, minimise la victoire, suggère que c'était de la chance."""
194
+
195
+ print(f"[AI Service] Calling OpenAI API...")
196
+ response = await asyncio.wait_for(
197
+ asyncio.to_thread(self._generate_text, prompt), timeout=10.0
198
+ )
199
+ print(f"[AI Service] OpenAI response received: {response}")
200
+
201
+ return response
202
+
203
+ except asyncio.TimeoutError:
204
+ print("[AI Service] AI victory comment generation timed out")
205
+ return None
206
+ except Exception as e:
207
+ import traceback
208
+ print(f"[AI Service] Error generating victory comment: {e}")
209
+ print(traceback.format_exc())
210
+ return None
211
+
212
  def _generate_text(self, prompt: str) -> str:
213
  """
214
  Internal method to generate text using OpenAI API.
backend/main.py CHANGED
@@ -192,7 +192,8 @@ async def get_game_state(game_id: str, player_id: str):
192
  ],
193
  "current_turn": {
194
  "player_name": current_player.name if current_player else None,
195
- "is_my_turn": current_player.id == player_id if current_player else False
 
196
  },
197
  "recent_actions": [
198
  {
@@ -225,7 +226,7 @@ async def roll_dice(game_id: str, req: DiceRollRequest):
225
  # Check if player already rolled
226
  player = next((p for p in game.players if p.id == req.player_id), None)
227
  if player and player.has_rolled:
228
- raise HTTPException(status_code=400, detail="Vous avez déjà lancé les dés ce tour")
229
 
230
  # Roll dice
231
  dice = GameEngine.roll_dice()
@@ -377,6 +378,7 @@ async def make_accusation(game_id: str, req: AccusationRequest):
377
 
378
  # Generate AI comment if enabled
379
  ai_comment = None
 
380
  if game.use_ai:
381
  try:
382
  from backend.ai_service import ai_service
@@ -390,6 +392,18 @@ async def make_accusation(game_id: str, req: AccusationRequest):
390
  game.narrative_tone
391
  )
392
  print(f"[AI] Generated comment: {ai_comment}")
 
 
 
 
 
 
 
 
 
 
 
 
393
  except Exception as e:
394
  print(f"[AI] AI comment generation failed: {e}")
395
 
@@ -410,7 +424,13 @@ async def make_accusation(game_id: str, req: AccusationRequest):
410
  return {
411
  "is_correct": is_correct,
412
  "message": message,
413
- "winner": game.winner
 
 
 
 
 
 
414
  }
415
 
416
 
 
192
  ],
193
  "current_turn": {
194
  "player_name": current_player.name if current_player else None,
195
+ "is_my_turn": current_player.id == player_id if current_player else False,
196
+ "has_rolled": player.has_rolled if player else False
197
  },
198
  "recent_actions": [
199
  {
 
226
  # Check if player already rolled
227
  player = next((p for p in game.players if p.id == req.player_id), None)
228
  if player and player.has_rolled:
229
+ raise HTTPException(status_code=400, detail="Vous avez déjà lancé les dés ce tour ! Faites une suggestion ou passez votre tour.")
230
 
231
  # Roll dice
232
  dice = GameEngine.roll_dice()
 
378
 
379
  # Generate AI comment if enabled
380
  ai_comment = None
381
+ victory_comment = None
382
  if game.use_ai:
383
  try:
384
  from backend.ai_service import ai_service
 
392
  game.narrative_tone
393
  )
394
  print(f"[AI] Generated comment: {ai_comment}")
395
+
396
+ # Generate victory comment if correct
397
+ if is_correct:
398
+ print(f"[AI] Generating victory comment for {player_name}...")
399
+ victory_comment = await ai_service.generate_victory_comment(
400
+ player_name,
401
+ req.suspect,
402
+ req.weapon,
403
+ req.room,
404
+ game.narrative_tone
405
+ )
406
+ print(f"[AI] Generated victory comment: {victory_comment}")
407
  except Exception as e:
408
  print(f"[AI] AI comment generation failed: {e}")
409
 
 
424
  return {
425
  "is_correct": is_correct,
426
  "message": message,
427
+ "winner": game.winner,
428
+ "victory_comment": victory_comment,
429
+ "solution": {
430
+ "suspect": req.suspect,
431
+ "weapon": req.weapon,
432
+ "room": req.room
433
+ } if is_correct else None
434
  }
435
 
436
 
frontend/src/pages/Game.jsx CHANGED
@@ -16,6 +16,7 @@ function Game() {
16
  const [selectedWeapon, setSelectedWeapon] = useState('')
17
  const [selectedRoom, setSelectedRoom] = useState('')
18
  const [revealedCard, setRevealedCard] = useState(null)
 
19
 
20
  useEffect(() => {
21
  loadGameState()
@@ -97,7 +98,17 @@ function Game() {
97
 
98
  setActionLoading(true)
99
  try {
100
- await makeAccusation(gameId, playerId, selectedSuspect, selectedWeapon, selectedRoom)
 
 
 
 
 
 
 
 
 
 
101
  await loadGameState()
102
  setSelectedSuspect('')
103
  setSelectedWeapon('')
@@ -257,10 +268,10 @@ function Game() {
257
  <div className="flex gap-4 mb-4">
258
  <button
259
  onClick={handleRollDice}
260
- disabled={actionLoading}
261
  className="px-6 py-3 bg-haunted-blood hover:bg-red-800 disabled:bg-dark-600 disabled:opacity-50 text-white font-bold rounded-lg transition-all hover:shadow-[0_0_20px_rgba(139,0,0,0.5)] border border-red-900"
262
  >
263
- 🎲 Lancer les dés
264
  </button>
265
  <button
266
  onClick={handlePassTurn}
@@ -346,6 +357,53 @@ function Game() {
346
  </div>
347
  </div>
348
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  </div>
350
  )
351
  }
 
16
  const [selectedWeapon, setSelectedWeapon] = useState('')
17
  const [selectedRoom, setSelectedRoom] = useState('')
18
  const [revealedCard, setRevealedCard] = useState(null)
19
+ const [victoryModal, setVictoryModal] = useState(null)
20
 
21
  useEffect(() => {
22
  loadGameState()
 
98
 
99
  setActionLoading(true)
100
  try {
101
+ const result = await makeAccusation(gameId, playerId, selectedSuspect, selectedWeapon, selectedRoom)
102
+
103
+ // Show victory modal if correct
104
+ if (result.is_correct && result.solution) {
105
+ setVictoryModal({
106
+ winner: result.winner,
107
+ solution: result.solution,
108
+ victoryComment: result.victory_comment
109
+ })
110
+ }
111
+
112
  await loadGameState()
113
  setSelectedSuspect('')
114
  setSelectedWeapon('')
 
268
  <div className="flex gap-4 mb-4">
269
  <button
270
  onClick={handleRollDice}
271
+ disabled={actionLoading || gameState.current_turn.has_rolled}
272
  className="px-6 py-3 bg-haunted-blood hover:bg-red-800 disabled:bg-dark-600 disabled:opacity-50 text-white font-bold rounded-lg transition-all hover:shadow-[0_0_20px_rgba(139,0,0,0.5)] border border-red-900"
273
  >
274
+ 🎲 {gameState.current_turn.has_rolled ? 'Dés lancés' : 'Lancer les dés'}
275
  </button>
276
  <button
277
  onClick={handlePassTurn}
 
357
  </div>
358
  </div>
359
  </div>
360
+
361
+ {/* Victory Modal */}
362
+ {victoryModal && (
363
+ <div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50 animate-fade-in">
364
+ <div className="bg-gradient-to-b from-haunted-blood to-black p-8 rounded-lg border-4 border-haunted-purple max-w-lg mx-4 animate-bounce-in shadow-[0_0_50px_rgba(139,0,0,0.8)]">
365
+ <div className="text-center">
366
+ <h2 className="text-4xl font-bold text-white mb-4 animate-flicker">
367
+ 🎉 Victoire !
368
+ </h2>
369
+ <p className="text-2xl text-haunted-purple mb-6">
370
+ {victoryModal.winner} a résolu l'enquête !
371
+ </p>
372
+
373
+ <div className="bg-black/60 p-6 rounded-lg mb-6 border-2 border-haunted-purple/50">
374
+ <h3 className="text-xl font-bold text-haunted-fog mb-4">Solution :</h3>
375
+ <div className="space-y-2 text-lg">
376
+ <p><span className="text-haunted-purple font-bold">Suspect :</span> {victoryModal.solution.suspect}</p>
377
+ <p><span className="text-haunted-purple font-bold">Arme :</span> {victoryModal.solution.weapon}</p>
378
+ <p><span className="text-haunted-purple font-bold">Lieu :</span> {victoryModal.solution.room}</p>
379
+ </div>
380
+ </div>
381
+
382
+ {victoryModal.victoryComment && (
383
+ <div className="bg-black/80 p-4 rounded-lg border-l-4 border-haunted-purple mb-6">
384
+ <p className="text-sm text-haunted-fog/70 mb-1">👻 Desland :</p>
385
+ <p className="text-haunted-fog italic">"{victoryModal.victoryComment}"</p>
386
+ </div>
387
+ )}
388
+
389
+ <div className="flex gap-4 justify-center">
390
+ <button
391
+ onClick={() => window.location.href = '/'}
392
+ className="px-6 py-3 bg-haunted-purple hover:bg-purple-800 text-white font-bold rounded-lg transition-all hover:shadow-[0_0_20px_rgba(107,33,168,0.5)] border border-purple-900"
393
+ >
394
+ 🏠 Accueil
395
+ </button>
396
+ <button
397
+ onClick={() => window.location.reload()}
398
+ className="px-6 py-3 bg-haunted-blood hover:bg-red-800 text-white font-bold rounded-lg transition-all hover:shadow-[0_0_20px_rgba(139,0,0,0.5)] border border-red-900"
399
+ >
400
+ 🔄 Nouvelle partie
401
+ </button>
402
+ </div>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ )}
407
  </div>
408
  )
409
  }