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
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)
|
|
|