Spaces:
Configuration error
Configuration error
Upload 2 files
Browse files- app.py +229 -18
- requirements.txt +2 -2
app.py
CHANGED
|
@@ -7,15 +7,192 @@ import io
|
|
| 7 |
import base64
|
| 8 |
from io import BytesIO
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
def analizar_y_mostrar(pgn_text):
|
| 13 |
"""Función que Gradio usará para la interfaz"""
|
| 14 |
try:
|
|
|
|
|
|
|
|
|
|
| 15 |
analizador = AnalizadorAjedrez()
|
| 16 |
-
blancas, negras, resultado = analizador.analizar_partida(pgn_text)
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
| 19 |
fig = analizador.generar_graficos_gradio(blancas, negras)
|
| 20 |
|
| 21 |
# Convertir figura a imagen para Gradio
|
|
@@ -26,27 +203,61 @@ def analizar_y_mostrar(pgn_text):
|
|
| 26 |
|
| 27 |
# Generar reporte textual
|
| 28 |
reporte = analizador.generar_reporte_textual()
|
|
|
|
| 29 |
|
| 30 |
return buf, reporte
|
| 31 |
|
| 32 |
except Exception as e:
|
| 33 |
-
return None, f"❌ Error: {str(e)}"
|
| 34 |
|
| 35 |
# Interfaz de Gradio
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
if __name__ == "__main__":
|
| 52 |
demo.launch()
|
|
|
|
| 7 |
import base64
|
| 8 |
from io import BytesIO
|
| 9 |
|
| 10 |
+
class AnalizadorAjedrez:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.datos_jugadas = []
|
| 13 |
+
plt.style.use('default')
|
| 14 |
+
|
| 15 |
+
def analizar_partida(self, pgn_string):
|
| 16 |
+
"""Analiza una partida completa desde PGN"""
|
| 17 |
+
try:
|
| 18 |
+
pgn = io.StringIO(pgn_string)
|
| 19 |
+
game = chess.pgn.read_game(pgn)
|
| 20 |
+
|
| 21 |
+
if game is None:
|
| 22 |
+
return "Error", "Error", "Error", "No se pudo leer el PGN"
|
| 23 |
+
|
| 24 |
+
# Metadatos
|
| 25 |
+
blancas = game.headers.get("White", "Anónimo")
|
| 26 |
+
negras = game.headers.get("Black", "Anónimo")
|
| 27 |
+
resultado = game.headers.get("Result", "*")
|
| 28 |
+
|
| 29 |
+
# Analizar cada jugada
|
| 30 |
+
tablero = game.board()
|
| 31 |
+
self.datos_jugadas = []
|
| 32 |
+
|
| 33 |
+
jugada_num = 0
|
| 34 |
+
for move in game.mainline_moves():
|
| 35 |
+
tablero.push(move)
|
| 36 |
+
jugada_num += 1
|
| 37 |
+
analisis = self._evaluar_posicion(tablero, jugada_num)
|
| 38 |
+
self.datos_jugadas.append(analisis)
|
| 39 |
+
|
| 40 |
+
return blancas, negras, resultado, ""
|
| 41 |
+
|
| 42 |
+
except Exception as e:
|
| 43 |
+
return "Error", "Error", "Error", f"Error procesando PGN: {str(e)}"
|
| 44 |
+
|
| 45 |
+
def _evaluar_posicion(self, tablero, jugada_num):
|
| 46 |
+
"""Evalúa una posición específica del tablero"""
|
| 47 |
+
# Valor material (puntos estándar)
|
| 48 |
+
valores = {'p': 1, 'n': 3, 'b': 3, 'r': 5, 'q': 9, 'k': 0}
|
| 49 |
+
|
| 50 |
+
material_blancas = 0
|
| 51 |
+
material_negras = 0
|
| 52 |
+
|
| 53 |
+
for square, pieza in tablero.piece_map().items():
|
| 54 |
+
valor = valores.get(pieza.symbol().lower(), 0)
|
| 55 |
+
if pieza.color == chess.WHITE:
|
| 56 |
+
material_blancas += valor
|
| 57 |
+
else:
|
| 58 |
+
material_negras += valor
|
| 59 |
+
|
| 60 |
+
ventaja_material = material_blancas - material_negras
|
| 61 |
+
|
| 62 |
+
# Movilidad
|
| 63 |
+
movilidad_blancas = len(list(tablero.legal_moves))
|
| 64 |
+
tablero.turn = not tablero.turn
|
| 65 |
+
movilidad_negras = len(list(tablero.legal_moves))
|
| 66 |
+
tablero.turn = not tablero.turn
|
| 67 |
+
|
| 68 |
+
ventaja_movilidad = movilidad_blancas - movilidad_negras
|
| 69 |
+
|
| 70 |
+
# Evaluación compuesta
|
| 71 |
+
evaluacion = ventaja_material + (ventaja_movilidad * 0.1)
|
| 72 |
+
|
| 73 |
+
return {
|
| 74 |
+
'jugada': jugada_num,
|
| 75 |
+
'evaluacion': evaluacion,
|
| 76 |
+
'material_blancas': material_blancas,
|
| 77 |
+
'material_negras': material_negras,
|
| 78 |
+
'ventaja_material': ventaja_material,
|
| 79 |
+
'movilidad_blancas': movilidad_blancas,
|
| 80 |
+
'movilidad_negras': movilidad_negras,
|
| 81 |
+
'es_blancas': jugada_num % 2 == 1
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
def generar_graficos_gradio(self, blancas, negras):
|
| 85 |
+
"""Genera todos los gráficos para Gradio"""
|
| 86 |
+
if not self.datos_jugadas:
|
| 87 |
+
# Crear gráfico vacío si no hay datos
|
| 88 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
| 89 |
+
ax.text(0.5, 0.5, 'No hay datos para mostrar',
|
| 90 |
+
horizontalalignment='center', verticalalignment='center',
|
| 91 |
+
transform=ax.transAxes, fontsize=16)
|
| 92 |
+
ax.set_xlim(0, 1)
|
| 93 |
+
ax.set_ylim(0, 1)
|
| 94 |
+
return fig
|
| 95 |
+
|
| 96 |
+
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
|
| 97 |
+
|
| 98 |
+
# 📊 1. GRÁFICO DE EVALUACIÓN DE POSICIÓN
|
| 99 |
+
jugadas = [d['jugada'] for d in self.datos_jugadas]
|
| 100 |
+
evaluaciones = [d['evaluacion'] for d in self.datos_jugadas]
|
| 101 |
+
|
| 102 |
+
ax1.plot(jugadas, evaluaciones, 'b-', linewidth=2, alpha=0.7)
|
| 103 |
+
ax1.fill_between(jugadas, evaluaciones, alpha=0.3)
|
| 104 |
+
ax1.axhline(y=0, color='black', linestyle='-', alpha=0.5)
|
| 105 |
+
ax1.set_title('📊 EVALUACIÓN DE POSICIÓN', fontweight='bold')
|
| 106 |
+
ax1.set_xlabel('Número de Jugada')
|
| 107 |
+
ax1.set_ylabel('Ventaja para Blancas')
|
| 108 |
+
ax1.grid(True, alpha=0.3)
|
| 109 |
+
|
| 110 |
+
# 📈 2. ANÁLISIS DE MATERIAL
|
| 111 |
+
material_data = [d['ventaja_material'] for d in self.datos_jugadas]
|
| 112 |
+
ax2.plot(jugadas, material_data, 'r-', linewidth=2, alpha=0.7)
|
| 113 |
+
ax2.axhline(y=0, color='black', linestyle='-', alpha=0.5)
|
| 114 |
+
ax2.set_title('📈 VENTAJA MATERIAL', fontweight='bold')
|
| 115 |
+
ax2.set_xlabel('Jugada')
|
| 116 |
+
ax2.set_ylabel('Ventaja Material')
|
| 117 |
+
ax2.grid(True, alpha=0.3)
|
| 118 |
+
|
| 119 |
+
# 🎯 3. MOVILIDAD
|
| 120 |
+
movilidad_b = [d['movilidad_blancas'] for d in self.datos_jugadas]
|
| 121 |
+
movilidad_n = [d['movilidad_negras'] for d in self.datos_jugadas]
|
| 122 |
+
|
| 123 |
+
ax3.plot(jugadas, movilidad_b, 'b-', label=f'{blancas}', linewidth=2)
|
| 124 |
+
ax3.plot(jugadas, movilidad_n, 'r-', label=f'{negras}', linewidth=2)
|
| 125 |
+
ax3.set_title('🎯 MOVILIDAD DE PIEZAS', fontweight='bold')
|
| 126 |
+
ax3.set_xlabel('Jugada')
|
| 127 |
+
ax3.set_ylabel('Movimientos Legales')
|
| 128 |
+
ax3.legend()
|
| 129 |
+
ax3.grid(True, alpha=0.3)
|
| 130 |
+
|
| 131 |
+
# 📋 4. ESTADÍSTICAS FINALES
|
| 132 |
+
ultimo = self.datos_jugadas[-1]
|
| 133 |
+
categorias = ['Material', 'Movilidad']
|
| 134 |
+
valores_b = [ultimo['material_blancas'], ultimo['movilidad_blancas']]
|
| 135 |
+
valores_n = [ultimo['material_negras'], ultimo['movilidad_negras']]
|
| 136 |
+
|
| 137 |
+
x = np.arange(len(categorias))
|
| 138 |
+
ax4.bar(x - 0.2, valores_b, 0.4, label=blancas, color='blue', alpha=0.7)
|
| 139 |
+
ax4.bar(x + 0.2, valores_n, 0.4, label=negras, color='red', alpha=0.7)
|
| 140 |
+
ax4.set_title('📋 COMPARACIÓN FINAL', fontweight='bold')
|
| 141 |
+
ax4.set_xticks(x)
|
| 142 |
+
ax4.set_xticklabels(categorias)
|
| 143 |
+
ax4.legend()
|
| 144 |
+
|
| 145 |
+
plt.tight_layout()
|
| 146 |
+
return fig
|
| 147 |
+
|
| 148 |
+
def generar_reporte_textual(self):
|
| 149 |
+
"""Genera un análisis textual de la partida"""
|
| 150 |
+
if not self.datos_jugadas:
|
| 151 |
+
return "No hay datos para generar reporte"
|
| 152 |
+
|
| 153 |
+
reporte = "🎯 ANÁLISIS DETALLADO DE LA PARTIDA\n"
|
| 154 |
+
reporte += "=" * 40 + "\n"
|
| 155 |
+
|
| 156 |
+
# Estadísticas básicas
|
| 157 |
+
total_jugadas = len(self.datos_jugadas)
|
| 158 |
+
eval_final = self.datos_jugadas[-1]['evaluacion']
|
| 159 |
+
|
| 160 |
+
reporte += f"• Total de jugadas: {total_jugadas}\n"
|
| 161 |
+
reporte += f"• Evaluación final: {eval_final:.2f}\n"
|
| 162 |
+
|
| 163 |
+
# Balance de material final
|
| 164 |
+
mat_final = self.datos_jugadas[-1]['ventaja_material']
|
| 165 |
+
if mat_final > 2:
|
| 166 |
+
reporte += f"• Ventaja material final: +{mat_final} para Blancas\n"
|
| 167 |
+
elif mat_final < -2:
|
| 168 |
+
reporte += f"• Ventaja material final: +{abs(mat_final)} para Negras\n"
|
| 169 |
+
else:
|
| 170 |
+
reporte += "• Material equilibrado al final\n"
|
| 171 |
+
|
| 172 |
+
# Jugador con mejor movilidad promedio
|
| 173 |
+
mov_avg_b = np.mean([d['movilidad_blancas'] for d in self.datos_jugadas])
|
| 174 |
+
mov_avg_n = np.mean([d['movilidad_negras'] for d in self.datos_jugadas])
|
| 175 |
+
|
| 176 |
+
if mov_avg_b > mov_avg_n:
|
| 177 |
+
reporte += f"• Mejor movilidad promedio: Blancas ({mov_avg_b:.1f} vs {mov_avg_n:.1f})\n"
|
| 178 |
+
else:
|
| 179 |
+
reporte += f"• Mejor movilidad promedio: Negras ({mov_avg_n:.1f} vs {mov_avg_b:.1f})\n"
|
| 180 |
+
|
| 181 |
+
return reporte
|
| 182 |
|
| 183 |
def analizar_y_mostrar(pgn_text):
|
| 184 |
"""Función que Gradio usará para la interfaz"""
|
| 185 |
try:
|
| 186 |
+
if not pgn_text.strip():
|
| 187 |
+
return None, "❌ Por favor, pega una partida PGN válida"
|
| 188 |
+
|
| 189 |
analizador = AnalizadorAjedrez()
|
| 190 |
+
blancas, negras, resultado, error = analizador.analizar_partida(pgn_text)
|
| 191 |
|
| 192 |
+
if error:
|
| 193 |
+
return None, f"❌ {error}"
|
| 194 |
+
|
| 195 |
+
# Generar gráficos
|
| 196 |
fig = analizador.generar_graficos_gradio(blancas, negras)
|
| 197 |
|
| 198 |
# Convertir figura a imagen para Gradio
|
|
|
|
| 203 |
|
| 204 |
# Generar reporte textual
|
| 205 |
reporte = analizador.generar_reporte_textual()
|
| 206 |
+
reporte = f"Partida: {blancas} vs {negras}\nResultado: {resultado}\n\n" + reporte
|
| 207 |
|
| 208 |
return buf, reporte
|
| 209 |
|
| 210 |
except Exception as e:
|
| 211 |
+
return None, f"❌ Error inesperado: {str(e)}"
|
| 212 |
|
| 213 |
# Interfaz de Gradio
|
| 214 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 215 |
+
gr.Markdown("# ♟️ Analizador de Partidas de Ajedrez")
|
| 216 |
+
gr.Markdown("Pega cualquier partida en formato PGN y obtén un análisis completo con gráficos y estadísticas.")
|
| 217 |
+
|
| 218 |
+
with gr.Row():
|
| 219 |
+
with gr.Column():
|
| 220 |
+
pgn_input = gr.Textbox(
|
| 221 |
+
label="Pega tu partida PGN aquí",
|
| 222 |
+
lines=15,
|
| 223 |
+
placeholder="""[Event \"Mi Torneo\"]
|
| 224 |
+
[White \"Jugador Blanco\"]
|
| 225 |
+
[Black \"Jugador Negro\"]
|
| 226 |
+
[Result \"1-0\"]
|
| 227 |
+
|
| 228 |
+
1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O"""
|
| 229 |
+
)
|
| 230 |
+
btn_analizar = gr.Button("Analizar Partida", variant="primary")
|
| 231 |
+
|
| 232 |
+
with gr.Column():
|
| 233 |
+
image_output = gr.Image(label="Análisis Gráfico de la Partida", type="filepath")
|
| 234 |
+
text_output = gr.Textbox(label="Reporte de Análisis", lines=8)
|
| 235 |
+
|
| 236 |
+
# Ejemplos de PGN
|
| 237 |
+
gr.Markdown("### 📋 Ejemplo de formato PGN:")
|
| 238 |
+
gr.Examples(
|
| 239 |
+
examples=[[
|
| 240 |
+
"""[Event "Ejemplo"]
|
| 241 |
+
[Site "?"]
|
| 242 |
+
[Date "2023.??.??"]
|
| 243 |
+
[Round "?"]
|
| 244 |
+
[White "Carlsen"]
|
| 245 |
+
[Black "Caruana"]
|
| 246 |
+
[Result "1-0"]
|
| 247 |
+
|
| 248 |
+
1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O
|
| 249 |
+
9. h3 Nb8 10. d4 Nbd7 11. Nbd2 Bb7 12. Bc2 Re8 13. Nf1 Bf8 14. Ng3 g6 15. a4 c5
|
| 250 |
+
16. d5 c4 17. Bg5 h6 18. Be3 Nc5 19. Qd2 h5 20. Bh6 Nh7 21. g3 Bd7 22. Rf1 Qc7
|
| 251 |
+
23. Ne2 Rec8 24. Ng5 Nxg5 25. Bxg5 Bg7 26. f4 exf4 27. gxf4 f6 28. Bf3 Be5
|
| 252 |
+
29. fxe5 fxe5 30. Bg5 Qd7 31. e6 Qe7 32. Bf6 Qf8 33. e7 1-0"""]],
|
| 253 |
+
inputs=pgn_input
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
btn_analizar.click(
|
| 257 |
+
fn=analizar_y_mostrar,
|
| 258 |
+
inputs=pgn_input,
|
| 259 |
+
outputs=[image_output, text_output]
|
| 260 |
+
)
|
| 261 |
|
| 262 |
if __name__ == "__main__":
|
| 263 |
demo.launch()
|
requirements.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
chess
|
| 2 |
matplotlib
|
| 3 |
numpy
|
| 4 |
-
|
| 5 |
-
|
|
|
|
| 1 |
chess
|
| 2 |
matplotlib
|
| 3 |
numpy
|
| 4 |
+
gradio
|
| 5 |
+
pillow
|