New functionality added inspired by Wicked Quest game.
This commit is contained in:
237
stat_tracker.py
Normal file
237
stat_tracker.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#!/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__()
|
Reference in New Issue
Block a user