Spaces:
Build error
Build error
| import streamlit as st | |
| import chess | |
| import chess.svg | |
| import os | |
| import git | |
| import sys | |
| import random | |
| # Configuration de la page | |
| st.set_page_config(page_title="Échecs contre IA", layout="wide") | |
| def render_svg(svg_string): | |
| """Render un SVG dans Streamlit.""" | |
| from streamlit.components.v1 import html | |
| html(f""" | |
| <div style="display: flex; justify-content: center;"> | |
| {svg_string} | |
| </div> | |
| """, height=400) | |
| def calculate_max_length(move_count): | |
| """ | |
| Calcule le max_length optimal basé sur le nombre de coups joués | |
| - Début de partie: besoin minimum (15 tokens) | |
| - Milieu de partie: augmentation progressive | |
| - Fin de partie: plafonnement à 50 tokens | |
| """ | |
| base_length = 15 # Longueur minimum pour un coup simple | |
| increment = 1 # Augmentation par coup | |
| max_length = 100 # Plafond maximum | |
| dynamic_length = base_length + (move_count * increment) | |
| return min(dynamic_length, max_length) | |
| # Setup du modèle | |
| def setup_inference(): | |
| if not os.path.exists('chess_char'): | |
| git.Repo.clone_from('https://github.com/l-pommeret/chess_char.git', 'chess_char') | |
| if 'chess_char' not in sys.path: | |
| sys.path.append('chess_char') | |
| from inference import InferenceConfig, ChessGenerator | |
| # Configuration initiale avec max_length minimal | |
| config = InferenceConfig( | |
| model_name="Zual/chess_char", | |
| temperature=0.1, | |
| max_length=6 # Sera ajusté dynamiquement pendant la partie | |
| ) | |
| return ChessGenerator(config) | |
| # Initialisation | |
| try: | |
| generator = setup_inference() | |
| except Exception as e: | |
| st.error("Erreur lors de l'initialisation.") | |
| st.stop() | |
| # Initialisation de l'état | |
| if 'board' not in st.session_state: | |
| st.session_state.board = chess.Board() | |
| st.session_state.moves = [] | |
| st.session_state.last_move = None | |
| def get_ai_move(prompt): | |
| """Obtient le coup de l'IA avec max_length dynamique""" | |
| print(f"\n=== Tour de l'IA ===") | |
| moves_count = len(st.session_state.moves) | |
| # Mise à jour du max_length en fonction du nombre de coups | |
| dynamic_max_length = calculate_max_length(moves_count) | |
| generator.config.max_length = dynamic_max_length | |
| print(f"max_length actuel: {dynamic_max_length} pour {moves_count} coups joués") | |
| print(f"État actuel de la partie: {prompt}") | |
| print(f"FEN actuel: {st.session_state.board.fen()}") | |
| print(f"Coups légaux: {[st.session_state.board.san(move) for move in st.session_state.board.legal_moves]}") | |
| try: | |
| # On ne génère que jusqu'au prochain coup | |
| if not prompt: # Premier coup | |
| response = generator.generate("1.") | |
| else: | |
| # On prend les derniers coups pour ne pas surcharger le contexte | |
| moves = prompt.split() | |
| last_moves = " ".join(moves[-4:]) # Garder seulement les 2 derniers coups complets | |
| if len(moves) % 2 == 0: # Si on vient de finir un coup noir | |
| next_move_num = f"{(len(moves)//2 + 1)}." | |
| response = generator.generate(f"{last_moves} {next_move_num}") | |
| else: # Si on vient de finir un coup blanc | |
| response = generator.generate(f"{last_moves}") | |
| print(f"Réponse brute de l'IA: {response}") | |
| # Gestion de la réponse quelle que soit sa forme | |
| moves = response[0].split() if isinstance(response, list) else response.split() | |
| print(f"Coups extraits: {moves}") | |
| # On prend toujours le dernier coup généré | |
| next_move = moves[-1] | |
| if '.' in next_move and len(moves) > 1: | |
| next_move = moves[-2] | |
| print(f"Coup candidat de l'IA: {next_move}") | |
| # Vérification de la validité | |
| try: | |
| move = st.session_state.board.parse_san(next_move) | |
| print(f"Coup parsé en UCI: {move}") | |
| if move in st.session_state.board.legal_moves: | |
| print(f"Coup valide trouvé: {next_move}") | |
| return next_move | |
| else: | |
| print(f"Coup non légal: {next_move}") | |
| except ValueError as e: | |
| print(f"Erreur de parsing: {e}") | |
| # En cas d'échec, jouer un coup légal aléatoire | |
| legal_moves = list(st.session_state.board.legal_moves) | |
| if legal_moves: | |
| random_move = random.choice(legal_moves) | |
| random_san = st.session_state.board.san(random_move) | |
| print(f"Utilisation d'un coup aléatoire: {random_san}") | |
| return random_san | |
| except Exception as e: | |
| print(f"Erreur dans get_ai_move: {e}") | |
| return None | |
| def try_move(move_str): | |
| """Applique un coup au plateau""" | |
| print(f"\n=== Tentative de coup: {move_str} ===") | |
| print(f"État avant le coup: {get_game_string()}") | |
| print(f"FEN avant: {st.session_state.board.fen()}") | |
| try: | |
| # Nettoyer le coup (enlever le numéro si présent) | |
| clean_move = move_str.split('.')[-1].strip() | |
| print(f"Coup nettoyé: {clean_move}") | |
| move = st.session_state.board.parse_san(clean_move) | |
| print(f"Coup parsé en UCI: {move}") | |
| if move in st.session_state.board.legal_moves: | |
| st.session_state.board.push(move) | |
| st.session_state.moves.append(clean_move) | |
| st.session_state.last_move = clean_move | |
| print(f"Coup appliqué avec succès") | |
| print(f"Nouvel état: {get_game_string()}") | |
| print(f"Nouveau FEN: {st.session_state.board.fen()}") | |
| return True | |
| print(f"Coup illégal") | |
| return False | |
| except ValueError as e: | |
| print(f"Erreur de parsing: {e}") | |
| return False | |
| def get_game_string(): | |
| """Renvoie la notation de la partie""" | |
| result = [] | |
| for i in range(0, len(st.session_state.moves), 2): | |
| move_num = i//2 + 1 | |
| result.append(f"{move_num}.{st.session_state.moves[i]}") | |
| if i+1 < len(st.session_state.moves): | |
| result.append(st.session_state.moves[i+1]) | |
| return " ".join(result) | |
| # Interface utilisateur | |
| st.title("♟️ Échecs contre IA") | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| # Plateau avec la nouvelle méthode d'affichage | |
| board_svg = chess.svg.board( | |
| board=st.session_state.board, | |
| lastmove=st.session_state.board.peek() if st.session_state.board.move_stack else None, | |
| size=400 # Taille fixe pour l'échiquier | |
| ) | |
| render_svg(board_svg) | |
| # Input du joueur avec key dynamique | |
| move = st.text_input( | |
| "Votre coup", | |
| key=f"move_input_{len(st.session_state.moves)}", | |
| placeholder="ex: e4, Nf3, O-O" | |
| ) | |
| with col2: | |
| # État actuel | |
| print(f"\n=== État actuel ===") | |
| print(f"Partie en cours: {get_game_string()}") | |
| print(f"FEN: {st.session_state.board.fen()}") | |
| # Affichage du max_length actuel | |
| current_max_length = calculate_max_length(len(st.session_state.moves)) | |
| st.info(f"Longueur de génération actuelle: {current_max_length} tokens") | |
| # Historique | |
| st.subheader("Partie en cours") | |
| game_str = get_game_string() | |
| if game_str: | |
| st.text_area("Historique", value=game_str, height=100, label_visibility="collapsed") | |
| # Instructions | |
| st.markdown(""" | |
| **Comment jouer:** | |
| - Pion: e4, d5 | |
| - Cavalier: Nf3, Nc6 | |
| - Fou: Bc4, Be7 | |
| - Tour: Ra1, Rd8 | |
| - Dame: Qd1, Qh4 | |
| - Roi: Ke2, Kg8 | |
| - Roque: O-O ou O-O-O | |
| """) | |
| # Nouvelle partie | |
| if st.button("Nouvelle partie"): | |
| st.session_state.board = chess.Board() | |
| st.session_state.moves = [] | |
| st.session_state.last_move = None | |
| st.rerun() | |
| # Logique du jeu | |
| if move: | |
| if try_move(move): | |
| game_str = get_game_string() | |
| # Tour de l'IA | |
| with st.spinner("L'IA réfléchit..."): | |
| ai_move = get_ai_move(game_str) | |
| if ai_move and try_move(ai_move): | |
| if st.session_state.board.is_checkmate(): | |
| st.success("Échec et mat!") | |
| elif st.session_state.board.is_game_over(): | |
| st.info("Partie terminée!") | |
| else: | |
| st.error("Problème avec le coup de l'IA") | |
| st.rerun() | |
| else: | |
| st.error("Coup invalide") | |
| # État du jeu | |
| if st.session_state.board.is_check(): | |
| st.warning("⚠️ Échec et mat !") |