You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

287 lines
9.8 KiB

#!/usr/bin/env python3
"""
Profile Manager - Business Logic
Handles profile data management without UI dependencies
"""
import os
import json
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict
from datetime import datetime
from pathlib import Path
# Import the user profile integration system
from user_profile_integration import UserProfileIntegration
from runtime_paths import DEFAULT_PROFILE_DATA, persistent_data_path
@dataclass
class UserProfile:
"""User profile data structure"""
name: str
created_date: str
last_played: str
games_played: int = 0
total_score: int = 0
best_score: int = 0
settings: Dict[str, Any] = None
achievements: List[str] = None
def __post_init__(self):
if self.settings is None:
self.settings = {
"difficulty": "normal",
"sound_volume": 50,
"music_volume": 50,
"screen_shake": True,
"auto_save": True
}
if self.achievements is None:
self.achievements = []
class ProfileDataManager:
"""Core business logic for profile management"""
def __init__(self, profiles_file: str = "user_profiles.json"):
self.profiles_file = persistent_data_path(
profiles_file,
default_text=DEFAULT_PROFILE_DATA,
)
self.profiles: Dict[str, UserProfile] = {}
self.active_profile: Optional[str] = None
# Initialize user profile integration system
self.integration = UserProfileIntegration(profiles_file)
self.device_id = self.integration.get_device_id()
self.api_enabled = self.integration.api_enabled
self.load_profiles()
def load_profiles(self) -> bool:
"""Load profiles from JSON file"""
if not os.path.exists(self.profiles_file):
return True
try:
with Path(self.profiles_file).open('r', encoding='utf-8') as f:
data = json.load(f)
self.profiles = {
name: UserProfile(**profile_data)
for name, profile_data in data.get('profiles', {}).items()
}
self.active_profile = data.get('active_profile')
return True
except (json.JSONDecodeError, KeyError) as e:
print(f"Error loading profiles: {e}")
self.profiles = {}
return False
def save_profiles(self) -> bool:
"""Save profiles to JSON file"""
data = {
'profiles': {
name: asdict(profile)
for name, profile in self.profiles.items()
},
'active_profile': self.active_profile
}
try:
with Path(self.profiles_file).open('w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
return True
except IOError as e:
print(f"Error saving profiles: {e}")
return False
def create_profile(self, name: str) -> tuple[bool, str]:
"""Create a new profile. Returns (success, message)"""
name = name.strip()
if not name:
return False, "Profile name cannot be empty"
if name in self.profiles:
return False, f"Profile '{name}' already exists"
now = datetime.now().isoformat()
profile = UserProfile(
name=name,
created_date=now,
last_played=now
)
self.profiles[name] = profile
if not self.save_profiles():
del self.profiles[name]
return False, "Failed to save profile"
# Register with API server if available
if self.api_enabled:
result = self.integration.register_new_user(name)
if result:
print(f"Profile {name} registered with server")
else:
print(f"Warning: Profile {name} created locally but not registered with server")
return True, f"Profile '{name}' created successfully"
def delete_profile(self, name: str) -> tuple[bool, str]:
"""Delete a profile. Returns (success, message)"""
if name not in self.profiles:
return False, f"Profile '{name}' not found"
del self.profiles[name]
if self.active_profile == name:
self.active_profile = None
if not self.save_profiles():
return False, "Failed to save changes"
return True, f"Profile '{name}' deleted"
def set_active_profile(self, name: str) -> tuple[bool, str]:
"""Set the active profile. Returns (success, message)"""
if name not in self.profiles:
return False, f"Profile '{name}' not found"
self.active_profile = name
self.profiles[name].last_played = datetime.now().isoformat()
if not self.save_profiles():
return False, "Failed to save changes"
# Update integration system to load the new profile
self.integration.reload_profile()
return True, f"Active profile set to '{name}'"
def get_profile_list(self) -> List[str]:
"""Get list of profile names"""
return list(self.profiles.keys())
def get_profile(self, name: str) -> Optional[UserProfile]:
"""Get a specific profile"""
return self.profiles.get(name)
def get_active_profile(self) -> Optional[UserProfile]:
"""Get the currently active profile"""
if self.active_profile:
return self.profiles.get(self.active_profile)
return None
def update_profile_settings(self, name: str, setting: str, value: Any) -> tuple[bool, str]:
"""Update a profile setting. Returns (success, message)"""
if name not in self.profiles:
return False, f"Profile '{name}' not found"
profile = self.profiles[name]
if setting not in profile.settings:
return False, f"Setting '{setting}' not found"
# Validate setting values
if not self._validate_setting(setting, value):
return False, f"Invalid value for setting '{setting}'"
profile.settings[setting] = value
if not self.save_profiles():
return False, "Failed to save changes"
return True, f"Setting '{setting}' updated"
def _validate_setting(self, setting: str, value: Any) -> bool:
"""Validate setting values"""
validations = {
'difficulty': lambda v: v in ['easy', 'normal', 'hard', 'expert'],
'sound_volume': lambda v: isinstance(v, int) and 0 <= v <= 100,
'music_volume': lambda v: isinstance(v, int) and 0 <= v <= 100,
'screen_shake': lambda v: isinstance(v, bool),
'auto_save': lambda v: isinstance(v, bool)
}
validator = validations.get(setting)
return validator(value) if validator else True
def get_leaderboard_data(self, leaderboard_type: str = "device") -> List[Dict[str, Any]]:
"""Get leaderboard data"""
if not self.api_enabled:
return []
try:
if leaderboard_type == "device":
return self.integration.get_device_leaderboard(10)
else: # global
return self.integration.get_global_leaderboard(10)
except Exception as e:
print(f"Error loading leaderboard: {e}")
return []
def get_profile_stats(self, name: str) -> Optional[Dict[str, Any]]:
"""Get detailed profile statistics"""
if name not in self.profiles:
return None
profile = self.profiles[name]
integration_info = self.integration.get_profile_info() if self.api_enabled else {}
try:
created = datetime.fromisoformat(profile.created_date).strftime("%Y-%m-%d")
last_played = datetime.fromisoformat(profile.last_played).strftime("%Y-%m-%d")
except:
created = "Unknown"
last_played = "Unknown"
return {
'profile': profile,
'created_formatted': created,
'last_played_formatted': last_played,
'integration_info': integration_info,
'api_enabled': self.api_enabled
}
class SettingsManager:
"""Helper class for managing profile settings"""
DIFFICULTY_OPTIONS = ["easy", "normal", "hard", "expert"]
VOLUME_RANGE = (0, 100, 5) # min, max, step
@classmethod
def adjust_difficulty(cls, current: str, direction: int) -> str:
"""Adjust difficulty setting"""
try:
current_index = cls.DIFFICULTY_OPTIONS.index(current)
new_index = (current_index + direction) % len(cls.DIFFICULTY_OPTIONS)
return cls.DIFFICULTY_OPTIONS[new_index]
except ValueError:
return "normal"
@classmethod
def adjust_volume(cls, current: int, direction: int) -> int:
"""Adjust volume setting"""
min_val, max_val, step = cls.VOLUME_RANGE
new_value = current + (direction * step)
return max(min_val, min(max_val, new_value))
@classmethod
def toggle_boolean(cls, current: bool) -> bool:
"""Toggle boolean setting"""
return not current
@classmethod
def get_setting_display_value(cls, setting: str, value: Any) -> str:
"""Get display string for setting value"""
if setting == 'difficulty':
return value.title()
elif setting in ['sound_volume', 'music_volume']:
return f"{value}%"
elif setting in ['screen_shake', 'auto_save']:
return "On" if value else "Off"
else:
return str(value)