Clemylia commited on
Commit
3965a9e
·
verified ·
1 Parent(s): 7b209a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +53 -57
app.py CHANGED
@@ -35,7 +35,7 @@ ACTION_MAP_USER = {
35
  }
36
 
37
  # ----------------------------------------------------------------------
38
- # 1. PRÉPARATION DU MODÈLE ET DE L'ENVIRONNEMENT
39
  # ----------------------------------------------------------------------
40
 
41
  model = None
@@ -53,7 +53,6 @@ try:
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)
@@ -74,17 +73,17 @@ try:
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
 
82
  print("✅ MiRobot prêt pour l'interface Gradio.")
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
  # ----------------------------------------------------------------------
@@ -98,6 +97,13 @@ game_state_initial = {
98
  'reward_asset_path': None
99
  }
100
 
 
 
 
 
 
 
 
101
  def _reset_game(reward_path):
102
  """Réinitialise les positions et l'état interne du chiot."""
103
 
@@ -105,8 +111,9 @@ def _reset_game(reward_path):
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:
111
  faim_display = 0.0
112
  humeur_display = 0.0
@@ -116,12 +123,12 @@ def _reset_game(reward_path):
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é !'
@@ -129,7 +136,7 @@ def handle_user_command(current_state, command_text, reward_path):
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)
@@ -138,22 +145,23 @@ def handle_user_command(current_state, command_text, reward_path):
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]
147
  return game_state, command_text, game_state['puppy_pos'][0], game_state['puppy_pos'][1], faim_display, humeur_display, game_state['message']
148
 
149
 
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
 
@@ -166,7 +174,6 @@ def handle_user_command(current_state, command_text, reward_path):
166
  rx, ry = game_state['reward_pos']
167
  px, py = game_state['puppy_pos']
168
 
169
- # Déplacement d'une unité vers la récompense
170
  if abs(rx - px) > abs(ry - py):
171
  dx = 1 if rx > px else -1
172
  elif abs(ry - py) > 0:
@@ -181,15 +188,17 @@ def handle_user_command(current_state, command_text, reward_path):
181
  new_y = np.clip(game_state['puppy_pos'][1] + dy, 0, GRID_SIZE - 1)
182
  game_state['puppy_pos'] = [new_x, new_y]
183
 
184
- faim_display = env.state[ETAT_FAIM] * 100
185
- humeur_display = env.state[ETAT_HUMEUR]
 
186
  return game_state, command_text, new_x, new_y, faim_display, humeur_display, game_state['message']
187
 
188
 
189
  def handle_bravo(current_state):
190
  """Gère l'événement de récompense."""
191
  game_state = current_state
192
-
 
193
  if env is None:
194
  return game_state, game_state['puppy_pos'][0], game_state['puppy_pos'][1], 0, 0, '❌ Erreur: Modèle non chargé.'
195
 
@@ -199,23 +208,21 @@ def handle_bravo(current_state):
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
 
205
  game_state['message'] = f'🥳 BRAVO ! MiRobot a bien réussi ! Niveau suivant : {game_state["level"]}. Repositionnez la récompense pour continuer !'
206
  else:
207
  game_state['message'] = '😕 MiRobot doit être sur la case de la récompense pour recevoir un "BRAVO" !'
208
 
209
- faim_display = env.state[ETAT_FAIM] * 100
210
- humeur_display = env.state[ETAT_HUMEUR]
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
 
@@ -240,6 +247,7 @@ def _draw_grid(px, py, rx, ry, reward_path):
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;'/>"
244
  style += "background-color: #fff3cd;"
245
 
@@ -259,10 +267,9 @@ def update_reward_pos(current_state, reward_x, reward_y, reward_path):
259
 
260
 
261
  # ----------------------------------------------------------------------
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)
@@ -291,7 +298,8 @@ else:
291
  with gr.Row():
292
 
293
  with gr.Column(scale=2):
294
- reward_file = gr.File(label="1. Télécharger Image Récompense (Obligatoire)", type="filepath")
 
295
 
296
  with gr.Row():
297
  reward_x = gr.Slider(minimum=0, maximum=GRID_SIZE - 1, step=1, value=0, label="2. Pos. Récompense X")
@@ -302,7 +310,6 @@ else:
302
  with gr.Column(scale=1):
303
  level_display = gr.Markdown(f"### Niveau Actuel : {INITIAL_LEVEL}")
304
 
305
- # Utilisation des valeurs initiales sûres
306
  faim_bar = gr.Slider(minimum=0, maximum=100, value=initial_faim, label="Faim de MiRobot (%)", interactive=False)
307
  humeur_bar = gr.Slider(minimum=-1.0, maximum=1.0, value=initial_humeur, label="Humeur de MiRobot", interactive=False)
308
 
@@ -324,48 +331,42 @@ else:
324
  faim_state = gr.State(initial_faim)
325
  humeur_state = gr.State(initial_humeur)
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
 
341
  reward_y.change(
342
  fn=update_reward_pos,
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],
356
  outputs=[game_state_json, command_input, puppy_pos_x, puppy_pos_y, faim_state, humeur_state, message_output]
357
  ).then(
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],
@@ -375,33 +376,28 @@ else:
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],
388
  outputs=[game_state_json, puppy_pos_x, puppy_pos_y, reward_x, reward_y, faim_state, humeur_state, message_output]
389
  ).then(
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
 
 
35
  }
36
 
37
  # ----------------------------------------------------------------------
38
+ # 1. PRÉPARATION DU MODÈLE ET DE L'ENVIRONNEMENT (CORRECTION UNWRAPPED)
39
  # ----------------------------------------------------------------------
40
 
41
  model = None
 
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
  env_globals = {'gym': gym, 'np': np, 'spaces': spaces}
57
  with open(env_path, 'r') as f:
58
  exec(f.read(), env_globals)
 
73
  model = PPO.load(model_path)
74
 
75
  env = gym.make(ENV_ID)
76
+ env.reset()
77
 
78
+ # **CORRECTION**: Accéder à l'état via env.unwrapped pour éviter l'erreur OrderEnforcing
79
+ initial_faim = env.unwrapped.state[ETAT_FAIM] * 100
80
+ initial_humeur = env.unwrapped.state[ETAT_HUMEUR]
81
 
82
  print("✅ MiRobot prêt pour l'interface Gradio.")
83
 
84
  except Exception as e:
85
  print(f"❌ ERREUR CRITIQUE lors du chargement de MiRobot: {e}")
86
+
 
87
  # ----------------------------------------------------------------------
88
  # 2. LOGIQUE DU JEU
89
  # ----------------------------------------------------------------------
 
97
  'reward_asset_path': None
98
  }
99
 
100
+ def _get_env_state(env_instance):
101
+ """Accès sécurisé à l'état de l'environnement, même avec un wrapper."""
102
+ if env_instance is None:
103
+ return None
104
+ # Retourne l'état de l'environnement sous-jacent (unwrapped)
105
+ return env_instance.unwrapped.state
106
+
107
  def _reset_game(reward_path):
108
  """Réinitialise les positions et l'état interne du chiot."""
109
 
 
111
 
112
  if env is not None:
113
  obs, info = env.reset()
114
+ current_state = _get_env_state(env)
115
+ faim_display = current_state[ETAT_FAIM] * 100
116
+ humeur_display = current_state[ETAT_HUMEUR]
117
  else:
118
  faim_display = 0.0
119
  humeur_display = 0.0
 
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
  """Gère une commande utilisateur et l'action du modèle RL."""
130
  game_state = current_state
131
+ current_env_state = _get_env_state(env)
132
 
133
  if model is None or env is None:
134
  return game_state, command_text, 5, 5, 0, 0, '❌ Erreur: Le modèle MiRobot n\'a pas pu être chargé !'
 
136
  game_state['reward_asset_path'] = reward_path
137
 
138
  # Vérification de la faim (défaite)
