MrSimple07 commited on
Commit
c30ca89
Β·
1 Parent(s): 054c349

added in the right github structure

Browse files
app.py CHANGED
@@ -6,9 +6,8 @@ import requests
6
  import google.generativeai as genai
7
  from collections import defaultdict
8
  import os
9
- import time
10
  import logging
11
- import csv
12
 
13
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
14
  logger = logging.getLogger(__name__)
@@ -17,478 +16,7 @@ GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
17
  genai.configure(api_key=GEMINI_API_KEY)
18
  model = genai.GenerativeModel('gemini-2.0-flash-exp')
19
 
20
- def load_opening_database():
21
- openings = {}
22
- try:
23
- with open('Chess Opening Reference - Sheet1.csv', 'r', encoding='utf-8') as f:
24
- reader = csv.DictReader(f)
25
- for row in reader:
26
- eco = row.get('ECO Code', '').strip()
27
- name = row.get('Name', '').strip()
28
- if eco and name:
29
- openings[eco] = name
30
- except Exception as e:
31
- logger.error(f"Failed to load openings CSV: {e}")
32
- return openings
33
 
34
- OPENING_DB = load_opening_database()
35
-
36
- def detect_opening(game):
37
- eco = game.headers.get("ECO", "")
38
- opening = game.headers.get("Opening", "")
39
-
40
- if eco and eco in OPENING_DB:
41
- return OPENING_DB[eco]
42
- elif opening:
43
- return opening
44
- else:
45
- return "Unknown Opening"
46
-
47
- def get_user_games_from_chess_com(username):
48
- try:
49
- logger.info(f"Fetching games for: {username}")
50
- username = username.strip().lower()
51
-
52
- user_url = f"https://api.chess.com/pub/player/{username}"
53
- response = requests.get(user_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
54
-
55
- if response.status_code != 200:
56
- return None, f"❌ Foydalanuvchi topilmadi: {username}"
57
-
58
- archives_url = f"https://api.chess.com/pub/player/{username}/games/archives"
59
- response = requests.get(archives_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
60
-
61
- if response.status_code != 200:
62
- return None, "❌ O'yinlar arxivi topilmadi."
63
-
64
- archives = response.json()['archives']
65
- if not archives:
66
- return None, "❌ O'yinlar topilmadi."
67
-
68
- all_games = []
69
- for archive_url in reversed(archives[-3:]):
70
- time.sleep(0.3)
71
- response = requests.get(archive_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
72
- if response.status_code == 200:
73
- games = response.json()['games']
74
- all_games.extend(games)
75
- if len(all_games) >= 50:
76
- break
77
-
78
- rapid_games = [g for g in all_games if g.get('time_class') in ['rapid', 'blitz']]
79
- if not rapid_games:
80
- rapid_games = all_games[:50]
81
-
82
- pgn_list = [g['pgn'] for g in rapid_games[:50] if 'pgn' in g]
83
-
84
- if not pgn_list:
85
- return None, "❌ PGN formatdagi o'yinlar topilmadi."
86
-
87
- return pgn_list, None
88
-
89
- except Exception as e:
90
- logger.error(f"Error fetching games: {str(e)}")
91
- return None, f"❌ Xatolik: {str(e)}"
92
-
93
- def parse_pgn_content(pgn_content):
94
- games = []
95
- if isinstance(pgn_content, list):
96
- for pgn_text in pgn_content:
97
- try:
98
- game = chess.pgn.read_game(io.StringIO(pgn_text))
99
- if game:
100
- games.append(game)
101
- except:
102
- pass
103
- else:
104
- pgn_io = io.StringIO(pgn_content)
105
- while True:
106
- try:
107
- game = chess.pgn.read_game(pgn_io)
108
- if game is None:
109
- break
110
- games.append(game)
111
- except:
112
- break
113
-
114
- return games
115
-
116
- def analyze_game_detailed(game, username):
117
- board = game.board()
118
- mistakes = []
119
- move_number = 0
120
-
121
- white_player = game.headers.get("White", "").strip().lower()
122
- black_player = game.headers.get("Black", "").strip().lower()
123
- username_lower = username.strip().lower()
124
-
125
- user_color = None
126
- if username_lower == white_player:
127
- user_color = chess.WHITE
128
- elif username_lower == black_player:
129
- user_color = chess.BLACK
130
-
131
- result = game.headers.get("Result", "*")
132
- opening = detect_opening(game)
133
-
134
- user_result = None
135
- if user_color is not None and result != "*":
136
- if result == "1-0":
137
- user_result = "win" if user_color == chess.WHITE else "loss"
138
- elif result == "0-1":
139
- user_result = "win" if user_color == chess.BLACK else "loss"
140
- elif result == "1/2-1/2":
141
- user_result = "draw"
142
-
143
- material_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9}
144
-
145
- def count_material(board):
146
- total = 0
147
- for piece_type in material_values:
148
- total += len(board.pieces(piece_type, chess.WHITE)) * material_values[piece_type]
149
- total -= len(board.pieces(piece_type, chess.BLACK)) * material_values[piece_type]
150
- return total
151
-
152
- for move in game.mainline_moves():
153
- move_number += 1
154
-
155
- if user_color is not None and board.turn != user_color:
156
- board.push(move)
157
- continue
158
-
159
- material_before = count_material(board)
160
- board.push(move)
161
- material_after = count_material(board)
162
-
163
- material_loss = abs(material_after - material_before) if board.turn == chess.WHITE else abs(material_before - material_after)
164
-
165
- moved_piece = board.piece_at(move.to_square)
166
- mistake_type = None
167
-
168
- if material_loss >= 3:
169
- mistake_type = 'blunder'
170
- elif material_loss >= 1:
171
- mistake_type = 'mistake'
172
- elif moved_piece and board.is_attacked_by(not board.turn, move.to_square):
173
- attackers = len(board.attackers(not board.turn, move.to_square))
174
- defenders = len(board.attackers(board.turn, move.to_square))
175
- if attackers > defenders:
176
- mistake_type = 'hanging_piece'
177
-
178
- if move_number <= 10:
179
- phase = 'opening'
180
- elif len(board.piece_map()) <= 10:
181
- phase = 'endgame'
182
- else:
183
- phase = 'middlegame'
184
-
185
- if mistake_type:
186
- mistakes.append({
187
- 'type': mistake_type,
188
- 'phase': phase,
189
- 'move_number': move_number
190
- })
191
-
192
- return {
193
- 'mistakes': mistakes,
194
- 'opening': opening,
195
- 'result': result,
196
- 'user_color': user_color,
197
- 'user_result': user_result
198
- }
199
-
200
- def categorize_mistakes(all_analyses):
201
- if not all_analyses:
202
- return []
203
-
204
- blunders = 0
205
- regular_mistakes = 0
206
- hanging = 0
207
- opening = 0
208
- middlegame = 0
209
- endgame = 0
210
-
211
- for analysis in all_analyses:
212
- for mistake in analysis.get('mistakes', []):
213
- mistake_type = mistake.get('type')
214
- phase = mistake.get('phase')
215
-
216
- if mistake_type == 'blunder':
217
- blunders += 1
218
- elif mistake_type == 'mistake':
219
- regular_mistakes += 1
220
- elif mistake_type == 'hanging_piece':
221
- hanging += 1
222
-
223
- if phase == 'opening':
224
- opening += 1
225
- elif phase == 'middlegame':
226
- middlegame += 1
227
- elif phase == 'endgame':
228
- endgame += 1
229
-
230
- total = blunders + regular_mistakes + hanging + opening + middlegame + endgame
231
-
232
- if total == 0:
233
- return []
234
-
235
- weaknesses = []
236
-
237
- if blunders > 0:
238
- weaknesses.append({
239
- 'category': "Qo'pol xatolar",
240
- 'count': blunders,
241
- 'percentage': (blunders / total * 100)
242
- })
243
-
244
- if regular_mistakes > 0:
245
- weaknesses.append({
246
- 'category': 'Kichik xatolar',
247
- 'count': regular_mistakes,
248
- 'percentage': (regular_mistakes / total * 100)
249
- })
250
-
251
- if hanging > 0:
252
- weaknesses.append({
253
- 'category': 'Himoyasiz qoldirish',
254
- 'count': hanging,
255
- 'percentage': (hanging / total * 100)
256
- })
257
-
258
- if opening > 0:
259
- weaknesses.append({
260
- 'category': 'Debyut xatolari',
261
- 'count': opening,
262
- 'percentage': (opening / total * 100)
263
- })
264
-
265
- if middlegame > 0:
266
- weaknesses.append({
267
- 'category': "O'rta o'yin xatolari",
268
- 'count': middlegame,
269
- 'percentage': (middlegame / total * 100)
270
- })
271
-
272
- if endgame > 0:
273
- weaknesses.append({
274
- 'category': 'Endshpil xatolari',
275
- 'count': endgame,
276
- 'percentage': (endgame / total * 100)
277
- })
278
-
279
- weaknesses.sort(key=lambda x: x['count'], reverse=True)
280
-
281
- return weaknesses
282
-
283
- def fetch_lichess_puzzles(themes, count=5):
284
- puzzles = []
285
-
286
- try:
287
- url = "https://lichess.org/training"
288
- for i in range(count):
289
- puzzles.append({
290
- 'id': f'puzzle_{i+1}',
291
- 'url': url,
292
- 'theme': themes[0] if themes else 'Tactics',
293
- 'rating': 1500
294
- })
295
- except Exception as e:
296
- logger.error(f"Error generating puzzle links: {str(e)}")
297
-
298
- return puzzles
299
-
300
- def get_comprehensive_analysis(weaknesses, opening_stats, color_stats, total_games):
301
- weakness_text = "\n".join([f"- {w['category']}: {w['count']} marta ({w['percentage']:.1f}%)" for w in weaknesses])
302
-
303
- opening_text = "\n".join([f"- {opening}: {stats['total']} o'yin (G'alabalar: {stats['wins']}, Yutqazishlar: {stats['losses']}, Duranglar: {stats['draws']})"
304
- for opening, stats in list(opening_stats.items())[:5]])
305
-
306
- color_text = f"Oq rangda: {color_stats['white']['wins']}G-{color_stats['white']['losses']}Y-{color_stats['white']['draws']}D\n"
307
- color_text += f"Qora rangda: {color_stats['black']['wins']}G-{color_stats['black']['losses']}Y-{color_stats['black']['draws']}D"
308
-
309
- prompt = f"""Siz professional ΡˆΠ°Ρ…ΠΌΠ°Ρ‚ murabbiy va tahlilchisiz. O'yinchining {total_games} ta o'yinini tahlil qildingiz.
310
-
311
- STATISTIKA:
312
-
313
- Zaif tomonlar:
314
- {weakness_text}
315
-
316
- Eng ko'p o'ynaladigan debyutlar:
317
- {opening_text}
318
-
319
- Rang bo'yicha natijalar:
320
- {color_text}
321
-
322
- Quyidagilarni taqdim eting:
323
-
324
- 1. **ZAIF TOMONLAR TAHLILI**: Har bir zaif tomonni chuqur tahlil qiling va nima uchun bu muammo kelib chiqayotganini tushuntiring.
325
-
326
- 2. **SHAXSIY O'QUV REJASI**: Kundalik mashg'ulotlar rejasini tuzing:
327
- - Har kuni nechta masala yechish kerak va qanday turdagi masalalar
328
- - Qaysi debyutlarni o'rganish kerak
329
- - Qaysi o'yin bosqichiga ko'proq e'tibor berish kerak
330
- - Kompyuter yoki botlar bilan qanday mashq qilish kerak
331
-
332
- 3. **TAVSIYA ETILGAN RESURSLAR**:
333
- - Kitoblar (muallif va nom bilan)
334
- - Onlayn kurslar (Uzchess, Chess.com, Lichess)
335
- - YouTube kanallari
336
- - Mashq uchun maxsus botlar yoki dasturlar
337
-
338
- 4. **DEBYUT TAVSIYALARI**: Statistikaga asoslanib, qaysi debyutlarni davom ettirish va qaysilarini o'zgartirish kerak.
339
-
340
- 5. **MOTIVATSION XULOSA**: Qisqa va rag'batlantiruvchi xulosa.
341
-
342
- MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing! Aniq va amaliy maslahatlar bering."""
343
-
344
- try:
345
- response = model.generate_content(prompt)
346
- return response.text
347
- except Exception as e:
348
- logger.error(f"AI analysis failed: {str(e)}")
349
- return f"AI tahlil hozircha mavjud emas: {str(e)}"
350
-
351
- def analyze_games(username_chesscom, pgn_file, username_pgn):
352
- actual_username = None
353
- pgn_content = None
354
-
355
- if username_chesscom:
356
- pgn_content, error = get_user_games_from_chess_com(username_chesscom)
357
- if error:
358
- return error, "", "", "", None, None, None, None, None
359
- actual_username = username_chesscom
360
-
361
- elif pgn_file:
362
- pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
363
-
364
- if username_pgn and username_pgn.strip():
365
- actual_username = username_pgn.strip()
366
- else:
367
- try:
368
- first_game = chess.pgn.read_game(io.StringIO(pgn_content))
369
- if first_game:
370
- white = first_game.headers.get("White", "")
371
- black = first_game.headers.get("Black", "")
372
- actual_username = white if white else black if black else "Player"
373
- else:
374
- actual_username = "Player"
375
- except:
376
- actual_username = "Player"
377
-
378
- else:
379
- return "❌ Chess.com foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", "", "", None, None, None, None, None
380
-
381
- games = parse_pgn_content(pgn_content)
382
-
383
- if not games:
384
- return "❌ O'yinlar topilmadi yoki tahlil qilinmadi", "", "", "", None, None, None, None, None
385
-
386
- all_analyses = []
387
- opening_stats = defaultdict(lambda: {'wins': 0, 'losses': 0, 'draws': 0, 'total': 0})
388
- color_stats = {
389
- 'white': {'wins': 0, 'losses': 0, 'draws': 0},
390
- 'black': {'wins': 0, 'losses': 0, 'draws': 0}
391
- }
392
-
393
- for game in games:
394
- analysis = analyze_game_detailed(game, actual_username)
395
- all_analyses.append(analysis)
396
-
397
- opening = analysis['opening']
398
- user_result = analysis.get('user_result')
399
- user_color = analysis['user_color']
400
-
401
- if user_color is not None:
402
- opening_stats[opening]['total'] += 1
403
-
404
- if user_result == 'win':
405
- opening_stats[opening]['wins'] += 1
406
- color_key = 'white' if user_color == chess.WHITE else 'black'
407
- color_stats[color_key]['wins'] += 1
408
- elif user_result == 'loss':
409
- opening_stats[opening]['losses'] += 1
410
- color_key = 'white' if user_color == chess.WHITE else 'black'
411
- color_stats[color_key]['losses'] += 1
412
- elif user_result == 'draw':
413
- opening_stats[opening]['draws'] += 1
414
- color_key = 'white' if user_color == chess.WHITE else 'black'
415
- color_stats[color_key]['draws'] += 1
416
-
417
- weaknesses = categorize_mistakes(all_analyses)
418
-
419
- all_mistakes = []
420
- for analysis in all_analyses:
421
- all_mistakes.extend(analysis.get('mistakes', []))
422
-
423
- stats_report = f"## πŸ“Š {len(games)} ta o'yin tahlili\n\n"
424
- stats_report += f"**Jami xatolar:** {len(all_mistakes)} ta\n\n"
425
-
426
- stats_report += "### 🎯 Eng zaif 3 tomoningiz:\n\n"
427
- if weaknesses:
428
- for i, w in enumerate(weaknesses[:3], 1):
429
- stats_report += f"**{i}. {w['category']}** - {w['count']} marta ({w['percentage']:.1f}%)\n"
430
- else:
431
- stats_report += "Xatolar topilmadi yoki tahlil qilinmadi.\n"
432
-
433
- opening_report = "\n\n## 🎭 Debyut Statistikasi\n\n"
434
- sorted_openings = sorted(opening_stats.items(), key=lambda x: x[1]['total'], reverse=True)[:10]
435
-
436
- for opening, stats in sorted_openings:
437
- total = stats['total']
438
- wins = stats['wins']
439
- losses = stats['losses']
440
- draws = stats['draws']
441
- win_rate = (wins / total * 100) if total > 0 else 0
442
-
443
- opening_report += f"**{opening}** ({total} o'yin)\n"
444
- opening_report += f"- G'alabalar: {wins} ({win_rate:.1f}%) | Yutqazishlar: {losses} | Duranglar: {draws}\n\n"
445
-
446
- color_report = "\n\n## βšͺ⚫ Rang bo'yicha natijalar\n\n"
447
-
448
- white_total = sum(color_stats['white'].values())
449
- black_total = sum(color_stats['black'].values())
450
-
451
- if white_total > 0:
452
- white_wr = color_stats['white']['wins'] / white_total * 100
453
- color_report += f"**Oq figuralar bilan:**\n"
454
- color_report += f"- G'alabalar: {color_stats['white']['wins']} ({white_wr:.1f}%)\n"
455
- color_report += f"- Yutqazishlar: {color_stats['white']['losses']}\n"
456
- color_report += f"- Duranglar: {color_stats['white']['draws']}\n\n"
457
-
458
- if black_total > 0:
459
- black_wr = color_stats['black']['wins'] / black_total * 100
460
- color_report += f"**Qora figuralar bilan:**\n"
461
- color_report += f"- G'alabalar: {color_stats['black']['wins']} ({black_wr:.1f}%)\n"
462
- color_report += f"- Yutqazishlar: {color_stats['black']['losses']}\n"
463
- color_report += f"- Duranglar: {color_stats['black']['draws']}\n"
464
-
465
- full_report = stats_report + opening_report + color_report
466
-
467
- ai_analysis = get_comprehensive_analysis(weaknesses, opening_stats, color_stats, len(games))
468
- ai_report = f"## πŸ€– AI Murabbiy: To'liq Tahlil va O'quv Rejasi\n\n{ai_analysis}"
469
-
470
- weakness_themes = [w['category'] for w in weaknesses[:3]]
471
- puzzles = fetch_lichess_puzzles(weakness_themes, count=5)
472
-
473
- puzzle_text = "## 🧩 Sizning shaxsiy masalalaringiz\n\n"
474
- puzzle_text += "Masalalarni yechish uchun quyidagi havoladan foydalaning:\n\n"
475
- for i, puzzle in enumerate(puzzles, 1):
476
- theme = puzzle.get('theme', 'Tactics')
477
- url = puzzle.get('url', 'https://lichess.org/training')
478
- puzzle_text += f"**Puzzle {i}: {theme}**\n"
479
- puzzle_text += f"- [Lichess Training]({url})\n\n"
480
-
481
- return (
482
- full_report,
483
- ai_report,
484
- puzzle_text,
485
- "",
486
- None,
487
- "",
488
- None,
489
- "",
490
- None
491
- )
492
 
493
  with gr.Blocks(title="Chess Study Plan Pro", theme=gr.themes.Soft()) as demo:
494
  gr.Markdown("""
 
6
  import google.generativeai as genai
7
  from collections import defaultdict
8
  import os
 
9
  import logging
10
+ from core.core import analyze_games
11
 
12
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
  logger = logging.getLogger(__name__)
 
16
  genai.configure(api_key=GEMINI_API_KEY)
17
  model = genai.GenerativeModel('gemini-2.0-flash-exp')
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  with gr.Blocks(title="Chess Study Plan Pro", theme=gr.themes.Soft()) as demo:
22
  gr.Markdown("""
config.py DELETED
@@ -1,97 +0,0 @@
1
- import os
2
-
3
- # ============= API SOZLAMALARI =============
4
- GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', '')
5
-
6
- # ============= STOCKFISH SOZLAMALARI =============
7
- # Stockfish dasturining yo'lini kiriting
8
- # Windows: "C:/stockfish/stockfish.exe"
9
- # Linux/Mac: "/usr/local/bin/stockfish"
10
- STOCKFISH_PATH = os.environ.get('STOCKFISH_PATH', 'stockfish')
11
- STOCKFISH_DEPTH = 15 # Tahlil chuqurligi (15-20 yetarli)
12
- STOCKFISH_TIME = 0.1 # Har bir harakat uchun vaqt (sekundlarda)
13
-
14
- # ============= O'YIN SOZLAMALARI =============
15
- MAX_GAMES_TO_ANALYZE = 50 # Tahlil qilinadigan o'yinlar soni
16
- BLUNDER_THRESHOLD = 200 # Qo'pol xato chegarasi (santipawn)
17
- MISTAKE_THRESHOLD = 100 # Xato chegarasi (santipawn)
18
-
19
- # ============= CHESS.COM API =============
20
- CHESS_COM_API_BASE = "https://api.chess.com/pub"
21
- REQUEST_TIMEOUT = 10 # Sekundlarda
22
-
23
- # ============= LICHESS API =============
24
- LICHESS_API_BASE = "https://lichess.org/api"
25
-
26
- # ============= GEMINI PROMPT SHABLONLARI =============
27
-
28
- WEAKNESS_ANALYSIS_PROMPT = """Siz ΡˆΠ°Ρ…ΠΌΠ°Ρ‚ murabbiysiz. O'yinchi o'zining so'nggi o'yinlarida quyidagi zaif tomonlarni ko'rsatdi:
29
-
30
- {weakness_text}
31
-
32
- Har bir zaif tomonni oddiy va rag'batlantiruvchi tilda tushuntiring (har biri uchun 2-3 jumla). Yaxshilash uchun amaliy maslahatlar bering. Do'stona va motivatsion bo'ling.
33
-
34
- MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing!"""
35
-
36
- MISTAKE_EXPLANATION_PROMPT = """Siz ΡˆΠ°Ρ…ΠΌΠ°Ρ‚ o'qituvchisisiz. O'yinchining quyidagi harakati tahlil qilindi:
37
-
38
- Harakat: {move}
39
- Pozitsiya: {position}
40
- Xato turi: {mistake_type}
41
- Baholash farqi: {eval_diff} santipawn
42
-
43
- Bu xatoni oddiy tilda tushuntiring va qanday qilib oldini olish mumkinligini aytib bering. 2-3 jumlada javob bering.
44
-
45
- MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing!"""
46
-
47
- # ============= XATO KATEGORIYALARI =============
48
-
49
- MISTAKE_CATEGORIES = {
50
- 'blunder': 'Qo\'pol xatolar',
51
- 'mistake': 'Kichik xatolar',
52
- 'hanging_piece': 'Himoyasiz qoldirish',
53
- 'tactical_miss': 'Taktik imkoniyatni o\'tkazib yuborish',
54
- 'positional_error': 'Pozitsion xatolar',
55
- 'time_trouble': 'Vaqt muammolari',
56
- 'opening_mistake': 'Debyut xatolari',
57
- 'middlegame_mistake': 'O\'rta o\'yin xatolari',
58
- 'endgame_mistake': 'Endshpil xatolari'
59
- }
60
-
61
- # ============= INTERFACE MATNLARI =============
62
-
63
- UI_TEXTS = {
64
- 'title': 'β™ŸοΈ Shaxsiy Π¨Π°Ρ…ΠΌΠ°Ρ‚ O\'quv Rejasi Generatori',
65
- 'description': """Chess.com foydalanuvchi nomingizni kiriting va quyidagilarni oling:
66
- - πŸ“Š Eng zaif 3 tomoningizni tahlil
67
- - πŸ€– AI murabbiy tushuntirishlari
68
- - 🧩 Yaxshilash uchun 5 ta shaxsiy masala""",
69
- 'username_label': 'Chess.com foydalanuvchi nomi',
70
- 'username_placeholder': 'Foydalanuvchi nomini kiriting',
71
- 'analyze_button': 'πŸ” O\'yinlarni tahlil qilish',
72
- 'upload_pgn': 'πŸ“ PGN faylni yuklash',
73
- 'weakness_title': 'πŸ“Š Zaif Tomonlar',
74
- 'explanation_title': 'πŸ€– AI Murabbiy Tahlili',
75
- 'puzzles_title': '🧩 Sizning Shaxsiy O\'quv Rejangiz',
76
- 'instructions': """
77
- ### πŸ“ Qanday foydalanish:
78
- 1. Chess.com foydalanuvchi nomingizni kiriting
79
- 2. "O'yinlarni tahlil qilish" tugmasini bosing
80
- 3. Dastur so'nggi 50 ta blitz o'yiningizni tahlil qiladi
81
- 4. Zaif tomonlaringiz va shaxsiy masalalarni ko'ring
82
-
83
- **Yoki** PGN faylni yuklashingiz mumkin
84
- """
85
- }
86
-
87
- # ============= PUZZLE TEMALARI =============
88
-
89
- PUZZLE_THEMES = {
90
- 'blunder': 'mix',
91
- 'hanging_piece': 'hangingPiece',
92
- 'tactical_miss': 'fork,pin,skewer',
93
- 'positional_error': 'advantage',
94
- 'opening_mistake': 'opening',
95
- 'middlegame_mistake': 'middlegame',
96
- 'endgame_mistake': 'endgame'
97
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/ai_integration.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import os
4
+ from collections import defaultdict
5
+ import logging
6
+ import genai
7
+
8
+ GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
9
+ genai.configure(api_key=GEMINI_API_KEY)
10
+ model = genai.GenerativeModel('gemini-2.0-flash-exp')
11
+
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def get_comprehensive_analysis(weaknesses, opening_stats, color_stats, total_games):
17
+ weakness_text = "\n".join([f"- {w['category']}: {w['count']} marta ({w['percentage']:.1f}%)" for w in weaknesses])
18
+
19
+ opening_text = "\n".join([f"- {opening}: {stats['total']} o'yin (G'alabalar: {stats['wins']}, Yutqazishlar: {stats['losses']}, Duranglar: {stats['draws']})"
20
+ for opening, stats in list(opening_stats.items())[:5]])
21
+
22
+ color_text = f"Oq rangda: {color_stats['white']['wins']}G-{color_stats['white']['losses']}Y-{color_stats['white']['draws']}D\n"
23
+ color_text += f"Qora rangda: {color_stats['black']['wins']}G-{color_stats['black']['losses']}Y-{color_stats['black']['draws']}D"
24
+
25
+ prompt = f"""Siz professional ΡˆΠ°Ρ…ΠΌΠ°Ρ‚ murabbiy va tahlilchisiz. O'yinchining {total_games} ta o'yinini tahlil qildingiz.
26
+
27
+ STATISTIKA:
28
+
29
+ Zaif tomonlar:
30
+ {weakness_text}
31
+
32
+ Eng ko'p o'ynaladigan debyutlar:
33
+ {opening_text}
34
+
35
+ Rang bo'yicha natijalar:
36
+ {color_text}
37
+
38
+ Quyidagilarni taqdim eting:
39
+
40
+ 1. **ZAIF TOMONLAR TAHLILI**: Har bir zaif tomonni chuqur tahlil qiling va nima uchun bu muammo kelib chiqayotganini tushuntiring.
41
+
42
+ 2. **SHAXSIY O'QUV REJASI**: Kundalik mashg'ulotlar rejasini tuzing:
43
+ - Har kuni nechta masala yechish kerak va qanday turdagi masalalar
44
+ - Qaysi debyutlarni o'rganish kerak
45
+ - Qaysi o'yin bosqichiga ko'proq e'tibor berish kerak
46
+ - Kompyuter yoki botlar bilan qanday mashq qilish kerak
47
+
48
+ 3. **TAVSIYA ETILGAN RESURSLAR**:
49
+ - Kitoblar (muallif va nom bilan)
50
+ - Onlayn kurslar (Uzchess, Chess.com, Lichess)
51
+ - YouTube kanallari
52
+ - Mashq uchun maxsus botlar yoki dasturlar
53
+
54
+ 4. **DEBYUT TAVSIYALARI**: Statistikaga asoslanib, qaysi debyutlarni davom ettirish va qaysilarini o'zgartirish kerak.
55
+
56
+ 5. **MOTIVATSION XULOSA**: Qisqa va rag'batlantiruvchi xulosa.
57
+
58
+ MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing! Aniq va amaliy maslahatlar bering."""
59
+
60
+ try:
61
+ response = model.generate_content(prompt)
62
+ return response.text
63
+ except Exception as e:
64
+ logger.error(f"AI analysis failed: {str(e)}")
65
+ return f"AI tahlil hozircha mavjud emas: {str(e)}"
core/chess_api.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import requests
3
+ import time
4
+
5
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def get_user_games_from_chess_com(username):
10
+ try:
11
+ logger.info(f"Fetching games for: {username}")
12
+ username = username.strip().lower()
13
+
14
+ user_url = f"https://api.chess.com/pub/player/{username}"
15
+ response = requests.get(user_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
16
+
17
+ if response.status_code != 200:
18
+ return None, f"❌ Foydalanuvchi topilmadi: {username}"
19
+
20
+ archives_url = f"https://api.chess.com/pub/player/{username}/games/archives"
21
+ response = requests.get(archives_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
22
+
23
+ if response.status_code != 200:
24
+ return None, "❌ O'yinlar arxivi topilmadi."
25
+
26
+ archives = response.json()['archives']
27
+ if not archives:
28
+ return None, "❌ O'yinlar topilmadi."
29
+
30
+ all_games = []
31
+ for archive_url in reversed(archives[-3:]):
32
+ time.sleep(0.3)
33
+ response = requests.get(archive_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
34
+ if response.status_code == 200:
35
+ games = response.json()['games']
36
+ all_games.extend(games)
37
+ if len(all_games) >= 50:
38
+ break
39
+
40
+ rapid_games = [g for g in all_games if g.get('time_class') in ['rapid', 'blitz']]
41
+ if not rapid_games:
42
+ rapid_games = all_games[:50]
43
+
44
+ pgn_list = [g['pgn'] for g in rapid_games[:50] if 'pgn' in g]
45
+
46
+ if not pgn_list:
47
+ return None, "❌ PGN formatdagi o'yinlar topilmadi."
48
+
49
+ return pgn_list, None
50
+
51
+ except Exception as e:
52
+ logger.error(f"Error fetching games: {str(e)}")
53
+ return None, f"❌ Xatolik: {str(e)}"
54
+
55
+
56
+ def fetch_lichess_puzzles(themes, count=5):
57
+ puzzles = []
58
+
59
+ try:
60
+ url = "https://lichess.org/training"
61
+ for i in range(count):
62
+ puzzles.append({
63
+ 'id': f'puzzle_{i+1}',
64
+ 'url': url,
65
+ 'theme': themes[0] if themes else 'Tactics',
66
+ 'rating': 1500
67
+ })
68
+ except Exception as e:
69
+ logger.error(f"Error generating puzzle links: {str(e)}")
70
+
71
+ return puzzles
core/core.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import chess
3
+ import chess.pgn
4
+ import io
5
+ import re
6
+ from collections import defaultdict
7
+ from core.ai_integration import get_comprehensive_analysis
8
+ from core.openings import detect_opening, load_opening_database
9
+ from core.chess_api import get_user_games_from_chess_com, fetch_lichess_puzzles
10
+
11
+
12
+
13
+ def analyze_games(username_chesscom, pgn_file, username_pgn):
14
+ actual_username = None
15
+ pgn_content = None
16
+
17
+ if username_chesscom:
18
+ pgn_content, error = get_user_games_from_chess_com(username_chesscom)
19
+ if error:
20
+ return error, "", "", "", None, None, None, None, None
21
+ actual_username = username_chesscom
22
+
23
+ elif pgn_file:
24
+ pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
25
+
26
+ if username_pgn and username_pgn.strip():
27
+ actual_username = username_pgn.strip()
28
+ else:
29
+ try:
30
+ first_game = chess.pgn.read_game(io.StringIO(pgn_content))
31
+ if first_game:
32
+ white = first_game.headers.get("White", "")
33
+ black = first_game.headers.get("Black", "")
34
+ actual_username = white if white else black if black else "Player"
35
+ else:
36
+ actual_username = "Player"
37
+ except:
38
+ actual_username = "Player"
39
+
40
+ else:
41
+ return "❌ Chess.com foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", "", "", None, None, None, None, None
42
+
43
+ games = parse_pgn_content(pgn_content)
44
+
45
+ if not games:
46
+ return "❌ O'yinlar topilmadi yoki tahlil qilinmadi", "", "", "", None, None, None, None, None
47
+
48
+ all_analyses = []
49
+ opening_stats = defaultdict(lambda: {'wins': 0, 'losses': 0, 'draws': 0, 'total': 0})
50
+ color_stats = {
51
+ 'white': {'wins': 0, 'losses': 0, 'draws': 0},
52
+ 'black': {'wins': 0, 'losses': 0, 'draws': 0}
53
+ }
54
+
55
+ for game in games:
56
+ analysis = analyze_game_detailed(game, actual_username)
57
+ all_analyses.append(analysis)
58
+
59
+ opening = analysis['opening']
60
+ user_result = analysis.get('user_result')
61
+ user_color = analysis['user_color']
62
+
63
+ if user_color is not None:
64
+ opening_stats[opening]['total'] += 1
65
+
66
+ if user_result == 'win':
67
+ opening_stats[opening]['wins'] += 1
68
+ color_key = 'white' if user_color == chess.WHITE else 'black'
69
+ color_stats[color_key]['wins'] += 1
70
+ elif user_result == 'loss':
71
+ opening_stats[opening]['losses'] += 1
72
+ color_key = 'white' if user_color == chess.WHITE else 'black'
73
+ color_stats[color_key]['losses'] += 1
74
+ elif user_result == 'draw':
75
+ opening_stats[opening]['draws'] += 1
76
+ color_key = 'white' if user_color == chess.WHITE else 'black'
77
+ color_stats[color_key]['draws'] += 1
78
+
79
+ weaknesses = categorize_mistakes(all_analyses)
80
+
81
+ all_mistakes = []
82
+ for analysis in all_analyses:
83
+ all_mistakes.extend(analysis.get('mistakes', []))
84
+
85
+ stats_report = f"## πŸ“Š {len(games)} ta o'yin tahlili\n\n"
86
+ stats_report += f"**Jami xatolar:** {len(all_mistakes)} ta\n\n"
87
+
88
+ stats_report += "### 🎯 Eng zaif 3 tomoningiz:\n\n"
89
+ if weaknesses:
90
+ for i, w in enumerate(weaknesses[:3], 1):
91
+ stats_report += f"**{i}. {w['category']}** - {w['count']} marta ({w['percentage']:.1f}%)\n"
92
+ else:
93
+ stats_report += "Xatolar topilmadi yoki tahlil qilinmadi.\n"
94
+
95
+ opening_report = "\n\n## 🎭 Debyut Statistikasi\n\n"
96
+ sorted_openings = sorted(opening_stats.items(), key=lambda x: x[1]['total'], reverse=True)[:10]
97
+
98
+ for opening, stats in sorted_openings:
99
+ total = stats['total']
100
+ wins = stats['wins']
101
+ losses = stats['losses']
102
+ draws = stats['draws']
103
+ win_rate = (wins / total * 100) if total > 0 else 0
104
+
105
+ opening_report += f"**{opening}** ({total} o'yin)\n"
106
+ opening_report += f"- G'alabalar: {wins} ({win_rate:.1f}%) | Yutqazishlar: {losses} | Duranglar: {draws}\n\n"
107
+
108
+ color_report = "\n\n## βšͺ⚫ Rang bo'yicha natijalar\n\n"
109
+
110
+ white_total = sum(color_stats['white'].values())
111
+ black_total = sum(color_stats['black'].values())
112
+
113
+ if white_total > 0:
114
+ white_wr = color_stats['white']['wins'] / white_total * 100
115
+ color_report += f"**Oq figuralar bilan:**\n"
116
+ color_report += f"- G'alabalar: {color_stats['white']['wins']} ({white_wr:.1f}%)\n"
117
+ color_report += f"- Yutqazishlar: {color_stats['white']['losses']}\n"
118
+ color_report += f"- Duranglar: {color_stats['white']['draws']}\n\n"
119
+
120
+ if black_total > 0:
121
+ black_wr = color_stats['black']['wins'] / black_total * 100
122
+ color_report += f"**Qora figuralar bilan:**\n"
123
+ color_report += f"- G'alabalar: {color_stats['black']['wins']} ({black_wr:.1f}%)\n"
124
+ color_report += f"- Yutqazishlar: {color_stats['black']['losses']}\n"
125
+ color_report += f"- Duranglar: {color_stats['black']['draws']}\n"
126
+
127
+ full_report = stats_report + opening_report + color_report
128
+
129
+ ai_analysis = get_comprehensive_analysis(weaknesses, opening_stats, color_stats, len(games))
130
+ ai_report = f"## πŸ€– AI Murabbiy: To'liq Tahlil va O'quv Rejasi\n\n{ai_analysis}"
131
+
132
+ weakness_themes = [w['category'] for w in weaknesses[:3]]
133
+ puzzles = fetch_lichess_puzzles(weakness_themes, count=5)
134
+
135
+ puzzle_text = "## 🧩 Sizning shaxsiy masalalaringiz\n\n"
136
+ puzzle_text += "Masalalarni yechish uchun quyidagi havoladan foydalaning:\n\n"
137
+ for i, puzzle in enumerate(puzzles, 1):
138
+ theme = puzzle.get('theme', 'Tactics')
139
+ url = puzzle.get('url', 'https://lichess.org/training')
140
+ puzzle_text += f"**Puzzle {i}: {theme}**\n"
141
+ puzzle_text += f"- [Lichess Training]({url})\n\n"
142
+
143
+ return (
144
+ full_report,
145
+ ai_report,
146
+ puzzle_text,
147
+ "",
148
+ None,
149
+ "",
150
+ None,
151
+ "",
152
+ None
153
+ )
154
+
155
+
156
+ def parse_pgn_content(pgn_content):
157
+ games = []
158
+ if isinstance(pgn_content, list):
159
+ for pgn_text in pgn_content:
160
+ try:
161
+ game = chess.pgn.read_game(io.StringIO(pgn_text))
162
+ if game:
163
+ games.append(game)
164
+ except:
165
+ pass
166
+ else:
167
+ pgn_io = io.StringIO(pgn_content)
168
+ while True:
169
+ try:
170
+ game = chess.pgn.read_game(pgn_io)
171
+ if game is None:
172
+ break
173
+ games.append(game)
174
+ except:
175
+ break
176
+
177
+ return games
178
+
179
+ def analyze_game_detailed(game, username):
180
+ board = game.board()
181
+ mistakes = []
182
+ move_number = 0
183
+
184
+ white_player = game.headers.get("White", "").strip().lower()
185
+ black_player = game.headers.get("Black", "").strip().lower()
186
+ username_lower = username.strip().lower()
187
+
188
+ user_color = None
189
+ if username_lower == white_player:
190
+ user_color = chess.WHITE
191
+ elif username_lower == black_player:
192
+ user_color = chess.BLACK
193
+
194
+ result = game.headers.get("Result", "*")
195
+ opening = detect_opening(game)
196
+
197
+ user_result = None
198
+ if user_color is not None and result != "*":
199
+ if result == "1-0":
200
+ user_result = "win" if user_color == chess.WHITE else "loss"
201
+ elif result == "0-1":
202
+ user_result = "win" if user_color == chess.BLACK else "loss"
203
+ elif result == "1/2-1/2":
204
+ user_result = "draw"
205
+
206
+ material_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9}
207
+
208
+ def count_material(board):
209
+ total = 0
210
+ for piece_type in material_values:
211
+ total += len(board.pieces(piece_type, chess.WHITE)) * material_values[piece_type]
212
+ total -= len(board.pieces(piece_type, chess.BLACK)) * material_values[piece_type]
213
+ return total
214
+
215
+ for move in game.mainline_moves():
216
+ move_number += 1
217
+
218
+ if user_color is not None and board.turn != user_color:
219
+ board.push(move)
220
+ continue
221
+
222
+ material_before = count_material(board)
223
+ board.push(move)
224
+ material_after = count_material(board)
225
+
226
+ material_loss = abs(material_after - material_before) if board.turn == chess.WHITE else abs(material_before - material_after)
227
+
228
+ moved_piece = board.piece_at(move.to_square)
229
+ mistake_type = None
230
+
231
+ if material_loss >= 3:
232
+ mistake_type = 'blunder'
233
+ elif material_loss >= 1:
234
+ mistake_type = 'mistake'
235
+ elif moved_piece and board.is_attacked_by(not board.turn, move.to_square):
236
+ attackers = len(board.attackers(not board.turn, move.to_square))
237
+ defenders = len(board.attackers(board.turn, move.to_square))
238
+ if attackers > defenders:
239
+ mistake_type = 'hanging_piece'
240
+
241
+ if move_number <= 10:
242
+ phase = 'opening'
243
+ elif len(board.piece_map()) <= 10:
244
+ phase = 'endgame'
245
+ else:
246
+ phase = 'middlegame'
247
+
248
+ if mistake_type:
249
+ mistakes.append({
250
+ 'type': mistake_type,
251
+ 'phase': phase,
252
+ 'move_number': move_number
253
+ })
254
+
255
+ return {
256
+ 'mistakes': mistakes,
257
+ 'opening': opening,
258
+ 'result': result,
259
+ 'user_color': user_color,
260
+ 'user_result': user_result
261
+ }
262
+
263
+ def categorize_mistakes(all_analyses):
264
+ if not all_analyses:
265
+ return []
266
+
267
+ blunders = 0
268
+ regular_mistakes = 0
269
+ hanging = 0
270
+ opening = 0
271
+ middlegame = 0
272
+ endgame = 0
273
+
274
+ for analysis in all_analyses:
275
+ for mistake in analysis.get('mistakes', []):
276
+ mistake_type = mistake.get('type')
277
+ phase = mistake.get('phase')
278
+
279
+ if mistake_type == 'blunder':
280
+ blunders += 1
281
+ elif mistake_type == 'mistake':
282
+ regular_mistakes += 1
283
+ elif mistake_type == 'hanging_piece':
284
+ hanging += 1
285
+
286
+ if phase == 'opening':
287
+ opening += 1
288
+ elif phase == 'middlegame':
289
+ middlegame += 1
290
+ elif phase == 'endgame':
291
+ endgame += 1
292
+
293
+ total = blunders + regular_mistakes + hanging + opening + middlegame + endgame
294
+
295
+ if total == 0:
296
+ return []
297
+
298
+ weaknesses = []
299
+
300
+ if blunders > 0:
301
+ weaknesses.append({
302
+ 'category': "Qo'pol xatolar",
303
+ 'count': blunders,
304
+ 'percentage': (blunders / total * 100)
305
+ })
306
+
307
+ if regular_mistakes > 0:
308
+ weaknesses.append({
309
+ 'category': 'Kichik xatolar',
310
+ 'count': regular_mistakes,
311
+ 'percentage': (regular_mistakes / total * 100)
312
+ })
313
+
314
+ if hanging > 0:
315
+ weaknesses.append({
316
+ 'category': 'Himoyasiz qoldirish',
317
+ 'count': hanging,
318
+ 'percentage': (hanging / total * 100)
319
+ })
320
+
321
+ if opening > 0:
322
+ weaknesses.append({
323
+ 'category': 'Debyut xatolari',
324
+ 'count': opening,
325
+ 'percentage': (opening / total * 100)
326
+ })
327
+
328
+ if middlegame > 0:
329
+ weaknesses.append({
330
+ 'category': "O'rta o'yin xatolari",
331
+ 'count': middlegame,
332
+ 'percentage': (middlegame / total * 100)
333
+ })
334
+
335
+ if endgame > 0:
336
+ weaknesses.append({
337
+ 'category': 'Endshpil xatolari',
338
+ 'count': endgame,
339
+ 'percentage': (endgame / total * 100)
340
+ })
341
+
342
+ weaknesses.sort(key=lambda x: x['count'], reverse=True)
343
+
344
+ return weaknesses
core/openings.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import csv
3
+ import logging
4
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
5
+ logger = logging.getLogger(__name__)
6
+
7
+ def load_opening_database():
8
+ openings = {}
9
+ try:
10
+ with open('data/Chess Opening Reference - Sheet1.csv', 'r', encoding='utf-8') as f:
11
+ reader = csv.DictReader(f)
12
+ for row in reader:
13
+ eco = row.get('ECO Code', '').strip()
14
+ name = row.get('Name', '').strip()
15
+ if eco and name:
16
+ openings[eco] = name
17
+ except Exception as e:
18
+ logger.error(f"Failed to load openings CSV: {e}")
19
+ return openings
20
+
21
+ OPENING_DB = load_opening_database()
22
+
23
+ def detect_opening(game):
24
+ eco = game.headers.get("ECO", "")
25
+ opening = game.headers.get("Opening", "")
26
+
27
+ if eco and eco in OPENING_DB:
28
+ return OPENING_DB[eco]
29
+ elif opening:
30
+ return opening
31
+ else:
32
+ return "Unknown Opening"
Chess Opening Reference - Sheet1.csv β†’ data/Chess Opening Reference - Sheet1.csv RENAMED
File without changes
handlers.py DELETED
@@ -1,378 +0,0 @@
1
- import chess
2
- import chess.pgn
3
- import chess.engine
4
- import io
5
- import requests
6
- import google.generativeai as genai
7
- from collections import Counter
8
- import logging
9
- from datetime import datetime
10
- import time
11
- import config
12
-
13
- # Logging sozlash
14
- logging.basicConfig(
15
- level=logging.INFO,
16
- format='%(asctime)s - %(levelname)s - %(message)s'
17
- )
18
- logger = logging.getLogger(__name__)
19
-
20
- # Gemini sozlash
21
- genai.configure(api_key=config.GEMINI_API_KEY)
22
- model = genai.GenerativeModel('gemini-2.0-flash-exp')
23
-
24
- # ============= CHESS.COM DAN O'YINLARNI OLISH =============
25
-
26
- def get_user_games(username):
27
- """Chess.com dan foydalanuvchi o'yinlarini olish"""
28
- logger.info(f"Foydalanuvchi o'yinlarini olish: {username}")
29
-
30
- try:
31
- # Foydalanuvchini tekshirish
32
- user_url = f"{config.CHESS_COM_API_BASE}/player/{username}"
33
- logger.info(f"Foydalanuvchini tekshirish: {user_url}")
34
-
35
- response = requests.get(user_url, timeout=config.REQUEST_TIMEOUT)
36
- if response.status_code != 200:
37
- logger.error(f"Foydalanuvchi topilmadi: {username}")
38
- return None, "❌ Foydalanuvchi topilmadi. Nomni tekshiring."
39
-
40
- logger.info(f"Foydalanuvchi topildi: {username}")
41
-
42
- # Arxivlarni olish
43
- archives_url = f"{config.CHESS_COM_API_BASE}/player/{username}/games/archives"
44
- logger.info(f"Arxivlarni olish: {archives_url}")
45
-
46
- response = requests.get(archives_url, timeout=config.REQUEST_TIMEOUT)
47
- if response.status_code != 200:
48
- logger.error("Arxivlar olinmadi")
49
- return None, "❌ O'yinlar arxivi topilmadi."
50
-
51
- archives = response.json()['archives']
52
- if not archives:
53
- logger.warning("Arxivlar bo'sh")
54
- return None, "❌ O'yinlar topilmadi."
55
-
56
- logger.info(f"Jami arxivlar: {len(archives)}")
57
-
58
- # So'nggi oylarning o'yinlarini olish
59
- all_games = []
60
- for archive_url in reversed(archives[-3:]): # So'nggi 3 oy
61
- logger.info(f"Arxivdan o'yinlarni olish: {archive_url}")
62
- time.sleep(0.5) # Rate limiting
63
-
64
- response = requests.get(archive_url, timeout=config.REQUEST_TIMEOUT)
65
- if response.status_code == 200:
66
- games = response.json()['games']
67
- all_games.extend(games)
68
- logger.info(f"Olindi: {len(games)} ta o'yin")
69
-
70
- if len(all_games) >= config.MAX_GAMES_TO_ANALYZE:
71
- break
72
-
73
- # Blitz o'yinlarni filtrlash
74
- blitz_games = [g for g in all_games if g.get('time_class') == 'blitz']
75
- logger.info(f"Blitz o'yinlar: {len(blitz_games)}")
76
-
77
- if not blitz_games:
78
- # Agar blitz bo'lmasa, bullet olish
79
- bullet_games = [g for g in all_games if g.get('time_class') == 'bullet']
80
- logger.info(f"Bullet o'yinlar: {len(bullet_games)}")
81
- selected_games = bullet_games[:config.MAX_GAMES_TO_ANALYZE]
82
- else:
83
- selected_games = blitz_games[:config.MAX_GAMES_TO_ANALYZE]
84
-
85
- logger.info(f"Tanlangan o'yinlar: {len(selected_games)}")
86
-
87
- # PGN formatga o'tkazish
88
- pgn_games = []
89
- for game in selected_games:
90
- if 'pgn' in game:
91
- pgn_games.append(game['pgn'])
92
-
93
- logger.info(f"PGN formatdagi o'yinlar: {len(pgn_games)}")
94
-
95
- if not pgn_games:
96
- return None, "❌ PGN formatdagi o'yinlar topilmadi."
97
-
98
- return pgn_games, None
99
-
100
- except Exception as e:
101
- logger.error(f"Xatolik yuz berdi: {str(e)}")
102
- return None, f"❌ Xatolik: {str(e)}"
103
-
104
- # ============= PGN TAHLIL =============
105
-
106
- def parse_pgn_games(pgn_list):
107
- """PGN formatdagi o'yinlarni tahlil qilish"""
108
- logger.info(f"PGN tahlil qilish: {len(pgn_list)} ta o'yin")
109
-
110
- games = []
111
- for i, pgn_text in enumerate(pgn_list):
112
- try:
113
- pgn_io = io.StringIO(pgn_text)
114
- game = chess.pgn.read_game(pgn_io)
115
- if game:
116
- games.append(game)
117
- logger.info(f"O'yin {i+1} tahlil qilindi")
118
- except Exception as e:
119
- logger.error(f"O'yin {i+1} tahlil xatosi: {str(e)}")
120
-
121
- logger.info(f"Jami tahlil qilindi: {len(games)} ta o'yin")
122
- return games
123
-
124
- # ============= STOCKFISH BILAN TAHLIL =============
125
-
126
- def analyze_game_with_engine(game, username):
127
- """Stockfish yordamida o'yinni tahlil qilish"""
128
- logger.info("Stockfish bilan tahlil boshlandi")
129
-
130
- try:
131
- # Stockfish ni ishga tushirish
132
- engine = chess.engine.SimpleEngine.popen_uci(config.STOCKFISH_PATH)
133
- logger.info("Stockfish muvaffaqiyatli ishga tushdi")
134
-
135
- board = game.board()
136
- mistakes = []
137
- move_number = 0
138
-
139
- # Foydalanuvchi qaysi rangda o'ynaganini aniqlash
140
- white_player = game.headers.get("White", "").lower()
141
- black_player = game.headers.get("Black", "").lower()
142
- user_color = None
143
-
144
- if username.lower() in white_player:
145
- user_color = chess.WHITE
146
- logger.info(f"Foydalanuvchi oq rangda: {username}")
147
- elif username.lower() in black_player:
148
- user_color = chess.BLACK
149
- logger.info(f"Foydalanuvchi qora rangda: {username}")
150
-
151
- # Har bir harakatni tahlil qilish
152
- for move in game.mainline_moves():
153
- move_number += 1
154
-
155
- # Faqat foydalanuvchi harakatlarini tahlil qilish
156
- if user_color is not None and board.turn != user_color:
157
- board.push(move)
158
- continue
159
-
160
- # Harakat oldin pozitsiyani baholash
161
- try:
162
- info_before = engine.analyse(
163
- board,
164
- chess.engine.Limit(depth=config.STOCKFISH_DEPTH)
165
- )
166
- eval_before = info_before['score'].relative.score(mate_score=10000)
167
-
168
- # Harakatni qo'llash
169
- board.push(move)
170
-
171
- # Harakat keyin pozitsiyani baholash
172
- info_after = engine.analyse(
173
- board,
174
- chess.engine.Limit(depth=config.STOCKFISH_DEPTH)
175
- )
176
- eval_after = info_after['score'].relative.score(mate_score=10000)
177
-
178
- # Baholash farqini hisoblash (foydalanuvchi nuqtai nazaridan)
179
- eval_diff = eval_before - eval_after
180
-
181
- # Xatolarni aniqlash
182
- mistake_type = None
183
- if eval_diff >= config.BLUNDER_THRESHOLD:
184
- mistake_type = 'blunder'
185
- logger.info(f"Qo'pol xato topildi: harakat {move_number}, farq {eval_diff}")
186
- elif eval_diff >= config.MISTAKE_THRESHOLD:
187
- mistake_type = 'mistake'
188
- logger.info(f"Xato topildi: harakat {move_number}, farq {eval_diff}")
189
-
190
- if mistake_type:
191
- # Eng yaxshi harakatni topish
192
- best_move = info_before['pv'][0] if 'pv' in info_before else None
193
-
194
- mistakes.append({
195
- 'move_number': move_number,
196
- 'move': move.uci(),
197
- 'best_move': best_move.uci() if best_move else None,
198
- 'type': mistake_type,
199
- 'eval_before': eval_before,
200
- 'eval_after': eval_after,
201
- 'eval_diff': eval_diff,
202
- 'fen': board.fen(),
203
- 'game_phase': get_game_phase(board)
204
- })
205
-
206
- except Exception as e:
207
- logger.error(f"Harakat {move_number} tahlil xatosi: {str(e)}")
208
- board.push(move)
209
-
210
- engine.quit()
211
- logger.info(f"Tahlil tugadi. Topildi: {len(mistakes)} ta xato")
212
- return mistakes
213
-
214
- except Exception as e:
215
- logger.error(f"Stockfish xatosi: {str(e)}")
216
- return []
217
-
218
- def get_game_phase(board):
219
- """O'yin bosqichini aniqlash"""
220
- move_count = board.fullmove_number
221
- piece_count = len(board.piece_map())
222
-
223
- if move_count <= 10:
224
- return 'opening_mistake'
225
- elif piece_count <= 10:
226
- return 'endgame_mistake'
227
- else:
228
- return 'middlegame_mistake'
229
-
230
- # ============= XATOLARNI KATEGORIYALASH =============
231
-
232
- def categorize_all_mistakes(all_mistakes):
233
- """Barcha xatolarni kategoriyalash"""
234
- logger.info(f"Kategoriyalash: {len(all_mistakes)} ta xato")
235
-
236
- # Xato turlarini sanash
237
- mistake_types = []
238
- for mistake in all_mistakes:
239
- mistake_types.append(mistake['type'])
240
- mistake_types.append(mistake['game_phase'])
241
-
242
- counts = Counter(mistake_types)
243
-
244
- # Eng ko'p uchragan 3 ta zaif tomonni topish
245
- top_weaknesses = []
246
- for mistake_type, count in counts.most_common(5):
247
- if mistake_type in config.MISTAKE_CATEGORIES:
248
- percentage = (count / len(all_mistakes) * 100) if all_mistakes else 0
249
- top_weaknesses.append({
250
- 'category': config.MISTAKE_CATEGORIES[mistake_type],
251
- 'type': mistake_type,
252
- 'count': count,
253
- 'percentage': percentage
254
- })
255
-
256
- logger.info(f"Eng ko'p zaif tomonlar: {len(top_weaknesses)}")
257
- return top_weaknesses[:3]
258
-
259
- # ============= AI TUSHUNTIRISH =============
260
-
261
- def get_ai_explanation(weaknesses):
262
- """Gemini dan tushuntirish olish"""
263
- logger.info("AI tushuntirish olish")
264
-
265
- weakness_text = "\n".join([
266
- f"- {w['category']}: {w['count']} marta ({w['percentage']:.1f}%)"
267
- for w in weaknesses
268
- ])
269
-
270
- prompt = config.WEAKNESS_ANALYSIS_PROMPT.format(weakness_text=weakness_text)
271
-
272
- try:
273
- response = model.generate_content(prompt)
274
- logger.info("AI javob olindi")
275
- return response.text
276
- except Exception as e:
277
- logger.error(f"AI xatosi: {str(e)}")
278
- return "AI tushuntirish hozircha mavjud emas."
279
-
280
- # ============= LICHESS DAN MASALALAR OLISH =============
281
-
282
- def fetch_puzzles_for_weaknesses(weaknesses, count=5):
283
- """Zaif tomonlar uchun masalalar olish"""
284
- logger.info(f"Masalalar olish: {count} ta")
285
-
286
- puzzles = []
287
-
288
- for weakness in weaknesses:
289
- theme = config.PUZZLE_THEMES.get(weakness['type'], 'mix')
290
- logger.info(f"Tema: {theme}")
291
-
292
- try:
293
- # Lichess dan tasodifiy masala olish
294
- url = f"{config.LICHESS_API_BASE}/puzzle/daily"
295
- response = requests.get(url, timeout=config.REQUEST_TIMEOUT)
296
-
297
- if response.status_code == 200:
298
- data = response.json()
299
- puzzle = data['puzzle']
300
- game = data['game']
301
-
302
- puzzles.append({
303
- 'id': puzzle['id'],
304
- 'fen': game['pgn'].split('\n')[-1], # Oxirgi pozitsiya
305
- 'rating': puzzle['rating'],
306
- 'theme': weakness['category'],
307
- 'solution': puzzle['solution'],
308
- 'url': f"https://lichess.org/training/{puzzle['id']}"
309
- })
310
- logger.info(f"Masala olindi: {puzzle['id']}")
311
-
312
- except Exception as e:
313
- logger.error(f"Masala olish xatosi: {str(e)}")
314
-
315
- # Yetarlicha bo'lmasa, standart masalalar qo'shish
316
- while len(puzzles) < count:
317
- puzzles.append({
318
- 'id': f'default_{len(puzzles)}',
319
- 'fen': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
320
- 'rating': 1500,
321
- 'theme': 'Amaliyot',
322
- 'solution': ['e2e4'],
323
- 'url': 'https://lichess.org/training'
324
- })
325
-
326
- logger.info(f"Jami masalalar: {len(puzzles)}")
327
- return puzzles[:count]
328
-
329
- # ============= ASOSIY TAHLIL FUNKSIYASI =============
330
-
331
- def analyze_user_games(username):
332
- """Foydalanuvchi o'yinlarini to'liq tahlil qilish"""
333
- logger.info(f"===== TAHLIL BOSHLANDI: {username} =====")
334
-
335
- # 1. O'yinlarni olish
336
- pgn_games, error = get_user_games(username)
337
- if error:
338
- logger.error(f"O'yinlar olinmadi: {error}")
339
- return error, "", "", []
340
-
341
- # 2. PGN tahlil qilish
342
- games = parse_pgn_games(pgn_games)
343
- if not games:
344
- logger.error("O'yinlar tahlil qilinmadi")
345
- return "❌ O'yinlar tahlil qilinmadi.", "", "", []
346
-
347
- # 3. Har bir o'yinni Stockfish bilan tahlil qilish
348
- all_mistakes = []
349
- for i, game in enumerate(games[:10], 1): # Birinchi 10 ta
350
- logger.info(f"O'yin {i}/{len(games[:10])} tahlil qilinmoqda...")
351
- mistakes = analyze_game_with_engine(game, username)
352
- all_mistakes.extend(mistakes)
353
-
354
- if not all_mistakes:
355
- logger.info("Xatolar topilmadi")
356
- return "βœ… Ajoyib! Sizning o'yinlaringizda katta xatolar topilmadi!", "", "", []
357
-
358
- # 4. Zaif tomonlarni aniqlash
359
- weaknesses = categorize_all_mistakes(all_mistakes)
360
-
361
- # 5. Hisobot tayyorlash
362
- report = f"## πŸ“Š {len(games)} ta o'yin tahlili\n\n"
363
- report += f"**Topilgan xatolar:** {len(all_mistakes)} ta\n\n"
364
- report += "### 🎯 Sizning eng zaif 3 tomoningiz:\n\n"
365
-
366
- for i, w in enumerate(weaknesses, 1):
367
- report += f"**{i}. {w['category']}**\n"
368
- report += f" - {w['count']} marta ({w['percentage']:.1f}%)\n\n"
369
-
370
- # 6. AI tushuntirish
371
- explanation = get_ai_explanation(weaknesses)
372
- explanation_text = f"## πŸ€– AI Murabbiy Tahlili\n\n{explanation}"
373
-
374
- # 7. Masalalar olish
375
- puzzles = fetch_puzzles_for_weaknesses(weaknesses, count=5)
376
-
377
- logger.info("===== TAHLIL TUGADI =====")
378
- return report, explanation_text, "", puzzles
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
openings.py DELETED
@@ -1,82 +0,0 @@
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
- "A34": "English Opening: Botvinnik System",
16
- "A36": "English Opening: King's English Variation",
17
- 'A40': 'Queen Pawn Game',
18
- 'A45': 'Indian Defense',
19
- 'A46': 'Indian Defense: 2.Nf3',
20
- 'A50': 'Indian Defense: Normal Variation',
21
- 'B00': 'Uncommon King Pawn Opening',
22
- 'B01': 'Scandinavian Defense',
23
- 'B02': "Alekhine's Defense",
24
- 'B03': "Alekhine's Defense: Four Pawns Attack",
25
- 'B10': 'Caro-Kann Defense',
26
- 'B12': 'Caro-Kann Defense: Advance Variation',
27
- "B13": 'Caro-Kann Defense: Classical Variation',
28
- "B14": 'Caro-Kann Defense: Panov-Botvinnik Attack',
29
- "B15": 'Caro-Kann Defense: Panov-Botvinnik Attack',
30
- 'B20': 'Sicilian Defense',
31
- 'B22': 'Sicilian Defense: Alapin Variation',
32
- 'B23': 'Sicilian Defense: Closed',
33
- 'B30': 'Sicilian Defense: 2...Nc6',
34
- "B32": 'Sicilian Defense: Hyperaccelerated Dragon',
35
- 'B40': 'Sicilian Defense: French Variation',
36
- 'B50': 'Sicilian Defense: 2...d6',
37
- 'B90': 'Sicilian Defense: Najdorf',
38
- 'C00': 'French Defense',
39
- "C01": 'French Defense: Exchange Variation',
40
- 'C02': 'French Defense: Advance Variation',
41
- 'C10': 'French Defense: Rubinstein Variation',
42
- 'C20': 'King Pawn Game',
43
- 'C30': "King's Gambit",
44
- 'C40': "King's Knight Opening",
45
- 'C41': 'Philidor Defense',
46
- 'C42': 'Russian Game (Petrov Defense)',
47
- 'C44': 'Scotch Game',
48
- 'C50': 'Italian Game',
49
- "C54": "Italian Game: Giuoco Piano",
50
- 'C55': 'Italian Game: Two Knights Defense',
51
- 'C60': 'Spanish Opening (Ruy Lopez)',
52
- 'C65': 'Spanish Opening: Berlin Defense',
53
- 'C70': 'Spanish Opening',
54
- 'C78': 'Spanish Opening: Morphy Defense',
55
- 'C80': 'Spanish Opening: Open Variation',
56
- 'D00': 'Queen Pawn Game',
57
- 'D02': 'Queen Pawn Game: 2.Nf3',
58
- 'D10': 'Slav Defense',
59
- 'D20': "Queen's Gambit Accepted",
60
- 'D30': "Queen's Gambit Declined",
61
- 'D50': "Queen's Gambit Declined: 4.Bg5",
62
- 'E00': 'Indian Defense',
63
- 'E10': 'Indian Defense: 3.Nf3',
64
- 'E20': 'Nimzo-Indian Defense',
65
- 'E30': 'Nimzo-Indian Defense: Leningrad Variation',
66
- 'E60': "King's Indian Defense",
67
- 'E70': "King's Indian Defense: Normal Variation",
68
- 'E90': "King's Indian Defense: Orthodox Variation",
69
- }
70
-
71
- return eco_openings.get(eco_code, f"Opening ECO {eco_code}")
72
-
73
- def detect_opening(game):
74
- opening = game.headers.get("Opening", "")
75
- eco = game.headers.get("ECO", "")
76
-
77
- if opening:
78
- return opening
79
- elif eco:
80
- return get_opening_name_from_eco(eco)
81
- else:
82
- return "Unknown Opening"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
project_info.md ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # β™ŸοΈ Chess Study Plan Pro - Professional Shaxmat O'quv Rejasi
2
+
3
+ ![Chess AI](https://img.shields.io/badge/Chess-AI%20Powered-blue)
4
+ ![Python](https://img.shields.io/badge/Python-3.8+-green)
5
+ ![Gradio](https://img.shields.io/badge/Gradio-UI-orange)
6
+
7
+ ## πŸ“‹ Loyiha Haqida
8
+
9
+ **Chess Study Plan Pro** - bu shaxmatchilar uchun sun'iy intellekt asosida ishlaydigan professional tahlil va o'quv rejasi yaratuvchi dastur. Loyiha UzChess AI Hackaton uchun ishlab chiqilgan bo'lib, shaxmatchilarning o'yin uslubini chuqur tahlil qilib, shaxsiy rivojlanish rejasini taklif etadi.
10
+
11
+ ### 🎯 Asosiy Maqsad
12
+
13
+ Har bir shaxmatchi o'zining zaif tomonlarini bilishi va ularni yaxshilash uchun aniq yo'l-yo'riq olishi kerak. Ushbu dastur aynan shu muammoni hal qiladi - o'yinlaringizni tahlil qilib, sizga maxsus o'quv rejasini tayyorlaydi.
14
+
15
+ ## ✨ Asosiy Imkoniyatlar
16
+
17
+ ### πŸ” To'liq O'yin Tahlili
18
+ - **Xatolarni aniqlash**: Qo'pol xatolar, kichik xatolar va himoyasiz qoldirishlarni topadi
19
+ - **Fazalar bo'yicha tahlil**: Debyut, o'rta o'yin va endshpil bo'yicha alohida statistika
20
+ - **Material yo'qotishlar**: Har bir xatoda qancha material yo'qotganingizni hisoblaydi
21
+
22
+ ### πŸ“Š Batafsil Statistika
23
+ - **Debyut tahlili**: Qaysi debyutlarda yaxshi/yomon natijalar ko'rsatasiz
24
+ - **Rang statistikasi**: Oq va qora figuralar bilan alohida natijalar
25
+ - **G'alaba foizi**: Har bir debyut va rang uchun g'alaba foizi
26
+ - **O'yinlar soni**: 50 tagacha oxirgi o'yinlarni tahlil qiladi
27
+
28
+ ### πŸ€– AI Murabbiy (Gemini 2.0)
29
+ - **Zaif tomonlar tahlili**: Nima uchun xatolar sodir bo'layotganini tushuntiradi
30
+ - **Kundalik o'quv rejasi**: Har kuni necha masala yechish, qaysi debyutlarni o'rganish
31
+ - **Resurslar tavsiyasi**: Kitoblar, kurslar, YouTube kanallari
32
+ - **Motivatsion maslahatlar**: Psixologik qo'llab-quvvatlash
33
+
34
+ ### 🧩 Mashq Masalalari
35
+ - Zaif tomonlaringizga mos Lichess masalalari havolalari
36
+ - Har bir muammo uchun maxsus tanlangan mashqlar
37
+ - To'g'ridan-to'g'ri Lichess Training bo'limiga yo'naltirish
38
+
39
+ ## πŸš€ O'rnatish va Ishga Tushirish
40
+
41
+ ### Talablar
42
+ ```bash
43
+ Python 3.8+
44
+ gradio
45
+ chess
46
+ requests
47
+ google-generativeai
48
+ O'rnatish