import gradio as gr import chess import chess.pgn import matplotlib.pyplot as plt import numpy as np import io import re from io import BytesIO plt.switch_backend('Agg') class ReparadorPGN: @staticmethod def reparar_pgn(pgn_text): """Repara PGNs extremadamente corruptos""" lineas = pgn_text.split('\n') lineas_reparadas = [] for linea in lineas: linea = linea.strip() # Reparar headers corruptos if linea.startswith('['): # Corregir comillas y paréntesis linea = re.sub(r"\[(.*?)[')](.*?)[')]", r"[\1 '\2']", linea) # Corregir nombres de headers linea = re.sub(r'\[Ulnite', '[White', linea) linea = re.sub(r'\[Result "I-0"\]', '[Result "1-0"]', linea) linea = re.sub(r'\[Result "O-I"\]', '[Result "0-1"]', linea) linea = re.sub(r'\[Result "I/2-I/2"\]', '[Result "1/2-1/2"]', linea) # Reparar notación de movimientos else: # Corregir piezas mal escritas correcciones = { r'\bnf3\b': 'Nf3', r'\bnc3\b': 'Nc3', r'\bng5\b': 'Ng5', r'\bhc6\b': 'Nc6', r'\bhf6\b': 'Nf6', r'\bhba\b': 'Nb8', r'\bhbe7\b': 'Nbd7', r'\bnn1\b': 'Nf1', r'\bhe2\b': 'Ne2', r'\bnh7\b': 'Nh7', r'\bhc5\b': 'Nc5', r'\bqu2\b': 'Qd2', r'\bre1\b': 'Re1', r'\brn\b': 'Rf1', r'\bbe4:\b': 'Be4', r'\bo-o-o\b': 'O-O-O', r'\bo-o\b': 'O-O' } for error, correccion in correcciones.items(): linea = re.sub(error, correccion, linea, flags=re.IGNORECASE) lineas_reparadas.append(linea) return '\n'.join(lineas_reparadas) class AnalizadorAjedrez: def __init__(self): self.datos_jugadas = [] def analizar_partida(self, pgn_string): """Analiza partida con múltiples intentos y reparación""" intentos = [ pgn_string, # Intento 1: Original ReparadorPGN.reparar_pgn(pgn_string), # Intento 2: Reparado self._crear_pgn_minimo(pgn_string) # Intento 3: PGN mínimo ] for i, pgn_intento in enumerate(intentos): try: pgn = io.StringIO(pgn_intento) game = chess.pgn.read_game(pgn) if game and len(list(game.mainline_moves())) > 0: blancas = game.headers.get("White", "Anónimo") negras = game.headers.get("Black", "Anónimo") resultado = game.headers.get("Result", "*") # Analizar jugadas tablero = game.board() self.datos_jugadas = [] for jugada_num, move in enumerate(game.mainline_moves(), 1): tablero.push(move) analisis = self._evaluar_posicion(tablero, jugada_num) self.datos_jugadas.append(analisis) print(f"✅ Análisis exitoso (Intento {i+1})") return blancas, negras, resultado, "" except Exception as e: continue return "Anónimo", "Anónimo", "*", "No se pudo analizar el PGN (formato muy corrupto)" def _crear_pgn_minimo(self, pgn_text): """Crea un PGN mínimo desde los movimientos""" # Extraer solo movimientos numéricos movimientos = re.findall(r'\d+\.\s*(\S+)\s+(\S+)', pgn_text) if not movimientos: movimientos = re.findall(r'\b([NBRQK]?[a-h]?[1-8]?x?[a-h][1-8]\+?\+?#?)\b', pgn_text, re.IGNORECASE) pgn_minimo = """[Event "Partida Reparada"] [White "Blancas"] [Black "Negras"] [Result "*"] """ for i, (blanca, negra) in enumerate(movimientos[:50], 1): # Máximo 50 jugadas pgn_minimo += f"{i}. {blanca} {negra} " return pgn_minimo.strip() def _evaluar_posicion(self, tablero, jugada_num): """Evaluación simple pero robusta""" valores = {'p': 1, 'n': 3, 'b': 3, 'r': 5, 'q': 9, 'k': 0} material_blancas = sum(valores.get(p.symbol().lower(), 0) for p in tablero.piece_map().values() if p.color == chess.WHITE) material_negras = sum(valores.get(p.symbol().lower(), 0) for p in tablero.piece_map().values() if p.color == chess.BLACK) ventaja_material = material_blancas - material_negras movilidad = len(list(tablero.legal_moves)) return { 'jugada': jugada_num, 'evaluacion': ventaja_material + (movilidad * 0.1), 'material_blancas': material_blancas, 'material_negras': material_negras, 'ventaja_material': ventaja_material, 'movilidad': movilidad } def generar_grafico(self, blancas, negras): """Genera gráfico incluso con datos mínimos""" if not self.datos_jugadas: # Gráfico de error informativo fig, ax = plt.subplots(figsize=(10, 6)) ax.text(0.5, 0.5, '⚠️ PGN no analizable\n\nEl archivo PGN está corrupto\no tiene formato inválido', ha='center', va='center', fontsize=14, transform=ax.transAxes, bbox=dict(boxstyle="round,pad=0.3", facecolor="lightcoral", alpha=0.7)) ax.set_xlim(0, 1) ax.set_ylim(0, 1) ax.set_xticks([]) ax.set_yticks([]) return fig # Gráfico normal con datos jugadas = [d['jugada'] for d in self.datos_jugadas] evaluaciones = [d['evaluacion'] for d in self.datos_jugadas] fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8)) ax1.plot(jugadas, evaluaciones, 'b-', linewidth=2) ax1.fill_between(jugadas, evaluaciones, alpha=0.3) ax1.axhline(y=0, color='black', linestyle='--') ax1.set_title(f'📊 Análisis: {blancas} vs {negras}', fontweight='bold') ax1.set_ylabel('Ventaja') ax1.grid(True, alpha=0.3) material = [d['ventaja_material'] for d in self.datos_jugadas] ax2.bar(jugadas, material, alpha=0.7, color=['green' if x >= 0 else 'red' for x in material]) ax2.set_xlabel('Jugada') ax2.set_ylabel('Material') ax2.grid(True, alpha=0.3) plt.tight_layout() return fig def generar_reporte(self, blancas, negras, resultado): if not self.datos_jugadas: reporte = """## ❌ PGN NO ANALIZABLE **El archivo PGN está corrupto o tiene formato inválido.** ### 🔧 Posibles soluciones: 1. **Verifica que el PGN sea de una partida real** 2. **Comprueba que la notación de movimientos sea correcta** 3. **Intenta con otro archivo PGN** 4. **Los headers deben usar comillas: [Event "Nombre"]** ### 📝 Formato PGN correcto: