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.

403 lines
17 KiB

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