Implement main menu, profile list, and profile stats screens; add screen manager and user profile integration
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user