Clemylia commited on
Commit
7b209a1
·
verified ·
1 Parent(s): 582ec29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -45
app.py CHANGED
@@ -9,7 +9,6 @@ from stable_baselines3 import PPO
9
  from huggingface_hub import hf_hub_download
10
 
11
  # --- PARAMÈTRES DU DÉPÔT HUGGING FACE ---
12
- # Basé sur vos fichiers.
13
  REPO_ID = "Clemylia/MiRobot"
14
  MODEL_FILE = "mirobot_final_model.zip"
15
  ENV_SCRIPT_FILE = "MiRobotEnv.py"
@@ -21,17 +20,14 @@ FAIM_PENALTY_THRESHOLD = 0.9
21
  INITIAL_LEVEL = 1
22
 
23
  # --- INDICES D'ÉTAT ---
24
- # Basé sur votre MiRobotEnv.py
25
  CMD_AVANCER = 0
26
  CMD_TOURNER = 1
27
  ETAT_FAIM = 2
28
  ETAT_SOMMEIL = 3
29
  ETAT_HUMEUR = 4
30
 
31
- # Map des actions du modèle pour l'affichage
32
  ACTION_MAP_MODEL = {0: "S'Arrêter", 1: "Avancer", 2: "Tourner G", 3: "Tourner D"}
33
-
34
- # Map des commandes utilisateur aux ID d'action du modèle
35
  ACTION_MAP_USER = {
36
  "AVANCER": 1,
37
  "TOURNER À GAUCHE": 2,
@@ -39,7 +35,7 @@ ACTION_MAP_USER = {
39
  }
40
 
41
  # ----------------------------------------------------------------------
42
- # 1. PRÉPARATION DU MODÈLE ET DE L'ENVIRONNEMENT (CORRECTION DE L'INITIALISATION)
43
  # ----------------------------------------------------------------------
44
 
45
  model = None
@@ -54,12 +50,11 @@ try:
54
  TEMP_DIR = "./mirobot_assets"
55
  os.makedirs(TEMP_DIR, exist_ok=True)
56
 
57
- # --- 1. Téléchargement et Chargement de la classe MiRobotEnv ---
58
  env_path = hf_hub_download(repo_id=REPO_ID, filename=ENV_SCRIPT_FILE, local_dir=TEMP_DIR)
59
 
60
  # INJECTION DE DÉPENDANCES pour que MiRobotEnv.py fonctionne
61
  env_globals = {'gym': gym, 'np': np, 'spaces': spaces}
62
-
63
  with open(env_path, 'r') as f:
64
  exec(f.read(), env_globals)
65
 
@@ -72,7 +67,6 @@ try:
72
  id=ENV_ID,
73
  entry_point=MiRobotEnv,
74
  )
75
- print(f"Environnement '{ENV_ID}' enregistré avec succès.")
76
 
77
 
78
  # --- 3. Chargement du Modèle et de l'Environnement ---
@@ -80,9 +74,8 @@ try:
80
  model = PPO.load(model_path)
81
 
82
  env = gym.make(ENV_ID)
83
- env.reset() # Initialise l'état interne (self.state est créé ici)
84
 
85
- # Lecture des états initiaux pour l'interface Gradio (Utilisation sûre)
86
  initial_faim = env.state[ETAT_FAIM] * 100
87
  initial_humeur = env.state[ETAT_HUMEUR]
88
 
@@ -90,8 +83,8 @@ try:
90
 
91
  except Exception as e:
92
  print(f"❌ ERREUR CRITIQUE lors du chargement de MiRobot: {e}")
93
- # Si échec, model et env restent None, et les valeurs initiales restent 0.0
94
-
95
  # ----------------------------------------------------------------------
96
  # 2. LOGIQUE DU JEU
97
  # ----------------------------------------------------------------------
@@ -106,12 +99,12 @@ game_state_initial = {
106
  }
107
 
108
  def _reset_game(reward_path):
109
- """Réinitialise les positions et l'état interne du chiot pour un nouveau jeu."""
110
 
111
  new_state = game_state_initial.copy()
112
 
113
  if env is not None:
114
- obs, info = env.reset() # IMPORTANT : Assure la création de self.state
115
  faim_display = env.state[ETAT_FAIM] * 100
116
  humeur_display = env.state[ETAT_HUMEUR]
117
  else:
@@ -123,34 +116,31 @@ def _reset_game(reward_path):
123
  'message': f'Jeu réinitialisé. Niveau {INITIAL_LEVEL}. Placez la récompense !'
