#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Statistics tracking system for Storm Games. Provides flexible stat tracking with separate level and total counters. Supports any pickle-serializable data type including nested structures. """ from typing import Dict, Any, Union, Optional import copy class StatTracker: """Flexible statistics tracking system. Tracks statistics with separate level and total counters, supporting any data type that can be added or assigned. Example usage: # Initialize with default stats stats = StatTracker({"kills": 0, "deaths": 0, "time_played": 0.0}) # Update stats during gameplay stats.update_stat("kills", 1) # Increment kills stats.update_stat("time_played", 1.5) # Add time # Reset level stats for new level stats.reset_level() # Get current stats level_kills = stats.level["kills"] total_kills = stats.total["kills"] """ def __init__(self, default_stats: Optional[Dict[str, Any]] = None): """Initialize stat tracker with optional default statistics. Args: default_stats: Dictionary of default stat definitions. If None, uses empty dict for maximum flexibility. """ if default_stats is None: default_stats = {} # Deep copy to prevent shared references self.total = copy.deepcopy(default_stats) self.level = copy.deepcopy(default_stats) def update_stat(self, stat_name: str, value: Any) -> None: """Update a statistic by adding the value to both level and total. Args: stat_name: Name of the statistic to update value: Value to add to the statistic Note: For numeric types, this performs addition. For other types, behavior depends on the type's __add__ method. If the stat doesn't exist, it will be created with the given value. """ if stat_name in self.level: try: self.level[stat_name] += value except TypeError: # Handle types that don't support += (assign directly) self.level[stat_name] = value else: self.level[stat_name] = value if stat_name in self.total: try: self.total[stat_name] += value except TypeError: # Handle types that don't support += (assign directly) self.total[stat_name] = value else: self.total[stat_name] = value def set_stat(self, stat_name: str, value: Any, level_only: bool = False) -> None: """Set a statistic to a specific value. Args: stat_name: Name of the statistic to set value: Value to set level_only: If True, only update level stats (not total) """ self.level[stat_name] = value if not level_only: self.total[stat_name] = value def get_stat(self, stat_name: str, from_total: bool = False) -> Any: """Get the current value of a statistic. Args: stat_name: Name of the statistic to retrieve from_total: If True, get from total stats, otherwise from level stats Returns: The current value of the statistic, or None if it doesn't exist """ source = self.total if from_total else self.level return source.get(stat_name) def reset_level(self) -> None: """Reset all level statistics to their initial values. Preserves the structure but resets values to what they were when the StatTracker was initialized. """ # Reset to initial state based on current total structure for stat_name in self.level: if isinstance(self.level[stat_name], (int, float)): self.level[stat_name] = 0 if isinstance(self.level[stat_name], int) else 0.0 elif isinstance(self.level[stat_name], str): self.level[stat_name] = "" elif isinstance(self.level[stat_name], list): self.level[stat_name] = [] elif isinstance(self.level[stat_name], dict): self.level[stat_name] = {} else: # For other types, try to create a new instance or set to None try: self.level[stat_name] = type(self.level[stat_name])() except: self.level[stat_name] = None def add_stat(self, stat_name: str, initial_value: Any = 0) -> None: """Add a new statistic to both level and total tracking. Args: stat_name: Name of the new statistic initial_value: Initial value for the statistic """ self.level[stat_name] = copy.deepcopy(initial_value) self.total[stat_name] = copy.deepcopy(initial_value) def remove_stat(self, stat_name: str) -> bool: """Remove a statistic from both level and total tracking. Args: stat_name: Name of the statistic to remove Returns: True if the statistic was removed, False if it didn't exist """ removed = False if stat_name in self.level: del self.level[stat_name] removed = True if stat_name in self.total: del self.total[stat_name] removed = True return removed def get_all_stats(self, include_level: bool = True, include_total: bool = True) -> Dict[str, Any]: """Get dictionary of all statistics. Args: include_level: Include level statistics in result include_total: Include total statistics in result Returns: Dictionary containing requested statistics """ result = {} if include_level: result["level"] = copy.deepcopy(self.level) if include_total: result["total"] = copy.deepcopy(self.total) return result def merge_stats(self, other_tracker: 'StatTracker') -> None: """Merge statistics from another StatTracker instance. Args: other_tracker: Another StatTracker to merge stats from Note: For numeric types, values are added together. For other types, behavior depends on the type's __add__ method. If addition fails, the other tracker's value is used. """ # Merge total stats for stat_name, value in other_tracker.total.items(): if stat_name in self.total: try: self.total[stat_name] += value except TypeError: self.total[stat_name] = copy.deepcopy(value) else: self.total[stat_name] = copy.deepcopy(value) # Merge level stats for stat_name, value in other_tracker.level.items(): if stat_name in self.level: try: self.level[stat_name] += value except TypeError: self.level[stat_name] = copy.deepcopy(value) else: self.level[stat_name] = copy.deepcopy(value) def to_dict(self) -> Dict[str, Any]: """Convert StatTracker to dictionary for serialization. Returns: Dictionary representation of all stats """ return { "level": copy.deepcopy(self.level), "total": copy.deepcopy(self.total) } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'StatTracker': """Create StatTracker from dictionary. Args: data: Dictionary containing level and total stats Returns: New StatTracker instance with loaded data """ tracker = cls() if "level" in data: tracker.level = copy.deepcopy(data["level"]) if "total" in data: tracker.total = copy.deepcopy(data["total"]) return tracker def __str__(self) -> str: """String representation of current stats.""" return f"StatTracker(level={self.level}, total={self.total})" def __repr__(self) -> str: """Detailed string representation.""" return self.__str__()