#!/usr/bin/env python3 """ User Profile Integration Module Provides integration between games and the user profile system """ import json import uuid import platform import hashlib import os from datetime import datetime from engine.score_api_client import ScoreAPIClient class UserProfileIntegration: """Integration layer between the game and profile system""" def __init__(self, profiles_file="user_profiles.json", api_url="http://172.27.23.245:8000"): self.profiles_file = profiles_file self.current_profile = None self.device_id = self.generate_device_id() self.api_client = ScoreAPIClient(api_url) self.api_enabled = self.api_client.is_server_available() self.load_active_profile() if self.api_enabled: print(f"✓ Connected to score server at {api_url}") else: print(f"✗ Score server not available at {api_url} - running offline") def generate_device_id(self): """Generate a unique device ID based on system information""" # Get system information system_info = f"{platform.system()}-{platform.machine()}-{platform.processor()}" # Try to get MAC address for more uniqueness try: mac = ':'.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff) for ele in range(0,8*6,8)][::-1]) system_info += f"-{mac}" except: pass # Create hash and take first 8 characters device_hash = hashlib.md5(system_info.encode()).hexdigest()[:8].upper() return f"DEV-{device_hash}" def load_active_profile(self): """Load the currently active profile""" print(f"[DEBUG] Attempting to load profile from: {self.profiles_file}") print(f"[DEBUG] File exists: {os.path.exists(self.profiles_file)}") try: with open(self.profiles_file, 'r') as f: raw_content = f.read() print(f"[DEBUG] File content length: {len(raw_content)} bytes") print(f"[DEBUG] File content (first 500 chars): {raw_content[:500]}") data = json.loads(raw_content) print(f"[DEBUG] Parsed JSON keys: {list(data.keys())}") print(f"[DEBUG] Active profile key value: {data.get('active_profile')}") print(f"[DEBUG] Available profiles: {list(data.get('profiles', {}).keys())}") active_name = data.get('active_profile') if active_name: print(f"[DEBUG] Looking for profile: '{active_name}'") if active_name in data['profiles']: self.current_profile = data['profiles'][active_name] print(f"✓ Loaded profile: {self.current_profile['name']}") # Sync with API if available if self.api_enabled: self.sync_profile_with_api() return True else: print(f"[DEBUG] Profile '{active_name}' not found in profiles dict") else: print(f"[DEBUG] No active_profile specified in JSON") except FileNotFoundError as e: print(f"✗ Profile file not found: {e}") except json.JSONDecodeError as e: print(f"✗ Failed to parse profile JSON: {e}") except Exception as e: print(f"✗ Unexpected error loading profile: {e}") self.current_profile = None print(f"[DEBUG] Profile loading failed, current_profile set to None") return False def get_profile_name(self): """Get current profile name or default""" return self.current_profile['name'] if self.current_profile else "Guest Player" def get_device_id(self): """Get the unique device identifier""" return self.device_id def get_setting(self, setting_name, default_value=None): """Get a setting from the current profile, or return default""" if self.current_profile and 'settings' in self.current_profile: return self.current_profile['settings'].get(setting_name, default_value) return default_value def sync_profile_with_api(self): """Ensure current profile is registered with the API server""" if not self.current_profile or not self.api_enabled: return False profile_name = self.current_profile['name'] # Check if user exists on server if not self.api_client.user_exists(self.device_id, profile_name): print(f"Registering {profile_name} with score server...") result = self.api_client.signup_user(self.device_id, profile_name) if result.get('success'): print(f"✓ {profile_name} registered successfully") return True else: print(f"✗ Failed to register {profile_name}: {result.get('message')}") return False else: print(f"✓ {profile_name} already registered on server") return True def register_new_user(self, user_id): """Register a new user both locally and on the API server""" if not self.api_enabled: print("API server not available - user will only be registered locally") return True result = self.api_client.signup_user(self.device_id, user_id) if result.get('success'): print(f"✓ {user_id} registered with server successfully") return True else: print(f"✗ Failed to register {user_id} with server: {result.get('message')}") return False def update_game_stats(self, score, completed=True): """Update the current profile's game statistics""" print(f"[DEBUG UPDATE_STATS] update_game_stats called with score={score}, completed={completed}") print(f"[DEBUG UPDATE_STATS] self.current_profile is None: {self.current_profile is None}") if not self.current_profile: print("[DEBUG UPDATE_STATS] No profile loaded - stats not saved") return False print(f"[DEBUG UPDATE_STATS] Profile name: {self.current_profile.get('name', 'UNKNOWN')}") # Submit score to API first if available if self.api_enabled: profile_name = self.current_profile['name'] print(f"[DEBUG UPDATE_STATS] API enabled, submitting score for {profile_name}") result = self.api_client.submit_score( self.device_id, profile_name, score, completed ) if result.get('success'): print(f"✓ Score {score} submitted to server successfully") # Print server stats if available if 'user_stats' in result: stats = result['user_stats'] print(f" Server stats - Games: {stats['total_games']}, Best: {stats['best_score']}") else: print(f"✗ Failed to submit score to server: {result.get('message')}") try: print(f"[DEBUG UPDATE_STATS] Attempting to read {self.profiles_file}") with open(self.profiles_file, 'r') as f: data = json.load(f) print(f"[DEBUG UPDATE_STATS] Read profiles file successfully") print(f"[DEBUG UPDATE_STATS] Profiles keys in file: {list(data.get('profiles', {}).keys())}") profile_name = self.current_profile['name'] print(f"[DEBUG UPDATE_STATS] Looking for profile '{profile_name}' in file") if profile_name in data['profiles']: profile = data['profiles'][profile_name] print(f"[DEBUG UPDATE_STATS] Found profile in file") # Update statistics if completed: profile['games_played'] = profile.get('games_played', 0) + 1 print(f"[DEBUG UPDATE_STATS] Game completed! Total games now: {profile['games_played']}") profile['total_score'] = profile.get('total_score', 0) + score old_best = profile.get('best_score', 0) if score > old_best: profile['best_score'] = score print(f"[DEBUG UPDATE_STATS] New best score for {profile_name}: {score}!") profile['last_played'] = datetime.now().isoformat() # Update our local copy self.current_profile = profile # Save back to file print(f"[DEBUG UPDATE_STATS] Writing updated profile back to {self.profiles_file}") with open(self.profiles_file, 'w') as f: json.dump(data, f, indent=2) print(f"[DEBUG UPDATE_STATS] Successfully saved! Score +{score}, New total: {profile['total_score']}, Best: {profile.get('best_score', 0)}") # Call JavaScript function to sync profile back to localStorage (Pyodide only) try: # noinspection PyUnresolvedReference from js import window window.syncProfileUpdateToLocalStorage( profile_name, profile['best_score'], profile['games_played'], profile['total_score'] ) print(f"[DEBUG UPDATE_STATS] JS sync call completed successfully") except ImportError: print(f"[DEBUG UPDATE_STATS] Note: 'js' module not available (running in non-Pyodide environment)") except Exception as js_err: print(f"[DEBUG UPDATE_STATS] Warning: Failed to call JS sync function: {js_err}") return True else: print(f"[DEBUG UPDATE_STATS] Profile '{profile_name}' NOT FOUND in profiles file!") print(f"[DEBUG UPDATE_STATS] Available profiles: {list(data.get('profiles', {}).keys())}") except FileNotFoundError as e: print(f"[DEBUG UPDATE_STATS] ERROR: Profile file not found: {e}") except json.JSONDecodeError as e: print(f"[DEBUG UPDATE_STATS] ERROR: Invalid JSON in profile file: {e}") except Exception as e: print(f"[DEBUG UPDATE_STATS] ERROR: Unexpected error updating profile stats: {e}") import traceback traceback.print_exc() return False def add_achievement(self, achievement_id): """Add an achievement to the current profile""" if not self.current_profile: return False try: with open(self.profiles_file, 'r') as f: data = json.load(f) profile_name = self.current_profile['name'] if profile_name in data['profiles']: profile = data['profiles'][profile_name] if achievement_id not in profile['achievements']: profile['achievements'].append(achievement_id) self.current_profile = profile with open(self.profiles_file, 'w') as f: json.dump(data, f, indent=2) print(f"Achievement unlocked for {profile_name}: {achievement_id}") return True except Exception as e: print(f"Error adding achievement: {e}") return False def get_profile_info(self): """Get current profile information for display""" if self.current_profile: info = { 'name': self.current_profile['name'], 'games_played': self.current_profile['games_played'], 'best_score': self.current_profile['best_score'], 'total_score': self.current_profile['total_score'], 'achievements': len(self.current_profile['achievements']), 'difficulty': self.current_profile['settings'].get('difficulty', 'normal'), 'device_id': self.device_id, 'api_connected': self.api_enabled } return info return None def get_device_leaderboard(self, limit=10): """Get leaderboard for the current device from API server""" if not self.api_enabled: print("API server not available - cannot get leaderboard") return [] leaderboard = self.api_client.get_leaderboard(self.device_id, limit) return leaderboard def get_global_leaderboard(self, limit=10): """Get global leaderboard across all devices from API server""" if not self.api_enabled: print("API server not available - cannot get global leaderboard") return [] leaderboard = self.api_client.get_global_leaderboard(limit) return leaderboard def get_all_device_users(self): """Get all users registered for this device from API server""" if not self.api_enabled: print("API server not available - cannot get user list") return [] users = self.api_client.get_device_users(self.device_id) return users def get_user_server_scores(self, user_id=None, limit=10): """Get recent scores from server for a user (defaults to current profile)""" if not self.api_enabled: return [] if user_id is None: if not self.current_profile: return [] user_id = self.current_profile['name'] scores = self.api_client.get_user_scores(self.device_id, user_id, limit) return scores def reload_profile(self): """Reload the current profile from disk (useful for external profile changes)""" return self.load_active_profile() # Convenience functions for quick integration def get_active_profile(): """Quick function to get active profile info""" integration = UserProfileIntegration() return integration.get_profile_info() def update_profile_score(score, completed=True): """Quick function to update profile score""" integration = UserProfileIntegration() return integration.update_game_stats(score, completed) def get_profile_setting(setting_name, default_value=None): """Quick function to get a profile setting""" integration = UserProfileIntegration() return integration.get_setting(setting_name, default_value) def get_device_leaderboard(limit=10): """Quick function to get device leaderboard""" integration = UserProfileIntegration() return integration.get_device_leaderboard(limit) def get_global_leaderboard(limit=10): """Quick function to get global leaderboard""" integration = UserProfileIntegration() return integration.get_global_leaderboard(limit) if __name__ == "__main__": # Test the integration print("Testing User Profile Integration with API...") integration = UserProfileIntegration() print(f"Device ID: {integration.get_device_id()}") print(f"Profile Name: {integration.get_profile_name()}") print(f"API Connected: {integration.api_enabled}") info = integration.get_profile_info() if info: print(f"Profile Info: {info}") else: print("No profile loaded") # Test settings difficulty = integration.get_setting('difficulty', 'normal') sound_volume = integration.get_setting('sound_volume', 50) print(f"Settings - Difficulty: {difficulty}, Sound: {sound_volume}%") # Test API features if connected if integration.api_enabled: print("\nTesting API features...") # Get leaderboard leaderboard = integration.get_device_leaderboard(5) if leaderboard: print("Device Leaderboard:") for entry in leaderboard: print(f" {entry['rank']}. {entry['user_id']}: {entry['best_score']} pts ({entry['total_games']} games)") else: print("No leaderboard data available") # Get all users users = integration.get_all_device_users() print(f"\nTotal users on device: {len(users)}") for user in users: print(f" {user['user_id']}: Best {user['best_score']}, {user['total_scores']} games") # Test score submission if integration.current_profile: print(f"\nTesting score submission for {integration.current_profile['name']}...") result = integration.update_game_stats(1234, True) print(f"Score update result: {result}") else: print("API features not available - server offline")