124
  })
125
 
 
126
  return new_state, new_state['puppy_pos'][0], new_state['puppy_pos'][1], new_state['reward_pos'][0], new_state['reward_pos'][1], faim_display, humeur_display, new_state['message']
127
 
128
  def handle_user_command(current_state, command_text, reward_path):
129
- """Fonction principale appelée par Gradio pour gérer une commande utilisateur."""
130
  game_state = current_state
131
 
132
  if model is None or env is None:
133
- # Sortie sécurisée en cas d'échec de chargement
134
  return game_state, command_text, 5, 5, 0, 0, '❌ Erreur: Le modèle MiRobot n\'a pas pu être chargé !'
135
 
136
  game_state['reward_asset_path'] = reward_path
137
 
138
- # 2. Vérification de la faim (condition de défaite)
139
  faim_actuelle = env.state[ETAT_FAIM]
140
  if faim_actuelle >= FAIM_PENALTY_THRESHOLD:
141
- game_state['message'] = f'💔 Défaite ! MiRobot a trop faim ({faim_actuelle:.0%}) et ne peut plus obéir. Jeu réinitialisé au Niveau 1.'
142
  return _reset_game(reward_path)
143
 
144
- # 3. Traitement de la commande utilisateur
145
  command_upper = command_text.upper()
146
 
147
  if command_upper not in ACTION_MAP_USER:
148
  game_state['message'] = f"🤔 MiRobot n'a pas compris l'ordre '{command_text}'. Sa faim augmente..."
149
-
150
- # Simuler un pas de temps (Action 0: S'arrêter) sans commande active pour laisser la faim monter
151
  env.state[CMD_AVANCER] = 0.0
152
  env.state[CMD_TOURNER] = 0.0
153
- new_obs, mirobot_action, reward, terminated, truncated, info = env.step(0)
154
 
155
  faim_display = env.state[ETAT_FAIM] * 100
156
  humeur_display = env.state[ETAT_HUMEUR]
@@ -160,19 +150,13 @@ def handle_user_command(current_state, command_text, reward_path):
160
  # 4. Exécution de la décision du Modèle
161
  command_action_name = command_upper
162
 
163
- # 4.1. Injecter la commande dans l'état de l'environnement (Observation)
164
  env.state[CMD_AVANCER] = 1.0 if command_action_name == "AVANCER" else 0.0
165
  env.state[CMD_TOURNER] = 1.0 if command_action_name.startswith("TOURNER") else 0.0
166
 
167
- # 4.2. Le modèle prédit l'action réelle (Obéissance ou gestion de besoin)
168
- obs = env.state
169
- mirobot_action_id, _ = model.predict(obs, deterministic=True)
170
-
171
- # 4.3. Exécuter l'action PRÉDITE par le modèle (mise à jour de la faim, humeur)
172
  new_obs, reward, terminated, truncated, info = env.step(mirobot_action_id)
173
 
174
 
175
- # 4.4. Traduire l'action réelle du modèle en mouvement sur la grille
176
  dx, dy = 0, 0
177
 
178
  if mirobot_action_id == ACTION_MAP_USER[command_action_name]:
@@ -190,9 +174,9 @@ def handle_user_command(current_state, command_text, reward_path):
190
 
191
  else:
192
  real_action_name = ACTION_MAP_MODEL[mirobot_action_id]
193
- game_state['message'] = f"😥 MiRobot a désobéi ! Il a fait '{real_action_name}' au lieu de '{command_action_name}'. Faim ou Humeur faible ? Récompense RL: {reward:.2f}"
194
 
195
- # 4.5. Mise à jour de la position et de l'état du chiot
196
  new_x = np.clip(game_state['puppy_pos'][0] + dx, 0, GRID_SIZE - 1)
197
  new_y = np.clip(game_state['puppy_pos'][1] + dy, 0, GRID_SIZE - 1)
198
  game_state['puppy_pos'] = [new_x, new_y]
@@ -215,7 +199,6 @@ def handle_bravo(current_state):
215
  if px == rx and py == ry:
216
  game_state['level'] += 1
217
 
218
- # Réduction de la faim et augmentation de l'humeur
219
  env.state[ETAT_FAIM] = np.clip(env.state[ETAT_FAIM] - 0.5, 0.0, 1.0)
220
  env.state[ETAT_HUMEUR] = np.clip(env.state[ETAT_HUMEUR] + 0.5, -1.0, 1.0)
221
 
