BryanBradfo commited on
Commit
d218eb0
·
verified ·
1 Parent(s): ef96755

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -104
app.py CHANGED
@@ -6,14 +6,13 @@ import pandas as pd
6
  import os
7
  import uuid
8
  from openai import OpenAI
9
- from collections import Counter
10
 
11
  # --- CONFIGURATION ---
12
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
13
  openai_client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
14
  STOCKFISH_PATH = "/usr/games/stockfish"
15
 
16
- # --- OUVERTURES ---
17
  def _load_lichess_openings(path_prefix="/app/data/lichess_openings/dist/"):
18
  try:
19
  files = [f"{path_prefix}{vol}.tsv" for vol in ("a", "b", "c", "d", "e")]
@@ -29,49 +28,44 @@ def _load_lichess_openings(path_prefix="/app/data/lichess_openings/dist/"):
29
 
30
  OPENINGS_DB = _load_lichess_openings()
31
 
32
- # --- ANALYSE ANTI-HALLUCINATION ---
33
 
34
  def get_material_list(board):
35
- """Retourne la liste exacte des pièces restantes pour éviter les hallucinations."""
36
  w_pieces = []
37
  b_pieces = []
38
- piece_map = board.piece_map()
39
  names = {chess.PAWN: "Pion", chess.KNIGHT: "Cavalier", chess.BISHOP: "Fou", chess.ROOK: "Tour", chess.QUEEN: "Dame", chess.KING: "Roi"}
40
-
41
- for sq, piece in piece_map.items():
42
  name = names[piece.piece_type]
43
  if piece.color == chess.WHITE: w_pieces.append(name)
44
  else: b_pieces.append(name)
45
-
46
- return f"Pièces Blanches restantes: {', '.join(w_pieces)}. Pièces Noires restantes: {', '.join(b_pieces)}."
47
 
48
  def describe_move_human(board, move):
49
- """Traduction humaine."""
50
- if not move: return "Rien"
51
  piece = board.piece_at(move.from_square)
52
  if not piece: return move.uci()
53
-
54
  names = {chess.PAWN: "Pion", chess.KNIGHT: "Cavalier", chess.BISHOP: "Fou", chess.ROOK: "Tour", chess.QUEEN: "Dame", chess.KING: "Roi"}
55
- name = names.get(piece.piece_type, "Pièce")
56
- dest = chess.square_name(move.to_square)
57
- origin = chess.square_name(move.from_square)
58
-
59
- return f"{name} ({origin}) vers {dest}", f"{origin} ➔ {dest}"
60
 
61
  def get_engine_analysis(board, time_limit=0.5):
62
- """Analyse Stockfish."""
63
- if not os.path.exists(STOCKFISH_PATH): return "0", "Inconnu", ""
 
 
64
 
65
  try:
66
  with chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH) as engine:
67
  info = engine.analyse(board, chess.engine.Limit(time=time_limit))
68
  score = info["score"].white()
69
 
 
70
  if score.is_mate():
71
- score_val = f"Mat en {abs(score.mate())}"
72
  else:
73
  score_val = str(score.score())
74
 
 
75
  best_move = info.get("pv", [None])[0]
76
  if best_move:
77
  desc_text, desc_arrow = describe_move_human(board, best_move)
@@ -79,11 +73,12 @@ def get_engine_analysis(board, time_limit=0.5):
79
  desc_text, desc_arrow = "Aucun coup", ""
80
 
81
  return score_val, desc_text, desc_arrow
82
- except:
83
- return "0", "Erreur", ""
 
84
 
85
  def get_ai_move(board, level):
