doctorlinux commited on
Commit
ea5840d
·
verified ·
1 Parent(s): cbeb866

Upload 2 files

Browse files
Files changed (1) hide show
  1. app.py +172 -250
app.py CHANGED
@@ -6,31 +6,53 @@ import numpy as np
6
  import io
7
  import re
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
@@ -40,17 +62,16 @@ class AnalizadorAjedrez:
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
@@ -60,15 +81,9 @@ class AnalizadorAjedrez:
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,
@@ -76,236 +91,172 @@ class AnalizadorAjedrez:
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
- # Crear fondo coloreado para ventaja
103
- ax1.axhspan(0.1, max(evaluaciones) if max(evaluaciones) > 0 else 0.1,
104
- alpha=0.2, color='blue', label='Ventaja Blancas')
105
- ax1.axhspan(min(evaluaciones) if min(evaluaciones) < 0 else -0.1, -0.1,
106
- alpha=0.2, color='red', label='Ventaja Negras')
107
 
108
- ax1.plot(jugadas, evaluaciones, 'b-', linewidth=2.5, alpha=0.8, marker='o', markersize=3)
109
- ax1.axhline(y=0, color='black', linestyle='-', alpha=0.5, linewidth=1)
110
- ax1.set_title('📊 EVALUACIÓN DE POSICIÓN', fontweight='bold', fontsize=14)
111
- ax1.set_xlabel('Número de Jugada')
 
112
  ax1.set_ylabel('Ventaja para Blancas')
113
- ax1.grid(True, alpha=0.3)
114
  ax1.legend()
115
-
116
- # 📈 2. ANÁLISIS DE MATERIAL
117
- material_data = [d['ventaja_material'] for d in self.datos_jugadas]
118
- ax2.bar(jugadas, material_data, alpha=0.7,
119
- color=['blue' if x >= 0 else 'red' for x in material_data])
120
- ax2.axhline(y=0, color='black', linestyle='-', alpha=0.5, linewidth=1)
121
- ax2.set_title('📈 VENTAJA MATERIAL', fontweight='bold', fontsize=14)
122
- ax2.set_xlabel('Jugada')
123
- ax2.set_ylabel('Ventaja Material')
124
  ax2.grid(True, alpha=0.3)
125
-
126
- # 🎯 3. MOVILIDAD
127
- movilidad_b = [d['movilidad_blancas'] for d in self.datos_jugadas]
128
- movilidad_n = [d['movilidad_negras'] for d in self.datos_jugadas]
129
 
130
- ax3.fill_between(jugadas, movilidad_b, alpha=0.3, color='blue')
131
- ax3.fill_between(jugadas, movilidad_n, alpha=0.3, color='red')
132
- ax3.plot(jugadas, movilidad_b, 'b-', label=f'{blancas}', linewidth=2.5)
133
- ax3.plot(jugadas, movilidad_n, 'r-', label=f'{negras}', linewidth=2.5)
134
- ax3.set_title('🎯 MOVILIDAD DE PIEZAS', fontweight='bold', fontsize=14)
135
  ax3.set_xlabel('Jugada')
136
  ax3.set_ylabel('Movimientos Legales')
137
  ax3.legend()
138
  ax3.grid(True, alpha=0.3)
139
-
140
- # 📋 4. ESTADÍSTICAS FINALES
141
  ultimo = self.datos_jugadas[-1]
142
  categorias = ['Material', 'Movilidad']
143
- valores_b = [ultimo['material_blancas'], ultimo['movilidad_blancas']]
144
- valores_n = [ultimo['material_negras'], ultimo['movilidad_negras']]
145
 
146
  x = np.arange(len(categorias))
147
- bar_width = 0.35
148
-
149
- ax4.bar(x - bar_width/2, valores_b, bar_width, label=blancas,
150
- color='blue', alpha=0.7, edgecolor='black')
151
- ax4.bar(x + bar_width/2, valores_n, bar_width, label=negras,
152
- color='red', alpha=0.7, edgecolor='black')
153
-
154
- # Añadir valores encima de las barras
155
- for i, v in enumerate(valores_b):
156
- ax4.text(i - bar_width/2, v + 0.5, str(v), ha='center', va='bottom', fontweight='bold')
157
- for i, v in enumerate(valores_n):
158
- ax4.text(i + bar_width/2, v + 0.5, str(v), ha='center', va='bottom', fontweight='bold')
159
-
160
- ax4.set_title('📋 COMPARACIÓN FINAL', fontweight='bold', fontsize=14)
161
  ax4.set_xticks(x)
162
  ax4.set_xticklabels(categorias)
163
  ax4.legend()
164
- ax4.grid(True, alpha=0.3, axis='y')
165
-
166
  plt.tight_layout()
167
  return fig
168
 
169
- def generar_reporte_textual(self):
170
- """Genera un análisis textual de la partida"""
171
  if not self.datos_jugadas:
172
- return "No hay datos para generar reporte"
173
-
174
- reporte = "🎯 ANÁLISIS DETALLADO DE LA PARTIDA\n"
175
- reporte += "=" * 50 + "\n\n"
176
 
177
- # Estadísticas básicas
178
  total_jugadas = len(self.datos_jugadas)
179
  eval_final = self.datos_jugadas[-1]['evaluacion']
180
-
181
- reporte += f"📊 **Estadísticas Básicas:**\n"
182
- reporte += f" • Total de jugadas: {total_jugadas}\n"
183
- reporte += f" • Evaluación final: {eval_final:.2f}\n"
184
-
185
- # Balance de material final
186
  mat_final = self.datos_jugadas[-1]['ventaja_material']
187
- if mat_final > 2:
188
- reporte += f" • ⚖️ Ventaja material final: +{mat_final} para Blancas\n"
189
- elif mat_final < -2:
190
- reporte += f" • ⚖️ Ventaja material final: +{abs(mat_final)} para Negras\n"
191
- else:
192
- reporte += " • ⚖️ Material equilibrado al final\n"
193
-
194
- # Jugador con mejor movilidad promedio
195
- mov_avg_b = np.mean([d['movilidad_blancas'] for d in self.datos_jugadas])
196
- mov_avg_n = np.mean([d['movilidad_negras'] for d in self.datos_jugadas])
197
 
198
- reporte += f"\n🎯 **Análisis de Movilidad:**\n"
199
- if mov_avg_b > mov_avg_n:
200
- reporte += f" • Mejor movilidad promedio: 🏆 Blancas ({mov_avg_b:.1f} vs {mov_avg_n:.1f})\n"
201
- else:
202
- reporte += f" • Mejor movilidad promedio: 🏆 Negras ({mov_avg_n:.1f} vs {mov_avg_b:.1f})\n"
 
203
 
204
- # Punto de mayor ventaja
205
- max_ventaja_b = max([d['evaluacion'] for d in self.datos_jugadas])
206
- min_ventaja_b = min([d['evaluacion'] for d in self.datos_jugadas])
207
 
208
- reporte += f"\n📈 **Momentos Clave:**\n"
209
- reporte += f" • Mayor ventaja de Blancas: +{max_ventaja_b:.1f}\n"
210
- reporte += f" • Mayor ventaja de Negras: +{abs(min_ventaja_b):.1f}\n"
 
 
 
 
 
 
 
 
 
 
 
211
 
212
- # Duración de la partida
213
- if total_jugadas < 30:
214
- reporte += f" Partida rápida ({total_jugadas} jugadas)\n"
215
- elif total_jugadas < 60:
216
- reporte += f" Partida de duración media ({total_jugadas} jugadas)\n"
 
217
  else:
218
- reporte += f" • Partida larga ({total_jugadas} jugadas)\n"
 
 
 
 
219
 
 
 
220
  return reporte
221
 