@@ -228,8 +211,11 @@ def handle_bravo(current_state):
228
  return game_state, game_state['puppy_pos'][0], game_state['puppy_pos'][1], faim_display, humeur_display, game_state['message']
229
 
230
 
231
- def _draw_grid(puppy_pos, reward_pos, reward_path):
232
- """Dessine la grille de jeu avec le chiot et la récompense (via HTML/CSS)."""
 
 
 
233
  if reward_path is None:
234
  return "<p style='text-align: center; color: red;'>Veuillez télécharger une image de récompense pour afficher la grille.</p>"
235
 
@@ -244,11 +230,14 @@ def _draw_grid(puppy_pos, reward_pos, reward_path):
244
  style = "border: 1px dotted #ccc; display: flex; align-items: center; justify-content: center; position: relative;"
245
  content = ""
246
 
247
- if [x, y] == puppy_pos:
 
 
 
248
  content = puppy_icon
249
 
250
- if [x, y] == reward_pos:
251
- if [x, y] == puppy_pos:
252
  style += "background-color: #d4edda;"
253
  else:
254
  content += f"<img src='{reward_src}' style='width: 80%; height: 80%; object-fit: contain;'/>"
@@ -273,6 +262,12 @@ def update_reward_pos(current_state, reward_x, reward_y, reward_path):
273
  # 3. INTERFACE GRADIO
274
  # ----------------------------------------------------------------------
275
 
 
 
 
 
 
 
276
  if model is None:
277
  demo = gr.Interface(
278
  fn=lambda: "Le modèle MiRobot n'a pas pu être chargé. Vérifiez les logs ou le REPO_ID.",
@@ -288,8 +283,6 @@ else:
288
  gr.Markdown(
289
  f"""
290
  # MiRobot - Le Jeu d'Obéissance 🐾
291
- **ATTENTION :** Votre Space a atteint sa limite de stockage (50G). Pour que l'application fonctionne, vous devez supprimer des fichiers ou augmenter la capacité de votre Space.
292
-
293
  Bienvenue dans la simulation interactive de votre modèle RL **{REPO_ID}** !
294
  **Objectif :** Guider MiRobot vers la récompense en donnant des ordres. Attention, sa **Faim** augmente à chaque pas !
295
  """
@@ -304,7 +297,7 @@ else:
304
  reward_x = gr.Slider(minimum=0, maximum=GRID_SIZE - 1, step=1, value=0, label="2. Pos. Récompense X")
305
  reward_y = gr.Slider(minimum=0, maximum=GRID_SIZE - 1, step=1, value=0, label="2. Pos. Récompense Y")
306
 
307
- grid_display = gr.HTML(label="Plateau de Jeu (10x10)", value=_draw_grid(game_state_initial['puppy_pos'], game_state_initial['reward_pos'], None))
308
 
309
  with gr.Column(scale=1):
310
  level_display = gr.Markdown(f"### Niveau Actuel : {INITIAL_LEVEL}")
@@ -333,13 +326,15 @@ else:
333
 
334
  # --- ÉVÉNEMENTS ---
335
 
 
336
  reward_x.change(
337
  fn=update_reward_pos,
338
  inputs=[game_state_json, reward_x, reward_y, reward_file],
339
  outputs=[game_state_json, message_output]
340
  ).then(
 
341
  fn=_draw_grid,
342
- inputs=[[puppy_pos_x, puppy_pos_y], [reward_x, reward_y], reward_file],
343
  outputs=grid_display
344
  )
345
 
@@ -348,11 +343,13 @@ else:
348
  inputs=[game_state_json, reward_x, reward_y, reward_file],
349
  outputs=[game_state_json, message_output]
350
  ).then(
 
351
  fn=_draw_grid,
352
- inputs=[[puppy_pos_x, puppy_pos_y], [reward_x, reward_y], reward_file],
353
  outputs=grid_display
354
  )
355
 
 
356
  action_btn.click(
357
  fn=handle_user_command,
358
  inputs=[game_state_json, command_input, reward_file],
@@ -361,8 +358,14 @@ else:
361
  fn=lambda g, f, h: [f"### Niveau Actuel : {g['level']}", f, h],
362
  inputs=[game_state_json, faim_state, humeur_state],
363
  outputs=[level_display, faim_bar, humeur_bar]
 
 
 
 
 
364
  )
365
 
 
366
  bravo_btn.click(
367
  fn=handle_bravo,
368
  inputs=[game_state_json],
@@ -371,8 +374,14 @@ else:
371
  fn=lambda g, f, h: [f"### Niveau Actuel : {g['level']}", f, h],
372
  inputs=[game_state_json, faim_state, humeur_state],
373
  outputs=[level_display, faim_bar, humeur_bar]
 
 
 
 
 
374
  )
375
 
 
376
  reset_btn.click(
377
  fn=_reset_game,
378
  inputs=[reward_file],
@@ -381,11 +390,18 @@ else:
381
  fn=lambda g: f"### Niveau Actuel : {g['level']}",
382
  inputs=[game_state_json],
383
  outputs=level_display
 
 
 
 
 
384
  )
385
 
 
386
  reward_file.change(
387
- fn=lambda path, x, y, px, py: _draw_grid([px, py], [x, y], path),
388
- inputs=[reward_file, reward_x, reward_y, puppy_pos_x, puppy_pos_y],
 
389
  outputs=grid_display
390
  )
391
 
 
9
  from huggingface_hub import hf_hub_download
10
 
11
  # --- PARAMÈTRES DU DÉPÔT HUGGING FACE ---
 
12
  REPO_ID = "Clemylia/MiRobot"
13
  MODEL_FILE = "mirobot_final_model.zip"
14
  ENV_SCRIPT_FILE = "MiRobotEnv.py"
 
20
  INITIAL_LEVEL = 1
21
 
22
  # --- INDICES D'ÉTAT ---
 
23
  CMD_AVANCER = 0
24
  CMD_TOURNER = 1
25
  ETAT_FAIM = 2
26
  ETAT_SOMMEIL = 3
27
  ETAT_HUMEUR = 4
28
 
29
+ # Map des actions
30
  ACTION_MAP_MODEL = {0: "S'Arrêter", 1: "Avancer", 2: "Tourner G", 3: "Tourner D"}
 
 
31
  ACTION_MAP_USER = {
32
  "AVANCER": 1,
33
  "TOURNER À GAUCHE": 2,
 
35
  }
36
 
37
  # ----------------------------------------------------------------------
38
+ # 1. PRÉPARATION DU MODÈLE ET DE L'ENVIRONNEMENT
39
  # ----------------------------------------------------------------------
40
 
41
  model = None
 
50
  TEMP_DIR = "./mirobot_assets"
51
  os.makedirs(TEMP_DIR, exist_ok=True)
52
 
53
+ # --- 1. Chargement de la classe MiRobotEnv ---
54
  env_path = hf_hub_download(repo_id=REPO_ID, filename=ENV_SCRIPT_FILE, local_dir=TEMP_DIR)
55
 
56
  # INJECTION DE DÉPENDANCES pour que MiRobotEnv.py fonctionne
57
  env_globals = {'gym': gym, 'np': np, 'spaces': spaces}
 
58
  with open(env_path, 'r') as f:
59
  exec(f.read(), env_globals)
60
 
 
67
  id=ENV_ID,
68
  entry_point=MiRobotEnv,
69
  )
 