86
- """L'adversaire (Noirs) joue."""
87
  levels = {
88
  "Débutant": {"time": 0.01, "skill": 1, "depth": 1},
89
  "Intermédiaire": {"time": 0.1, "skill": 8, "depth": 6},
@@ -99,18 +94,31 @@ def get_ai_move(board, level):
99
  except:
100
  return None
101
 
102
- # --- AUDIO & CHAT ---
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
  def generate_voice(text):
105
  if not openai_client or not text: return None
106
  try:
107
  response = openai_client.audio.speech.create(
108
  model="tts-1",
109
- voice="fable",
110
  input=text,
111
- speed=1.25
112
  )
113
- unique = f"garry_{uuid.uuid4()}.mp3"
114
  path = os.path.join("/tmp", unique)
115
  response.stream_to_file(path)
116
  return path
@@ -126,51 +134,34 @@ def transcribe(audio_path):
126
  except:
127
  return None
128
 
129
- # --- CERVEAU KASPAROV ---
130
 
131
- SYSTEM_PROMPT = """
132
- Tu es Garry Kasparov. Tu coaches le joueur (Blancs).
133
- Tu dois être PRECIS sur le matériel (ne parle pas de pièces qui n'existent plus, regarde la liste fournie).
134
-
135
- STRUCTURE DE TA RÉPONSE :
136
- 1. **L'ORDRE** : Donne le coup exact à jouer (ex: "Bouge ta Tour en a8").
137
- 2. **LA STRATÉGIE** : Explique la situation de l'adversaire (Noirs).
138
-
139
- Si c'est Échec et Mat : CÉLÈBRE LA VICTOIRE ou SOIS DIGNE DANS LA DÉFAITE.
140
- """
141
-
142
- def consult_garry(fen, mode="auto", user_audio=None):
143
  board = chess.Board(fen)
144
-
145
- # 1. Données
146
  score, best_move_text, arrow = get_engine_analysis(board)
147
- material = get_material_list(board) # ANTI-HALLUCINATION
148
 
149
- opening = "Inconnue"
150
  if not OPENINGS_DB.empty:
151
  match = OPENINGS_DB[OPENINGS_DB["epd"] == board.epd()]
152
  if not match.empty: opening = match.iloc[0]['name']
153
 
154
- # 2. Contexte LLM
155
  context = f"""
156
- [ÉTAT]
157
  Trait : {'Blancs' if board.turn == chess.WHITE else 'Noirs'}.
158
  Ouverture : {opening}.
159
- Score : {score}.
160
- MATÉRIEL RÉEL SUR LE PLATEAU : {material} (Ne parle pas d'autres pièces !).
161
-
162
- [CONSEIL MOTEUR]
163
- Le meilleur coup calculé est : {best_move_text}.
164
  """
165
 
166
  if mode == "question" and user_audio:
167
  q = transcribe(user_audio)
168
- context += f"\n[QUESTION JOUEUR] : {q}"
169
  else:
170
- context += "\nDis au joueur de jouer le meilleur coup et explique la menace noire."
171
 
172
- # 3. LLM
173
- reply = "..."
174
  if openai_client:
175
  try:
176
  response = openai_client.chat.completions.create(
@@ -179,92 +170,79 @@ def consult_garry(fen, mode="auto", user_audio=None):
179
  )
180
  reply = response.choices[0].message.content
181
  except Exception as e:
182
- reply = f"Erreur IA: {e}"
183
 
184
- # 4. Audio
185
  audio = generate_voice(reply)
186
  return reply, audio, arrow
187
 
188
- # --- GAME LOOP ---
189
-
190
  def game_cycle(fen, level):
191
  board = chess.Board(fen)
192
 
193
- # Check fin de partie immédiate
194
  if board.is_checkmate():
195
- if board.turn == chess.BLACK: # C'est aux noirs de jouer, donc Blancs ont gagné
196
- return fen, "ÉCHEC ET MAT ! VICTOIRE !", None, "🏆", gr.Confetti()
197
  else:
198
- return fen, "Échec et mat... Tu as perdu.", None, "💀", None
 
 
 
199
 
200
- # Tour des Noirs (IA)
201
  if board.turn == chess.BLACK:
202
  ai_move = get_ai_move(board, level)
203
  if ai_move: board.push(ai_move)
204
 
205
- # Check après coup IA
206
  if board.is_checkmate():
207
- return board.fen(), "ÉCHEC ET MAT ! L'IA a gagné.", None, "💀", None
208
 
209
- # Conseil de Garry pour le coup blanc suivant
210
- text, audio, arrow = consult_garry(board.fen(), mode="auto")
211
  return board.fen(), text, audio, arrow, None
212
 
213
- # Tour des Blancs (Attente)
214
- return fen, "À toi de jouer !", None, "", None
215
 
216
  def reset_game():
217
- """Remet tout à zéro."""
218
- return chess.STARTING_FEN, "Nouvelle partie commencée. Bonne chance !", None, ""
219
 
220
- def ask_garry(fen, audio):
221
- text, aud, arrow = consult_garry(fen, mode="question", user_audio=audio)
222
  return text, aud, arrow
223
 
224
  # --- INTERFACE ---
225
 
226
- with gr.Blocks(title="ChessCoachKasparov") as demo:
227
- gr.Markdown("# 👑 ChessCoachKasparov")
228
- gr.Markdown("Coach Garry Kasparov. Voix rapide. Analyse précise. Anti-Hallucination.")
229
 
230
  with gr.Row():
231
  with gr.Column(scale=2):
232
- board = Chessboard(label="Plateau", value=chess.STARTING_FEN, game_mode=True, interactive=True)
233
 
234
- # Indicateur visuel du coup conseillé
235
- arrow_display = gr.Textbox(label="Coup Conseillé (Illumination Textuelle)", interactive=False, text_align="center", show_label=True)
236
 
237
  with gr.Row():
238
- btn_reset = gr.Button("🔄 Nouvelle Partie", variant="secondary")
239
- level = gr.Dropdown(["Débutant", "Intermédiaire", "Avancé", "Grand Maître"], value="Débutant", label="Niveau IA")
240
 
241
  with gr.Column(scale=1):
242
- coach_txt = gr.Textbox(label="Kasparov", interactive=False, lines=4)
243
- coach_audio = gr.Audio(label="Voix", autoplay=True, interactive=False, type="filepath")
244
 
245
- gr.Markdown("### 🎤 Parler au Coach")
246
  mic = gr.Audio(sources=["microphone"], type="filepath", show_label=False)
247
- btn_ask = gr.Button("Envoyer Question")
248
 
249
- # Confettis cachés (s'activent si victoire)
250
- confetti_trigger = gr.Label(visible=False)
251
 
252
- # 1. Jeu
253
- board.move(
254
- fn=game_cycle,
255
- inputs=[board, level],
256
- outputs=[board, coach_txt, coach_audio, arrow_display, confetti_trigger]
257
- )
258
-
259
- # 2. Reset
260
- btn_reset.click(
261
- fn=reset_game,
262
- outputs=[board, coach_txt, coach_audio, arrow_display]
263
- )
264
-
265
- # 3. Question
266
- btn_ask.click(fn=ask_garry, inputs=[board, mic], outputs=[coach_txt, coach_audio, arrow_display])
267
- mic.stop_recording(fn=ask_garry, inputs=[board, mic], outputs=[coach_txt, coach_audio, arrow_display])
268
 
269
  if __name__ == "__main__":
270
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, allowed_paths=["/tmp"])
 
