File size: 14,834 Bytes
bf22ad3 79fe228 0356fb2 79fe228 7bc796e aa815df 0356fb2 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 1a3554c 7bc796e 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 0356fb2 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 1a3554c 79fe228 03755d8 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 0356fb2 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 56eb498 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 56eb498 bf22ad3 79fe228 56eb498 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 56eb498 79fe228 bf22ad3 79fe228 bf22ad3 79fe228 56eb498 79fe228 0efcaab 79fe228 |
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 |
from flask import Flask, render_template, request, jsonify, session
import chess
import chess.svg
from stockfish import Stockfish, StockfishException # Assurez-vous que ce module est accessible
import os
import logging
app = Flask(__name__)
# --- Configuration de la clé secrète ---
# IMPORTANT: Pour la production, utilisez une clé secrète forte et ne la codez pas en dur.
# Chargez-la depuis une variable d'environnement ou un fichier de configuration.
# Pour le DÉVELOPPEMENT UNIQUEMENT, une clé statique aide à éviter la perte de session due aux rechargements.
if app.debug:
app.secret_key = 'dev_secret_key_pour_eviter_perte_session_rechargement'
logging.warning("UTILISATION D'UNE CLÉ SECRÈTE STATIQUE POUR LE DÉVELOPPEMENT. NE PAS UTILISER EN PRODUCTION.")
else:
app.secret_key = os.environ.get("FLASK_SECRET_KEY", os.urandom(24))
if app.secret_key == os.urandom(24): # Check si on a dû fallback sur urandom
logging.warning("FLASK_SECRET_KEY non définie. Utilisation d'une clé générée aléatoirement. "
"Les sessions ne persisteront pas entre les redémarrages du serveur de production.")
# --- Configuration du logging ---
logging.basicConfig(level=logging.DEBUG)
# Vous pouvez ajuster le niveau de logging pour la production, ex: logging.INFO
# werkzeug_logger = logging.getLogger('werkzeug')
# werkzeug_logger.setLevel(logging.INFO) # Pour réduire le bruit des requêtes GET/POST en console
# --- Configuration de Stockfish ---
STOCKFISH_PATH = "stockfish" # Ou le chemin complet, ex: "/usr/games/stockfish"
if not os.path.exists(STOCKFISH_PATH) or not os.access(STOCKFISH_PATH, os.X_OK):
common_paths = ["/usr/games/stockfish", "/usr/local/bin/stockfish", "/opt/homebrew/bin/stockfish", "stockfish.exe"]
found_stockfish = False
for p in common_paths:
if os.path.exists(p) and os.access(p, os.X_OK):
STOCKFISH_PATH = p
found_stockfish = True
app.logger.info(f"Stockfish trouvé à: {STOCKFISH_PATH}")
break
if not found_stockfish:
app.logger.warning(f"AVERTISSEMENT: Stockfish non trouvé à '{STOCKFISH_PATH}' ou dans les chemins communs. "
"L'IA ne fonctionnera pas. Veuillez installer Stockfish et/ou configurer STOCKFISH_PATH.")
def get_stockfish_engine():
"""Initialise et retourne une instance de Stockfish."""
if not os.path.exists(STOCKFISH_PATH) or not os.access(STOCKFISH_PATH, os.X_OK):
app.logger.error(f"Stockfish non exécutable à {STOCKFISH_PATH}. L'IA ne peut pas démarrer.")
return None
try:
engine = Stockfish(path=STOCKFISH_PATH, depth=15, parameters={"Threads": 1, "Hash": 64}) # Ajustez si besoin
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.get_best_move() # Test rapide
engine.set_fen_position(chess.STARTING_FEN)
app.logger.info("Moteur Stockfish initialisé avec succès.")
return engine
except Exception as e:
app.logger.error(f"Erreur lors de l'initialisation de Stockfish: {e}")
app.logger.error(f"Vérifiez que Stockfish est installé et que STOCKFISH_PATH ('{STOCKFISH_PATH}') est correct.")
return None
@app.route('/')
def index():
app.logger.debug(f"Session au début de GET /: {dict(session.items())}")
if 'board_fen' 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), # Renommé pour clarifier
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 = "Blancs" if board.turn == chess.BLACK else "Noirs" # Celui dont ce n'est PAS le tour gagne
return f"Échec et mat ! {winner} 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_variant_draw() or board.is_variant_loss() or board.is_variant_win(): # Autres types de fins
return "Partie terminée (variante)."
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!")
# Optionnel: essayer de réinitialiser ou informer le client
# session['board_fen'] = chess.Board().fen() # Réinitialisation d'urgence
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 = request.json.get('move')
if not move_uci:
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
last_player_move = None
try:
move = board.parse_uci(move_uci)
app.logger.info(f"Mouvement joueur (UCI parsed): {move.uci()}")
except ValueError:
try:
move = board.parse_san(move_uci) # Essayer SAN
app.logger.info(f"Mouvement joueur (SAN parsed): {board.san(move)}")
except ValueError:
app.logger.warning(f"Mouvement invalide (ni UCI ni SAN): {move_uci}")
return jsonify({'error': f'Mouvement invalide: {move_uci}', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
if move in board.legal_moves:
board.push(move)
last_player_move = move # Sauvegarder le dernier coup du joueur pour le SVG
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("Mode IA, tour de l'IA.")
stockfish_engine = get_stockfish_engine()
if stockfish_engine:
stockfish_engine.set_fen_position(board.fen())
# best_move_ai = stockfish_engine.get_best_move_time(500) # 0.5 seconde pour plus de réactivité
best_move_ai = stockfish_engine.get_best_move()
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
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}")
else:
app.logger.warning("L'IA (Stockfish) n'a pas retourné de coup (peut-être mat/pat pour l'IA).")
stockfish_engine.send_quit_command()
app.logger.info("Moteur Stockfish arrêté après le coup de l'IA.")
else:
app.logger.error("Moteur Stockfish non disponible pour le coup de l'IA.")
return jsonify({'error': 'Moteur Stockfish non disponible.', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
else:
app.logger.warning(f"Mouvement illégal tenté: {move.uci()} depuis FEN: {session['board_fen']}")
return jsonify({'error': f'Mouvement illégal: {move.uci()}', 'fen': board.fen(), 'game_over': board.is_game_over(), 'board_svg': chess.svg.board(board=board, size=400)})
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}")
# Déterminer le dernier coup à surligner
move_to_highlight = None
if ai_move_made:
move_to_highlight = ai_move_obj
elif last_player_move:
move_to_highlight = last_player_move
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()
# Conserver le mode de jeu et la couleur du joueur actuels, ou les réinitialiser comme souhaité
# session['game_mode'] = 'pvp' # Optionnel: forcer un mode par défaut
# session['player_color'] = 'white' # Optionnel
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', 'pvp'), # Fournir une valeur par défaut si non défini
'player_color': session.get('player_color', 'white'),
'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}. Couleur joueur (si IA): {player_color}. FEN réinitialisé: {session['board_fen']}")
board = chess.Board() # Nouvelle instance pour la réponse initiale
initial_ai_move_uci = None
move_to_highlight_init = None
if mode == 'ai' and player_color == 'black': # L'IA (Blanc) joue en premier
app.logger.info("L'IA (Blanc) 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() # Mettre à jour le FEN de la session
initial_ai_move_uci = best_move_ai
move_to_highlight_init = ai_move_obj
app.logger.info(f"Premier coup de l'IA (Blanc): {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}")
stockfish_engine.send_quit_command()
app.logger.info("Moteur Stockfish arrêté après le premier coup de l'IA.")
else:
app.logger.error("Moteur Stockfish non disponible pour le premier coup de l'IA.")
return jsonify({'error': 'Moteur Stockfish non disponible.'}), 500
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()}. {"Joueur: " + 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__':
# Pour la production, utilisez un serveur WSGI comme Gunicorn ou uWSGI.
# Par exemple: gunicorn -w 4 -b 0.0.0.0:5000 app:app
# Le port 7860 est souvent utilisé par Gradio, assurez-vous qu'il n'y a pas de conflit.
app.run(debug=True, host='0.0.0.0', port=5000) # Utilisez un port standard comme 5000 |