70
 
71
 
72
  # --- 3. Chargement du Modèle et de l'Environnement ---
 
74
  model = PPO.load(model_path)
75
 
76
  env = gym.make(ENV_ID)
77
+ env.reset() # IMPORTANT : Assure la création de self.state
78
 
 
79
  initial_faim = env.state[ETAT_FAIM] * 100
80
  initial_humeur = env.state[ETAT_HUMEUR]
81
 
 
83
 
84
  except Exception as e:
85
  print(f"❌ ERREUR CRITIQUE lors du chargement de MiRobot: {e}")
86
+ # initial_faim/humeur restent à 0.0
87
+
88
  # ----------------------------------------------------------------------
89
  # 2. LOGIQUE DU JEU
90
  # ----------------------------------------------------------------------
 
99
  }
100
 
101
  def _reset_game(reward_path):
102
+ """Réinitialise les positions et l'état interne du chiot."""
103
 
104
  new_state = game_state_initial.copy()
105
 
106
  if env is not None:
107
+ obs, info = env.reset()
108
  faim_display = env.state[ETAT_FAIM] * 100
109
  humeur_display = env.state[ETAT_HUMEUR]
110
  else:
 
116
  'message': f'Jeu réinitialisé. Niveau {INITIAL_LEVEL}. Placez la récompense !'
117
  })
118
 
119
+ # On retourne les composants séparément
120
  return new_state, new_state['puppy_pos'][0], new_state['puppy_pos'][1], new_state['reward_pos'][0], new_state['reward_pos'][1], faim_display, humeur_display, new_state['message']