6
  import os
7
  import uuid
8
  from openai import OpenAI
 
9
 
10
  # --- CONFIGURATION ---
11
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
12
  openai_client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
13
  STOCKFISH_PATH = "/usr/games/stockfish"
14
 
15
+ # --- CHARGEMENT DONNÉES ---
16
  def _load_lichess_openings(path_prefix="/app/data/lichess_openings/dist/"):
17
  try:
18
  files = [f"{path_prefix}{vol}.tsv" for vol in ("a", "b", "c", "d", "e")]
 
28
 
29
  OPENINGS_DB = _load_lichess_openings()
30
 
31
+ # --- ANALYSE ANTI-BUG & ANTI-HALLUCINATION ---
32
 
33
  def get_material_list(board):
34
+ """Liste précise des pièces pour éviter les hallucinations."""
35
  w_pieces = []
36
  b_pieces = []
 
37
  names = {chess.PAWN: "Pion", chess.KNIGHT: "Cavalier", chess.BISHOP: "Fou", chess.ROOK: "Tour", chess.QUEEN: "Dame", chess.KING: "Roi"}
38
+ for sq, piece in board.piece_map().items():
 
39
  name = names[piece.piece_type]
40
  if piece.color == chess.WHITE: w_pieces.append(name)
41
  else: b_pieces.append(name)
42
+ return f"BLANCS: {', '.join(w_pieces)}. NOIRS: {', '.join(b_pieces)}."
 
43
 
44
  def describe_move_human(board, move):
45
+ if not move: return "Aucun"
 
46
  piece = board.piece_at(move.from_square)
47
  if not piece: return move.uci()
 
48
  names = {chess.PAWN: "Pion", chess.KNIGHT: "Cavalier", chess.BISHOP: "Fou", chess.ROOK: "Tour", chess.QUEEN: "Dame", chess.KING: "Roi"}
49
+ return f"{names.get(piece.piece_type, 'Pièce')} en {chess.square_name(move.to_square)}", f"{chess.square_name(move.from_square)} ➔ {chess.square_name(move.to_square)}"
 
 
 
 
50
 
51
  def get_engine_analysis(board, time_limit=0.5):
52
+ """Analyse Stockfish sécurisée (gère la fin de partie)."""
53
+ if board.is_game_over(): return "Terminé", "Partie finie", ""
54
+
55
+ if not os.path.exists(STOCKFISH_PATH): return "0", "Engine missing", ""
56
 
57
  try:
58
  with chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH) as engine:
59
  info = engine.analyse(board, chess.engine.Limit(time=time_limit))
60
  score = info["score"].white()
61
 
62
+ # Gestion du score
63
  if score.is_mate():
64
+ score_val = f"MAT EN {abs(score.mate())}"
65
  else:
66
  score_val = str(score.score())
67
 
68
+ # Meilleur coup (Gère le cas où aucun coup n'est dispo)
69
  best_move = info.get("pv", [None])[0]
70
  if best_move:
71
  desc_text, desc_arrow = describe_move_human(board, best_move)
 
73
  desc_text, desc_arrow = "Aucun coup", ""
74
 
75
  return score_val, desc_text, desc_arrow
76
+ except Exception as e:
77
+ print(f"Erreur Engine: {e}")
78
+ return "N/A", "Erreur Analyse", ""
79
 
80
  def get_ai_move(board, level):
81
+ """L'IA joue."""
82
  levels = {
83
  "Débutant": {"time": 0.01, "skill": 1, "depth": 1},
84
  "Intermédiaire": {"time": 0.1, "skill": 8, "depth": 6},
 
94
  except:
95
  return None
96
 
97
+ # --- DEEP BLUE PERSONA ---
98
+
99
+ SYSTEM_PROMPT = """
100
+ Tu es DEEP BLUE, le supercalculateur d'IBM.
101
+ Ton style est : Robotique, Analytique, Précis. Pas d'émotion humaine.
102
+ Tu t'adresses au "Sujet Humain" (Joueur Blanc).
103
+
104
+ FORMAT DE RÉPONSE :
105
+ 1. **ANALYSE** : "Situation critique." ou "Avantage détecté."
106
+ 2. **OPTIMISATION** : Dis impérativement le coup à jouer. (ex: "Séquence optimale : Cavalier vers f3.").
107
+ 3. **LOGIQUE** : Explique pourquoi brièvement (ex: "Contrôle du centre augmenté de 15%.").
108
+
109
+ Si le joueur gagne : "Échec et mat confirmé. Capacité humaine supérieure aux prévisions."
110
+ """
111
 
112
  def generate_voice(text):
113
  if not openai_client or not text: return None
114
  try:
115
  response = openai_client.audio.speech.create(
116
  model="tts-1",
117
+ voice="onyx", # Voix grave et sérieuse pour Deep Blue
118
  input=text,
119
+ speed=1.1
120
  )
121
+ unique = f"deepblue_{uuid.uuid4()}.mp3"
122
  path = os.path.join("/tmp", unique)
123
  response.stream_to_file(path)
124
  return path
 
134
  except:
135
  return None
136
 
137
+ # --- LOGIQUE ---
138
 
139
+ def consult_deepblue(fen, mode="auto", user_audio=None):
 
 
 
 
 
 
 
 
 
 
 
140
  board = chess.Board(fen)
 
 
141
  score, best_move_text, arrow = get_engine_analysis(board)
142
+ material = get_material_list(board)
143
 
144
+ opening = "Non indexée"
145
  if not OPENINGS_DB.empty:
146
  match = OPENINGS_DB[OPENINGS_DB["epd"] == board.epd()]
147
  if not match.empty: opening = match.iloc[0]['name']
148
 
 
149
  context = f"""
150
+ [DONNÉES SYSTÈME]
151
  Trait : {'Blancs' if board.turn == chess.WHITE else 'Noirs'}.
152
  Ouverture : {opening}.
153
+ Score CP : {score}.
154
+ INVENTAIRE PIÈCES : {material}.
155
+ COUP RECOMMANDÉ : {best_move_text}.
 
 
156
  """
157
 
158
  if mode == "question" and user_audio:
159
  q = transcribe(user_audio)
160
+ context += f"\n[INPUT HUMAIN] : {q}"
161
  else:
162
+ context += "\nInstruction : Fournir le coup optimal."
163
 
164
+ reply = "Traitement..."
 
165
  if openai_client:
166
  try:
167
  response = openai_client.chat.completions.create(
 
170
  )
171
  reply = response.choices[0].message.content
172
  except Exception as e:
173
+ reply = f"Erreur Système: {e}"
174
 
 
175
  audio = generate_voice(reply)
176
  return reply, audio, arrow
177
 
 
 
178
  def game_cycle(fen, level):
179
  board = chess.Board(fen)
180
 
181
+ # VICTOIRE (Mat AVANT que l'IA joue, c'est que le joueur vient de mater)
182
  if board.is_checkmate():
183
+ if board.turn == chess.BLACK:
184
+ return fen, "ÉCHEC ET MAT CONFIRMÉ. VICTOIRE HUMAINE.", None, "🏆 WIN", gr.Confetti()
185
  else:
186
+ return fen, "ÉCHEC ET MAT. LES NOIRS GAGNENT.", None, "💀 LOSS", None
187
+
188
+ if board.is_game_over():
189
+ return fen, "PARTIE TERMINÉE (Pat ou Nulle).", None, "⏹️ DRAW", None
190
 
191
+ # Tour IA (Noirs)
192
  if board.turn == chess.BLACK:
193
  ai_move = get_ai_move(board, level)
194
  if ai_move: board.push(ai_move)
195
 
196
+ # Check après IA
197
  if board.is_checkmate():
198
+ return board.fen(), "ÉCHEC ET MAT. DEEP BLUE WINS.", None, "💀", None
199
 
200
+ # Conseil Deep Blue
201
+ text, audio, arrow = consult_deepblue(board.fen(), mode="auto")
202
  return board.fen(), text, audio, arrow, None
203
 
204
+ return fen, "En attente input joueur...", None, "", None
 
205
 
206
  def reset_game():
207
+ return chess.STARTING_FEN, "Initialisation nouvelle partie...", None, ""
 
208
 
209
+ def ask_deepblue(fen, audio):
210
+ text, aud, arrow = consult_deepblue(fen, mode="question", user_audio=audio)
211
  return text, aud, arrow
212
 
213
  # --- INTERFACE ---
214
 
215
+ with gr.Blocks(title="Deep Blue Coach") as demo:
216
+ gr.Markdown("# 🤖 Deep Blue Legacy")
217
+ gr.Markdown("Système d'analyse tactique. Interface Vocale. Mode 'Deep Blue'.")
218
 
219
  with gr.Row():
220
  with gr.Column(scale=2):
221
+ board = Chessboard(label="Zone de Combat", value=chess.STARTING_FEN, game_mode=True, interactive=True)
222
 
223
+ # Zone visuelle d'aide
224
+ arrow_display = gr.Textbox(label="Coup Optimal Détecté", interactive=False, text_align="center")
225
 
226
  with gr.Row():
227
+ btn_reset = gr.Button("INITIALISER NOUVELLE PARTIE", variant="primary")
228
+ level = gr.Dropdown(["Débutant", "Intermédiaire", "Avancé", "Grand Maître"], value="Débutant", label="Niveau CPU")
229
 
230
  with gr.Column(scale=1):
231
+ coach_txt = gr.Textbox(label="Terminal Deep Blue", interactive=False, lines=4)
232
+ coach_audio = gr.Audio(label="Synthèse Vocale", autoplay=True, interactive=False, type="filepath")
233
 
234
+ gr.Markdown("### 🎤 Interface Humaine")
235
  mic = gr.Audio(sources=["microphone"], type="filepath", show_label=False)
236
+ btn_ask = gr.Button("Interroger le Système")
237
 
238
+ # Confettis
239
+ confetti = gr.Label(visible=False)
240
 
241
+ # Logique
242
+ board.move(fn=game_cycle, inputs=[board, level], outputs=[board, coach_txt, coach_audio, arrow_display, confetti])
243
+ btn_reset.click(fn=reset_game, outputs=[board, coach_txt, coach_audio, arrow_display])
244
+ btn_ask.click(fn=ask_deepblue, inputs=[board, mic], outputs=[coach_txt, coach_audio, arrow_display])
245
+ mic.stop_recording(fn=ask_deepblue, inputs=[board, mic], outputs=[coach_txt, coach_audio, arrow_display])
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  if __name__ == "__main__":
248
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, allowed_paths=["/tmp"])