222
- def limpiar_y_corregir_pgn(pgn_text):
223
- """Limpia y corrige errores comunes en PGN automáticamente"""
224
- correcciones = {
225
- 'N75': 'Nf3', 'N76': 'Nf6', 'N15': 'Nf5', 'N16': 'Nf6',
226
- 'Re6': 'Nc6', 'Re3': 'Ne3', 'Re4': 'Ne4',
227
- 'Rel': 'Re1', 'Ral': 'Ra1', 'Rbl': 'Rb1', 'Rcl': 'Rc1', 'Rdl': 'Rd1',
228
- 'Nba': 'Nb8', 'Nbc': 'Nbc6', 'Nbd': 'Nbd7',
229
- 'Of8': 'Qf8', 'Oc7': 'Qc7', 'Od8': 'Qd8',
230
- 'B6': 'Bf6', 'B7': 'Bb7', 'B5': 'Bb5',
231
- 'ae': 'a6', 'af': 'a5'
232
- }
233
-
234
- # Aplicar correcciones
235
- for error, correccion in correcciones.items():
236
- pgn_text = pgn_text.replace(error, correccion)
237
-
238
- # Corregir números de jugada mal formados
239
- pgn_text = re.sub(r'(\d+)\.([A-Z])', r'\1. \2', pgn_text)
240
-
241
- # Asegurar espacios después de puntos
242
- pgn_text = re.sub(r'(\d+)\.(\S)', r'\1. \2', pgn_text)
243
-
244
- return pgn_text
245
-
246
- def analizar_y_mostrar(pgn_text):
247
- """Función que Gradio usará para la interfaz"""
248
  try:
 
 
 
 
 
249
  if not pgn_text.strip():
250
- return None, "❌ Por favor, pega una partida PGN válida"
251
 
252
- # Limpiar y corregir PGN automáticamente
253
- pgn_limpio = limpiar_y_corregir_pgn(pgn_text)
254
-
255
  analizador = AnalizadorAjedrez()
256
- blancas, negras, resultado, error = analizador.analizar_partida(pgn_limpio)
257
-
258
- if "Error" in blancas:
259
- # Intentar análisis con PGN original como fallback
260
- try:
261
- analizador = AnalizadorAjedrez()
262
- blancas, negras, resultado, error = analizador.analizar_partida(pgn_text)
263
- if "Error" in blancas:
264
- return None, f"❌ No se pudo analizar el PGN.\n\n💡 **Sugerencias:**\n• Verifica que la notación sea correcta\n• Asegúrate de que los movimientos sigan formato estándar\n• Revisa que no haya caracteres especiales extraños\n\nError técnico: {error}"
265
- except:
266
- return None, f"❌ Error crítico en el PGN.\n\n📝 **Formato esperado:**\n```\n[Event \"Nombre\"]\n[White \"JugadorB\"]\n[Black \"JugadorN\"]\n\n1. e4 e5 2. Nf3 Nc6 3. Bb5 a6\n```"
267
 
268
- # Generar gráficos
269
- fig = analizador.generar_graficos_gradio(blancas, negras)
270
 
271
- # Convertir figura a imagen para Gradio
 
272
  buf = BytesIO()
273
- fig.savefig(buf, format='png', dpi=120, bbox_inches='tight', facecolor='white')
274
  buf.seek(0)
275
  plt.close(fig)
276
 
277
- # Generar reporte textual
278
- reporte = analizador.generar_reporte_textual()
279
- reporte_header = f"**♟️ PARTIDA ANALIZADA:**\n"
280
- reporte_header += f"**Blancas:** {blancas}\n"
281
- reporte_header += f"**Negras:** {negras}\n"
282
- reporte_header += f"**Resultado:** {resultado}\n\n"
283
 
284
- full_reporte = reporte_header + reporte
285
-
286
- return buf, full_reporte
287
 
288
  except Exception as e:
289
- return None, f"❌ Error inesperado: {str(e)}\n\nPor favor, verifica el formato de tu PGN."
290
 
291
- # Interfaz de Gradio mejorada
292
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="slate")) as demo:
293
  gr.Markdown("""
294
- # ♟️ Analizador Avanzado de Partidas de Ajedrez
295
-
296
- **Pega cualquier partida en formato PGN y obtén un análisis completo con gráficos y estadísticas detalladas.**
297
-
298
- *El sistema corrige automáticamente errores comunes de notación.*
299
  """)
300
 
301
  with gr.Row():
302
  with gr.Column(scale=1):
303
- gr.Markdown("### 📝 Ingresa tu partida")
 
 
 
 
 
 
 
 
304
  pgn_input = gr.Textbox(
305
  label="Pega tu partida PGN aquí",
306
- lines=18,
307
  placeholder="""[Event \"Mi Torneo\"]
308
- [White \"Jugador Blanco\"]
309
  [Black \"Jugador Negro\"]
310
  [Result \"1-0\"]
311
 
@@ -313,88 +264,59 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="slate"))
313
  9. h3 Nb8 10. d4 Nbd7 11. Nbd2 Bb7 12. Bc2 Re8 13. Nf1 Bf8 14. Ng3 g6 15. a4 c5
314
  16. d5 c4 17. Bg5 h6 18. Be3 Nc5 19. Qd2 h5 20. Bh6 Nh7 21. g3 Bd7 22. Rf1 Qc7
315
  23. Ne2 Rec8 24. Ng5 Nxg5 25. Bxg5 Bg7 26. f4 exf4 27. gxf4 f6 28. Bf3 Be5
316
- 29. fxe5 fxe5 30. Bg5 Qd7 31. e6 Qe7 32. Bf6 Qf8 33. e7 1-0""",
317
- info="Puedes pegar PGNs con pequeños errores, el sistema los corregirá automáticamente."
318
  )
319
 
320
- with gr.Row():
321
- btn_analizar = gr.Button("🧠 Analizar Partida", variant="primary", size="lg")
322
- btn_limpiar = gr.Button("🗑️ Limpiar", variant="secondary")
323
 
324
- gr.Markdown("### 💡 Ejemplos rápidos:")
325
- with gr.Row():
326
- gr.Examples(
327
- examples=[[
328
- """[Event "Partida Rápida"]
329
- [White "Aman"]
330
- [Black "Boris"]
331
- [Result "1-0"]
332
-
333
- 1. e4 e5 2. Nf3 Nc6 3. Bc4 Nf6 4. Ng5 d5 5. exd5 Nxd5 6. Nxf7 Kxf7 7. Qf3+ Ke6
334
- 8. Nc3 Ne7 9. d4 c6 10. Bg5 h6 11. Bxe7 Bxe7 12. O-O-O Rf8 13. Qe4 Rxf2 14. d5+ Kd6
335
- 15. Nxd5 cxd5 16. Rxd5+ Kc6 17. Qc4+ Kb6 18. Rb5+ Ka6 19. Qc6+ 1-0"""]],
336
- inputs=pgn_input,
337
- label="Ataque Legal"
338
- )
339
-
340
  with gr.Column(scale=1):
341
  gr.Markdown("### 📊 Resultados del Análisis")
342
  image_output = gr.Image(
343
- label="Análisis Gráfico de la Partida",
344
  type="filepath",
345
  height=400
346
  )
347
- text_output = gr.Markdown(
348
- label="Reporte de Análisis Detallado"
 
349
  )
350
 
351
- # Funcionalidades adicionales
352
- gr.Markdown("---")
353
- with gr.Accordion("📚 Guía de Formato PGN", open=False):
354
- gr.Markdown("""
355
- **Formato PGN Correcto:**
356
- ```
357
- [Event "Nombre del Torneo"]
358
- [Site "Ciudad, País"]
359
- [Date "2024.01.01"]
360
- [Round "1"]
361
- [White "Apellido, Nombre"]
362
- [Black "Apellido, Nombre"]
363
- [Result "1-0"]
364
- [WhiteElo "2500"]
365
- [BlackElo "2400"]
366
-
367
- 1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6
368
- 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7 11. Nbd2 Bb7 12. Bc2 Re8 13. Nf1 Bf8
369
- 14. Ng3 g6 15. a4 c5 16. d5 c4 17. Bg5 h6 18. Be3 Nc5 19. Qd2 h5
370
- ...
371
- 33. e7 1-0
372
- ```
373
 
374
- **Notación de Piezas:**
375
- - Rey: **K** (King)
376
- - Reina: **Q** (Queen)
377
- - Torre: **R** (Rook)
378
- - Alfil: **B** (Bishop)
379
- - Caballo: **N** (Knight)
380
- - Peón: (sin letra, ej: e4, d5)
381
- """)
 
 
 
 
382
 
383
- # Conectores de eventos
384
  btn_analizar.click(
385
- fn=analizar_y_mostrar,
386
- inputs=pgn_input,
387
- outputs=[image_output, text_output]
388
  )
389
 
390
  btn_limpiar.click(
391
- fn=lambda: [None, "### 📊 Reporte de Análisis\n\n*Los resultados aparecerán aquí después del análisis...*"],
392
  inputs=[],
393
- outputs=[image_output, text_output]
394
  )
395
 
396
  if __name__ == "__main__":
397
- demo.launch(
398
- share=True,
399
- show_error=True
400
- )
 
6
  import io
7
  import re
8
  from io import BytesIO
9
+ import tempfile
10
+ import os
11
+
12
+ # Configurar matplotlib
13
+ plt.switch_backend('Agg')
14
 
15
  class AnalizadorAjedrez:
16
  def __init__(self):
17
  self.datos_jugadas = []
 
18
 
19
+ def corregir_pgn_automatico(self, pgn_text):
20
+ """Corrección AGGRESIVA de errores comunes"""
21
+ correcciones = {
22
+ # Piezas mal escritas
23
+ 'nf3': 'Nf3', 'nc3': 'Nc3', 'ng5': 'Ng5', 'hc6': 'Nc6', 'hf6': 'Nf6',
24
+ 'he7': 'Ne7', 'fxd5': 'Nxd5', 'fxf7': 'Nxf7',
25
+ 'kxf7': 'Kxf7', 'ke6': 'Ke6', 'kd6': 'Kd6', 'kc6': 'Kc6', 'kb6': 'Kb6', 'ka6': 'Ka6',
26
+ 'of3': 'Qf3', 'oc6': 'Qc6', 'oc4': 'Qc4',
27
+ 'hf8': 'Rf8', 'fxf2': 'Rxf2', 'fxd5': 'Rxd5', 'fb5': 'Rb5',
28
+ # Notación de enroque
29
+ 'o-o-o': 'O-O-O', 'o-o': 'O-O',
30
+ }
31
+
32
+ for error, correccion in correcciones.items():
33
+ pgn_text = re.sub(r'\b' + error + r'\b', correccion, pgn_text, flags=re.IGNORECASE)
34
+
35
+ return pgn_text
36
+
37
  def analizar_partida(self, pgn_string):
 
38
  try:
39
+ # Corrección automática
40
+ pgn_corregido = self.corregir_pgn_automatico(pgn_string)
41
+ pgn = io.StringIO(pgn_corregido)
42
  game = chess.pgn.read_game(pgn)
43
 
44
  if game is None:
45
+ return "Anónimo", "Anónimo", "*", "No se pudo leer el PGN (formato inválido)"
46
 
 
47
  blancas = game.headers.get("White", "Anónimo")
48
+ negras = game.headers.get("Black", "Anónimo")
49
  resultado = game.headers.get("Result", "*")
50
 
51
+ # Analizar jugadas
52
  tablero = game.board()
53
  self.datos_jugadas = []
 
54
  jugada_num = 0
55
+
56
  for move in game.mainline_moves():
57
  tablero.push(move)
58
  jugada_num += 1
 
62
  return blancas, negras, resultado, ""
63
 
64
  except Exception as e:
65
+ return "Error", "Error", "*", f"Error: {str(e)}"
66
 
67
  def _evaluar_posicion(self, tablero, jugada_num):
68
+ """Evaluación simple de la posición"""
 
69
  valores = {'p': 1, 'n': 3, 'b': 3, 'r': 5, 'q': 9, 'k': 0}
70
 
71
  material_blancas = 0
72
  material_negras = 0
73
 
74
+ for pieza in tablero.piece_map().values():
75
  valor = valores.get(pieza.symbol().lower(), 0)
76
  if pieza.color == chess.WHITE:
77
  material_blancas += valor
 
81
  ventaja_material = material_blancas - material_negras
82
 
83
  # Movilidad
84
+ movilidad = len(list(tablero.legal_moves))
 
 
 
85
 
86
+ evaluacion = ventaja_material + (movilidad * 0.1)
 
 
 
87
 
88
  return {
89
  'jugada': jugada_num,
 
91
  'material_blancas': material_blancas,
92
  'material_negras': material_negras,
93
  'ventaja_material': ventaja_material,
94
+ 'movilidad': movilidad,
 
95
  'es_blancas': jugada_num % 2 == 1
96
  }
97
 
98
+ def generar_grafico_completo(self, blancas, negras):
99
+ """Genera gráficos completos"""
100
  if not self.datos_jugadas:
 
101
  fig, ax = plt.subplots(figsize=(10, 6))
102
+ ax.text(0.5, 0.5, 'No hay datos para analizar',
103
+ ha='center', va='center', fontsize=16)
 
 
 
104
  return fig
105
 
 
 
 
106
  jugadas = [d['jugada'] for d in self.datos_jugadas]
107
  evaluaciones = [d['evaluacion'] for d in self.datos_jugadas]
108
+ material = [d['ventaja_material'] for d in self.datos_jugadas]
109
+ movilidad_b = [d['movilidad'] if d['es_blancas'] else 0 for d in self.datos_jugadas]
110
+ movilidad_n = [d['movilidad'] if not d['es_blancas'] else 0 for d in self.datos_jugadas]
111
 
112
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 10))
 
 
 
 
113
 
114
+ # Gráfico 1: Evaluación general
115
+ ax1.plot(jugadas, evaluaciones, 'b-', linewidth=2, label='Evaluación')
116
+ ax1.fill_between(jugadas, evaluaciones, alpha=0.3)
117
+ ax1.axhline(y=0, color='black', linestyle='--')
118
+ ax1.set_title('📊 Evaluación de la Partida', fontweight='bold')
119
  ax1.set_ylabel('Ventaja para Blancas')
 
120
  ax1.legend()
121
+ ax1.grid(True, alpha=0.3)
122
+
123
+ # Gráfico 2: Ventaja material
124
+ ax2.bar(jugadas, material, alpha=0.7, color=['green' if x >= 0 else 'red' for x in material])
125
+ ax2.axhline(y=0, color='black', linestyle='--')
126
+ ax2.set_title('⚖️ Ventaja Material', fontweight='bold')
127
+ ax2.set_ylabel('Material')
 
 
128
  ax2.grid(True, alpha=0.3)
 
 
 
 
129
 
130
+ # Gráfico 3: Movilidad
131
+ ax3.plot(jugadas, movilidad_b, 'blue', label=blancas, linewidth=2)
132
+ ax3.plot(jugadas, movilidad_n, 'red', label=negras, linewidth=2)
133
+ ax3.set_title('🎯 Movilidad de Piezas', fontweight='bold')
 
134
  ax3.set_xlabel('Jugada')
135
  ax3.set_ylabel('Movimientos Legales')
136
  ax3.legend()
137
  ax3.grid(True, alpha=0.3)
138
+
139
+ # Gráfico 4: Resumen final
140
  ultimo = self.datos_jugadas[-1]
141
  categorias = ['Material', 'Movilidad']
142
+ blancas_stats = [ultimo['material_blancas'], ultimo['movilidad']]
143
+ negras_stats = [ultimo['material_negras'], ultimo['movilidad']]
144
 
145
  x = np.arange(len(categorias))
146
+ ax4.bar(x - 0.2, blancas_stats, 0.4, label=blancas, color='blue', alpha=0.7)
147
+ ax4.bar(x + 0.2, negras_stats, 0.4, label=negras, color='red', alpha=0.7)
148
+ ax4.set_title('📋 Comparación Final', fontweight='bold')
 
 
 
 
 
 
 
 
 
 
 
149
  ax4.set_xticks(x)
150
  ax4.set_xticklabels(categorias)
151
  ax4.legend()
152
+
 
153
  plt.tight_layout()
