Docfile commited on
Commit
166ccea
·
verified ·
1 Parent(s): ded7221

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -78
app.py CHANGED
@@ -1,87 +1,139 @@
1
  from flask import Flask, render_template, request, jsonify, session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import chess
3
  import chess.svg
4
- from stockfish import Stockfish, StockfishException # Assurez-vous que ce module est accessible
5
- import os
6
  import logging
7
 
8
  app = Flask(__name__)
9
 
10
  # --- Configuration de la clé secrète ---
11
- # IMPORTANT: Pour la production, utilisez une clé secrète forte et ne la codez pas en dur.
12
- # Chargez-la depuis une variable d'environnement ou un fichier de configuration.
13
- # Pour le DÉVELOPPEMENT UNIQUEMENT, une clé statique aide à éviter la perte de session due aux rechargements.
14
- IS_DEBUG_MODE = os.environ.get("FLASK_DEBUG", "0") == "1" or app.debug
15
 
16
  if IS_DEBUG_MODE:
17
- app.secret_key = 'dev_secret_key_pour_eviter_perte_session_rechargement_et_tests'
18
- logging.warning("UTILISATION D'UNE CLÉ SECRÈTE STATIQUE POUR LE DÉVELOPPEMENT. NE PAS UTILISER EN PRODUCTION.")
 
19
  else:
20
  app.secret_key = os.environ.get("FLASK_SECRET_KEY")
21
  if not app.secret_key:
22
- app.secret_key = os.urandom(32) # Génère une clé plus longue pour la prod si non définie
23
- logging.warning("FLASK_SECRET_KEY non définie. Utilisation d'une clé générée aléatoirement. "
24
- "Les sessions ne persisteront pas entre les redémarrages du serveur de production.")
 
25
 
26
  # --- Configuration des Cookies de Session ---
27
- # Ces configurations sont importantes pour le développement en HTTP.
28
  app.config.update(
29
- SESSION_COOKIE_SECURE=False, # Permet les cookies de session sur HTTP (développement)
30
- SESSION_COOKIE_HTTPONLY=True, # Empêche l'accès au cookie via JavaScript (bonne pratique)
31
- SESSION_COOKIE_SAMESITE='Lax', # Politique SameSite raisonnable pour la plupart des cas
32
- # PERMANENT_SESSION_LIFETIME=timedelta(days=7) # Optionnel: durée de vie de la session
33
  )
34
 
35
-
36
- # --- Configuration du logging ---
37
  logging.basicConfig(level=logging.DEBUG)
38
- # Pour réduire le bruit des requêtes GET/POST en console en production:
39
- # if not IS_DEBUG_MODE:
40
- # werkzeug_logger = logging.getLogger('werkzeug')
41
- # werkzeug_logger.setLevel(logging.WARNING)
42
-
43
-
44
- # --- Configuration de Stockfish ---
45
- STOCKFISH_PATH = "stockfish"
46
- def find_stockfish():
47
- global STOCKFISH_PATH
48
- if os.path.exists(STOCKFISH_PATH) and os.access(STOCKFISH_PATH, os.X_OK):
49
- app.logger.info(f"Stockfish trouvé directement à: {STOCKFISH_PATH}")
50
- return True
51
 
52
- common_paths = ["/usr/games/stockfish", "/usr/local/bin/stockfish", "/opt/homebrew/bin/stockfish", "stockfish.exe", "./stockfish"]
53
- for p in common_paths:
54
- if os.path.exists(p) and os.access(p, os.X_OK):
55
- STOCKFISH_PATH = p
56
- app.logger.info(f"Stockfish trouvé à: {STOCKFISH_PATH}")
57
- return True
58
- app.logger.warning(f"AVERTISSEMENT: Stockfish non trouvé. L'IA ne fonctionnera pas. "
59
- "Veuillez installer Stockfish et/ou configurer STOCKFISH_PATH.")
60
- return False
61
 
62
- HAS_STOCKFISH = find_stockfish()
63
 
64
 
65
  def get_stockfish_engine():
66
  """Initialise et retourne une instance de Stockfish."""
67
- if not HAS_STOCKFISH:
68
- app.logger.error("Stockfish non disponible. L'IA ne peut pas démarrer.")
69
  return None
70
  try:
71
- engine = Stockfish(path=STOCKFISH_PATH, depth=15, parameters={"Threads": 1, "Hash": 64})
 
72
  if engine._stockfish.poll() is not None:
73
  app.logger.error("Le processus Stockfish s'est terminé de manière inattendue lors de l'initialisation.")
74
  raise StockfishException("Stockfish process terminated unexpectedly on init.")
75
- # Test rapide pour s'assurer que Stockfish répond
76
  engine.set_fen_position(chess.STARTING_FEN)
77
- engine.get_best_move()
78
- engine.set_fen_position(chess.STARTING_FEN) # Reset après test
79
- app.logger.info(f"Moteur Stockfish initialisé avec succès depuis {STOCKFISH_PATH}.")
80
  return engine
81
  except Exception as e:
82
- app.logger.error(f"Erreur lors de l'initialisation de Stockfish ({STOCKFISH_PATH}): {e}", exc_info=True)
83
  return None
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  @app.route('/')
86
  def index():
87
  app.logger.debug(f"Session au début de GET /: {dict(session.items())}")
@@ -107,17 +159,15 @@ def get_outcome_message(board):
107
  if board.is_checkmate():
108
  winner_color = "Blancs" if board.turn == chess.BLACK else "Noirs"
109
  return f"Échec et mat ! {winner_color} gagnent."
110
- # Vérifier d'abord les conditions de nullité spécifiques avant is_game_over() générique
111
  if board.is_stalemate(): return "Pat ! Partie nulle."
112
  if board.is_insufficient_material(): return "Matériel insuffisant. Partie nulle."
113
  if board.is_seventyfive_moves(): return "Règle des 75 coups. Partie nulle."
114
  if board.is_fivefold_repetition(): return "Répétition (5 fois). Partie nulle."
115
- # if board.is_variant_draw(): return "Partie nulle (variante)." # Moins courant pour échecs std
116
- if board.is_game_over(): return "Partie terminée." # Cas générique si aucune des conditions ci-dessus
117
  return ""
118
 
119
  @app.route('/make_move', methods=['POST'])
120
- def make_move():
121
  app.logger.debug(f"Session au début de POST /make_move: {dict(session.items())}")
122
  if 'board_fen' not in session:
123
  app.logger.error("ERREUR CRITIQUE: 'board_fen' non trouvé dans la session pour POST /make_move!")
@@ -138,10 +188,9 @@ def make_move():
138
  ai_move_uci = None
139
  last_player_move = None
140
  move_to_highlight = None
 
141
 
142
  try:
143
- # python-chess parse_uci peut aussi prendre SAN pour des coups simples.
144
- # Pour être plus strict, on pourrait essayer parse_uci d'abord, puis parse_san.
145
  move = board.parse_uci(move_uci_san)
146
  app.logger.info(f"Mouvement joueur (tentative UCI): {move.uci()}")
147
  except ValueError:
@@ -152,10 +201,10 @@ def make_move():
152
  app.logger.warning(f"Mouvement invalide (ni UCI ni SAN): {move_uci_san} pour FEN {board.fen()}")
