02202e4d3d
- Added `image_clean.png` to the output directory for the clean image representation. - Added `image_clean_preview.png` for the preview of the clean image. - Introduced `image_svg_clean.png` for the SVG clean image representation.
308 lines
12 KiB
Python
308 lines
12 KiB
Python
#!/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):
|
|
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):
|
|
return self._get_local_leaderboard(limit)
|
|
|
|
def get_global_leaderboard(self, limit=10):
|
|
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 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")
|