121
 
122
  def handle_user_command(current_state, command_text, reward_path):
123
+ """Gère une commande utilisateur et l'action du modèle RL."""
124
  game_state = current_state
125
 
126
  if model is None or env is None:
 
127
  return game_state, command_text, 5, 5, 0, 0, '❌ Erreur: Le modèle MiRobot n\'a pas pu être chargé !'
128
 
129
  game_state['reward_asset_path'] = reward_path
130
 
131
+ # Vérification de la faim (défaite)
132
  faim_actuelle = env.state[ETAT_FAIM]
133
  if faim_actuelle >= FAIM_PENALTY_THRESHOLD:
134
+ game_state['message'] = f'💔 Défaite ! MiRobot a trop faim ({faim_actuelle:.0%}). Jeu réinitialisé.'
135
  return _reset_game(reward_path)
136
 
 
137
  command_upper = command_text.upper()
138
 
139
  if command_upper not in ACTION_MAP_USER:
140
  game_state['message'] = f"🤔 MiRobot n'a pas compris l'ordre '{command_text}'. Sa faim augmente..."
 
 
141
  env.state[CMD_AVANCER] = 0.0
142
  env.state[CMD_TOURNER] = 0.0
143
+ env.step(0) # Action 0: S'arrêter
144
 
145
  faim_display = env.state[ETAT_FAIM] * 100
146
  humeur_display = env.state[ETAT_HUMEUR]
 
150
  # 4. Exécution de la décision du Modèle
151
  command_action_name = command_upper
152
 
 
153
  env.state[CMD_AVANCER] = 1.0 if command_action_name == "AVANCER" else 0.0
154
  env.state[CMD_TOURNER] = 1.0 if command_action_name.startswith("TOURNER") else 0.0
155
 
156
+ mirobot_action_id, _ = model.predict(env.state, deterministic=True)
 
 
 
 
157
  new_obs, reward, terminated, truncated, info = env.step(mirobot_action_id)
158
 
159
 
 
160
  dx, dy = 0, 0
161
 
162
  if mirobot_action_id == ACTION_MAP_USER[command_action_name]:
 
174
 
175
  else:
176
  real_action_name = ACTION_MAP_MODEL[mirobot_action_id]
177
+ game_state['message'] = f"😥 MiRobot a désobéi ! Il a fait '{real_action_name}' au lieu de '{command_action_name}'. Récompense RL: {reward:.2f}"
178
 
179
+ # Mise à jour de la position
180
  new_x = np.clip(game_state['puppy_pos'][0] + dx, 0, GRID_SIZE - 1)
181
  new_y = np.clip(game_state['puppy_pos'][1] + dy, 0, GRID_SIZE - 1)
182
  game_state['puppy_pos'] = [new_x, new_y]
 
199
  if px == rx and py == ry:
200
  game_state['level'] += 1
201
 
 
202
  env.state[ETAT_FAIM] = np.clip(env.state[ETAT_FAIM] - 0.5, 0.0, 1.0)
203
  env.state[ETAT_HUMEUR] = np.clip(env.state[ETAT_HUMEUR] + 0.5, -1.0, 1.0)
204
 
 
211
  return game_state, game_state['puppy_pos'][0], game_state['puppy_pos'][1], faim_display, humeur_display, game_state['message']
212
 
213
 
214
+ def _draw_grid(px, py, rx, ry, reward_path):
215
+ """
216
+ Dessine la grille de jeu avec le chiot et la récompense.
217
+ REMARQUE : Prend maintenant les coordonnées séparément.
218
+ """
219
  if reward_path is None:
220
  return "<p style='text-align: center; color: red;'>Veuillez télécharger une image de récompense pour afficher la grille.</p>"
221
 
 
230
  style = "border: 1px dotted #ccc; display: flex; align-items: center; justify-content: center; position: relative;"
231
  content = ""
232
 
233
+ is_puppy = (x == px and y == py)
234
+ is_reward = (x == rx and y == ry)
235
+
236
+ if is_puppy:
237
  content = puppy_icon
238
 
239
+ if is_reward:
240
+ if is_puppy:
241
  style += "background-color: #d4edda;"
242
  else:
243
  content += f"<img src='{reward_src}' style='width: 80%; height: 80%; object-fit: contain;'/>"
 
262
  # 3. INTERFACE GRADIO
263
  # ----------------------------------------------------------------------
264
 
