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.
 
 

289 lines
11 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
from datetime import datetime
from runtime_paths import DEFAULT_PROFILE_DATA, persistent_data_path
class UserProfileIntegration:
"""Integration layer between the game and profile system"""
def __init__(self, profiles_file="user_profiles.json"):
self.profiles_file = persistent_data_path(
profiles_file,
default_text=DEFAULT_PROFILE_DATA,
)
self.current_profile = None
self.device_id = self.generate_device_id()
self.api_enabled = False
self.load_active_profile()
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"""
try:
with self.profiles_file.open('r', encoding='utf-8') as f:
data = json.load(f)
active_name = data.get('active_profile')
if active_name and active_name in data['profiles']:
self.current_profile = data['profiles'][active_name]
print(f"Loaded profile: {self.current_profile['name']}")
return True
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Could not load profile: {e}")
self.current_profile = 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 register_new_user(self, user_id):
"""Registration is handled locally via the profile manager."""
return True
def update_game_stats(self, score, completed=True):
"""Update the current profile's game statistics"""
if not self.current_profile:
print("No profile loaded - stats not saved")
return False
try:
# Update local profile
with self.profiles_file.open('r', encoding='utf-8') as f:
data = json.load(f)
profile_name = self.current_profile['name']
if profile_name in data['profiles']:
profile = data['profiles'][profile_name]
profile['games_played'] += 1
profile['total_score'] += max(0, score)
if score > profile['best_score']:
profile['best_score'] = score
print(f"New best score for {profile_name}: {score}!")
if completed and 'first_win' not in profile['achievements']:
profile['achievements'].append('first_win')
profile['last_played'] = datetime.now().isoformat()
# Update our local copy
self.current_profile = profile
# Save back to file
with self.profiles_file.open('w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
print(f"Local profile stats updated: Score +{score}, Total: {profile['total_score']}")
return True
except Exception as e:
print(f"Error updating profile stats: {e}")
return False
def add_achievement(self, achievement_id):
"""Add an achievement to the current profile"""
if not self.current_profile:
return False
try:
with self.profiles_file.open('r', encoding='utf-8') 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 self.profiles_file.open('w', encoding='utf-8') 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 local profiles on this device."""
return self._get_local_leaderboard(limit)
def get_global_leaderboard(self, limit=10):
"""Global leaderboard falls back to local profiles for this standalone build."""
return self._get_local_leaderboard(limit)
def get_all_device_users(self):
leaderboard = self._get_local_leaderboard(limit=None)
return [
{
'user_id': entry['user_id'],
'best_score': entry['best_score'],
'total_games': entry['total_games'],
'device_id': entry['device_id'],
}
for entry in leaderboard
]
def get_user_server_scores(self, user_id=None, limit=10):
"""Get recent local scores for a user (defaults to current profile)."""
if user_id is None:
if not self.current_profile:
return []
user_id = self.current_profile['name']
table = []
try:
score_file_path = persistent_data_path('scores.txt', default_text='')
with score_file_path.open(encoding='utf-8') as score_file:
for row in score_file.read().splitlines():
parts = row.split(' - ')
if len(parts) >= 4 and parts[2] == user_id:
table.append({
'last_play': parts[0],
'score': int(parts[1]),
'user_id': parts[2],
'device_id': parts[3],
})
except FileNotFoundError:
return []
table.sort(key=lambda entry: entry['score'], reverse=True)
return table[:limit]
def _get_local_leaderboard(self, limit=10):
try:
with self.profiles_file.open('r', encoding='utf-8') as profile_file:
data = json.load(profile_file)
except (FileNotFoundError, json.JSONDecodeError):
return []
entries = []
for profile in data.get('profiles', {}).values():
entries.append({
'user_id': profile.get('name', 'Unknown'),
'best_score': profile.get('best_score', 0),
'total_games': profile.get('games_played', 0),
'device_id': self.device_id,
'last_play': profile.get('last_played', ''),
})
entries.sort(key=lambda entry: (entry['best_score'], entry['total_games']), reverse=True)
if limit is None:
return entries
return entries[:limit]
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...")
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}%")
leaderboard = integration.get_device_leaderboard(5)
if leaderboard:
print("Local Leaderboard:")
for index, entry in enumerate(leaderboard, start=1):
print(f" {index}. {entry['user_id']}: {entry['best_score']} pts ({entry['total_games']} games)")