154
  return fig
155
 
156
+ def generar_reporte_detallado(self, blancas, negras, resultado):
 
157
  if not self.datos_jugadas:
158
+ return "No se pudo generar el reporte"
 
 
 
159
 
 
160
  total_jugadas = len(self.datos_jugadas)
161
  eval_final = self.datos_jugadas[-1]['evaluacion']
 
 
 
 
 
 
162
  mat_final = self.datos_jugadas[-1]['ventaja_material']
 
 
 
 
 
 
 
 
 
 
163
 
164
+ # Calcular precision aproximada
165
+ cambios_bruscos = 0
166
+ for i in range(1, len(self.datos_jugadas)):
167
+ cambio = abs(self.datos_jugadas[i]['evaluacion'] - self.datos_jugadas[i-1]['evaluacion'])
168
+ if cambio > 3: # Cambio brusco
169
+ cambios_bruscos += 1
170
 
171
+ precision = max(0, 100 - (cambios_bruscos * 5))
 
 
172
 
173
+ reporte = f"""
174
+ ## ♟️ ANÁLISIS DE PARTIDA
175
+
176
+ **Jugadores:** {blancas} vs {negras}
177
+ **Resultado:** {resultado}
178
+ **Total de jugadas:** {total_jugadas}
179
+
180
+ ### 📊 Métricas Finales:
181
+ - **Evaluación final:** {eval_final:+.2f}
182
+ - **Ventaja material:** {mat_final:+d}
183
+ - **Precisión estimada:** {precision:.0f}%
184
+
185
+ ### 🎯 Resumen:
186
+ """
187
 
188
+ if eval_final > 3:
189
+ reporte += "• ✅ **Victoria clara de las Blancas**\n"
190
+ reporte += "• Las Blancas mantuvieron ventaja consistente\n"
191
+ elif eval_final < -3:
192
+ reporte += "• **Victoria clara de las Negras**\n"
193
+ reporte += "• Las Negras dominaron la partida\n"
194
  else:
195
+ reporte += "• ⚖️ **Partida equilibrada**\n"
196
+ reporte += "• Ambos bandos jugaron a un nivel similar\n"
197
+
198
+ if cambios_bruscos > 5:
199
+ reporte += "• ⚠️ **Varios cambios bruscos** - Posibles errores tácticos\n"
200
 
201
+ reporte += f"\n*Análisis generado automáticamente*"
202
+
203
  return reporte
204
 
205
+ def procesar_pgn(pgn_text, archivo_pgn=None):
206
+ """Procesa PGN desde texto o archivo"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  try:
208
+ # Si se subió un archivo, leer su contenido
209
+ if archivo_pgn is not None:
210
+ with open(archivo_pgn.name, 'r', encoding='utf-8') as f:
211
+ pgn_text = f.read()
212
+
213
  if not pgn_text.strip():
214
+ return None, "❌ Por favor, ingresa un PGN o sube un archivo"
215
 
 
 
 
216
  analizador = AnalizadorAjedrez()
217
+ blancas, negras, resultado, error = analizador.analizar_partida(pgn_text)
 
 
 
 
 
 
 
 
 
 
218
 
219
+ if error:
220
+ return None, f"❌ Error al procesar: {error}"
221
 
222
+ # Generar gráfico
223
+ fig = analizador.generar_grafico_completo(blancas, negras)
224
  buf = BytesIO()
225
+ fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
226
  buf.seek(0)
227
  plt.close(fig)
228
 
229
+ # Generar reporte
230
+ reporte = analizador.generar_reporte_detallado(blancas, negras, resultado)
 
 
 
 
231
 
232
+ return buf, reporte
 
 
233
 
234
  except Exception as e:
235
+ return None, f"❌ Error inesperado: {str(e)}"
236
 
237
+ # Interfaz de Gradio con uploader
238
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
239
  gr.Markdown("""
240
+ # ♟️ Analizador de Partidas de Ajedrez
241
+ **Carga archivos PGN o pega el texto directamente - Análisis completo con gráficos profesionales**
 
 
 
242
  """)
243
 
244
  with gr.Row():
245
  with gr.Column(scale=1):
246
+ gr.Markdown("### 📁 Subir archivo PGN")
247
+ uploader = gr.File(
248
+ label="Selecciona tu archivo .pgn",
249
+ file_types=[".pgn"],
250
+ type="filepath",
251
+ height=100
252
+ )
253
+
254
+ gr.Markdown("### 📝 O pegar PGN como texto")
255
  pgn_input = gr.Textbox(
256
  label="Pega tu partida PGN aquí",
257
+ lines=15,
258
  placeholder="""[Event \"Mi Torneo\"]
259
+ [White \"Jugador Blanco\"]
260
  [Black \"Jugador Negro\"]
261
  [Result \"1-0\"]
262
 
 
264
  9. h3 Nb8 10. d4 Nbd7 11. Nbd2 Bb7 12. Bc2 Re8 13. Nf1 Bf8 14. Ng3 g6 15. a4 c5
265
  16. d5 c4 17. Bg5 h6 18. Be3 Nc5 19. Qd2 h5 20. Bh6 Nh7 21. g3 Bd7 22. Rf1 Qc7
266
  23. Ne2 Rec8 24. Ng5 Nxg5 25. Bxg5 Bg7 26. f4 exf4 27. gxf4 f6 28. Bf3 Be5
267
+ 29. fxe5 fxe5 30. Bg5 Qd7 31. e6 Qe7 32. Bf6 Qf8 33. e7 1-0"""
 
268
  )
269
 
270
+ btn_analizar = gr.Button("🧠 Analizar Partida", variant="primary", size="lg")
271
+ btn_limpiar = gr.Button("🗑️ Limpiar Todo", variant="secondary")
 
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  with gr.Column(scale=1):
274
  gr.Markdown("### 📊 Resultados del Análisis")
275
  image_output = gr.Image(
276
+ label="Análisis Gráfico",
277
  type="filepath",
278
  height=400
279
  )
280
+ report_output = gr.Markdown(
281
+ label="Reporte Detallado",
282
+ value="### 📋 Reporte de Análisis\n\n*Los resultados aparecerán aquí después del análisis...*"
283
  )
284
 
285
+ # Ejemplos
286
+ gr.Markdown("### 💡 Partidas de Ejemplo")
287
+ with gr.Row():
288
+ gr.Examples(
289
+ examples=[[
290
+ """[Event "Ejemplo Clásico"]
291
+ [White "Fischer"]
292
+ [Black "Spassky"]
293
+ [Result "1-0"]
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
+ 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
296
+ 9. h3 Nb8 10. d4 Nbd7 11. Nbd2 Bb7 12. Bc2 Re8 13. Nf1 Bf8 14. Ng3 g6 15. a4 c5
297
+ 16. d5 c4 17. Bg5 h6 18. Be3 Nc5 19. Qd2 h5 20. Bh6 Nh7 21. g3 Bd7 22. Rf1 Qc7
298
+ 23. Ne2 Rec8 24. Ng5 Nxg5 25. Bxg5 Bg7 26. f4 exf4 27. gxf4 f6 28. Bf3 Be5
299
+ 29. fxe5 fxe5 30. Bg5 Qd7 31. e6 Qe7 32. Bf6 Qf8 33. e7 1-0"""]],
300
+ inputs=pgn_input,
301
+ label="Partida de Ejemplo"
302
+ )
303
+
304
+ # Funcionalidades
305
+ def limpiar_todo():
306
+ return None, None, "### 📋 Reporte de Análisis\n\n*Los resultados aparecerán aquí después del análisis...*"
307
 
308
+ # Conectores
309
  btn_analizar.click(
310
+ fn=procesar_pgn,
311
+ inputs=[pgn_input, uploader],
312
+ outputs=[image_output, report_output]
313
  )
314
 
315
  btn_limpiar.click(
316
+ fn=limpiar_todo,
317
  inputs=[],
318
+ outputs=[uploader, pgn_input, report_output]
319
  )
320
 
321
  if __name__ == "__main__":
322
+ demo.launch(share=True)