265
+ # Assurer une valeur initiale pour l'affichage de la grille
266
+ initial_grid_html = _draw_grid(game_state_initial['puppy_pos'][0], game_state_initial['puppy_pos'][1],
267
+ game_state_initial['reward_pos'][0], game_state_initial['reward_pos'][1],
268
+ None)
269
+
270
+
271
  if model is None:
272
  demo = gr.Interface(
273
  fn=lambda: "Le modèle MiRobot n'a pas pu être chargé. Vérifiez les logs ou le REPO_ID.",
 
283
  gr.Markdown(
284
  f"""
285
  # MiRobot - Le Jeu d'Obéissance 🐾
 
 
286
  Bienvenue dans la simulation interactive de votre modèle RL **{REPO_ID}** !
287
  **Objectif :** Guider MiRobot vers la récompense en donnant des ordres. Attention, sa **Faim** augmente à chaque pas !
288
  """
 
297
  reward_x = gr.Slider(minimum=0, maximum=GRID_SIZE - 1, step=1, value=0, label="2. Pos. Récompense X")
298
  reward_y = gr.Slider(minimum=0, maximum=GRID_SIZE - 1, step=1, value=0, label="2. Pos. Récompense Y")
299
 
300
+ grid_display = gr.HTML(label="Plateau de Jeu (10x10)", value=initial_grid_html)
301
 
302
  with gr.Column(scale=1):
303
  level_display = gr.Markdown(f"### Niveau Actuel : {INITIAL_LEVEL}")
 
326
 
327
  # --- ÉVÉNEMENTS ---
328
 
329
+ # 1. Mise à jour de la position de la récompense
330
  reward_x.change(
331
  fn=update_reward_pos,
332
  inputs=[game_state_json, reward_x, reward_y, reward_file],
333
  outputs=[game_state_json, message_output]
334
  ).then(
335
+ # CORRECTION MAJEURE: Passer puppy_pos_x et puppy_pos_y séparément
336
  fn=_draw_grid,
337
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_file],
338
  outputs=grid_display
339
  )
340
 
 
343
  inputs=[game_state_json, reward_x, reward_y, reward_file],
344
  outputs=[game_state_json, message_output]
345
  ).then(
346
+ # CORRECTION MAJEURE: Passer puppy_pos_x et puppy_pos_y séparément
347
  fn=_draw_grid,
348
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_file],
349
  outputs=grid_display
350
  )
351
 
352
+ # 2. Gestion de l'Action (Bouton "Donner l'Ordre")
353
  action_btn.click(
354
  fn=handle_user_command,
355
  inputs=[game_state_json, command_input, reward_file],
 
358
  fn=lambda g, f, h: [f"### Niveau Actuel : {g['level']}", f, h],
359
  inputs=[game_state_json, faim_state, humeur_state],
360
  outputs=[level_display, faim_bar, humeur_bar]
361
+ ).then(
362
+ # Mise à jour de la grille après le mouvement du chiot
363
+ fn=_draw_grid,
364
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_file],
365
+ outputs=grid_display
366
  )
367
 
368
+ # 3. Gestion du "Bravo"
369
  bravo_btn.click(
370
  fn=handle_bravo,
371
  inputs=[game_state_json],
 
374
  fn=lambda g, f, h: [f"### Niveau Actuel : {g['level']}", f, h],
375
  inputs=[game_state_json, faim_state, humeur_state],
376
  outputs=[level_display, faim_bar, humeur_bar]
377
+ ).then(
378
+ # Mise à jour de la grille (même si la position ne change pas, les couleurs peuvent)
379
+ fn=_draw_grid,
380
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_file],
381
+ outputs=grid_display
382
  )
383
 
384
+ # 4. Réinitialisation du Jeu
385
  reset_btn.click(
386
  fn=_reset_game,
387
  inputs=[reward_file],
 
390
  fn=lambda g: f"### Niveau Actuel : {g['level']}",
391
  inputs=[game_state_json],
392
  outputs=level_display
393
+ ).then(
394
+ # Mise à jour de la grille après réinitialisation
395
+ fn=_draw_grid,
396
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_file],
397
+ outputs=grid_display
398
  )
399
 
400
+ # Mise à jour de la grille lorsque le fichier de récompense change
401
  reward_file.change(
402
+ # CORRECTION MAJEURE: on passe les états de position (x, y) au lieu de les reconstruire
403
+ fn=_draw_grid,
404
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_file],
405
  outputs=grid_display
406
  )
407