Spaces:
Configuration error
Configuration error
| 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: | |
| 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: |