139
+ faim_actuelle = current_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%}). Jeu réinitialisé.'
142
  return _reset_game(reward_path)
 
145
 
146
  if command_upper not in ACTION_MAP_USER:
147
  game_state['message'] = f"🤔 MiRobot n'a pas compris l'ordre '{command_text}'. Sa faim augmente..."
148
+
149
+ current_env_state[CMD_AVANCER] = 0.0
150
+ current_env_state[CMD_TOURNER] = 0.0
151
  env.step(0) # Action 0: S'arrêter
152
 
153
+ faim_display = current_env_state[ETAT_FAIM] * 100
154
+ humeur_display = current_env_state[ETAT_HUMEUR]
155
  return game_state, command_text, game_state['puppy_pos'][0], game_state['puppy_pos'][1], faim_display, humeur_display, game_state['message']
156
 
157
 
158
  # 4. Exécution de la décision du Modèle
159
  command_action_name = command_upper
160
 
161
+ current_env_state[CMD_AVANCER] = 1.0 if command_action_name == "AVANCER" else 0.0
162
+ current_env_state[CMD_TOURNER] = 1.0 if command_action_name.startswith("TOURNER") else 0.0
163
 
164
+ mirobot_action_id, _ = model.predict(current_env_state, deterministic=True)
165
  new_obs, reward, terminated, truncated, info = env.step(mirobot_action_id)
166
 
167
 
 
174
  rx, ry = game_state['reward_pos']
175
  px, py = game_state['puppy_pos']
176
 
 
177
  if abs(rx - px) > abs(ry - py):
178
  dx = 1 if rx > px else -1
179
  elif abs(ry - py) > 0:
 
188
  new_y = np.clip(game_state['puppy_pos'][1] + dy, 0, GRID_SIZE - 1)
189
  game_state['puppy_pos'] = [new_x, new_y]
190
 
191
+ current_env_state = _get_env_state(env) # Relecture après le step
192
+ faim_display = current_env_state[ETAT_FAIM] * 100
193
+ humeur_display = current_env_state[ETAT_HUMEUR]
194
  return game_state, command_text, new_x, new_y, faim_display, humeur_display, game_state['message']
195
 
196
 
197
  def handle_bravo(current_state):
198
  """Gère l'événement de récompense."""
199
  game_state = current_state
200
+ current_env_state = _get_env_state(env)
201
+
202
  if env is None:
203
  return game_state, game_state['puppy_pos'][0], game_state['puppy_pos'][1], 0, 0, '❌ Erreur: Modèle non chargé.'
204
 
 
208
  if px == rx and py == ry:
209
  game_state['level'] += 1
210
 
211
+ # Modification de l'état via current_env_state
212
+ current_env_state[ETAT_FAIM] = np.clip(current_env_state[ETAT_FAIM] - 0.5, 0.0, 1.0)
213
+ current_env_state[ETAT_HUMEUR] = np.clip(current_env_state[ETAT_HUMEUR] + 0.5, -1.0, 1.0)
214
 
215
  game_state['message'] = f'🥳 BRAVO ! MiRobot a bien réussi ! Niveau suivant : {game_state["level"]}. Repositionnez la récompense pour continuer !'
216
  else:
217
  game_state['message'] = '😕 MiRobot doit être sur la case de la récompense pour recevoir un "BRAVO" !'
218
 
219
+ faim_display = current_env_state[ETAT_FAIM] * 100
220
+ humeur_display = current_env_state[ETAT_HUMEUR]
221
  return game_state, game_state['puppy_pos'][0], game_state['puppy_pos'][1], faim_display, humeur_display, game_state['message']
222
 
223
 
224
  def _draw_grid(px, py, rx, ry, reward_path):
225
+ """Dessine la grille de jeu avec le chiot et la récompense."""
 
 
 
226
  if reward_path is None:
227
  return "<p style='text-align: center; color: red;'>Veuillez télécharger une image de récompense pour afficher la grille.</p>"
