File size: 17,802 Bytes
bf22ad3 166ccea b293718 166ccea bf22ad3 79fe228 7bc796e aa815df 0356fb2 79fe228 b293718 e17de57 a8e8b41 e17de57 ded7221 b293718 166ccea b293718 79fe228 ded7221 166ccea ded7221 166ccea ded7221 79fe228 166ccea b293718 166ccea b293718 ded7221 166ccea b293718 166ccea b293718 ded7221 b293718 bf22ad3 b293718 79fe228 bf22ad3 e17de57 79fe228 bf22ad3 b293718 166ccea b293718 bf22ad3 b293718 bf22ad3 1a3554c b293718 166ccea 7bc796e 79fe228 ded7221 79fe228 bf22ad3 79fe228 b293718 bf22ad3 79fe228 bf22ad3 ded7221 bf22ad3 79fe228 bf22ad3 ded7221 166ccea bf22ad3 b293718 79fe228 ded7221 79fe228 bf22ad3 79fe228 bf22ad3 ded7221 79fe228 bf22ad3 79fe228 bf22ad3 ded7221 b293718 bf22ad3 0356fb2 ded7221 bf22ad3 ded7221 bf22ad3 ded7221 bf22ad3 b293718 bf22ad3 166ccea bf22ad3 79fe228 bf22ad3 ded7221 166ccea 56eb498 ded7221 e17de57 ded7221 166ccea ded7221 166ccea b293718 166ccea ded7221 b293718 ded7221 b293718 166ccea bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 ded7221 79fe228 ded7221 79fe228 bf22ad3 79fe228 bf22ad3 166ccea 56eb498 bf22ad3 79fe228 ded7221 56eb498 ded7221 bf22ad3 79fe228 bf22ad3 ded7221 bf22ad3 79fe228 166ccea 79fe228 ded7221 79fe228 ded7221 166ccea b293718 bf22ad3 79fe228 bf22ad3 79fe228 56eb498 ded7221 bf22ad3 79fe228 bf22ad3 79fe228 166ccea 79fe228 56eb498 79fe228 0efcaab 166ccea |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
from flask import Flask, render_template, request, jsonify, session
import sys
import os
import inspect # Ajout pour inspecter
# --- VÉRIFICATION DE L'IMPORT DE STOCKFISH (Doit être au tout début) ---
# Cet import doit être celui de la bibliothèque zhelyabuzhsky-stockfish
try:
from stockfish import Stockfish, StockfishException
STOCKFISH_LIB_PATH = inspect.getfile(Stockfish)
print(f"--- Stockfish class imported from: {STOCKFISH_LIB_PATH} ---")
except ImportError as e:
print(f"ERREUR CRITIQUE: Impossible d'importer la bibliothèque Stockfish: {e}")
print("Assurez-vous que la bibliothèque zhelyabuzhsky-stockfish est dans votre PYTHONPATH ou installée.")
sys.exit(1) # Arrêter si la lib principale manque
# --- Fin de la vérification ---
import chess
import chess.svg
import logging
app = Flask(__name__)
# --- Configuration de la clé secrète ---
IS_DEBUG_MODE = True # Forcer le mode debug pour le développement local
# Pour la production, gérez FLASK_DEBUG et FLASK_SECRET_KEY via les variables d'environnement
default_depth_for_max_strength = 22
max_strength_params = {
"Debug Log File": "",
"Contempt": 0,
"Min Split Depth": 0,
"Threads": 4,
"Ponder": "false",
"Hash": 8192,
"MultiPV": 1,
"Skill Level": 20,
"Move Overhead": 30,
"Minimum Thinking Time": 20,
"Slow Mover": 100,
"UCI_Chess960": "false",
"UCI_LimitStrength": "false",
"UCI_Elo": 3500
}
if IS_DEBUG_MODE:
app.secret_key = 'dev_secret_key_pour_eviter_perte_session_rechargement_et_tests_v3' # Changez si besoin
logging.warning("MODE DEBUG ACTIVÉ. Utilisation d'une clé secrète statique.")
app.debug = True
else:
app.secret_key = os.environ.get("FLASK_SECRET_KEY")
if not app.secret_key:
app.secret_key = os.urandom(32)
logging.warning("MODE PRODUCTION. FLASK_SECRET_KEY non définie. Clé aléatoire générée.")
app.debug = False
# --- Configuration des Cookies de Session ---
app.config.update(
SESSION_COOKIE_SECURE=False,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)
logging.basicConfig(level=logging.DEBUG)
# --- Configuration Globale de Stockfish (Exécutable) ---
STOCKFISH_EXECUTABLE_PATH_GLOBAL = "stockfish" # Valeur par défaut
HAS_STOCKFISH_EXECUTABLE_GLOBAL = False
def find_stockfish_on_startup():
global STOCKFISH_EXECUTABLE_PATH_GLOBAL, HAS_STOCKFISH_EXECUTABLE_GLOBAL
# Priorité à une variable d'environnement si définie
env_path = os.environ.get("STOCKFISH_EXECUTABLE_PATH")
paths_to_check = [env_path] if env_path else []
paths_to_check.extend([
"stockfish", "./stockfish", # Relatif au répertoire de travail
"/usr/games/stockfish", "/usr/local/bin/stockfish",
"/opt/homebrew/bin/stockfish", "stockfish.exe" # Chemins communs
])
for p in paths_to_check:
if p and os.path.exists(p) and os.access(p, os.X_OK):
STOCKFISH_EXECUTABLE_PATH_GLOBAL = p
HAS_STOCKFISH_EXECUTABLE_GLOBAL = True
app.logger.info(f"Stockfish exécutable trouvé à: {STOCKFISH_EXECUTABLE_PATH_GLOBAL}")
return
app.logger.warning(
"AVERTISSEMENT: Exécutable Stockfish non trouvé dans les chemins courants ou via STOCKFISH_EXECUTABLE_PATH. "
"L'IA ne fonctionnera pas. Veuillez installer Stockfish ou définir la variable d'environnement."
)
HAS_STOCKFISH_EXECUTABLE_GLOBAL = False
find_stockfish_on_startup() # Exécuter au démarrage de l'application
def get_stockfish_engine():
"""Initialise et retourne une instance de la classe Stockfish."""
if not HAS_STOCKFISH_EXECUTABLE_GLOBAL:
app.logger.error("Exécutable Stockfish non disponible. L'IA ne peut pas démarrer.")
return None
try:
#engine = Stockfish(path=STOCKFISH_EXECUTABLE_PATH_GLOBAL, depth=15, parameters={"Threads": 1, "Hash": 64})
engine = Stockfish(path=STOCKFISH_EXECUTABLE_PATH_GLOBAL,
depth=default_depth_for_max_strength,
parameters=max_strength_params)
if engine._stockfish.poll() is not None:
app.logger.error("Le processus Stockfish s'est terminé de manière inattendue lors de l'initialisation.")
raise StockfishException("Stockfish process terminated unexpectedly on init.")
engine.set_fen_position(chess.STARTING_FEN) # Test léger
engine.get_best_move()
engine.set_fen_position(chess.STARTING_FEN) # Reset après test
app.logger.info(f"Moteur Stockfish (classe) initialisé avec succès utilisant l'exécutable: {STOCKFISH_EXECUTABLE_PATH_GLOBAL}.")
return engine
except Exception as e:
app.logger.error(f"Erreur lors de l'initialisation de la classe Stockfish (exécutable: {STOCKFISH_EXECUTABLE_PATH_GLOBAL}): {e}", exc_info=True)
return None
# --- Définition des Routes ---
@app.route('/')
def index():
app.logger.debug(f"Session au début de GET /: {dict(session.items())}")
if 'board_fen' not in session or 'game_mode' not in session:
app.logger.info("Initialisation de la session (board_fen, game_mode, player_color) dans GET /")
session['board_fen'] = chess.Board().fen()
session['game_mode'] = 'pvp'
session['player_color'] = 'white'
board = chess.Board(session['board_fen'])
app.logger.debug(f"Session à la fin de GET /: {dict(session.items())}")
return render_template('index.html',
initial_board_svg=chess.svg.board(board=board, size=400),
initial_fen=session['board_fen'],
game_mode=session['game_mode'],
player_color=session.get('player_color', 'white'),
is_game_over=board.is_game_over(),
outcome=get_outcome_message(board) if board.is_game_over() else "",
current_turn = 'white' if board.turn == chess.WHITE else 'black')
def get_outcome_message(board):
if board.is_checkmate():
winner_color = "Blancs" if board.turn == chess.BLACK else "Noirs"
return f"Échec et mat ! {winner_color} gagnent."
if board.is_stalemate(): return "Pat ! Partie nulle."
if board.is_insufficient_material(): return "Matériel insuffisant. Partie nulle."
if board.is_seventyfive_moves(): return "Règle des 75 coups. Partie nulle."
if board.is_fivefold_repetition(): return "Répétition (5 fois). Partie nulle."
if board.is_game_over(): return "Partie terminée."
return ""
@app.route('/make_move', methods=['POST'])
def make_move():
app.logger.debug(f"Session au début de POST /make_move: {dict(session.items())}")
if 'board_fen' not in session:
app.logger.error("ERREUR CRITIQUE: 'board_fen' non trouvé dans la session pour POST /make_move!")
return jsonify({'error': 'Erreur de session, "board_fen" manquant. Veuillez rafraîchir ou réinitialiser.',
'fen': chess.Board().fen(), 'game_over': False, 'board_svg': chess.svg.board(board=chess.Board(), size=400)}), 500
board = chess.Board(session['board_fen'])
if board.is_game_over():
app.logger.info("Tentative de jouer alors que la partie est terminée.")
return jsonify({'error': 'La partie est terminée.', 'fen': board.fen(), 'game_over': True, 'outcome': get_outcome_message(board), 'board_svg': chess.svg.board(board=board, size=400)})
move_uci_san = request.json.get('move')
if not move_uci_san:
app.logger.warning("Aucun mouvement fourni dans la requête POST /make_move.")
return jsonify({'error': 'Mouvement non fourni.', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
ai_move_made = False
ai_move_uci = None
move_to_highlight = None
move = None
try:
move = board.parse_uci(move_uci_san)
app.logger.info(f"Mouvement joueur (tentative UCI): {move.uci()}")
except ValueError:
try:
move = board.parse_san(move_uci_san)
app.logger.info(f"Mouvement joueur (tentative SAN): {board.san(move)}")
except ValueError:
app.logger.warning(f"Mouvement invalide (ni UCI ni SAN): {move_uci_san} pour FEN {board.fen()}")
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)})
if move and move in board.legal_moves:
board.push(move)
move_to_highlight = move
session['board_fen'] = board.fen()
app.logger.info(f"Mouvement joueur {move.uci()} appliqué. Nouveau FEN: {board.fen()}")
if session.get('game_mode') == 'ai' and not board.is_game_over():
app.logger.info(f"Mode IA, tour de l'IA. Joueur humain est {session.get('player_color')}.")
current_turn_is_ai_turn = (session.get('player_color') == 'white' and board.turn == chess.BLACK) or \
(session.get('player_color') == 'black' and board.turn == chess.WHITE)
if not current_turn_is_ai_turn:
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.")
else:
stockfish_engine = get_stockfish_engine()
if stockfish_engine:
stockfish_engine.set_fen_position(board.fen())
thinking_time_ms = 20000
best_move_ai = stockfish_engine.get_best_move(thinking_time_ms)
if best_move_ai:
app.logger.info(f"Stockfish propose le coup: {best_move_ai}")
try:
ai_move_obj = board.parse_uci(best_move_ai)
board.push(ai_move_obj)
session['board_fen'] = board.fen()
ai_move_made = True
ai_move_uci = best_move_ai
move_to_highlight = ai_move_obj
app.logger.info(f"Mouvement IA {ai_move_uci} appliqué. Nouveau FEN: {board.fen()}")
except Exception as e:
app.logger.error(f"Erreur en appliquant le coup de l'IA {best_move_ai}: {e}", exc_info=True)
else:
app.logger.warning("L'IA (Stockfish) n'a pas retourné de coup.")
# APPEL À send_quit_command() COMMENTÉ COMME DEMANDÉ
# if hasattr(stockfish_engine, 'send_quit_command'):
# stockfish_engine.send_quit_command()
# app.logger.info("Moteur Stockfish arrêté après le coup de l'IA.")
# elif hasattr(stockfish_engine, '__del__') and callable(getattr(stockfish_engine, '__del__')):
# app.logger.warning("send_quit_command() n'existe pas, __del__ sera potentiellement utilisé.")
# else:
# app.logger.error("Ni send_quit_command ni __del__ ne semblent disponibles pour arrêter Stockfish proprement.")
app.logger.info("Appel explicite à send_quit_command retiré (après coup IA). Le processus Stockfish peut rester actif.")
# del stockfish_engine # Optionnel: pour essayer de forcer __del__ si la lib le gère bien
else:
app.logger.error("Moteur Stockfish non disponible pour le coup de l'IA.")
elif move:
app.logger.warning(f"Mouvement illégal tenté: {move.uci() if hasattr(move, 'uci') else move_uci_san} depuis FEN: {session['board_fen']}")
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)})
else:
app.logger.error("La variable 'move' est None après la tentative de parsing, ce qui ne devrait pas arriver ici.")
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
game_over = board.is_game_over()
outcome = get_outcome_message(board) if game_over else ""
if game_over:
app.logger.info(f"Partie terminée. Résultat: {outcome}")
app.logger.debug(f"Session à la fin de POST /make_move: {dict(session.items())}")
return jsonify({
'fen': board.fen(),
'board_svg': chess.svg.board(board=board, size=400, lastmove=move_to_highlight),
'game_over': game_over,
'outcome': outcome,
'ai_move_uci': ai_move_uci,
'turn': 'white' if board.turn == chess.WHITE else 'black'
})
@app.route('/reset_game', methods=['POST'])
def reset_game():
app.logger.debug(f"Session au début de POST /reset_game: {dict(session.items())}")
session['board_fen'] = chess.Board().fen()
if 'game_mode' not in session: session['game_mode'] = 'pvp'
if 'player_color' not in session: session['player_color'] = 'white'
app.logger.info(f"Jeu réinitialisé. Nouveau FEN: {session['board_fen']}. Mode: {session.get('game_mode')}, Couleur joueur: {session.get('player_color')}")
board = chess.Board(session['board_fen'])
app.logger.debug(f"Session à la fin de POST /reset_game: {dict(session.items())}")
return jsonify({
'fen': session['board_fen'],
'board_svg': chess.svg.board(board=board, size=400),
'game_mode': session.get('game_mode'),
'player_color': session.get('player_color'),
'turn': 'white' if board.turn == chess.WHITE else 'black',
'game_over': False,
'outcome': ""
})
@app.route('/set_mode', methods=['POST'])
def set_mode_route():
app.logger.debug(f"Session au début de POST /set_mode: {dict(session.items())}")
mode = request.json.get('game_mode')
player_color = request.json.get('player_color', 'white')
if mode in ['pvp', 'ai']:
session['game_mode'] = mode
session['player_color'] = player_color
session['board_fen'] = chess.Board().fen()
app.logger.info(f"Mode de jeu réglé sur {mode}. Joueur humain: {player_color if mode == 'ai' else 'N/A'}. FEN réinitialisé.")
board = chess.Board()
initial_ai_move_uci = None
move_to_highlight_init = None
if mode == 'ai' and player_color == 'black':
app.logger.info("L'IA (Blancs) commence la partie.")
stockfish_engine = get_stockfish_engine()
if stockfish_engine:
stockfish_engine.set_fen_position(board.fen())
best_move_ai = stockfish_engine.get_best_move()
if best_move_ai:
try:
ai_move_obj = board.parse_uci(best_move_ai)
board.push(ai_move_obj)
session['board_fen'] = board.fen()
initial_ai_move_uci = best_move_ai
move_to_highlight_init = ai_move_obj
app.logger.info(f"Premier coup de l'IA (Blancs): {initial_ai_move_uci}. Nouveau FEN: {board.fen()}")
except Exception as e:
app.logger.error(f"Erreur en appliquant le premier coup de l'IA {best_move_ai}: {e}", exc_info=True)
# APPEL À send_quit_command() COMMENTÉ COMME DEMANDÉ
# if hasattr(stockfish_engine, 'send_quit_command'):
# stockfish_engine.send_quit_command()
# app.logger.info("Moteur Stockfish arrêté après le premier coup de l'IA.")
# elif hasattr(stockfish_engine, '__del__') and callable(getattr(stockfish_engine, '__del__')):
# app.logger.warning("send_quit_command() n'existe pas, __del__ sera potentiellement utilisé.")
# else:
# app.logger.error("Ni send_quit_command ni __del__ pour arrêter Stockfish (premier coup IA).")
app.logger.info("Appel explicite à send_quit_command retiré (après premier coup IA). Le processus Stockfish peut rester actif.")
# del stockfish_engine # Optionnel
else:
app.logger.error("Moteur Stockfish non disponible pour le premier coup de l'IA.")
app.logger.debug(f"Session à la fin de POST /set_mode: {dict(session.items())}")
return jsonify({
'message': f'Mode de jeu réglé sur {mode.upper()}. {"Vous jouez " + player_color.capitalize() if mode == "ai" else ""}',
'fen': session['board_fen'],
'board_svg': chess.svg.board(board=board, size=400, lastmove=move_to_highlight_init),
'game_mode': mode,
'player_color': player_color,
'turn': 'white' if board.turn == chess.WHITE else 'black',
'initial_ai_move_uci': initial_ai_move_uci,
'game_over': board.is_game_over(),
'outcome': get_outcome_message(board) if board.is_game_over() else ""
})
else:
app.logger.warning(f"Tentative de définir un mode de jeu invalide: {mode}")
return jsonify({'error': 'Mode invalide'}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) # IS_DEBUG_MODE et app.debug sont gérés plus haut |