humanchess / app.py
Zual's picture
Update app.py
e34aa4b verified
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
@st.cache_resource
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 !")