153
  return jsonify({'error': f'Mouvement invalide: {move_uci_san}', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
154
 
155
- if move in board.legal_moves:
156
  board.push(move)
157
  last_player_move = move
158
- move_to_highlight = move # Le coup du joueur est le dernier pour l'instant
159
  session['board_fen'] = board.fen()
160
  app.logger.info(f"Mouvement joueur {move.uci()} appliqué. Nouveau FEN: {board.fen()}")
161
 
@@ -165,12 +214,12 @@ def make_move():
165
  (session.get('player_color') == 'black' and board.turn == chess.WHITE)
166
 
167
  if not current_turn_is_ai_turn:
168
- app.logger.error(f"Logique d'alternance erronée: C'est le tour de {board.turn}, mais l'IA ne devrait pas jouer.")
169
  else:
170
  stockfish_engine = get_stockfish_engine()
171
  if stockfish_engine:
172
  stockfish_engine.set_fen_position(board.fen())
173
- best_move_ai = stockfish_engine.get_best_move() # ou get_best_move_time(500)
174
  if best_move_ai:
175
  app.logger.info(f"Stockfish propose le coup: {best_move_ai}")
176
  try:
@@ -179,23 +228,37 @@ def make_move():
179
  session['board_fen'] = board.fen()
180
  ai_move_made = True
181
  ai_move_uci = best_move_ai
182
- move_to_highlight = ai_move_obj # Le coup de l'IA est le plus récent
183
  app.logger.info(f"Mouvement IA {ai_move_uci} appliqué. Nouveau FEN: {board.fen()}")
184
  except Exception as e:
185
  app.logger.error(f"Erreur en appliquant le coup de l'IA {best_move_ai}: {e}", exc_info=True)
186
  else:
187
  app.logger.warning("L'IA (Stockfish) n'a pas retourné de coup.")
188
- stockfish_engine.send_quit_command()
189
- app.logger.info("Moteur Stockfish arrêté après le coup de l'IA.")
 
 
 
 
 
 
 
 
 
 
190
  else:
191
  app.logger.error("Moteur Stockfish non disponible pour le coup de l'IA.")
192
- # Ne pas retourner d'erreur au client ici, laisser la partie continuer sans coup IA pour ce tour
193
- # ou envisager une meilleure gestion de cet échec.
194
- else:
195
  app.logger.warning(f"Mouvement illégal tenté: {move.uci() if hasattr(move, 'uci') else move_uci_san} depuis FEN: {session['board_fen']}")
196
  legal_moves_str = ", ".join([board.san(m) for m in board.legal_moves])
197
- app.logger.debug(f"Coups légaux: {legal_moves_str[:200]}") # Limiter la longueur
198
  return jsonify({'error': f'Mouvement illégal: {move.uci() if hasattr(move, "uci") else move_uci_san}', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
 
 
 
 
 
 
199
 
200
  game_over = board.is_game_over()
201
  outcome = get_outcome_message(board) if game_over else ""
@@ -216,8 +279,6 @@ def make_move():
216
  def reset_game():
217
  app.logger.debug(f"Session au début de POST /reset_game: {dict(session.items())}")
218
  session['board_fen'] = chess.Board().fen()
219
- # Conserver le mode et la couleur, ou les réinitialiser si nécessaire.
220
- # Si le mode ou la couleur ne sont pas dans la session, les initialiser.
221
  if 'game_mode' not in session: session['game_mode'] = 'pvp'
222
  if 'player_color' not in session: session['player_color'] = 'white'
223
 
@@ -239,7 +300,7 @@ def reset_game():
239
  def set_mode_route():
240
  app.logger.debug(f"Session au début de POST /set_mode: {dict(session.items())}")
241
  mode = request.json.get('game_mode')
242
- player_color = request.json.get('player_color', 'white') # Couleur du joueur humain
243
 
244
  if mode in ['pvp', 'ai']:
245
  session['game_mode'] = mode
@@ -251,7 +312,6 @@ def set_mode_route():
251
  initial_ai_move_uci = None
252
  move_to_highlight_init = None
253
 
254
- # Si le mode est IA et le joueur humain a choisi les Noirs, l'IA (Blancs) joue en premier.
255
  if mode == 'ai' and player_color == 'black':
256
  app.logger.info("L'IA (Blancs) commence la partie.")
257
  stockfish_engine = get_stockfish_engine()
@@ -262,18 +322,22 @@ def set_mode_route():
262
  try:
263
  ai_move_obj = board.parse_uci(best_move_ai)
264
  board.push(ai_move_obj)
265
- session['board_fen'] = board.fen()
266
  initial_ai_move_uci = best_move_ai
267
  move_to_highlight_init = ai_move_obj
268
  app.logger.info(f"Premier coup de l'IA (Blancs): {initial_ai_move_uci}. Nouveau FEN: {board.fen()}")
269
  except Exception as e:
270
  app.logger.error(f"Erreur en appliquant le premier coup de l'IA {best_move_ai}: {e}", exc_info=True)
271
- stockfish_engine.send_quit_command()
272
- app.logger.info("Moteur Stockfish arrêté après le premier coup de l'IA.")
 
 
 
 
 
 
273
  else:
274
  app.logger.error("Moteur Stockfish non disponible pour le premier coup de l'IA.")
275
- # Pas idéal de retourner une erreur ici, le client pourrait être bloqué.
276
- # On pourrait laisser le FEN initial.
277
 
278
  app.logger.debug(f"Session à la fin de POST /set_mode: {dict(session.items())}")
279
  return jsonify({
@@ -284,13 +348,13 @@ def set_mode_route():
284
  'player_color': player_color,
285
  'turn': 'white' if board.turn == chess.WHITE else 'black',
286
  'initial_ai_move_uci': initial_ai_move_uci,
287
- 'game_over': board.is_game_over(), # Devrait être False ici en général
288
  'outcome': get_outcome_message(board) if board.is_game_over() else ""
289
  })
290
  else:
291
  app.logger.warning(f"Tentative de définir un mode de jeu invalide: {mode}")
292
  return jsonify({'error': 'Mode invalide'}), 400
293
 
 
294
  if __name__ == '__main__':
295
- # IS_DEBUG_MODE est défini plus haut
296
- app.run(debug=IS_DEBUG_MODE, host='0.0.0.0', port=5000)
 
1
  from flask import Flask, render_template, request, jsonify, session
2
+ # -- Modification pour vérifier l'import --
3
+ import sys
4
+ import os
5
+ import inspect # Ajout pour inspecter
6
+
7
+ # --- Forcer l'utilisation de la bibliothèque locale si elle est à côté ---
8
+ # Supposons la structure:
9
+ # votre_projet/
10
+ # ├── app.py
11
+ # └── zhelyabuzhsky-stockfish/ <-- le répertoire complet de la bibliothèque
12
+ # ├── stockfish/
13
+ # │ ├── __init__.py
14
+ # │ └── models.py
15
+ # └── ... autres fichiers de la lib (setup.py, etc.)
16
+
17
+ # Pour que "from stockfish import Stockfish" fonctionne avec cette structure,
18
+ # le répertoire "zhelyabuzhsky-stockfish" doit être traitable comme un package.
19
+ # Python recherche d'abord dans le répertoire courant, puis dans PYTHONPATH, puis dans les sites-packages.
20
+ # Si vous avez une autre lib "stockfish" installée globalement, elle pourrait prendre le dessus.
21
+ # On peut ajouter le répertoire parent de zhelyabuzhsky-stockfish/stockfish au path,
22
+ # ou directement le chemin vers zhelyabuzhsky-stockfish si on l'importe comme un package.
23
+
24
+ # Option 1: Ajouter le répertoire parent de votre projet au sys.path
25
+ # pour que zhelyabuzhsky-stockfish soit vu comme un package de haut niveau.
26
+ # current_script_dir = os.path.dirname(os.path.abspath(__file__))
27
+ # project_root = os.path.dirname(current_script_dir) # Si app.py est dans un sous-dossier
28
+ # if project_root not in sys.path:
29
+ # sys.path.insert(0, project_root)
30
+
31
+ # Option plus simple si zhelyabuzhsky-stockfish est à côté de app.py
32
+ # et que vous voulez importer "from stockfish import ..."
33
+ # Cela suppose que Python va scanner "zhelyabuzhsky-stockfish" et trouver le sous-module "stockfish".
34
+ # Le plus sûr est d'installer votre bibliothèque localement.
35
+ # `pip install -e ./zhelyabuzhsky-stockfish` depuis le répertoire parent.
36
+
37
+ from stockfish import Stockfish, StockfishException # L'import
38
+ print(f"--- Stockfish class imported from: {inspect.getfile(Stockfish)} ---") # VÉRIFICATION
39
+ # --- Fin de la modification pour vérifier l'import --
40
+
41
  import chess
42
  import chess.svg
 
 
43
  import logging
44
 
45
  app = Flask(__name__)
46
 
47
  # --- Configuration de la clé secrète ---
48
+ # FORCER LE MODE DEBUG POUR LE DÉVELOPPEMENT LOCAL
49
+ IS_DEBUG_MODE = True # Modifier cette ligne manuellement pour tester en mode prod-like si besoin
50
+ # OU utiliser la variable d'environnement:
51
+ # IS_DEBUG_MODE = os.environ.get("FLASK_DEBUG", "0") == "1"
52
 
53
  if IS_DEBUG_MODE:
54
+ app.secret_key = 'dev_secret_key_pour_eviter_perte_session_rechargement_et_tests_v2'
55
+ logging.warning("MODE DEBUG ACTIVÉ. Utilisation d'une clé secrète statique.")
56
+ app.debug = True # S'assurer que Flask est aussi en mode debug
57
  else:
58
  app.secret_key = os.environ.get("FLASK_SECRET_KEY")
59
  if not app.secret_key:
60
+ app.secret_key = os.urandom(32)
61
+ logging.warning("MODE PRODUCTION. FLASK_SECRET_KEY non définie. Clé aléatoire générée.")
62
+ app.debug = False
63
+
64
 
65
  # --- Configuration des Cookies de Session ---
 
66
  app.config.update(
67
+ SESSION_COOKIE_SECURE=False,
68
+ SESSION_COOKIE_HTTPONLY=True,
69
+ SESSION_COOKIE_SAMESITE='Lax',
 
70
  )
71
 
 
 
72
  logging.basicConfig(level=logging.DEBUG)
73
+
74
+ # ... (le reste du code de app.py reste identique à la version précédente)
75
+ # ... (find_stockfish, get_stockfish_engine, routes, etc.)
76
+
77
+ # --- Assurez-vous que le find_stockfish et HAS_STOCKFISH sont définis globalement ---
78
+ STOCKFISH_PATH_GLOBAL = "stockfish" # Variable globale pour le chemin
79
+ HAS_STOCKFISH_GLOBAL = False
80
+
81
+ def find_stockfish_on_startup():
82
+ global STOCKFISH_PATH_GLOBAL, HAS_STOCKFISH_GLOBAL
83
+ # Tenter le chemin par défaut ou celui de l'env
84
+ stockfish_env_path = os.environ.get("STOCKFISH_EXECUTABLE_PATH", "stockfish")
85
+ paths_to_check = [stockfish_env_path, STOCKFISH_PATH_GLOBAL, "/usr/games/stockfish", "/usr/local/bin/stockfish", "/opt/homebrew/bin/stockfish", "stockfish.exe", "./stockfish"]
86
 
87
+ for p in paths_to_check:
88
+ if p and os.path.exists(p) and os.access(p, os.X_OK):
89
+ STOCKFISH_PATH_GLOBAL = p
90
+ HAS_STOCKFISH_GLOBAL = True
91
+ app.logger.info(f"Stockfish exécutable trouvé à: {STOCKFISH_PATH_GLOBAL}")
92
+ return
93
+ app.logger.warning(f"AVERTISSEMENT: Stockfish non trouvé. L'IA ne fonctionnera pas.")
94
+ HAS_STOCKFISH_GLOBAL = False
 
95
 
96
+ find_stockfish_on_startup() # Exécuter au démarrage
97
 
98
 
99
  def get_stockfish_engine():
100
  """Initialise et retourne une instance de Stockfish."""
101
+ if not HAS_STOCKFISH_GLOBAL:
102
+ app.logger.error("Stockfish non disponible (vérification au démarrage échouée). L'IA ne peut pas démarrer.")
103
  return None
104
  try:
105
+ # Utiliser STOCKFISH_PATH_GLOBAL qui a été déterminé au démarrage
106
+ engine = Stockfish(path=STOCKFISH_PATH_GLOBAL, depth=15, parameters={"Threads": 1, "Hash": 64})
107
  if engine._stockfish.poll() is not None:
108
  app.logger.error("Le processus Stockfish s'est terminé de manière inattendue lors de l'initialisation.")
109
  raise StockfishException("Stockfish process terminated unexpectedly on init.")
 
110
  engine.set_fen_position(chess.STARTING_FEN)
111
+ engine.get_best_move()
112
+ engine.set_fen_position(chess.STARTING_FEN)
113
+ app.logger.info(f"Moteur Stockfish initialisé avec succès depuis {STOCKFISH_PATH_GLOBAL}.")
114
  return engine
115
  except Exception as e:
116
+ app.logger.error(f"Erreur lors de l'initialisation de Stockfish ({STOCKFISH_PATH_GLOBAL}): {e}", exc_info=True)
117
  return None
118
 
119
+ # --- Collez ici le reste de vos routes : index, get_outcome_message, make_move, reset_game, set_mode_route ---
120
+ # (Pour éviter de répéter 500 lignes, je ne les remets pas, mais elles restent inchangées par rapport à votre dernière version
121
+ # SAUF si le AttributeError persiste, auquel cas il faudra vérifier la version de la lib Stockfish)
122
+
123
+ # ... (Collez ici le reste des routes : index, get_outcome_message, make_move, reset_game, set_mode_route)
124
+ # ... (depuis la version précédente que je vous ai donnée)
125
+
126
+ # Par exemple, la route make_move commencerait comme ça:
127
+ @app.route('/make_move', methods=['POST'])
128
+ def make_move():
129
+ app.logger.debug(f"Session au début de POST /make_move: {dict(session.items())}")
130
+ if 'board_fen' not in session:
131
+ app.logger.error("ERREUR CRITIQUE: 'board_fen' non trouvé dans la session pour POST /make_move!")
132
+ return jsonify({'error': 'Erreur de session, "board_fen" manquant. Veuillez rafraîchir ou réinitialiser.',
133
+ 'fen': chess.Board().fen(), 'game_over': False, 'board_svg': chess.svg.board(board=chess.Board(), size=400)}), 500
134
+ # ... et ainsi de suite pour toute la fonction make_move et les autres routes.
135
+
136
+ # (Le code des routes est omis pour la brièveté, mais doit être inclus)
137
  @app.route('/')
138
  def index():
139
  app.logger.debug(f"Session au début de GET /: {dict(session.items())}")
 
159
  if board.is_checkmate():
160
  winner_color = "Blancs" if board.turn == chess.BLACK else "Noirs"
161
  return f"Échec et mat ! {winner_color} gagnent."
 
162
  if board.is_stalemate(): return "Pat ! Partie nulle."
163
  if board.is_insufficient_material(): return "Matériel insuffisant. Partie nulle."
164
  if board.is_seventyfive_moves(): return "Règle des 75 coups. Partie nulle."
165
  if board.is_fivefold_repetition(): return "Répétition (5 fois). Partie nulle."
166
+ if board.is_game_over(): return "Partie terminée."
 
167
  return ""
168
 
169
  @app.route('/make_move', methods=['POST'])
170
+ def make_move_impl(): # Renommé pour éviter conflit de nom si vous avez collé
171
  app.logger.debug(f"Session au début de POST /make_move: {dict(session.items())}")
172
  if 'board_fen' not in session:
173
  app.logger.error("ERREUR CRITIQUE: 'board_fen' non trouvé dans la session pour POST /make_move!")
 
188
  ai_move_uci = None
189
  last_player_move = None
190
  move_to_highlight = None
191
+ move = None # initialisation
192
 
193
  try:
 
 
194
  move = board.parse_uci(move_uci_san)
195
  app.logger.info(f"Mouvement joueur (tentative UCI): {move.uci()}")
196
  except ValueError:
 
201
  app.logger.warning(f"Mouvement invalide (ni UCI ni SAN): {move_uci_san} pour FEN {board.fen()}")
202
  return jsonify({'error': f'Mouvement invalide: {move_uci_san}', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
203
 
204
+ if move and move in board.legal_moves: # Vérifier que 'move' est non None
205
  board.push(move)
206
  last_player_move = move
207
+ move_to_highlight = move
208
  session['board_fen'] = board.fen()
209
  app.logger.info(f"Mouvement joueur {move.uci()} appliqué. Nouveau FEN: {board.fen()}")
210
 
 
214
  (session.get('player_color') == 'black' and board.turn == chess.WHITE)
215
 
216
  if not current_turn_is_ai_turn:
217
+ app.logger.error(f"Logique d'alternance erronée: C'est le tour de {'Blanc' if board.turn == chess.WHITE else 'Noir'}, mais l'IA ne devrait pas jouer.")
218
  else:
219
  stockfish_engine = get_stockfish_engine()
220
  if stockfish_engine:
221
  stockfish_engine.set_fen_position(board.fen())
222
+ best_move_ai = stockfish_engine.get_best_move()
223
  if best_move_ai:
224
  app.logger.info(f"Stockfish propose le coup: {best_move_ai}")
225
  try:
 
228
  session['board_fen'] = board.fen()
229
  ai_move_made = True
230
  ai_move_uci = best_move_ai
231
+ move_to_highlight = ai_move_obj
232
  app.logger.info(f"Mouvement IA {ai_move_uci} appliqué. Nouveau FEN: {board.fen()}")
233
  except Exception as e:
234
  app.logger.error(f"Erreur en appliquant le coup de l'IA {best_move_ai}: {e}", exc_info=True)
235
  else:
236
  app.logger.warning("L'IA (Stockfish) n'a pas retourné de coup.")
237
+
238
+ # Vérifier si la méthode existe avant de l'appeler
239
+ if hasattr(stockfish_engine, 'send_quit_command'):
240
+ stockfish_engine.send_quit_command()
241
+ app.logger.info("Moteur Stockfish arrêté après le coup de l'IA.")
242
+ elif hasattr(stockfish_engine, '__del__'):
243
+ # Si send_quit_command n'existe pas, on espère que __del__ fait le travail.
244
+ # Ou on pourrait juste faire `del stockfish_engine` pour déclencher __del__
245
+ app.logger.warning("send_quit_command() n'existe pas, __del__ sera utilisé implicitement.")
246
+ else:
247
+ app.logger.error("Ni send_quit_command ni __del__ ne semblent disponibles pour arrêter Stockfish proprement.")
248
+
249
  else:
250
  app.logger.error("Moteur Stockfish non disponible pour le coup de l'IA.")
251
+ elif move: # 'move' est défini mais pas légal
 
 
252
  app.logger.warning(f"Mouvement illégal tenté: {move.uci() if hasattr(move, 'uci') else move_uci_san} depuis FEN: {session['board_fen']}")
253
  legal_moves_str = ", ".join([board.san(m) for m in board.legal_moves])
254
+ app.logger.debug(f"Coups légaux: {legal_moves_str[:200]}")
255
  return jsonify({'error': f'Mouvement illégal: {move.uci() if hasattr(move, "uci") else move_uci_san}', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
256
+ else: # 'move' est None, n'a pas pu être parsé
257
+ # L'erreur a déjà été gérée par le bloc try/except du parsing
258
+ # Ce bloc else ne devrait pas être atteint si le parsing échoue et retourne.
259
+ app.logger.error("La variable 'move' est None après la tentative de parsing, ce qui ne devrait pas arriver ici.")
260
+ return jsonify({'error': 'Erreur interne de parsing de coup.', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)}), 500
261
+
262
 
263
  game_over = board.is_game_over()
264
  outcome = get_outcome_message(board) if game_over else ""
 
279
  def reset_game():
280
  app.logger.debug(f"Session au début de POST /reset_game: {dict(session.items())}")
281
  session['board_fen'] = chess.Board().fen()
 
 
282
  if 'game_mode' not in session: session['game_mode'] = 'pvp'
283
  if 'player_color' not in session: session['player_color'] = 'white'
284
 
 
300
  def set_mode_route():
301
  app.logger.debug(f"Session au début de POST /set_mode: {dict(session.items())}")
302
  mode = request.json.get('game_mode')
303
+ player_color = request.json.get('player_color', 'white')
304
 
305
  if mode in ['pvp', 'ai']:
306
  session['game_mode'] = mode
 
312
  initial_ai_move_uci = None
313
  move_to_highlight_init = None
314
 
 
315
  if mode == 'ai' and player_color == 'black':
316
  app.logger.info("L'IA (Blancs) commence la partie.")
317
  stockfish_engine = get_stockfish_engine()
 
322
  try:
323
  ai_move_obj = board.parse_uci(best_move_ai)
324
  board.push(ai_move_obj)
325
+ session['board_fen'] = board.fen()
326
  initial_ai_move_uci = best_move_ai
327
  move_to_highlight_init = ai_move_obj
328
  app.logger.info(f"Premier coup de l'IA (Blancs): {initial_ai_move_uci}. Nouveau FEN: {board.fen()}")
329
  except Exception as e:
330
  app.logger.error(f"Erreur en appliquant le premier coup de l'IA {best_move_ai}: {e}", exc_info=True)
331
+
332
+ if hasattr(stockfish_engine, 'send_quit_command'):
333
+ stockfish_engine.send_quit_command()
334
+ app.logger.info("Moteur Stockfish arrêté après le premier coup de l'IA.")
335
+ elif hasattr(stockfish_engine, '__del__'):
336
+ app.logger.warning("send_quit_command() n'existe pas, __del__ sera utilisé implicitement pour le premier coup IA.")
337
+ else:
338
+ app.logger.error("Ni send_quit_command ni __del__ pour arrêter Stockfish (premier coup IA).")
339
  else:
340
  app.logger.error("Moteur Stockfish non disponible pour le premier coup de l'IA.")
 
 
341
 
342
  app.logger.debug(f"Session à la fin de POST /set_mode: {dict(session.items())}")
343
  return jsonify({
 
348
  'player_color': player_color,
349
  'turn': 'white' if board.turn == chess.WHITE else 'black',
350
  'initial_ai_move_uci': initial_ai_move_uci,
351
+ 'game_over': board.is_game_over(),
352
  'outcome': get_outcome_message(board) if board.is_game_over() else ""
353
  })
354
  else:
355
  app.logger.warning(f"Tentative de définir un mode de jeu invalide: {mode}")
356
  return jsonify({'error': 'Mode invalide'}), 400
357
 
358
+
359
  if __name__ == '__main__':
360
+ app.run(host='0.0.0.0', port=5000) # IS_DEBUG_MODE et app.debug sont gérés plus haut