import chess import chess.pgn import io import re import logging from collections import defaultdict from core.ai_integration import get_comprehensive_analysis from core.openings import detect_opening, load_opening_database from core.chess_api import get_user_games_from_chess_com, fetch_lichess_puzzles logger = logging.getLogger(__name__) def extract_user_rating(games, username): ratings = [] username_lower = username.strip().lower() for game in games: white_player = game.headers.get("White", "").strip().lower() black_player = game.headers.get("Black", "").strip().lower() if username_lower == white_player: white_elo = game.headers.get("WhiteElo", "") if white_elo and white_elo.isdigit(): ratings.append(int(white_elo)) elif username_lower == black_player: black_elo = game.headers.get("BlackElo", "") if black_elo and black_elo.isdigit(): ratings.append(int(black_elo)) if ratings: avg_rating = sum(ratings) // len(ratings) return avg_rating return 1500 def analyze_games(username_chesscom, pgn_file, username_pgn): actual_username = None pgn_content = None user_rating = 1500 # Default rating if username_chesscom: pgn_content, error = get_user_games_from_chess_com(username_chesscom) if error: return error, "", "", "", None, None, None, None, None actual_username = username_chesscom elif pgn_file: pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file if username_pgn and username_pgn.strip(): actual_username = username_pgn.strip() else: try: first_game = chess.pgn.read_game(io.StringIO(pgn_content)) if first_game: white = first_game.headers.get("White", "") black = first_game.headers.get("Black", "") actual_username = white if white else black if black else "Player" else: actual_username = "Player" except: actual_username = "Player" else: return "❌ Chess.com foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", "", "", None, None, None, None, None games = parse_pgn_content(pgn_content) if not games: return "❌ O'yinlar topilmadi yoki tahlil qilinmadi", "", "", "", None, None, None, None, None # Extract user rating from games user_rating = extract_user_rating(games, actual_username) logger.info(f"Extracted user rating: {user_rating}") all_analyses = [] opening_stats = defaultdict(lambda: {'wins': 0, 'losses': 0, 'draws': 0, 'total': 0}) color_stats = { 'white': {'wins': 0, 'losses': 0, 'draws': 0}, 'black': {'wins': 0, 'losses': 0, 'draws': 0} } for game in games: analysis = analyze_game_detailed(game, actual_username) all_analyses.append(analysis) opening = analysis['opening'] user_result = analysis.get('user_result') user_color = analysis['user_color'] if user_color is not None: opening_stats[opening]['total'] += 1 if user_result == 'win': opening_stats[opening]['wins'] += 1 color_key = 'white' if user_color == chess.WHITE else 'black' color_stats[color_key]['wins'] += 1 elif user_result == 'loss': opening_stats[opening]['losses'] += 1 color_key = 'white' if user_color == chess.WHITE else 'black' color_stats[color_key]['losses'] += 1 elif user_result == 'draw': opening_stats[opening]['draws'] += 1 color_key = 'white' if user_color == chess.WHITE else 'black' color_stats[color_key]['draws'] += 1 weaknesses = categorize_mistakes(all_analyses) all_mistakes = [] for analysis in all_analyses: all_mistakes.extend(analysis.get('mistakes', [])) stats_report = f"## 📊 {len(games)} ta o'yin tahlili\n\n" stats_report += f"**Sizning o'rtacha reytingingiz:** {user_rating}\n\n" stats_report += f"**Jami xatolar:** {len(all_mistakes)} ta\n\n" stats_report += "### 🎯 Eng zaif 5 tomoningiz:\n\n" if weaknesses: for i, w in enumerate(weaknesses[:5], 1): stats_report += f"**{i}. {w['category']}** - {w['count']} marta ({w['percentage']:.1f}%)\n" else: stats_report += "Xatolar topilmadi yoki tahlil qilinmadi.\n" opening_report = "\n\n## 🎭 Debyut Statistikasi\n\n" sorted_openings = sorted(opening_stats.items(), key=lambda x: x[1]['total'], reverse=True)[:10] for opening, stats in sorted_openings: total = stats['total'] wins = stats['wins'] losses = stats['losses'] draws = stats['draws'] win_rate = (wins / total * 100) if total > 0 else 0 opening_report += f"**{opening}** ({total} o'yin)\n" opening_report += f"- G'alabalar: {wins} ({win_rate:.1f}%) | Yutqazishlar: {losses} | Duranglar: {draws}\n\n" color_report = "\n\n## ⚪⚫ Rang bo'yicha natijalar\n\n" white_total = sum(color_stats['white'].values()) black_total = sum(color_stats['black'].values()) if white_total > 0: white_wr = color_stats['white']['wins'] / white_total * 100 color_report += f"**Oq figuralar bilan:**\n" color_report += f"- G'alabalar: {color_stats['white']['wins']} ({white_wr:.1f}%)\n" color_report += f"- Yutqazishlar: {color_stats['white']['losses']}\n" color_report += f"- Duranglar: {color_stats['white']['draws']}\n\n" if black_total > 0: black_wr = color_stats['black']['wins'] / black_total * 100 color_report += f"**Qora figuralar bilan:**\n" color_report += f"- G'alabalar: {color_stats['black']['wins']} ({black_wr:.1f}%)\n" color_report += f"- Yutqazishlar: {color_stats['black']['losses']}\n" color_report += f"- Duranglar: {color_stats['black']['draws']}\n" full_report = stats_report + opening_report + color_report ai_analysis = get_comprehensive_analysis(weaknesses, opening_stats, color_stats, len(games)) ai_report = f"## 🤖 AI Murabbiy: To'liq Tahlil va O'quv Rejasi\n\n{ai_analysis}" weakness_themes = [w['category'] for w in weaknesses[:5]] puzzles = fetch_lichess_puzzles(weakness_themes, user_rating=user_rating, count=5) puzzle_text = "## 🧩 Sizning shaxsiy masalalaringiz\n\n" puzzle_text += f"Sizning reytingingiz: **{user_rating}** - Masalalar shu darajaga moslashtirilgan\n\n" for i, puzzle in enumerate(puzzles, 1): theme = puzzle.get('theme', 'Tactics') rating = puzzle.get('rating', user_rating) url = puzzle.get('url', 'https://lichess.org/training') puzzle_text += f"**Puzzle {i}: {theme}** (Rating: {rating})\n" puzzle_text += f"- [Lichess Training]({url})\n\n" return ( full_report, ai_report, puzzle_text, "", None, "", None, "", None ) def parse_pgn_content(pgn_content): games = [] if isinstance(pgn_content, list): for pgn_text in pgn_content: try: game = chess.pgn.read_game(io.StringIO(pgn_text)) if game: games.append(game) except: pass else: pgn_io = io.StringIO(pgn_content) while True: try: game = chess.pgn.read_game(pgn_io) if game is None: break games.append(game) except: break return games def analyze_game_detailed(game, username): board = game.board() mistakes = [] move_number = 0 white_player = game.headers.get("White", "").strip().lower() black_player = game.headers.get("Black", "").strip().lower() username_lower = username.strip().lower() user_color = None if username_lower == white_player: user_color = chess.WHITE elif username_lower == black_player: user_color = chess.BLACK result = game.headers.get("Result", "*") opening = detect_opening(game) user_result = None if user_color is not None and result != "*": if result == "1-0": user_result = "win" if user_color == chess.WHITE else "loss" elif result == "0-1": user_result = "win" if user_color == chess.BLACK else "loss" elif result == "1/2-1/2": user_result = "draw" material_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9} def count_material(board): total = 0 for piece_type in material_values: total += len(board.pieces(piece_type, chess.WHITE)) * material_values[piece_type] total -= len(board.pieces(piece_type, chess.BLACK)) * material_values[piece_type] return total for move in game.mainline_moves(): move_number += 1 if user_color is not None and board.turn != user_color: board.push(move) continue material_before = count_material(board) board.push(move) material_after = count_material(board) material_loss = abs(material_after - material_before) if board.turn == chess.WHITE else abs(material_before - material_after) moved_piece = board.piece_at(move.to_square) mistake_type = None if material_loss >= 3: mistake_type = 'blunder' elif material_loss >= 1: mistake_type = 'mistake' elif moved_piece and board.is_attacked_by(not board.turn, move.to_square): attackers = len(board.attackers(not board.turn, move.to_square)) defenders = len(board.attackers(board.turn, move.to_square)) if attackers > defenders: mistake_type = 'hanging_piece' if move_number <= 10: phase = 'opening' elif len(board.piece_map()) <= 10: phase = 'endgame' else: phase = 'middlegame' if mistake_type: mistakes.append({ 'type': mistake_type, 'phase': phase, 'move_number': move_number }) return { 'mistakes': mistakes, 'opening': opening, 'result': result, 'user_color': user_color, 'user_result': user_result } def categorize_mistakes(all_analyses): if not all_analyses: return [] blunders = 0 regular_mistakes = 0 hanging = 0 opening = 0 middlegame = 0 endgame = 0 for analysis in all_analyses: for mistake in analysis.get('mistakes', []): mistake_type = mistake.get('type') phase = mistake.get('phase') if mistake_type == 'blunder': blunders += 1 elif mistake_type == 'mistake': regular_mistakes += 1 elif mistake_type == 'hanging_piece': hanging += 1 if phase == 'opening': opening += 1 elif phase == 'middlegame': middlegame += 1 elif phase == 'endgame': endgame += 1 total = blunders + regular_mistakes + hanging + opening + middlegame + endgame if total == 0: return [] weaknesses = [] if blunders > 0: weaknesses.append({ 'category': "Qo'pol xatolar", 'count': blunders, 'percentage': (blunders / total * 100) }) if regular_mistakes > 0: weaknesses.append({ 'category': 'Kichik xatolar', 'count': regular_mistakes, 'percentage': (regular_mistakes / total * 100) }) if hanging > 0: weaknesses.append({ 'category': 'Himoyasiz qoldirish', 'count': hanging, 'percentage': (hanging / total * 100) }) if opening > 0: weaknesses.append({ 'category': 'Debyut xatolari', 'count': opening, 'percentage': (opening / total * 100) }) if middlegame > 0: weaknesses.append({ 'category': "O'rta o'yin xatolari", 'count': middlegame, 'percentage': (middlegame / total * 100) }) if endgame > 0: weaknesses.append({ 'category': 'Endshpil xatolari', 'count': endgame, 'percentage': (endgame / total * 100) }) weaknesses.sort(key=lambda x: x['count'], reverse=True) return weaknesses