MrSimple07 commited on
Commit
bb06bb4
Β·
1 Parent(s): 500ef8a
Files changed (2) hide show
  1. app.py +97 -103
  2. openings.py +73 -0
app.py CHANGED
@@ -11,6 +11,7 @@ import time
11
  import re
12
  import chess.polyglot
13
  import logging
 
14
 
15
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
  logger = logging.getLogger(__name__)
@@ -107,78 +108,6 @@ def parse_pgn_content(pgn_content):
107
  logger.info(f"Successfully parsed {len(games)} games")
108
  return games
109
 
110
- def get_opening_name_from_eco(eco_code):
111
- eco_openings = {
112
- 'A00': 'Uncommon Opening',
113
- 'A01': "Nimzowitsch-Larsen Attack",
114
- 'A02': "Bird's Opening",
115
- 'A03': "Bird's Opening: Dutch Variation",
116
- 'A04': 'Reti Opening',
117
- 'A05': 'Reti Opening: 1...Nf6',
118
- 'A06': 'Reti Opening: 2.b3',
119
- 'A10': 'English Opening',
120
- 'A15': 'English Opening: Anglo-Indian Defense',
121
- 'A20': 'English Opening: 1...e5',
122
- 'A30': 'English Opening: Symmetrical Variation',
123
- 'A40': 'Queen Pawn Game',
124
- 'A45': 'Indian Defense',
125
- 'A46': 'Indian Defense: 2.Nf3',
126
- 'A50': 'Indian Defense: Normal Variation',
127
- 'B00': 'Uncommon King Pawn Opening',
128
- 'B01': 'Scandinavian Defense',
129
- 'B02': "Alekhine's Defense",
130
- 'B03': "Alekhine's Defense: Four Pawns Attack",
131
- 'B10': 'Caro-Kann Defense',
132
- 'B12': 'Caro-Kann Defense: Advance Variation',
133
- 'B20': 'Sicilian Defense',
134
- 'B22': 'Sicilian Defense: Alapin Variation',
135
- 'B23': 'Sicilian Defense: Closed',
136
- 'B30': 'Sicilian Defense: 2...Nc6',
137
- 'B40': 'Sicilian Defense: French Variation',
138
- 'B50': 'Sicilian Defense: 2...d6',
139
- 'B90': 'Sicilian Defense: Najdorf',
140
- 'C00': 'French Defense',
141
- 'C02': 'French Defense: Advance Variation',
142
- 'C10': 'French Defense: Rubinstein Variation',
143
- 'C20': 'King Pawn Game',
144
- 'C30': "King's Gambit",
145
- 'C40': "King's Knight Opening",
146
- 'C41': 'Philidor Defense',
147
- 'C42': 'Russian Game (Petrov Defense)',
148
- 'C44': 'Scotch Game',
149
- 'C50': 'Italian Game',
150
- 'C60': 'Spanish Opening (Ruy Lopez)',
151
- 'C65': 'Spanish Opening: Berlin Defense',
152
- 'C70': 'Spanish Opening',
153
- 'C78': 'Spanish Opening: Morphy Defense',
154
- 'C80': 'Spanish Opening: Open Variation',
155
- 'D00': 'Queen Pawn Game',
156
- 'D02': 'Queen Pawn Game: 2.Nf3',
157
- 'D10': 'Slav Defense',
158
- 'D20': "Queen's Gambit Accepted",
159
- 'D30': "Queen's Gambit Declined",
160
- 'D50': "Queen's Gambit Declined: 4.Bg5",
161
- 'E00': 'Indian Defense',
162
- 'E10': 'Indian Defense: 3.Nf3',
163
- 'E20': 'Nimzo-Indian Defense',
164
- 'E30': 'Nimzo-Indian Defense: Leningrad Variation',
165
- 'E60': "King's Indian Defense",
166
- 'E70': "King's Indian Defense: Normal Variation",
167
- 'E90': "King's Indian Defense: Orthodox Variation",
168
- }
169
-
170
- return eco_openings.get(eco_code, f"Opening ECO {eco_code}")
171
-
172
- def detect_opening(game):
173
- opening = game.headers.get("Opening", "")
174
- eco = game.headers.get("ECO", "")
175
-
176
- if opening:
177
- return opening
178
- elif eco:
179
- return get_opening_name_from_eco(eco)
180
- else:
181
- return "Unknown Opening"
182
 
183
  def analyze_game_detailed(game, username):
184
  board = game.board()
@@ -382,11 +311,50 @@ def fetch_lichess_puzzles(themes, count=5):
382
  logger.info(f"Selected Lichess themes: {lichess_themes}")
383
 
384
  try:
 
385
  for theme in lichess_themes:
386
  if len(puzzles) >= count:
387
  break
388
 
389
- url = f"https://lichess.org/api/puzzle/daily"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  response = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
391
 
392
  if response.status_code == 200:
@@ -403,27 +371,22 @@ def fetch_lichess_puzzles(themes, count=5):
403
  'themes': daily['puzzle'].get('themes', []),
404
  'url': f"https://lichess.org/training/{puzzle_id}"
405
  })
406
- logger.info(f"Added puzzle {puzzle_id} with rating {daily['puzzle'].get('rating')}")
407
-
408
- time.sleep(0.5)
409
- except Exception as e:
410
- logger.error(f"Error fetching puzzles: {str(e)}")
411
 
 
412
  while len(puzzles) < count:
413
- try:
414
- url = "https://lichess.org/training"
415
- puzzles.append({
416
- 'id': f'puzzle_{len(puzzles)+1}',
417
- 'fen': '',
418
- 'moves': '',
419
- 'solution': [],
420
- 'rating': 1500,
421
- 'themes': ['Mixed'],
422
- 'url': url
423
- })
424
- logger.info(f"Added fallback puzzle link {len(puzzles)}")
425
- except:
426
- break
427
 
428
  logger.info(f"Total puzzles prepared: {len(puzzles)}")
429
  return puzzles[:count]
@@ -482,23 +445,44 @@ MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing! Aniq va amaliy maslahatlar bering."""
482
  logger.error(f"AI analysis failed: {str(e)}")
483
  return f"AI tahlil hozircha mavjud emas: {str(e)}"
484
 
485
- def analyze_games(username, pgn_file):
486
  logger.info("=== Starting game analysis ===")
487
- actual_username = username
 
488
 
489
- if username:
490
- pgn_content, error = get_user_games_from_chess_com(username)
 
491
  if error:
492
  logger.error(f"Failed to fetch games: {error}")
493
  return error, "", "", "", None, None, None, None, None
494
- actual_username = username
 
 
495
  elif pgn_file:
496
  logger.info("Processing uploaded PGN file")
497
- actual_username = "Player"
498
  pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  else:
500
  logger.error("No username or file provided")
501
- return "❌ Foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", "", "", None, None, None, None, None
502
 
503
  games = parse_pgn_content(pgn_content)
504
 
@@ -644,16 +628,26 @@ with gr.Blocks(title="Chess Study Plan Pro", theme=gr.themes.Soft()) as demo:
644
 
645
  with gr.Row():
646
  with gr.Column():
647
- username_input = gr.Textbox(
 
648
  label="Chess.com foydalanuvchi nomi",
649
  placeholder="Foydalanuvchi nomini kiriting",
650
  )
 
 
 
651
  pgn_upload = gr.File(
652
- label="πŸ“ Yoki PGN faylni yuklang",
653
  file_types=[".pgn"],
654
  type="binary"
655
  )
656
- analyze_btn = gr.Button("πŸ” To'liq tahlil qilish", variant="primary", size="lg")
 
 
 
 
 
 
657
 
658
  with gr.Row():
659
  stats_output = gr.Markdown(label="Statistika")
@@ -680,7 +674,7 @@ with gr.Blocks(title="Chess Study Plan Pro", theme=gr.themes.Soft()) as demo:
680
 
681
  analyze_btn.click(
682
  fn=analyze_games,
683
- inputs=[username_input, pgn_upload],
684
  outputs=[
685
  stats_output,
686
  ai_output,
 
11
  import re
12
  import chess.polyglot
13
  import logging
14
+ from openings import get_opening_name_from_eco, detect_opening
15
 
16
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
  logger = logging.getLogger(__name__)
 
108
  logger.info(f"Successfully parsed {len(games)} games")
109
  return games
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  def analyze_game_detailed(game, username):
113
  board = game.board()
 
311
  logger.info(f"Selected Lichess themes: {lichess_themes}")
312
 
313
  try:
314
+ # Fetch puzzles from Lichess database API
315
  for theme in lichess_themes:
316
  if len(puzzles) >= count:
317
  break
318
 
319
+ # Use the correct API endpoint with theme filter
320
+ url = f"https://lichess.org/api/puzzle/batch/mix?nb=1&themes={theme}"
321
+ response = requests.get(url, timeout=10, headers={
322
+ 'User-Agent': 'Mozilla/5.0',
323
+ 'Accept': 'application/x-ndjson'
324
+ })
325
+
326
+ if response.status_code == 200:
327
+ # Parse NDJSON response
328
+ lines = response.text.strip().split('\n')
329
+ for line in lines:
330
+ if len(puzzles) >= count:
331
+ break
332
+ try:
333
+ puzzle_data = eval(line) # or use json.loads(line)
334
+ puzzle_id = puzzle_data['puzzle']['id']
335
+
336
+ if not any(p['id'] == puzzle_id for p in puzzles):
337
+ puzzles.append({
338
+ 'id': puzzle_id,
339
+ 'fen': puzzle_data['puzzle'].get('fen', ''),
340
+ 'moves': puzzle_data['puzzle'].get('plays', ''),
341
+ 'solution': puzzle_data['puzzle'].get('solution', []),
342
+ 'rating': puzzle_data['puzzle'].get('rating', 1500),
343
+ 'themes': puzzle_data['puzzle'].get('themes', []),
344
+ 'url': f"https://lichess.org/training/{puzzle_id}"
345
+ })
346
+ logger.info(f"Added puzzle {puzzle_id} with rating {puzzle_data['puzzle'].get('rating')}")
347
+ except Exception as e:
348
+ logger.warning(f"Failed to parse puzzle line: {str(e)}")
349
+
350
+ time.sleep(0.5)
351
+ except Exception as e:
352
+ logger.error(f"Error fetching puzzles: {str(e)}")
353
+
354
+ # Fill remaining with daily puzzle if needed
355
+ if len(puzzles) < count:
356
+ try:
357
+ url = "https://lichess.org/api/puzzle/daily"
358
  response = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
359
 
360
  if response.status_code == 200:
 
371
  'themes': daily['puzzle'].get('themes', []),
372
  'url': f"https://lichess.org/training/{puzzle_id}"
373
  })
374
+ logger.info(f"Added daily puzzle {puzzle_id}")
375
+ except Exception as e:
376
+ logger.error(f"Error fetching daily puzzle: {str(e)}")
 
 
377
 
378
+ # Fill with fallback links if still not enough
379
  while len(puzzles) < count:
380
+ puzzles.append({
381
+ 'id': f'puzzle_{len(puzzles)+1}',
382
+ 'fen': '',
383
+ 'moves': '',
384
+ 'solution': [],
385
+ 'rating': 1500,
386
+ 'themes': ['Mixed'],
387
+ 'url': "https://lichess.org/training"
388
+ })
389
+ logger.info(f"Added fallback puzzle link {len(puzzles)}")
 
 
 
 
390
 
391
  logger.info(f"Total puzzles prepared: {len(puzzles)}")
392
  return puzzles[:count]
 
445
  logger.error(f"AI analysis failed: {str(e)}")
446
  return f"AI tahlil hozircha mavjud emas: {str(e)}"
447
 
448
+ def analyze_games(username_chesscom, pgn_file, username_pgn):
449
  logger.info("=== Starting game analysis ===")
450
+ actual_username = None
451
+ pgn_content = None
452
 
453
+ # Chess.com user
454
+ if username_chesscom:
455
+ pgn_content, error = get_user_games_from_chess_com(username_chesscom)
456
  if error:
457
  logger.error(f"Failed to fetch games: {error}")
458
  return error, "", "", "", None, None, None, None, None
459
+ actual_username = username_chesscom
460
+
461
+ # PGN file upload
462
  elif pgn_file:
463
  logger.info("Processing uploaded PGN file")
 
464
  pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
465
+
466
+ if username_pgn and username_pgn.strip():
467
+ actual_username = username_pgn.strip()
468
+ else:
469
+ # Try to extract username from first game headers
470
+ try:
471
+ first_game = chess.pgn.read_game(io.StringIO(pgn_content))
472
+ if first_game:
473
+ white = first_game.headers.get("White", "")
474
+ black = first_game.headers.get("Black", "")
475
+ actual_username = white if white else black if black else "Player"
476
+ else:
477
+ actual_username = "Player"
478
+ except:
479
+ actual_username = "Player"
480
+
481
+ logger.info(f"Extracted username from PGN: {actual_username}")
482
+
483
  else:
484
  logger.error("No username or file provided")
485
+ return "❌ Chess.com foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", "", "", None, None, None, None, None
486
 
487
  games = parse_pgn_content(pgn_content)
488
 
 
628
 
629
  with gr.Row():
630
  with gr.Column():
631
+ gr.Markdown("### 🌐 Chess.com dan tahlil")
632
+ username_chesscom = gr.Textbox(
633
  label="Chess.com foydalanuvchi nomi",
634
  placeholder="Foydalanuvchi nomini kiriting",
635
  )
636
+
637
+ with gr.Column():
638
+ gr.Markdown("### πŸ“ PGN fayl yuklash")
639
  pgn_upload = gr.File(
640
+ label="PGN faylni yuklang",
641
  file_types=[".pgn"],
642
  type="binary"
643
  )
644
+ username_pgn = gr.Textbox(
645
+ label="Foydalanuvchi nomi (PGN uchun)",
646
+ placeholder="PGN dagi o'yinchi nomi (ixtiyoriy)",
647
+ info="Bo'sh qoldiring, avtomatik aniqlanadi"
648
+ )
649
+
650
+ analyze_btn = gr.Button("πŸ” To'liq tahlil qilish", variant="primary", size="lg")
651
 
652
  with gr.Row():
653
  stats_output = gr.Markdown(label="Statistika")
 
674
 
675
  analyze_btn.click(
676
  fn=analyze_games,
677
+ inputs=[username_chesscom, pgn_upload, username_pgn],
678
  outputs=[
679
  stats_output,
680
  ai_output,
openings.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def get_opening_name_from_eco(eco_code):
3
+ eco_openings = {
4
+ 'A00': 'Uncommon Opening',
5
+ 'A01': "Nimzowitsch-Larsen Attack",
6
+ 'A02': "Bird's Opening",
7
+ 'A03': "Bird's Opening: Dutch Variation",
8
+ 'A04': 'Reti Opening',
9
+ 'A05': 'Reti Opening: 1...Nf6',
10
+ 'A06': 'Reti Opening: 2.b3',
11
+ 'A10': 'English Opening',
12
+ 'A15': 'English Opening: Anglo-Indian Defense',
13
+ 'A20': 'English Opening: 1...e5',
14
+ 'A30': 'English Opening: Symmetrical Variation',
15
+ 'A40': 'Queen Pawn Game',
16
+ 'A45': 'Indian Defense',
17
+ 'A46': 'Indian Defense: 2.Nf3',
18
+ 'A50': 'Indian Defense: Normal Variation',
19
+ 'B00': 'Uncommon King Pawn Opening',
20
+ 'B01': 'Scandinavian Defense',
21
+ 'B02': "Alekhine's Defense",
22
+ 'B03': "Alekhine's Defense: Four Pawns Attack",
23
+ 'B10': 'Caro-Kann Defense',
24
+ 'B12': 'Caro-Kann Defense: Advance Variation',
25
+ 'B20': 'Sicilian Defense',
26
+ 'B22': 'Sicilian Defense: Alapin Variation',
27
+ 'B23': 'Sicilian Defense: Closed',
28
+ 'B30': 'Sicilian Defense: 2...Nc6',
29
+ 'B40': 'Sicilian Defense: French Variation',
30
+ 'B50': 'Sicilian Defense: 2...d6',
31
+ 'B90': 'Sicilian Defense: Najdorf',
32
+ 'C00': 'French Defense',
33
+ 'C02': 'French Defense: Advance Variation',
34
+ 'C10': 'French Defense: Rubinstein Variation',
35
+ 'C20': 'King Pawn Game',
36
+ 'C30': "King's Gambit",
37
+ 'C40': "King's Knight Opening",
38
+ 'C41': 'Philidor Defense',
39
+ 'C42': 'Russian Game (Petrov Defense)',
40
+ 'C44': 'Scotch Game',
41
+ 'C50': 'Italian Game',
42
+ 'C60': 'Spanish Opening (Ruy Lopez)',
43
+ 'C65': 'Spanish Opening: Berlin Defense',
44
+ 'C70': 'Spanish Opening',
45
+ 'C78': 'Spanish Opening: Morphy Defense',
46
+ 'C80': 'Spanish Opening: Open Variation',
47
+ 'D00': 'Queen Pawn Game',
48
+ 'D02': 'Queen Pawn Game: 2.Nf3',
49
+ 'D10': 'Slav Defense',
50
+ 'D20': "Queen's Gambit Accepted",
51
+ 'D30': "Queen's Gambit Declined",
52
+ 'D50': "Queen's Gambit Declined: 4.Bg5",
53
+ 'E00': 'Indian Defense',
54
+ 'E10': 'Indian Defense: 3.Nf3',
55
+ 'E20': 'Nimzo-Indian Defense',
56
+ 'E30': 'Nimzo-Indian Defense: Leningrad Variation',
57
+ 'E60': "King's Indian Defense",
58
+ 'E70': "King's Indian Defense: Normal Variation",
59
+ 'E90': "King's Indian Defense: Orthodox Variation",
60
+ }
61
+
62
+ return eco_openings.get(eco_code, f"Opening ECO {eco_code}")
63
+
64
+ def detect_opening(game):
65
+ opening = game.headers.get("Opening", "")
66
+ eco = game.headers.get("ECO", "")
67
+
68
+ if opening:
69
+ return opening
70
+ elif eco:
71
+ return get_opening_name_from_eco(eco)
72
+ else:
73
+ return "Unknown Opening"