#!/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)