228
 
 
247
  if is_puppy:
248
  style += "background-color: #d4edda;"
249
  else:
250
+ # Affichage de l'image
251
  content += f"<img src='{reward_src}' style='width: 80%; height: 80%; object-fit: contain;'/>"
252
  style += "background-color: #fff3cd;"
253
 
 
267
 
268
 
269
  # ----------------------------------------------------------------------
270
+ # 3. INTERFACE GRADIO (Correction gr.Image)
271
  # ----------------------------------------------------------------------
272
 
 
273
  initial_grid_html = _draw_grid(game_state_initial['puppy_pos'][0], game_state_initial['puppy_pos'][1],
274
  game_state_initial['reward_pos'][0], game_state_initial['reward_pos'][1],
275
  None)
 
298
  with gr.Row():
299
 
300
  with gr.Column(scale=2):
301
+ # **CORRECTION**: Utilisation de gr.Image pour une meilleure gestion des uploads
302
+ reward_image = gr.Image(label="1. Télécharger Image Récompense (Obligatoire)", type="filepath", height=150, width=150)
303
 
304
  with gr.Row():
305
  reward_x = gr.Slider(minimum=0, maximum=GRID_SIZE - 1, step=1, value=0, label="2. Pos. Récompense X")
 
310
  with gr.Column(scale=1):
311
  level_display = gr.Markdown(f"### Niveau Actuel : {INITIAL_LEVEL}")
312
 
 
313
  faim_bar = gr.Slider(minimum=0, maximum=100, value=initial_faim, label="Faim de MiRobot (%)", interactive=False)
314
  humeur_bar = gr.Slider(minimum=-1.0, maximum=1.0, value=initial_humeur, label="Humeur de MiRobot", interactive=False)
315
 
 
331
  faim_state = gr.State(initial_faim)
332
  humeur_state = gr.State(initial_humeur)
333
 
334
+ # --- ÉVÉNEMENTS (Mise à jour des inputs pour 'reward_image') ---
335
 
 
336
  reward_x.change(
337
  fn=update_reward_pos,
338
+ inputs=[game_state_json, reward_x, reward_y, reward_image],
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_image],
343
  outputs=grid_display
344
  )
345
 
346
  reward_y.change(
347
  fn=update_reward_pos,
348
+ inputs=[game_state_json, reward_x, reward_y, reward_image],
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_image],
353
  outputs=grid_display
354
  )
355
 
 
356
  action_btn.click(
357
  fn=handle_user_command,
358
+ inputs=[game_state_json, command_input, reward_image],
359
  outputs=[game_state_json, command_input, puppy_pos_x, puppy_pos_y, faim_state, humeur_state, message_output]
360
  ).then(
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
  ).then(
 
365
  fn=_draw_grid,
366
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_image],
367
  outputs=grid_display
368
  )
369
 
 
370
  bravo_btn.click(
371
  fn=handle_bravo,
372
  inputs=[game_state_json],
 
376
  inputs=[game_state_json, faim_state, humeur_state],
377
  outputs=[level_display, faim_bar, humeur_bar]
378
  ).then(
 
379
  fn=_draw_grid,
380
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_image],
381
  outputs=grid_display
382
  )
383
 
 
384
  reset_btn.click(
385
  fn=_reset_game,
386
+ inputs=[reward_image],
387
  outputs=[game_state_json, puppy_pos_x, puppy_pos_y, reward_x, reward_y, faim_state, humeur_state, message_output]
388
  ).then(
389
  fn=lambda g: f"### Niveau Actuel : {g['level']}",
390
  inputs=[game_state_json],
391
  outputs=level_display
392
  ).then(
 
393
  fn=_draw_grid,
394
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_image],
395
  outputs=grid_display
396
  )
397
 
398
+ reward_image.change(
 
 
399
  fn=_draw_grid,
400
+ inputs=[puppy_pos_x, puppy_pos_y, reward_x, reward_y, reward_image],
401
  outputs=grid_display
402
  )
403