Repository: Chelyocarpus/Delta-Force-Leaderboard
Pull request #61: ✨ Add Achievements Feature and Tab to Player Details
Author: login: Chelyocarpus
Labels: name: enhancement
description: New feature or request
Created: 2025-03-04T07:53:09Z | Merged: 2025-03-04T11:39:51Z

This pull request introduces a new achievements feature in the application, including the addition of achievement icons and the implementation of an achievements tab in the player details dialog. The most important changes are summarized below:

### Achievements Feature:

* [`resources/icons/README.md`](diffhunk://#diff-f607162729a5192ed2194898b3acc07657d4ab70c3a2a306aab9d46f01d43f76R1-R25): Added documentation for achievement icons, specifying the required icons and their purposes.

### GUI Updates:

* [`src/gui/dialogs/player_details.py`](diffhunk://#diff-1cc04e8b0a77bfe28fc45fecbfb8e15a03fb78896982654bf5bb92abaab27574R5): Imported the new `setup_achievement_tab` function to integrate the achievements tab into the player details dialog.
* [`src/gui/dialogs/player_details.py`](diffhunk://#diff-1cc04e8b0a77bfe28fc45fecbfb8e15a03fb78896982654bf5bb92abaab27574R64-R72): Updated the `init_ui` method to include the new achievements tab in the tab configurations and handle its special setup.

Closes #12

Linked issue #12: Achievements
Add in-house achievements ala Battlefield, like "revived 1000,2500,5000" etc
Linked issue comments: user: login: Chelyocarpus
created_at: 2025-03-04T08:37:44Z
body: Added by pull request #61 but needs better map related achievements
author_association: OWNER

Diff:
diff --git a/resources/icons/README.md b/resources/icons/README.md
new file mode 100644
index 0000000..c0ef09d
--- /dev/null
+++ b/resources/icons/README.md
@@ -0,0 +1,25 @@
+# Achievement Icons
+
+This directory should contain the icons used for achievements in the application.
+
+## Required Icons
+
+The following icons are used for achievements:
+- medal_default.png - Default medal icon
+- medal_kills.png - For the Marksman achievement
+- medal_kd.png - For the Headhunter achievement
+- medal_vehicle.png - For the Vehicle Destroyer achievement
+- medal_assists.png - For the Team Player achievement
+- medal_revives.png - For the Guardian Angel achievement
+- medal_respawn.png - For the Reinforcement achievement
+- medal_captures.png - For the Flag Bearer achievement
+- medal_victory.png - For the Victory achievement
+- medal_top_rank.png - For the Top Rank achievement
+- medal_assault.png - For the Assault Master achievement
+- medal_medic.png - For the Medic Master achievement
+- medal_support.png - For the Support Master achievement
+- medal_recon.png - For the Recon Master achievement
+- medal_games.png - For the Veteran achievement
+- medal_points.png - For the Commitment achievement
+
+You can add your own icons as 48x48 PNG files in this directory.
diff --git a/src/gui/dialogs/player_details.py b/src/gui/dialogs/player_details.py
index d163072..270f9c9 100644
--- a/src/gui/dialogs/player_details.py
+++ b/src/gui/dialogs/player_details.py
@@ -2,6 +2,7 @@
                            QLabel, QPushButton, QWidget)
 from PyQt5.QtCore import QSettings
 from ..tabs.overall_tab import setup_overall_tab
+from ..tabs.achievement_tab import setup_achievement_tab  # Import the new tab
 from ..tabs.class_tab import ClassTab
 from ..tabs.map_tab import MapTab
 from ..tabs.medals_tab import MedalsTab
@@ -60,10 +61,15 @@ def init_ui(self):
             ("Match History", MatchHistoryTab, [self, self.player_name, self.parent.db.db_path])
         )
 
+        # Add the new achievements tab
+        tab_configs.append(
+            ("Achievements", setup_achievement_tab, self)
+        )
+
         # Create and add tabs
         for tab_name, tab_class, args in tab_configs:
-            if tab_name == "Overall Stats":
-                tab = tab_class(args)  # Special handling for setup_overall_tab
+            if tab_name == "Overall Stats" or tab_name == "Achievements":
+                tab = tab_class(args)  # Special handling for setup_overall_tab and setup_achievement_tab
             else:
                 tab = tab_class(*args)  # All other tabs use positional args
             
diff --git a/src/gui/tabs/achievement_tab.py b/src/gui/tabs/achievement_tab.py
new file mode 100644
index 0000000..d4de6bb
--- /dev/null
+++ b/src/gui/tabs/achievement_tab.py
@@ -0,0 +1,637 @@
+from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                            QLabel, QProgressBar, QScrollArea, QSizePolicy)
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QFont, QIcon
+import sqlite3
+import logging
+import os
+
+# Setup logging
+logger = logging.getLogger(__name__)
+
+# Achievement definitions
+ACHIEVEMENTS = {
+    "Combat": [
+        {
+            "id": "marksman",
+            "name": "Marksman",
+            "description": "Get kills in battle",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(kills AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_kills.png"
+        },
+        {
+            "id": "headhunter",
+            "name": "Headhunter",
+            "description": "Get high K/D (>=2.0) ratio in matches",
+            "base_threshold": 10,
+            "growth_factor": 1.5,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches
+                WHERE name = ?
+                AND CAST(kills AS FLOAT) / CASE 
+                    WHEN CAST(deaths AS INTEGER) = 0 THEN 1.0
+                    ELSE CAST(deaths AS FLOAT)
+                END >= 2.0
+                AND CAST(kills AS INTEGER) > 0
+            """,
+            "icon": "medal_kd.png"
+        },
+        {
+            "id": "vehicle_destroyer",
+            "name": "Vehicle Destroyer",
+            "description": "Accumulate vehicle damage",
+            "base_threshold": 1000,
+            "growth_factor": 2.5,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(vehicle_damage AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_vehicle.png"
+        }
+    ],
+    "Teamplay": [
+        {
+            "id": "team_player",
+            "name": "Team Player",
+            "description": "Get assists helping your teammates",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(assists AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_assists.png"
+        },
+        {
+            "id": "guardian_angel",
+            "name": "Guardian Angel",
+            "description": "Revive fallen teammates",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(revives AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_revives.png"
+        },
+        {
+            "id": "reinforcement",
+            "name": "Reinforcement",
+            "description": "Provide tactical respawns for your team",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(tactical_respawn AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_respawn.png"
+        }
+    ],
+    "Objective": [
+        {
+            "id": "flag_bearer",
+            "name": "Flag Bearer",
+            "description": "Capture objectives",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(captures AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_captures.png"
+        },
+        {
+            "id": "victory",
+            "name": "Victory",
+            "description": "Win matches",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND outcome LIKE '%VICTORY%'
+            """,
+            "icon": "medal_victory.png"
+        },
+        {
+            "id": "top_rank",
+            "name": "Top Rank",
+            "description": "Finish matches in the top 3",
+            "base_threshold": 5,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND CAST(rank AS INTEGER) <= 3
+            """,
+            "icon": "medal_top_rank.png"
+        }
+    ],
+    "Specialist": [
+        {
+            "id": "assault_master",
+            "name": "Assault Master",
+            "description": "Play matches as Assault class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Assault'
+            """,
+            "icon": "medal_assault.png"
+        },
+        {
+            "id": "engineer_master",
+            "name": "Engineer Master",
+            "description": "Play matches as Engineer class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Engineer'
+            """,
+            "icon": "medal_engineer.png"
+        },
+        {
+            "id": "support_master",
+            "name": "Support Master",
+            "description": "Play matches as Support class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Support'
+            """,
+            "icon": "medal_support.png"
+        },
+        {
+            "id": "recon_master",
+            "name": "Recon Master",
+            "description": "Play matches as Recon class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Recon'
+            """,
+            "icon": "medal_recon.png"
+        }
+    ],
+    "Dedication": [
+        {
+            "id": "veteran",
+            "name": "Veteran",
+            "description": "Play many matches",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_games.png"
+        },
+        {
+            "id": "commitment",
+            "name": "Commitment",
+            "description": "Score total points across all matches",
+            "base_threshold": 1000000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(score AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_points.png"
+        },
+        {
+            "id": "win_streak",
+            "name": "Win Streak",
+            "description": "Achieve streaks of 3+ consecutive victories",
+            "base_threshold": 5,  # Changed to require 5 streaks for first level
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                WITH RECURSIVE
+                matches_with_streak AS (
+                    SELECT 
+                        outcome,
+                        data,
+                        ROW_NUMBER() OVER (ORDER BY data) as row_num,
+                        CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END as is_victory
+                    FROM matches 
+                    WHERE name = ?
+                ),
+                streak_calc AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        CASE 
+                            WHEN is_victory = 0 THEN 0
+                            WHEN LAG(is_victory, 1, 0) OVER (ORDER BY row_num) = 0 THEN 1
+                            ELSE NULL
+                        END as streak_start
+                    FROM matches_with_streak
+                ),
+                streak_groups AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        SUM(CASE WHEN streak_start IS NOT NULL THEN 1 ELSE 0 END) 
+                            OVER (ORDER BY row_num) as streak_group
+                    FROM streak_calc
+                    WHERE is_victory = 1
+                ),
+                streak_lengths AS (
+                    SELECT 
+                        streak_group,
+                        COUNT(*) as streak_length
+                    FROM streak_groups
+                    GROUP BY streak_group
+                )
+                SELECT 
+                    COUNT(*) as streak_count
+                FROM streak_lengths
+                WHERE streak_length >= 3
+            """,
+            "icon": "medal_streak.png"
+        }
+    ],
+    # New category for Map achievements
+    "Map Mastery": [
+        {
+            "id": "map_specialist",
+            "name": "Map Specialist",
+            "description": "Play many matches on a single map",
+            "base_threshold": 10,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT map, COUNT(*) as map_count 
+                FROM matches 
+                WHERE name = ? AND map IS NOT NULL AND map != ''
+                GROUP BY map
+                ORDER BY map_count DESC
+                LIMIT 1
+            """,
+            "icon": "medal_map_expert.png"
+        },
+        {
+            "id": "map_domination",
+            "name": "Map Domination",
+            "description": "Achieve high win rate on a specific map (min. 5 matches)",
+            "thresholds": [  # Fixed win rate thresholds for each level
+                55,  # Level 1: 55%
+                60,  # Level 2: 60%
+                65,  # Level 3: 65%
+                70,  # Level 4: 70%
+                75,  # Level 5: 75%
+                80,  # Level 6: 80%
+                85,  # Level 7: 85%
+                90,  # Level 8: 90%
+                95,  # Level 9: 95%
+                100  # Level 10: 100%
+            ],
+            "query": """
+                SELECT 
+                    map,
+                    COUNT(*) as total_matches,
+                    ROUND(SUM(CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as win_percentage
+                FROM matches 
+                WHERE name = ? AND map IS NOT NULL AND map != ''
+                GROUP BY map
+                HAVING COUNT(*) >= 5
+                ORDER BY win_percentage DESC
+                LIMIT 1
+            """,
+            "icon": "medal_domination.png"
+        }
+    ]
+}
+
+def check_achievement_progress(db_path, player_name, achievement):
+    """Check the progress of an achievement for a player"""
+    try:
+        with sqlite3.connect(db_path) as conn:
+            cursor = conn.cursor()
+            if achievement["id"] == "map_domination":
+                cursor.execute(achievement["query"], (player_name,))
+                result = cursor.fetchone()
+                # Return tuple of (win_rate, map_name, total_matches) for map_domination
+                return (0, None, 0) if not result else (float(result[2]), result[0], int(result[1]))
+            elif achievement["id"] == "map_specialist":
+                cursor.execute(achievement["query"], (player_name,))
+                result = cursor.fetchone()
+                # Return tuple of (count, map_name) for map_specialist
+                return (0, None) if not result else (int(result[1]), result[0])
+            else:
+                # Original behavior for other achievements
+                cursor.execute(achievement["query"], (player_name,))
+                result = cursor.fetchone()
+                return 0 if not result or result[0] is None else float(result[0])
+    except Exception as e:
+        logger.error(f"Error checking achievement {achievement['name']}: {str(e)}")
+        return (0, None) if achievement["id"] == "map_specialist" else (0 if achievement["id"] != "map_domination" else (0, None, 0))
+
+def calculate_level_thresholds(achievement):
+    """Calculate exponentially increasing thresholds for achievement levels"""
+    base = achievement["base_threshold"]
+    factor = achievement["growth_factor"]
+    max_level = achievement["max_level"]
+
+    return [
+        int(base * (factor ** (level - 1)))
+        for level in range(1, max_level + 1)
+    ]
+
+def get_achievement_level(value, achievement):
+    """Determine the achievement level based on progress using exponential thresholds"""
+    thresholds = calculate_level_thresholds(achievement)
+
+    return next(
+        (
+            (i, threshold, thresholds)
+            for i, threshold in enumerate(thresholds)
+            if value < threshold
+        ),
+        (achievement["max_level"], thresholds[-1], thresholds),
+    )
+
+def create_achievement_widget(achievement, current_value):
+    """Create a widget to display an achievement with exponential progression"""
+    widget = QWidget()
+    layout = QHBoxLayout()
+    
+    # Icon placeholder (will be replaced with actual icons)
+    icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 
+                           "resources", "icons", achievement.get("icon", "medal_default.png"))
+    
+    icon_label = QLabel()
+    if os.path.exists(icon_path):
+        pixmap = QPixmap(icon_path).scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation)
+        icon_label.setPixmap(pixmap)
+    else:
+        icon_label.setText("🏅")
+        icon_label.setFont(QFont("Arial", 24))
+    
+    icon_label.setFixedSize(50, 50)
+    layout.addWidget(icon_label)
+    
+    # Achievement info
+    info_layout = QVBoxLayout()
+    
+    # Name and description with improved styling
+    name_label = QLabel(f"<b>{achievement['name']}</b>")
+    name_label.setStyleSheet("font-size: 14px;")
+    desc_label = QLabel(achievement['description'])
+    desc_label.setStyleSheet("color: #666;")
+    desc_label.setWordWrap(True)
+    
+    info_layout.addWidget(name_label)
+    info_layout.addWidget(desc_label)
+
+    if achievement["id"] == "map_domination":
+        win_rate, map_name, total_matches = current_value
+        if map_name:
+            # Calculate level based on fixed thresholds
+            thresholds = achievement["thresholds"]
+            level = 0
+            for i, threshold in enumerate(thresholds):
+                if win_rate >= threshold:
+                    level = i + 1
+            
+            # Create level indicator with stars
+            max_level = len(thresholds)
+            level_text = "★" * level + "☆" * (max_level - level)
+            level_label = QLabel(f"Level {level} {level_text}")
+            level_label.setStyleSheet(f"color: {'#FFC125' if level > 0 else '#666'}; font-size: 12px;")
+            info_layout.addWidget(level_label)
+            
+            # Show map info
+            map_label = QLabel(f"<span style='color: #666; font-size: 12px;'>{map_name} ({total_matches} matches)</span>")
+            map_label.setTextFormat(Qt.RichText)
+            info_layout.addWidget(map_label)
+
+            # Add progress bar for win rate
+            progress_bar = QProgressBar()
+            progress_bar.setRange(0, 100)
+            progress_bar.setValue(int(win_rate))
+            
+            # Color-code based on win rate
+            if win_rate >= 75:
+                style_color = "#4CAF50"  # Green
+            elif win_rate >= 60:
+                style_color = "#FFA726"  # Orange
+            else:
+                style_color = "#2196F3"  # Blue
+                
+            progress_bar.setStyleSheet(f"""
+                QProgressBar {{
+                    border: 1px solid #CCC;
+                    border-radius: 3px;
+                    text-align: center;
+                    height: 20px;
+                }}
+                QProgressBar::chunk {{
+                    background-color: {style_color};
+                    border-radius: 2px;
+                }}
+            """)
+            
+            progress_bar.setFormat(f"{win_rate:.1f}% Win Rate")
+            info_layout.addWidget(progress_bar)
+            
+            if level < max_level:
+                next_threshold = thresholds[level]
+                points_needed = next_threshold - win_rate
+                next_level_label = QLabel(f"+{points_needed:.1f}% to next level ({next_threshold}%)")
+                next_level_label.setStyleSheet("color: #666; font-size: 10px;")
+                info_layout.addWidget(next_level_label)
+        else:
+            # No qualifying maps yet
+            no_data_label = QLabel("Play at least 5 matches on a map")
+            no_data_label.setStyleSheet("color: #666; font-style: italic;")
+            info_layout.addWidget(no_data_label)
+    elif achievement["id"] == "map_specialist":
+        matches_played, map_name = current_value
+        if map_name:
+            # Calculate level and thresholds
+            level, current_level_threshold, all_thresholds = get_achievement_level(matches_played, achievement)
+            
+            # Create level indicator with stars
+            level_text = "★" * level + "☆" * (achievement["max_level"] - level)
+            level_label = QLabel(f"Level {level} {level_text}")
+            level_label.setStyleSheet(f"color: {'#FFC125' if level > 0 else '#666'}; font-size: 12px;")
+            info_layout.addWidget(level_label)
+            
+            # Show map info
+            map_label = QLabel(f"<span style='color: #666; font-size: 12px;'>Most played: {map_name}</span>")
+            map_label.setTextFormat(Qt.RichText)
+            info_layout.addWidget(map_label)
+
+            # Add progress bar
+            progress = int(round((matches_played / current_level_threshold) * 100)) if current_level_threshold > 0 else 0
+            progress = min(100, progress)
+            
+            progress_bar = QProgressBar()
+            progress_bar.setRange(0, 100)
+            progress_bar.setValue(progress)
+            
+            # Color-code based on progress
+            if progress >= 66:
+                style_color = "#4CAF50"  # Green
+            elif progress >= 33:
+                style_color = "#FFA726"  # Orange
+            else:
+                style_color = "#2196F3"  # Blue
+                
+            progress_bar.setStyleSheet(f"""
+                QProgressBar {{
+                    border: 1px solid #CCC;
+                    border-radius: 3px;
+                    text-align: center;
+                    height: 20px;
+                }}
+                QProgressBar::chunk {{
+                    background-color: {style_color};
+                    border-radius: 2px;
+                }}
+            """)
+            
+            progress_bar.setFormat(f"{matches_played:,} matches played")
+            info_layout.addWidget(progress_bar)
+            
+            if level < achievement["max_level"]:
+                next_threshold = all_thresholds[level]
+                remaining = next_threshold - matches_played
+                next_level_label = QLabel(f"{remaining:,} matches to next level")
+                next_level_label.setStyleSheet("color: #666; font-size: 10px;")
+                info_layout.addWidget(next_level_label)
+        else:
+            no_data_label = QLabel("No matches played yet")
+            no_data_label.setStyleSheet("color: #666; font-style: italic;")
+            info_layout.addWidget(no_data_label)
+    else:
+        # Calculate level and thresholds
+        level, current_level_threshold, all_thresholds = get_achievement_level(current_value, achievement)
+        
+        # Format numbers with thousand separators
+        current_value_formatted = f"{int(current_value):,}"
+        threshold_formatted = f"{current_level_threshold:,}"
+        
+        if level < achievement["max_level"]:
+            if current_level_threshold > 0:
+                progress = int(round((current_value / current_level_threshold) * 100))
+                progress = max(0, min(100, progress))
+            else:
+                progress = 0
+            
+            # Create level indicator with stars - using a more readable gold color
+            level_text = "★" * level + "☆" * (achievement["max_level"] - level)
+            level_label = QLabel(f"Level {level} {level_text}")
+            level_label.setStyleSheet(f"""
+                color: {'#FFC125' if level > 0 else '#666'};  /* Brighter gold */
+                font-size: 12px;
+                margin-bottom: 2px;
+            """)
+            info_layout.addWidget(level_label)
+            
+            # Enhanced progress bar
+            progress_bar = QProgressBar()
+            progress_bar.setRange(0, 100)
+            progress_bar.setValue(progress)
+            
+            # Color-code progress bar based on completion
+            if progress >= 66:
+                style_color = "#4CAF50"  # Green
+            elif progress >= 33:
+                style_color = "#FFA726"  # Orange
+            else:
+                style_color = "#2196F3"  # Blue
+                
+            progress_bar.setStyleSheet(f"""
+                QProgressBar {{
+                    border: 1px solid #CCC;
+                    border-radius: 3px;
+                    text-align: center;
+                    height: 20px;
+                }}
+                QProgressBar::chunk {{
+                    background-color: {style_color};
+                    border-radius: 2px;
+                }}
+            """)
+            
+            # Show detailed progress information
+            progress_text = f"{current_value_formatted}/{threshold_formatted}"
+            progress_bar.setFormat(f"{progress_text} ({progress}%)")
+            info_layout.addWidget(progress_bar)
+            
+            # Show next level info - fixed calculation
+            if level < achievement["max_level"]:  # Changed condition to show for all non-max levels
+                next_threshold = all_thresholds[level]
+                remaining = int(next_threshold - current_value)  # Convert to integer
+                next_level_label = QLabel(f"{remaining:,} points to next level")
+                next_level_label.setStyleSheet("color: #666; font-size: 10px;")
+                info_layout.addWidget(next_level_label)
+        else:
+            # Max level reached styling - adjusted gold color
+            max_label = QLabel(f"ELITE ACHIEVED - Level {level}")
+            max_label.setStyleSheet("""
+                color: #FFC125;  /* Brighter gold */
+                font-weight: bold;
+                font-size: 14px;
+                padding: 5px;
+                background-color: #2C3E50;
+                border-radius: 3px;
+            """)
+            info_layout.addWidget(max_label)
+            
+            total_label = QLabel(f"Total Score: {current_value_formatted}")
+            total_label.setStyleSheet("color: #666; font-size: 12px;")
+            info_layout.addWidget(total_label)
+    
+    layout.addLayout(info_layout)
+    layout.setStretch(1, 1)  # Make info section expand
+    widget.setLayout(layout)
+    return widget
+
+def setup_achievement_tab(dialog):
+    """Setup the achievement tab with the given dialog"""
+    tab = QWidget()
+    layout = QVBoxLayout()
+    
+    # Create a scrollable area
+    scroll_area = QScrollArea()
+    scroll_area.setWidgetResizable(True)
+    scroll_content = QWidget()
+    scroll_layout = QVBoxLayout(scroll_content)
+    
+    db_path = dialog.parent.db.db_path
+    player_name = dialog.player_name
+    
+    # Add achievements by category
+    for category, achievements in ACHIEVEMENTS.items():
+        group_box = QGroupBox(category)
+        group_layout = QVBoxLayout()
+        
+        for achievement in achievements:
+            current_value = check_achievement_progress(db_path, player_name, achievement)
+            achievement_widget = create_achievement_widget(achievement, current_value)
+            group_layout.addWidget(achievement_widget)
+        
+        group_box.setLayout(group_layout)
+        scroll_layout.addWidget(group_box)
+    
+    # Add some vertical spacing at the bottom
+    spacer = QWidget()
+    spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+    scroll_layout.addWidget(spacer)
+    
+    scroll_area.setWidget(scroll_content)
+    layout.addWidget(scroll_area)
+    
+    tab.setLayout(layout)
+    return tab
\ No newline at end of file


Reviews:
user: login: sourcery-ai[bot]
body: Hey @Chelyocarpus - I've reviewed your changes and they look great!

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:54:23Z

Review comments:
diff_hunk: @@ -0,0 +1,537 @@
+from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                            QLabel, QProgressBar, QScrollArea, QSizePolicy)
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QFont, QIcon
+import sqlite3
+import logging
+import os
+
+# Setup logging
+logger = logging.getLogger(__name__)
+
+# Achievement definitions
+ACHIEVEMENTS = {
+    "Combat": [
+        {
+            "id": "marksman",
+            "name": "Marksman",
+            "description": "Get kills in battle",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(kills AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_kills.png"
+        },
+        {
+            "id": "headhunter",
+            "name": "Headhunter",
+            "description": "Get high K/D (>=2.0) ratio in matches",
+            "base_threshold": 10,
+            "growth_factor": 1.5,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches
+                WHERE name = ?
+                AND CAST(kills AS FLOAT) / CASE 
+                    WHEN CAST(deaths AS INTEGER) = 0 THEN 1.0
+                    ELSE CAST(deaths AS FLOAT)
+                END >= 2.0
+                AND CAST(kills AS INTEGER) > 0
+            """,
+            "icon": "medal_kd.png"
+        },
+        {
+            "id": "vehicle_destroyer",
+            "name": "Vehicle Destroyer",
+            "description": "Accumulate vehicle damage",
+            "base_threshold": 1000,
+            "growth_factor": 2.5,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(vehicle_damage AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_vehicle.png"
+        }
+    ],
+    "Teamplay": [
+        {
+            "id": "team_player",
+            "name": "Team Player",
+            "description": "Get assists helping your teammates",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(assists AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_assists.png"
+        },
+        {
+            "id": "guardian_angel",
+            "name": "Guardian Angel",
+            "description": "Revive fallen teammates",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(revives AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_revives.png"
+        },
+        {
+            "id": "reinforcement",
+            "name": "Reinforcement",
+            "description": "Provide tactical respawns for your team",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(tactical_respawn AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_respawn.png"
+        }
+    ],
+    "Objective": [
+        {
+            "id": "flag_bearer",
+            "name": "Flag Bearer",
+            "description": "Capture objectives",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(captures AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_captures.png"
+        },
+        {
+            "id": "victory",
+            "name": "Victory",
+            "description": "Win matches",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND outcome LIKE '%VICTORY%'
+            """,
+            "icon": "medal_victory.png"
+        },
+        {
+            "id": "top_rank",
+            "name": "Top Rank",
+            "description": "Finish matches in the top 3",
+            "base_threshold": 5,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND CAST(rank AS INTEGER) <= 3
+            """,
+            "icon": "medal_top_rank.png"
+        }
+    ],
+    "Specialist": [
+        {
+            "id": "assault_master",
+            "name": "Assault Master",
+            "description": "Play matches as Assault class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Assault'
+            """,
+            "icon": "medal_assault.png"
+        },
+        {
+            "id": "engineer_master",
+            "name": "Engineer Master",
+            "description": "Play matches as Engineer class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Engineer'
+            """,
+            "icon": "medal_engineer.png"
+        },
+        {
+            "id": "support_master",
+            "name": "Support Master",
+            "description": "Play matches as Support class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Support'
+            """,
+            "icon": "medal_support.png"
+        },
+        {
+            "id": "recon_master",
+            "name": "Recon Master",
+            "description": "Play matches as Recon class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Recon'
+            """,
+            "icon": "medal_recon.png"
+        }
+    ],
+    "Dedication": [
+        {
+            "id": "veteran",
+            "name": "Veteran",
+            "description": "Play many matches",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_games.png"
+        },
+        {
+            "id": "commitment",
+            "name": "Commitment",
+            "description": "Score total points across all matches",
+            "base_threshold": 1000000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(score AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_points.png"
+        },
+        {
+            "id": "win_streak",
+            "name": "Win Streak",
+            "description": "Achieve streaks of 3+ consecutive victories",
+            "base_threshold": 5,  # Changed to require 5 streaks for first level
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                WITH RECURSIVE
+                matches_with_streak AS (
+                    SELECT 
+                        outcome,
+                        data,
+                        ROW_NUMBER() OVER (ORDER BY data) as row_num,
+                        CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END as is_victory
+                    FROM matches 
+                    WHERE name = ?
+                ),
+                streak_calc AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        CASE 
+                            WHEN is_victory = 0 THEN 0
+                            WHEN LAG(is_victory, 1, 0) OVER (ORDER BY row_num) = 0 THEN 1
+                            ELSE NULL
+                        END as streak_start
+                    FROM matches_with_streak
+                ),
+                streak_groups AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        SUM(CASE WHEN streak_start IS NOT NULL THEN 1 ELSE 0 END) 
+                            OVER (ORDER BY row_num) as streak_group
+                    FROM streak_calc
+                    WHERE is_victory = 1
+                ),
+                streak_lengths AS (
+                    SELECT 
+                        streak_group,
+                        COUNT(*) as streak_length
+                    FROM streak_groups
+                    GROUP BY streak_group
+                )
+                SELECT 
+                    COUNT(*) as streak_count
+                FROM streak_lengths
+                WHERE streak_length >= 3
+            """,
+            "icon": "medal_streak.png"
+        }
+    ],
+    # New category for Map achievements
+    "Map Mastery": [
+        {
+            "id": "map_diversification",
+            "name": "Map Explorer",
+            "description": "Play on different maps at least 5 times each",
+            "base_threshold": 1,
+            "growth_factor": 2,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_maps.png"
+        },
+        {
+            "id": "map_specialist",
+            "name": "Map Specialist",
+            "description": "Play many matches on a single map",
+            "base_threshold": 10,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(map_count) FROM (
+                    SELECT map, COUNT(*) as map_count 
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                )
+            """,
+            "icon": "medal_map_expert.png"
+        },
+        {
+            "id": "conquest_master",
+            "name": "Conquest Master",
+            "description": "Win matches on different maps (at least 2 wins each)",
+            "base_threshold": 2,
+            "growth_factor": 1.5,
+            "max_level": 5,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND outcome LIKE '%VICTORY%' AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 2
+                )
+            """,
+            "icon": "medal_conquest.png"
+        },
+        {
+            "id": "map_domination",
+            "name": "Map Domination",
+            "description": "Achieve high win rate on a specific map (min. 5 matches)",
+            "base_threshold": 60,
+            "growth_factor": 1.2,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(win_percentage) FROM (
+                    SELECT 
+                        map, 
+                        COUNT(*) as total_matches,
+                        ROUND(SUM(CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as win_percentage
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_domination.png"
+        }
+    ]
+}
+
+def check_achievement_progress(db_path, player_name, achievement):
+    """Check the progress of an achievement for a player"""
+    try:
+        with sqlite3.connect(db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute(achievement["query"], (player_name,))
+            result = cursor.fetchone()
+            
+            if not result or result[0] is None:
+                return 0
+            
+            # Convert to float for consistent handling
+            current_value = float(result[0])
+            return current_value
+    except Exception as e:
+        logger.error(f"Error checking achievement {achievement['name']}: {str(e)}")
+        return 0
+
+def calculate_level_thresholds(achievement):
+    """Calculate exponentially increasing thresholds for achievement levels"""
+    base = achievement["base_threshold"]
+    factor = achievement["growth_factor"]
+    max_level = achievement["max_level"]
+    
+    thresholds = []
+    for level in range(1, max_level + 1):
+        threshold = int(base * (factor ** (level - 1)))
+        thresholds.append(threshold)
+    
+    return thresholds
+
+def get_achievement_level(value, achievement):
+    """Determine the achievement level based on progress using exponential thresholds"""
+    thresholds = calculate_level_thresholds(achievement)
+    
+    for i, threshold in enumerate(thresholds):
+        if value < threshold:
+            return i, threshold, thresholds
+    
+    # If player has passed all thresholds, they're at max level
+    max_level = achievement["max_level"]
+    return max_level, thresholds[-1], thresholds
+
+def create_achievement_widget(achievement, current_value):
path: src/gui/tabs/achievement_tab.py
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider refactoring `create_achievement_widget` by extracting helper functions for icon, progress bar, and level label creation.

The new changes work as intended but can benefit from isolating responsibilities inside `create_achievement_widget`. Splitting the widget into smaller helper functions for tasks like creating the icon, building the progress bar, and making the level label can reduce complexity and improve maintainability without altering functionality.

For example, you might create a helper for the icon display:

```python
def create_icon_label(achievement):
    icon_path = os.path.join(
        os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))),
        "resources", "icons", achievement.get("icon", "medal_default.png")
    )
    label = QLabel()
    if os.path.exists(icon_path):
        pixmap = QPixmap(icon_path).scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        label.setPixmap(pixmap)
    else:
        label.setText("🏅")
        label.setFont(QFont("Arial", 24))
    label.setFixedSize(50, 50)
    return label
```

Similarly, extract the progress bar creation:

```python
def create_progress_bar(current_value, current_threshold, progress):
    progress_bar = QProgressBar()
    progress_bar.setRange(0, 100)
    progress_bar.setValue(progress)

    # Color-code progress bar based on completion
    if progress >= 66:
        style_color = "#4CAF50"  # Green
    elif progress >= 33:
        style_color = "#FFA726"  # Orange
    else:
        style_color = "#2196F3"  # Blue

    progress_bar.setStyleSheet(
        f"""
        QProgressBar {{
            border: 1px solid #CCC;
            border-radius: 3px;
            text-align: center;
            height: 20px;
        }}
        QProgressBar::chunk {{
            background-color: {style_color};
            border-radius: 2px;
        }}
        """
    )
    progress_text = f"{int(current_value):,}/{int(current_threshold):,}"
    progress_bar.setFormat(f"{progress_text} ({progress}%)")
    return progress_bar
```

And for building the level label:

```python
def create_level_label(level, max_level):
    level_text = "★" * level + "☆" * (max_level - level)
    label = QLabel(f"Level {level} {level_text}")
    label.setStyleSheet(
        f"""
        color: {'#FFC125' if level > 0 else '#666'};
        font-size: 12px;
        margin-bottom: 2px;
        """
    )
    return label
```

You can then refactor `create_achievement_widget` to call these helpers. This approach keeps the UI-building code modular and easier to follow.
created_at: 2025-03-04T07:54:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,10 +61,15 @@ def init_ui(self):
             ("Match History", MatchHistoryTab, [self, self.player_name, self.parent.db.db_path])
         )
 
+        # Add the new achievements tab
+        tab_configs.append(
+            ("Achievements", setup_achievement_tab, self)
+        )
+
         # Create and add tabs
         for tab_name, tab_class, args in tab_configs:
-            if tab_name == "Overall Stats":
-                tab = tab_class(args)  # Special handling for setup_overall_tab
+            if tab_name == "Overall Stats" or tab_name == "Achievements":
+                tab = tab_class(args)  # Special handling for setup_overall_tab and setup_achievement_tab
             else:
                 tab = tab_class(*args)  # All other tabs use positional args
path: src/gui/dialogs/player_details.py
user: login: sourcery-ai[bot]
body: **issue (code-quality):** Replace multiple comparisons of same variable with `in` operator ([`merge-comparisons`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/merge-comparisons/))
created_at: 2025-03-04T07:54:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,537 @@
+from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                            QLabel, QProgressBar, QScrollArea, QSizePolicy)
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QFont, QIcon
+import sqlite3
+import logging
+import os
+
+# Setup logging
+logger = logging.getLogger(__name__)
+
+# Achievement definitions
+ACHIEVEMENTS = {
+    "Combat": [
+        {
+            "id": "marksman",
+            "name": "Marksman",
+            "description": "Get kills in battle",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(kills AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_kills.png"
+        },
+        {
+            "id": "headhunter",
+            "name": "Headhunter",
+            "description": "Get high K/D (>=2.0) ratio in matches",
+            "base_threshold": 10,
+            "growth_factor": 1.5,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches
+                WHERE name = ?
+                AND CAST(kills AS FLOAT) / CASE 
+                    WHEN CAST(deaths AS INTEGER) = 0 THEN 1.0
+                    ELSE CAST(deaths AS FLOAT)
+                END >= 2.0
+                AND CAST(kills AS INTEGER) > 0
+            """,
+            "icon": "medal_kd.png"
+        },
+        {
+            "id": "vehicle_destroyer",
+            "name": "Vehicle Destroyer",
+            "description": "Accumulate vehicle damage",
+            "base_threshold": 1000,
+            "growth_factor": 2.5,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(vehicle_damage AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_vehicle.png"
+        }
+    ],
+    "Teamplay": [
+        {
+            "id": "team_player",
+            "name": "Team Player",
+            "description": "Get assists helping your teammates",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(assists AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_assists.png"
+        },
+        {
+            "id": "guardian_angel",
+            "name": "Guardian Angel",
+            "description": "Revive fallen teammates",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(revives AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_revives.png"
+        },
+        {
+            "id": "reinforcement",
+            "name": "Reinforcement",
+            "description": "Provide tactical respawns for your team",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(tactical_respawn AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_respawn.png"
+        }
+    ],
+    "Objective": [
+        {
+            "id": "flag_bearer",
+            "name": "Flag Bearer",
+            "description": "Capture objectives",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(captures AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_captures.png"
+        },
+        {
+            "id": "victory",
+            "name": "Victory",
+            "description": "Win matches",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND outcome LIKE '%VICTORY%'
+            """,
+            "icon": "medal_victory.png"
+        },
+        {
+            "id": "top_rank",
+            "name": "Top Rank",
+            "description": "Finish matches in the top 3",
+            "base_threshold": 5,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND CAST(rank AS INTEGER) <= 3
+            """,
+            "icon": "medal_top_rank.png"
+        }
+    ],
+    "Specialist": [
+        {
+            "id": "assault_master",
+            "name": "Assault Master",
+            "description": "Play matches as Assault class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Assault'
+            """,
+            "icon": "medal_assault.png"
+        },
+        {
+            "id": "engineer_master",
+            "name": "Engineer Master",
+            "description": "Play matches as Engineer class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Engineer'
+            """,
+            "icon": "medal_engineer.png"
+        },
+        {
+            "id": "support_master",
+            "name": "Support Master",
+            "description": "Play matches as Support class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Support'
+            """,
+            "icon": "medal_support.png"
+        },
+        {
+            "id": "recon_master",
+            "name": "Recon Master",
+            "description": "Play matches as Recon class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Recon'
+            """,
+            "icon": "medal_recon.png"
+        }
+    ],
+    "Dedication": [
+        {
+            "id": "veteran",
+            "name": "Veteran",
+            "description": "Play many matches",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_games.png"
+        },
+        {
+            "id": "commitment",
+            "name": "Commitment",
+            "description": "Score total points across all matches",
+            "base_threshold": 1000000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(score AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_points.png"
+        },
+        {
+            "id": "win_streak",
+            "name": "Win Streak",
+            "description": "Achieve streaks of 3+ consecutive victories",
+            "base_threshold": 5,  # Changed to require 5 streaks for first level
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                WITH RECURSIVE
+                matches_with_streak AS (
+                    SELECT 
+                        outcome,
+                        data,
+                        ROW_NUMBER() OVER (ORDER BY data) as row_num,
+                        CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END as is_victory
+                    FROM matches 
+                    WHERE name = ?
+                ),
+                streak_calc AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        CASE 
+                            WHEN is_victory = 0 THEN 0
+                            WHEN LAG(is_victory, 1, 0) OVER (ORDER BY row_num) = 0 THEN 1
+                            ELSE NULL
+                        END as streak_start
+                    FROM matches_with_streak
+                ),
+                streak_groups AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        SUM(CASE WHEN streak_start IS NOT NULL THEN 1 ELSE 0 END) 
+                            OVER (ORDER BY row_num) as streak_group
+                    FROM streak_calc
+                    WHERE is_victory = 1
+                ),
+                streak_lengths AS (
+                    SELECT 
+                        streak_group,
+                        COUNT(*) as streak_length
+                    FROM streak_groups
+                    GROUP BY streak_group
+                )
+                SELECT 
+                    COUNT(*) as streak_count
+                FROM streak_lengths
+                WHERE streak_length >= 3
+            """,
+            "icon": "medal_streak.png"
+        }
+    ],
+    # New category for Map achievements
+    "Map Mastery": [
+        {
+            "id": "map_diversification",
+            "name": "Map Explorer",
+            "description": "Play on different maps at least 5 times each",
+            "base_threshold": 1,
+            "growth_factor": 2,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_maps.png"
+        },
+        {
+            "id": "map_specialist",
+            "name": "Map Specialist",
+            "description": "Play many matches on a single map",
+            "base_threshold": 10,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(map_count) FROM (
+                    SELECT map, COUNT(*) as map_count 
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                )
+            """,
+            "icon": "medal_map_expert.png"
+        },
+        {
+            "id": "conquest_master",
+            "name": "Conquest Master",
+            "description": "Win matches on different maps (at least 2 wins each)",
+            "base_threshold": 2,
+            "growth_factor": 1.5,
+            "max_level": 5,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND outcome LIKE '%VICTORY%' AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 2
+                )
+            """,
+            "icon": "medal_conquest.png"
+        },
+        {
+            "id": "map_domination",
+            "name": "Map Domination",
+            "description": "Achieve high win rate on a specific map (min. 5 matches)",
+            "base_threshold": 60,
+            "growth_factor": 1.2,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(win_percentage) FROM (
+                    SELECT 
+                        map, 
+                        COUNT(*) as total_matches,
+                        ROUND(SUM(CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as win_percentage
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_domination.png"
+        }
+    ]
+}
+
+def check_achievement_progress(db_path, player_name, achievement):
+    """Check the progress of an achievement for a player"""
+    try:
+        with sqlite3.connect(db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute(achievement["query"], (player_name,))
+            result = cursor.fetchone()
+            
+            if not result or result[0] is None:
+                return 0
+            
+            # Convert to float for consistent handling
+            current_value = float(result[0])
+            return current_value
path: src/gui/tabs/achievement_tab.py
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** We've found these issues:

- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))
- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))

```suggestion

            return 0 if not result or result[0] is None else float(result[0])
```
created_at: 2025-03-04T07:54:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,537 @@
+from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                            QLabel, QProgressBar, QScrollArea, QSizePolicy)
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QFont, QIcon
+import sqlite3
+import logging
+import os
+
+# Setup logging
+logger = logging.getLogger(__name__)
+
+# Achievement definitions
+ACHIEVEMENTS = {
+    "Combat": [
+        {
+            "id": "marksman",
+            "name": "Marksman",
+            "description": "Get kills in battle",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(kills AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_kills.png"
+        },
+        {
+            "id": "headhunter",
+            "name": "Headhunter",
+            "description": "Get high K/D (>=2.0) ratio in matches",
+            "base_threshold": 10,
+            "growth_factor": 1.5,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches
+                WHERE name = ?
+                AND CAST(kills AS FLOAT) / CASE 
+                    WHEN CAST(deaths AS INTEGER) = 0 THEN 1.0
+                    ELSE CAST(deaths AS FLOAT)
+                END >= 2.0
+                AND CAST(kills AS INTEGER) > 0
+            """,
+            "icon": "medal_kd.png"
+        },
+        {
+            "id": "vehicle_destroyer",
+            "name": "Vehicle Destroyer",
+            "description": "Accumulate vehicle damage",
+            "base_threshold": 1000,
+            "growth_factor": 2.5,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(vehicle_damage AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_vehicle.png"
+        }
+    ],
+    "Teamplay": [
+        {
+            "id": "team_player",
+            "name": "Team Player",
+            "description": "Get assists helping your teammates",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(assists AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_assists.png"
+        },
+        {
+            "id": "guardian_angel",
+            "name": "Guardian Angel",
+            "description": "Revive fallen teammates",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(revives AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_revives.png"
+        },
+        {
+            "id": "reinforcement",
+            "name": "Reinforcement",
+            "description": "Provide tactical respawns for your team",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(tactical_respawn AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_respawn.png"
+        }
+    ],
+    "Objective": [
+        {
+            "id": "flag_bearer",
+            "name": "Flag Bearer",
+            "description": "Capture objectives",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(captures AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_captures.png"
+        },
+        {
+            "id": "victory",
+            "name": "Victory",
+            "description": "Win matches",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND outcome LIKE '%VICTORY%'
+            """,
+            "icon": "medal_victory.png"
+        },
+        {
+            "id": "top_rank",
+            "name": "Top Rank",
+            "description": "Finish matches in the top 3",
+            "base_threshold": 5,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND CAST(rank AS INTEGER) <= 3
+            """,
+            "icon": "medal_top_rank.png"
+        }
+    ],
+    "Specialist": [
+        {
+            "id": "assault_master",
+            "name": "Assault Master",
+            "description": "Play matches as Assault class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Assault'
+            """,
+            "icon": "medal_assault.png"
+        },
+        {
+            "id": "engineer_master",
+            "name": "Engineer Master",
+            "description": "Play matches as Engineer class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Engineer'
+            """,
+            "icon": "medal_engineer.png"
+        },
+        {
+            "id": "support_master",
+            "name": "Support Master",
+            "description": "Play matches as Support class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Support'
+            """,
+            "icon": "medal_support.png"
+        },
+        {
+            "id": "recon_master",
+            "name": "Recon Master",
+            "description": "Play matches as Recon class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Recon'
+            """,
+            "icon": "medal_recon.png"
+        }
+    ],
+    "Dedication": [
+        {
+            "id": "veteran",
+            "name": "Veteran",
+            "description": "Play many matches",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_games.png"
+        },
+        {
+            "id": "commitment",
+            "name": "Commitment",
+            "description": "Score total points across all matches",
+            "base_threshold": 1000000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(score AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_points.png"
+        },
+        {
+            "id": "win_streak",
+            "name": "Win Streak",
+            "description": "Achieve streaks of 3+ consecutive victories",
+            "base_threshold": 5,  # Changed to require 5 streaks for first level
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                WITH RECURSIVE
+                matches_with_streak AS (
+                    SELECT 
+                        outcome,
+                        data,
+                        ROW_NUMBER() OVER (ORDER BY data) as row_num,
+                        CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END as is_victory
+                    FROM matches 
+                    WHERE name = ?
+                ),
+                streak_calc AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        CASE 
+                            WHEN is_victory = 0 THEN 0
+                            WHEN LAG(is_victory, 1, 0) OVER (ORDER BY row_num) = 0 THEN 1
+                            ELSE NULL
+                        END as streak_start
+                    FROM matches_with_streak
+                ),
+                streak_groups AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        SUM(CASE WHEN streak_start IS NOT NULL THEN 1 ELSE 0 END) 
+                            OVER (ORDER BY row_num) as streak_group
+                    FROM streak_calc
+                    WHERE is_victory = 1
+                ),
+                streak_lengths AS (
+                    SELECT 
+                        streak_group,
+                        COUNT(*) as streak_length
+                    FROM streak_groups
+                    GROUP BY streak_group
+                )
+                SELECT 
+                    COUNT(*) as streak_count
+                FROM streak_lengths
+                WHERE streak_length >= 3
+            """,
+            "icon": "medal_streak.png"
+        }
+    ],
+    # New category for Map achievements
+    "Map Mastery": [
+        {
+            "id": "map_diversification",
+            "name": "Map Explorer",
+            "description": "Play on different maps at least 5 times each",
+            "base_threshold": 1,
+            "growth_factor": 2,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_maps.png"
+        },
+        {
+            "id": "map_specialist",
+            "name": "Map Specialist",
+            "description": "Play many matches on a single map",
+            "base_threshold": 10,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(map_count) FROM (
+                    SELECT map, COUNT(*) as map_count 
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                )
+            """,
+            "icon": "medal_map_expert.png"
+        },
+        {
+            "id": "conquest_master",
+            "name": "Conquest Master",
+            "description": "Win matches on different maps (at least 2 wins each)",
+            "base_threshold": 2,
+            "growth_factor": 1.5,
+            "max_level": 5,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND outcome LIKE '%VICTORY%' AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 2
+                )
+            """,
+            "icon": "medal_conquest.png"
+        },
+        {
+            "id": "map_domination",
+            "name": "Map Domination",
+            "description": "Achieve high win rate on a specific map (min. 5 matches)",
+            "base_threshold": 60,
+            "growth_factor": 1.2,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(win_percentage) FROM (
+                    SELECT 
+                        map, 
+                        COUNT(*) as total_matches,
+                        ROUND(SUM(CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as win_percentage
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_domination.png"
+        }
+    ]
+}
+
+def check_achievement_progress(db_path, player_name, achievement):
+    """Check the progress of an achievement for a player"""
+    try:
+        with sqlite3.connect(db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute(achievement["query"], (player_name,))
+            result = cursor.fetchone()
+            
+            if not result or result[0] is None:
+                return 0
+            
+            # Convert to float for consistent handling
+            current_value = float(result[0])
+            return current_value
+    except Exception as e:
+        logger.error(f"Error checking achievement {achievement['name']}: {str(e)}")
+        return 0
+
+def calculate_level_thresholds(achievement):
+    """Calculate exponentially increasing thresholds for achievement levels"""
+    base = achievement["base_threshold"]
+    factor = achievement["growth_factor"]
+    max_level = achievement["max_level"]
+    
+    thresholds = []
+    for level in range(1, max_level + 1):
+        threshold = int(base * (factor ** (level - 1)))
+        thresholds.append(threshold)
+    
+    return thresholds
path: src/gui/tabs/achievement_tab.py
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** We've found these issues:

- Convert for loop into list comprehension ([`list-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/list-comprehension/))
- Inline variable that is only used once ([`inline-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-variable/))
- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))

```suggestion

    return [
        int(base * (factor ** (level - 1)))
        for level in range(1, max_level + 1)
    ]
```
created_at: 2025-03-04T07:54:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,537 @@
+from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                            QLabel, QProgressBar, QScrollArea, QSizePolicy)
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QFont, QIcon
+import sqlite3
+import logging
+import os
+
+# Setup logging
+logger = logging.getLogger(__name__)
+
+# Achievement definitions
+ACHIEVEMENTS = {
+    "Combat": [
+        {
+            "id": "marksman",
+            "name": "Marksman",
+            "description": "Get kills in battle",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(kills AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_kills.png"
+        },
+        {
+            "id": "headhunter",
+            "name": "Headhunter",
+            "description": "Get high K/D (>=2.0) ratio in matches",
+            "base_threshold": 10,
+            "growth_factor": 1.5,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches
+                WHERE name = ?
+                AND CAST(kills AS FLOAT) / CASE 
+                    WHEN CAST(deaths AS INTEGER) = 0 THEN 1.0
+                    ELSE CAST(deaths AS FLOAT)
+                END >= 2.0
+                AND CAST(kills AS INTEGER) > 0
+            """,
+            "icon": "medal_kd.png"
+        },
+        {
+            "id": "vehicle_destroyer",
+            "name": "Vehicle Destroyer",
+            "description": "Accumulate vehicle damage",
+            "base_threshold": 1000,
+            "growth_factor": 2.5,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(vehicle_damage AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_vehicle.png"
+        }
+    ],
+    "Teamplay": [
+        {
+            "id": "team_player",
+            "name": "Team Player",
+            "description": "Get assists helping your teammates",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(assists AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_assists.png"
+        },
+        {
+            "id": "guardian_angel",
+            "name": "Guardian Angel",
+            "description": "Revive fallen teammates",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(revives AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_revives.png"
+        },
+        {
+            "id": "reinforcement",
+            "name": "Reinforcement",
+            "description": "Provide tactical respawns for your team",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(tactical_respawn AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_respawn.png"
+        }
+    ],
+    "Objective": [
+        {
+            "id": "flag_bearer",
+            "name": "Flag Bearer",
+            "description": "Capture objectives",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(captures AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_captures.png"
+        },
+        {
+            "id": "victory",
+            "name": "Victory",
+            "description": "Win matches",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND outcome LIKE '%VICTORY%'
+            """,
+            "icon": "medal_victory.png"
+        },
+        {
+            "id": "top_rank",
+            "name": "Top Rank",
+            "description": "Finish matches in the top 3",
+            "base_threshold": 5,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND CAST(rank AS INTEGER) <= 3
+            """,
+            "icon": "medal_top_rank.png"
+        }
+    ],
+    "Specialist": [
+        {
+            "id": "assault_master",
+            "name": "Assault Master",
+            "description": "Play matches as Assault class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Assault'
+            """,
+            "icon": "medal_assault.png"
+        },
+        {
+            "id": "engineer_master",
+            "name": "Engineer Master",
+            "description": "Play matches as Engineer class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Engineer'
+            """,
+            "icon": "medal_engineer.png"
+        },
+        {
+            "id": "support_master",
+            "name": "Support Master",
+            "description": "Play matches as Support class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Support'
+            """,
+            "icon": "medal_support.png"
+        },
+        {
+            "id": "recon_master",
+            "name": "Recon Master",
+            "description": "Play matches as Recon class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Recon'
+            """,
+            "icon": "medal_recon.png"
+        }
+    ],
+    "Dedication": [
+        {
+            "id": "veteran",
+            "name": "Veteran",
+            "description": "Play many matches",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_games.png"
+        },
+        {
+            "id": "commitment",
+            "name": "Commitment",
+            "description": "Score total points across all matches",
+            "base_threshold": 1000000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(score AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_points.png"
+        },
+        {
+            "id": "win_streak",
+            "name": "Win Streak",
+            "description": "Achieve streaks of 3+ consecutive victories",
+            "base_threshold": 5,  # Changed to require 5 streaks for first level
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                WITH RECURSIVE
+                matches_with_streak AS (
+                    SELECT 
+                        outcome,
+                        data,
+                        ROW_NUMBER() OVER (ORDER BY data) as row_num,
+                        CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END as is_victory
+                    FROM matches 
+                    WHERE name = ?
+                ),
+                streak_calc AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        CASE 
+                            WHEN is_victory = 0 THEN 0
+                            WHEN LAG(is_victory, 1, 0) OVER (ORDER BY row_num) = 0 THEN 1
+                            ELSE NULL
+                        END as streak_start
+                    FROM matches_with_streak
+                ),
+                streak_groups AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        SUM(CASE WHEN streak_start IS NOT NULL THEN 1 ELSE 0 END) 
+                            OVER (ORDER BY row_num) as streak_group
+                    FROM streak_calc
+                    WHERE is_victory = 1
+                ),
+                streak_lengths AS (
+                    SELECT 
+                        streak_group,
+                        COUNT(*) as streak_length
+                    FROM streak_groups
+                    GROUP BY streak_group
+                )
+                SELECT 
+                    COUNT(*) as streak_count
+                FROM streak_lengths
+                WHERE streak_length >= 3
+            """,
+            "icon": "medal_streak.png"
+        }
+    ],
+    # New category for Map achievements
+    "Map Mastery": [
+        {
+            "id": "map_diversification",
+            "name": "Map Explorer",
+            "description": "Play on different maps at least 5 times each",
+            "base_threshold": 1,
+            "growth_factor": 2,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_maps.png"
+        },
+        {
+            "id": "map_specialist",
+            "name": "Map Specialist",
+            "description": "Play many matches on a single map",
+            "base_threshold": 10,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(map_count) FROM (
+                    SELECT map, COUNT(*) as map_count 
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                )
+            """,
+            "icon": "medal_map_expert.png"
+        },
+        {
+            "id": "conquest_master",
+            "name": "Conquest Master",
+            "description": "Win matches on different maps (at least 2 wins each)",
+            "base_threshold": 2,
+            "growth_factor": 1.5,
+            "max_level": 5,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND outcome LIKE '%VICTORY%' AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 2
+                )
+            """,
+            "icon": "medal_conquest.png"
+        },
+        {
+            "id": "map_domination",
+            "name": "Map Domination",
+            "description": "Achieve high win rate on a specific map (min. 5 matches)",
+            "base_threshold": 60,
+            "growth_factor": 1.2,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(win_percentage) FROM (
+                    SELECT 
+                        map, 
+                        COUNT(*) as total_matches,
+                        ROUND(SUM(CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as win_percentage
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_domination.png"
+        }
+    ]
+}
+
+def check_achievement_progress(db_path, player_name, achievement):
+    """Check the progress of an achievement for a player"""
+    try:
+        with sqlite3.connect(db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute(achievement["query"], (player_name,))
+            result = cursor.fetchone()
+            
+            if not result or result[0] is None:
+                return 0
+            
+            # Convert to float for consistent handling
+            current_value = float(result[0])
+            return current_value
+    except Exception as e:
+        logger.error(f"Error checking achievement {achievement['name']}: {str(e)}")
+        return 0
+
+def calculate_level_thresholds(achievement):
+    """Calculate exponentially increasing thresholds for achievement levels"""
+    base = achievement["base_threshold"]
+    factor = achievement["growth_factor"]
+    max_level = achievement["max_level"]
+    
+    thresholds = []
+    for level in range(1, max_level + 1):
+        threshold = int(base * (factor ** (level - 1)))
+        thresholds.append(threshold)
+    
+    return thresholds
+
+def get_achievement_level(value, achievement):
+    """Determine the achievement level based on progress using exponential thresholds"""
+    thresholds = calculate_level_thresholds(achievement)
+    
+    for i, threshold in enumerate(thresholds):
+        if value < threshold:
+            return i, threshold, thresholds
+    
+    # If player has passed all thresholds, they're at max level
+    max_level = achievement["max_level"]
+    return max_level, thresholds[-1], thresholds
path: src/gui/tabs/achievement_tab.py
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** We've found these issues:

- Use the built-in function `next` instead of a for-loop ([`use-next`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-next/))
- Inline variable that is only used once ([`inline-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-variable/))

```suggestion

    return next(
        (
            (i, threshold, thresholds)
            for i, threshold in enumerate(thresholds)
            if value < threshold
        ),
        (achievement["max_level"], thresholds[-1], thresholds),
    )
```
created_at: 2025-03-04T07:54:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,537 @@
+from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, 
+                            QLabel, QProgressBar, QScrollArea, QSizePolicy)
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPixmap, QFont, QIcon
+import sqlite3
+import logging
+import os
+
+# Setup logging
+logger = logging.getLogger(__name__)
+
+# Achievement definitions
+ACHIEVEMENTS = {
+    "Combat": [
+        {
+            "id": "marksman",
+            "name": "Marksman",
+            "description": "Get kills in battle",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(kills AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_kills.png"
+        },
+        {
+            "id": "headhunter",
+            "name": "Headhunter",
+            "description": "Get high K/D (>=2.0) ratio in matches",
+            "base_threshold": 10,
+            "growth_factor": 1.5,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches
+                WHERE name = ?
+                AND CAST(kills AS FLOAT) / CASE 
+                    WHEN CAST(deaths AS INTEGER) = 0 THEN 1.0
+                    ELSE CAST(deaths AS FLOAT)
+                END >= 2.0
+                AND CAST(kills AS INTEGER) > 0
+            """,
+            "icon": "medal_kd.png"
+        },
+        {
+            "id": "vehicle_destroyer",
+            "name": "Vehicle Destroyer",
+            "description": "Accumulate vehicle damage",
+            "base_threshold": 1000,
+            "growth_factor": 2.5,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(vehicle_damage AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_vehicle.png"
+        }
+    ],
+    "Teamplay": [
+        {
+            "id": "team_player",
+            "name": "Team Player",
+            "description": "Get assists helping your teammates",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(assists AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_assists.png"
+        },
+        {
+            "id": "guardian_angel",
+            "name": "Guardian Angel",
+            "description": "Revive fallen teammates",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(revives AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_revives.png"
+        },
+        {
+            "id": "reinforcement",
+            "name": "Reinforcement",
+            "description": "Provide tactical respawns for your team",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(tactical_respawn AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_respawn.png"
+        }
+    ],
+    "Objective": [
+        {
+            "id": "flag_bearer",
+            "name": "Flag Bearer",
+            "description": "Capture objectives",
+            "base_threshold": 1000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(captures AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_captures.png"
+        },
+        {
+            "id": "victory",
+            "name": "Victory",
+            "description": "Win matches",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND outcome LIKE '%VICTORY%'
+            """,
+            "icon": "medal_victory.png"
+        },
+        {
+            "id": "top_rank",
+            "name": "Top Rank",
+            "description": "Finish matches in the top 3",
+            "base_threshold": 5,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND CAST(rank AS INTEGER) <= 3
+            """,
+            "icon": "medal_top_rank.png"
+        }
+    ],
+    "Specialist": [
+        {
+            "id": "assault_master",
+            "name": "Assault Master",
+            "description": "Play matches as Assault class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Assault'
+            """,
+            "icon": "medal_assault.png"
+        },
+        {
+            "id": "engineer_master",
+            "name": "Engineer Master",
+            "description": "Play matches as Engineer class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Engineer'
+            """,
+            "icon": "medal_engineer.png"
+        },
+        {
+            "id": "support_master",
+            "name": "Support Master",
+            "description": "Play matches as Support class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Support'
+            """,
+            "icon": "medal_support.png"
+        },
+        {
+            "id": "recon_master",
+            "name": "Recon Master",
+            "description": "Play matches as Recon class",
+            "base_threshold": 100,
+            "growth_factor": 2.0,
+            "max_level": 8,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ? AND class = 'Recon'
+            """,
+            "icon": "medal_recon.png"
+        }
+    ],
+    "Dedication": [
+        {
+            "id": "veteran",
+            "name": "Veteran",
+            "description": "Play many matches",
+            "base_threshold": 50,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_games.png"
+        },
+        {
+            "id": "commitment",
+            "name": "Commitment",
+            "description": "Score total points across all matches",
+            "base_threshold": 1000000,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT SUM(CAST(score AS INTEGER)) FROM matches WHERE name = ?
+            """,
+            "icon": "medal_points.png"
+        },
+        {
+            "id": "win_streak",
+            "name": "Win Streak",
+            "description": "Achieve streaks of 3+ consecutive victories",
+            "base_threshold": 5,  # Changed to require 5 streaks for first level
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                WITH RECURSIVE
+                matches_with_streak AS (
+                    SELECT 
+                        outcome,
+                        data,
+                        ROW_NUMBER() OVER (ORDER BY data) as row_num,
+                        CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END as is_victory
+                    FROM matches 
+                    WHERE name = ?
+                ),
+                streak_calc AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        CASE 
+                            WHEN is_victory = 0 THEN 0
+                            WHEN LAG(is_victory, 1, 0) OVER (ORDER BY row_num) = 0 THEN 1
+                            ELSE NULL
+                        END as streak_start
+                    FROM matches_with_streak
+                ),
+                streak_groups AS (
+                    SELECT 
+                        row_num,
+                        is_victory,
+                        SUM(CASE WHEN streak_start IS NOT NULL THEN 1 ELSE 0 END) 
+                            OVER (ORDER BY row_num) as streak_group
+                    FROM streak_calc
+                    WHERE is_victory = 1
+                ),
+                streak_lengths AS (
+                    SELECT 
+                        streak_group,
+                        COUNT(*) as streak_length
+                    FROM streak_groups
+                    GROUP BY streak_group
+                )
+                SELECT 
+                    COUNT(*) as streak_count
+                FROM streak_lengths
+                WHERE streak_length >= 3
+            """,
+            "icon": "medal_streak.png"
+        }
+    ],
+    # New category for Map achievements
+    "Map Mastery": [
+        {
+            "id": "map_diversification",
+            "name": "Map Explorer",
+            "description": "Play on different maps at least 5 times each",
+            "base_threshold": 1,
+            "growth_factor": 2,
+            "max_level": 10,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_maps.png"
+        },
+        {
+            "id": "map_specialist",
+            "name": "Map Specialist",
+            "description": "Play many matches on a single map",
+            "base_threshold": 10,
+            "growth_factor": 2.0,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(map_count) FROM (
+                    SELECT map, COUNT(*) as map_count 
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                )
+            """,
+            "icon": "medal_map_expert.png"
+        },
+        {
+            "id": "conquest_master",
+            "name": "Conquest Master",
+            "description": "Win matches on different maps (at least 2 wins each)",
+            "base_threshold": 2,
+            "growth_factor": 1.5,
+            "max_level": 5,
+            "query": """
+                SELECT COUNT(*) FROM (
+                    SELECT map
+                    FROM matches 
+                    WHERE name = ? AND outcome LIKE '%VICTORY%' AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 2
+                )
+            """,
+            "icon": "medal_conquest.png"
+        },
+        {
+            "id": "map_domination",
+            "name": "Map Domination",
+            "description": "Achieve high win rate on a specific map (min. 5 matches)",
+            "base_threshold": 60,
+            "growth_factor": 1.2,
+            "max_level": 10,
+            "query": """
+                SELECT MAX(win_percentage) FROM (
+                    SELECT 
+                        map, 
+                        COUNT(*) as total_matches,
+                        ROUND(SUM(CASE WHEN outcome LIKE '%VICTORY%' THEN 1 ELSE 0 END) * 100.0 / COUNT(*)) as win_percentage
+                    FROM matches 
+                    WHERE name = ? AND map IS NOT NULL AND map != ''
+                    GROUP BY map
+                    HAVING COUNT(*) >= 5
+                )
+            """,
+            "icon": "medal_domination.png"
+        }
+    ]
+}
+
+def check_achievement_progress(db_path, player_name, achievement):
+    """Check the progress of an achievement for a player"""
+    try:
+        with sqlite3.connect(db_path) as conn:
+            cursor = conn.cursor()
+            cursor.execute(achievement["query"], (player_name,))
+            result = cursor.fetchone()
+            
+            if not result or result[0] is None:
+                return 0
+            
+            # Convert to float for consistent handling
+            current_value = float(result[0])
+            return current_value
+    except Exception as e:
+        logger.error(f"Error checking achievement {achievement['name']}: {str(e)}")
+        return 0
+
+def calculate_level_thresholds(achievement):
+    """Calculate exponentially increasing thresholds for achievement levels"""
+    base = achievement["base_threshold"]
+    factor = achievement["growth_factor"]
+    max_level = achievement["max_level"]
+    
+    thresholds = []
+    for level in range(1, max_level + 1):
+        threshold = int(base * (factor ** (level - 1)))
+        thresholds.append(threshold)
+    
+    return thresholds
+
+def get_achievement_level(value, achievement):
+    """Determine the achievement level based on progress using exponential thresholds"""
+    thresholds = calculate_level_thresholds(achievement)
+    
+    for i, threshold in enumerate(thresholds):
+        if value < threshold:
+            return i, threshold, thresholds
+    
+    # If player has passed all thresholds, they're at max level
+    max_level = achievement["max_level"]
+    return max_level, thresholds[-1], thresholds
+
+def create_achievement_widget(achievement, current_value):
+    """Create a widget to display an achievement with exponential progression"""
+    widget = QWidget()
+    layout = QHBoxLayout()
+    
+    # Icon placeholder (will be replaced with actual icons)
+    icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 
+                           "resources", "icons", achievement.get("icon", "medal_default.png"))
+    
+    icon_label = QLabel()
+    if os.path.exists(icon_path):
+        pixmap = QPixmap(icon_path).scaled(48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation)
+        icon_label.setPixmap(pixmap)
+    else:
+        icon_label.setText("🏅")
+        icon_label.setFont(QFont("Arial", 24))
+    
+    icon_label.setFixedSize(50, 50)
+    layout.addWidget(icon_label)
+    
+    # Achievement info
+    info_layout = QVBoxLayout()
+    
+    # Name and description with improved styling
+    name_label = QLabel(f"<b>{achievement['name']}</b>")
+    name_label.setStyleSheet("font-size: 14px;")
+    desc_label = QLabel(achievement['description'])
+    desc_label.setStyleSheet("color: #666;")
+    desc_label.setWordWrap(True)
+    
+    info_layout.addWidget(name_label)
+    info_layout.addWidget(desc_label)
+    
+    # Calculate level and thresholds
+    level, current_level_threshold, all_thresholds = get_achievement_level(current_value, achievement)
+    
+    # Format numbers with thousand separators
+    current_value_formatted = f"{int(current_value):,}"
+    threshold_formatted = f"{current_level_threshold:,}"
+    
+    if level < achievement["max_level"]:
+        if current_level_threshold > 0:
+            progress = int(round((current_value / current_level_threshold) * 100))
+            progress = max(0, min(100, progress))
+        else:
+            progress = 0
+        
+        # Create level indicator with stars - using a more readable gold color
+        level_text = "★" * level + "☆" * (achievement["max_level"] - level)
+        level_label = QLabel(f"Level {level} {level_text}")
+        level_label.setStyleSheet(f"""
+            color: {'#FFC125' if level > 0 else '#666'};  /* Brighter gold */
+            font-size: 12px;
+            margin-bottom: 2px;
+        """)
+        info_layout.addWidget(level_label)
+        
+        # Enhanced progress bar
+        progress_bar = QProgressBar()
+        progress_bar.setRange(0, 100)
+        progress_bar.setValue(progress)
+        
+        # Color-code progress bar based on completion
+        if progress >= 66:
+            style_color = "#4CAF50"  # Green
+        elif progress >= 33:
+            style_color = "#FFA726"  # Orange
+        else:
+            style_color = "#2196F3"  # Blue
+            
+        progress_bar.setStyleSheet(f"""
+            QProgressBar {{
+                border: 1px solid #CCC;
+                border-radius: 3px;
+                text-align: center;
+                height: 20px;
+            }}
+            QProgressBar::chunk {{
+                background-color: {style_color};
+                border-radius: 2px;
+            }}
+        """)
+        
+        # Show detailed progress information
+        progress_text = f"{current_value_formatted}/{threshold_formatted}"
+        progress_bar.setFormat(f"{progress_text} ({progress}%)")
+        info_layout.addWidget(progress_bar)
+        
+        # Show next level info - fixed calculation
+        if level < achievement["max_level"]:  # Changed condition to show for all non-max levels
+            next_threshold = all_thresholds[level]
+            remaining = int(next_threshold - current_value)  # Convert to integer
+            next_level_label = QLabel(f"{remaining:,} points to next level")
+            next_level_label.setStyleSheet("color: #666; font-size: 10px;")
+            info_layout.addWidget(next_level_label)
+    else:
+        # Max level reached styling - adjusted gold color
+        max_label = QLabel(f"ELITE ACHIEVED - Level {level}")
path: src/gui/tabs/achievement_tab.py
user: login: sourcery-ai[bot]
body: **issue (code-quality):** Extract duplicate code into function ([`extract-duplicate-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-duplicate-method/))
created_at: 2025-03-04T07:54:23Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-04T07:53:14Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request introduces a new achievements feature by adding an achievements tab to the player details dialog and implementing the necessary backend logic to support achievement tracking and display. It also includes the addition of achievement icons and their documentation.

#### Class diagram for the new achievements feature

```mermaid
classDiagram
  class PlayerDetailsDialog {
    +init_ui()
  }
  class AchievementTab {
    +setup_achievement_tab(dialog)
    +check_achievement_progress(db_path, player_name, achievement)
    +calculate_level_thresholds(achievement)
    +get_achievement_level(value, achievement)
    +create_achievement_widget(achievement, current_value)
  }
  PlayerDetailsDialog --> AchievementTab : uses
  class Achievement {
    +id: String
    +name: String
    +description: String
    +base_threshold: int
    +growth_factor: float
    +max_level: int
    +query: String
    +icon: String
  }
  AchievementTab --> Achievement : uses
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Added a new achievements tab to the player details dialog. | <ul><li>Imported the new `setup_achievement_tab` function.</li><li>Updated the `init_ui` method to include the new achievements tab in the tab configurations.</li><li>Handled special setup for the achievements tab.</li></ul> | `src/gui/dialogs/player_details.py` |
| Implemented the achievements tab with detailed logic for achievement tracking and display. | <ul><li>Defined various achievements with categories such as Combat, Teamplay, Objective, Specialist, Dedication, and Map Mastery.</li><li>Implemented functions to check achievement progress, calculate level thresholds, and determine achievement levels.</li><li>Created a widget to display achievements with exponential progression and styled it with icons and progress bars.</li><li>Set up the achievements tab with a scrollable area and grouped achievements by category.</li></ul> | `src/gui/tabs/achievement_tab.py` |
| Added documentation for achievement icons. | <ul><li>Listed required icons and their purposes in a README file.</li><li>Provided guidelines for adding custom icons.</li></ul> | `resources/icons/README.md` |

### Assessment against linked issues

| Issue | Objective | Addressed | Explanation |
| ------ | ------- | ----- | ----- |
| #12 | Implement in-house achievements similar to Battlefield, such as 'revived 1000, 2500, 5000'. | ✅ |  |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR