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.

282 lines
9.6 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
# Import the user profile integration system
from user_profile_integration import UserProfileIntegration
@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 = profiles_file
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 open(self.profiles_file, 'r') 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 open(self.profiles_file, 'w') 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)