nice
This commit is contained in:
@@ -14,3 +14,4 @@ rats.spec
|
||||
release/
|
||||
unit/__pycache__
|
||||
conf/keybindings.yaml
|
||||
conf/keybindings.json
|
||||
@@ -2089,3 +2089,37 @@ INFO: 127.0.0.1:52734 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:52740 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:41304 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:41308 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:33472 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:33474 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:52616 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:35690 - "GET /leaderboard/global/top?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:35706 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:35722 - "GET /leaderboard/global/top?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:35724 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:35736 - "GET /leaderboard/global/top?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:57592 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:57608 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:44530 - "POST /signup/DEV-3A2D87B7/AA HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:37822 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:37834 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:46744 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:46752 - "GET /leaderboard/global/top?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:43292 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:43296 - "GET /leaderboard/global/top?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:43300 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:43310 - "GET /leaderboard/global/top?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:59822 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:59832 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:36158 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:36170 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:40986 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:40990 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:39030 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:39046 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:38630 - "GET /users/DEV-3A2D87B7 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:38640 - "POST /signup/DEV-3A2D87B7/Alice HTTP/1.1" 409 Conflict
|
||||
INFO: 127.0.0.1:38490 - "GET /leaderboard/DEV-3A2D87B7?limit=10 HTTP/1.1" 200 OK
|
||||
INFO: Shutting down
|
||||
INFO: Waiting for application shutdown.
|
||||
INFO: Application shutdown complete.
|
||||
INFO: Finished server process [41455]
|
||||
|
||||
@@ -9,7 +9,7 @@ import uuid
|
||||
import platform
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from score_api_client import ScoreAPIClient
|
||||
from engine.score_api_client import ScoreAPIClient
|
||||
|
||||
|
||||
class UserProfileIntegration:
|
||||
+408
-23
@@ -23,6 +23,9 @@ import sdl2
|
||||
import sdl2.ext
|
||||
from sdl2.ext.compat import byteify
|
||||
|
||||
# Import the user profile integration system
|
||||
from engine.user_profile_integration import UserProfileIntegration
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserProfile:
|
||||
@@ -57,13 +60,27 @@ class ProfileManager:
|
||||
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
|
||||
|
||||
# UI State
|
||||
self.current_screen = "main_menu" # main_menu, profile_list, create_profile, edit_profile
|
||||
self.current_screen = "main_menu" # main_menu, profile_list, create_profile, edit_profile, leaderboard, profile_stats
|
||||
self.selected_index = 0
|
||||
self.input_text = ""
|
||||
self.input_active = False
|
||||
self.editing_field = None
|
||||
|
||||
# Error dialog state
|
||||
self.show_error = False
|
||||
self.error_message = ""
|
||||
self.error_timer = 0
|
||||
|
||||
# Leaderboard data
|
||||
self.leaderboard_data = []
|
||||
self.leaderboard_type = "device" # "device" or "global"
|
||||
|
||||
# Virtual keyboard state
|
||||
self.vk_cursor_x = 0 # Virtual keyboard cursor X
|
||||
self.vk_cursor_y = 0 # Virtual keyboard cursor Y
|
||||
@@ -191,6 +208,15 @@ class ProfileManager:
|
||||
|
||||
self.profiles[name.strip()] = profile
|
||||
self.save_profiles()
|
||||
|
||||
# Register with API server if available
|
||||
if self.api_enabled:
|
||||
result = self.integration.register_new_user(name.strip())
|
||||
if result:
|
||||
print(f"Profile {name.strip()} registered with server")
|
||||
else:
|
||||
print(f"Warning: Profile {name.strip()} created locally but not registered with server")
|
||||
|
||||
return True
|
||||
|
||||
def delete_profile(self, name: str) -> bool:
|
||||
@@ -209,6 +235,10 @@ class ProfileManager:
|
||||
self.active_profile = name
|
||||
self.profiles[name].last_played = datetime.now().isoformat()
|
||||
self.save_profiles()
|
||||
|
||||
# Update integration system to load the new profile
|
||||
self.integration.reload_profile()
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -264,23 +294,31 @@ class ProfileManager:
|
||||
|
||||
def handle_button_press(self, button: int):
|
||||
"""Handle gamepad button presses"""
|
||||
# If error dialog is shown, dismiss it on any button press
|
||||
if self.show_error:
|
||||
self.show_error = False
|
||||
return
|
||||
|
||||
# Button mappings (common gamepad layout)
|
||||
# 0: A/Cross - Confirm
|
||||
# 1: B/Circle - Back
|
||||
# 2: X/Square - Delete/Special
|
||||
# 3: Y/Triangle - Menu
|
||||
|
||||
if button == 0: # A/Confirm
|
||||
if button == 3: # A/Confirm
|
||||
self.handle_confirm()
|
||||
elif button == 1: # B/Back
|
||||
elif button == 4: # B/Back
|
||||
self.handle_back()
|
||||
elif button == 2: # X/Delete
|
||||
elif button == 9: # X/Delete
|
||||
self.handle_delete()
|
||||
elif button == 3: # Y/Menu
|
||||
elif button == 10: # Y/Menu
|
||||
self.handle_menu()
|
||||
|
||||
def navigate_up(self):
|
||||
"""Navigate up in current screen"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "main_menu":
|
||||
self.selected_index = max(0, self.selected_index - 1)
|
||||
elif self.current_screen == "profile_list":
|
||||
@@ -296,8 +334,11 @@ class ProfileManager:
|
||||
|
||||
def navigate_down(self):
|
||||
"""Navigate down in current screen"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "main_menu":
|
||||
max_index = 3 # Create, Select, Settings, Exit (0-3)
|
||||
max_index = 5 # Create, Select, Settings, Leaderboard, Stats, Exit (0-5)
|
||||
self.selected_index = min(max_index, self.selected_index + 1)
|
||||
elif self.current_screen == "profile_list":
|
||||
max_index = len(self.profiles) # Profiles + Back
|
||||
@@ -305,6 +346,12 @@ class ProfileManager:
|
||||
elif self.current_screen == "edit_profile":
|
||||
max_index = 5 # 4 settings + save + back (0-5)
|
||||
self.selected_index = min(max_index, self.selected_index + 1)
|
||||
elif self.current_screen == "leaderboard":
|
||||
max_index = 2 # Toggle type, refresh, back (0-2)
|
||||
self.selected_index = min(max_index, self.selected_index + 1)
|
||||
elif self.current_screen == "profile_stats":
|
||||
max_index = 0 # Just back button
|
||||
self.selected_index = min(max_index, self.selected_index + 1)
|
||||
elif self.current_screen == "create_profile" and self.input_active:
|
||||
# Virtual keyboard navigation
|
||||
max_y = len(self.virtual_keyboard) - 1
|
||||
@@ -315,16 +362,32 @@ class ProfileManager:
|
||||
|
||||
def navigate_left(self):
|
||||
"""Navigate left (for adjusting values)"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "edit_profile":
|
||||
self.adjust_setting(-1)
|
||||
elif self.current_screen == "leaderboard":
|
||||
# Toggle between device and global leaderboard
|
||||
if self.selected_index == 0:
|
||||
self.leaderboard_type = "global" if self.leaderboard_type == "device" else "device"
|
||||
self.load_leaderboard_data()
|
||||
elif self.current_screen == "create_profile" and self.input_active:
|
||||
# Virtual keyboard navigation
|
||||
self.vk_cursor_x = max(0, self.vk_cursor_x - 1)
|
||||
|
||||
def navigate_right(self):
|
||||
"""Navigate right (for adjusting values)"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "edit_profile":
|
||||
self.adjust_setting(1)
|
||||
elif self.current_screen == "leaderboard":
|
||||
# Toggle between device and global leaderboard
|
||||
if self.selected_index == 0:
|
||||
self.leaderboard_type = "global" if self.leaderboard_type == "device" else "device"
|
||||
self.load_leaderboard_data()
|
||||
elif self.current_screen == "create_profile" and self.input_active:
|
||||
# Virtual keyboard navigation
|
||||
max_x = len(self.virtual_keyboard[self.vk_cursor_y]) - 1
|
||||
@@ -332,6 +395,9 @@ class ProfileManager:
|
||||
|
||||
def handle_confirm(self):
|
||||
"""Handle confirm action (A button)"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "main_menu":
|
||||
if self.selected_index == 0: # Create Profile
|
||||
self.current_screen = "create_profile"
|
||||
@@ -344,7 +410,16 @@ class ProfileManager:
|
||||
if self.active_profile:
|
||||
self.current_screen = "edit_profile"
|
||||
self.selected_index = 0
|
||||
elif self.selected_index == 3: # Exit
|
||||
elif self.selected_index == 3: # Leaderboard
|
||||
if self.api_enabled:
|
||||
self.current_screen = "leaderboard"
|
||||
self.selected_index = 0
|
||||
self.load_leaderboard_data()
|
||||
elif self.selected_index == 4: # Profile Stats
|
||||
if self.active_profile:
|
||||
self.current_screen = "profile_stats"
|
||||
self.selected_index = 0
|
||||
elif self.selected_index == 5: # Exit
|
||||
self.running = False
|
||||
|
||||
elif self.current_screen == "profile_list":
|
||||
@@ -378,17 +453,35 @@ class ProfileManager:
|
||||
self.current_screen = "main_menu"
|
||||
self.selected_index = 0
|
||||
|
||||
elif self.current_screen == "leaderboard":
|
||||
if self.selected_index == 1: # Refresh
|
||||
self.load_leaderboard_data()
|
||||
elif self.selected_index == 2: # Back
|
||||
self.current_screen = "main_menu"
|
||||
self.selected_index = 3
|
||||
|
||||
elif self.current_screen == "profile_stats":
|
||||
if self.selected_index == 0: # Back
|
||||
self.current_screen = "main_menu"
|
||||
self.selected_index = 4
|
||||
|
||||
def handle_back(self):
|
||||
"""Handle back action (B button)"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "main_menu":
|
||||
self.running = False
|
||||
elif self.current_screen in ["profile_list", "create_profile", "edit_profile"]:
|
||||
elif self.current_screen in ["profile_list", "create_profile", "edit_profile", "leaderboard", "profile_stats"]:
|
||||
self.current_screen = "main_menu"
|
||||
self.selected_index = 0
|
||||
self.input_active = False
|
||||
|
||||
def handle_delete(self):
|
||||
"""Handle delete action (X button)"""
|
||||
if self.show_error:
|
||||
return
|
||||
|
||||
if self.current_screen == "profile_list":
|
||||
profile_names = list(self.profiles.keys())
|
||||
if self.selected_index < len(profile_names):
|
||||
@@ -426,10 +519,18 @@ class ProfileManager:
|
||||
elif selected_char == 'DONE':
|
||||
# Finish input
|
||||
if self.input_text.strip():
|
||||
if self.create_profile(self.input_text):
|
||||
self.current_screen = "main_menu"
|
||||
self.selected_index = 0
|
||||
self.input_active = False
|
||||
# Check if profile already exists
|
||||
if self.input_text.strip() in self.profiles:
|
||||
self.show_error_dialog(f"Profile '{self.input_text.strip()}' already exists!")
|
||||
else:
|
||||
if self.create_profile(self.input_text):
|
||||
self.current_screen = "main_menu"
|
||||
self.selected_index = 0
|
||||
self.input_active = False
|
||||
else:
|
||||
self.show_error_dialog("Failed to create profile!")
|
||||
else:
|
||||
self.show_error_dialog("Profile name cannot be empty!")
|
||||
elif selected_char == 'CANCEL':
|
||||
# Cancel input
|
||||
self.input_text = ""
|
||||
@@ -461,6 +562,27 @@ class ProfileManager:
|
||||
elif self.selected_index == 3: # Screen Shake
|
||||
profile.settings["screen_shake"] = not profile.settings["screen_shake"]
|
||||
|
||||
def load_leaderboard_data(self):
|
||||
"""Load leaderboard data based on current type"""
|
||||
if not self.api_enabled:
|
||||
self.leaderboard_data = []
|
||||
return
|
||||
|
||||
try:
|
||||
if self.leaderboard_type == "device":
|
||||
self.leaderboard_data = self.integration.get_device_leaderboard(10)
|
||||
else: # global
|
||||
self.leaderboard_data = self.integration.get_global_leaderboard(10)
|
||||
except Exception as e:
|
||||
print(f"Error loading leaderboard: {e}")
|
||||
self.leaderboard_data = []
|
||||
|
||||
def show_error_dialog(self, message: str):
|
||||
"""Show an error dialog with the given message"""
|
||||
self.show_error = True
|
||||
self.error_message = message
|
||||
self.error_timer = time.time() + 3.0 # Show error for 3 seconds
|
||||
|
||||
def render(self):
|
||||
"""Main rendering method"""
|
||||
# Clear with dark background
|
||||
@@ -481,6 +603,17 @@ class ProfileManager:
|
||||
self.render_create_profile()
|
||||
elif self.current_screen == "edit_profile":
|
||||
self.render_edit_profile()
|
||||
elif self.current_screen == "leaderboard":
|
||||
self.render_leaderboard()
|
||||
elif self.current_screen == "profile_stats":
|
||||
self.render_profile_stats()
|
||||
|
||||
# Render error dialog on top if active
|
||||
if self.show_error:
|
||||
self.render_error_dialog()
|
||||
# Auto-dismiss after timer expires
|
||||
if time.time() > self.error_timer:
|
||||
self.show_error = False
|
||||
|
||||
self.renderer.present()
|
||||
|
||||
@@ -493,16 +626,25 @@ class ProfileManager:
|
||||
title = "Profile Manager"
|
||||
self.draw_text(title, 320, 20, self.colors['light_blue'], 'title', center=True)
|
||||
|
||||
# Subtitle with active profile
|
||||
# Subtitle with active profile and API status
|
||||
if self.active_profile:
|
||||
active_text = f"Active: {self.active_profile}"
|
||||
self.draw_text(active_text, 320, 60, self.colors['light_green'], 'medium', center=True)
|
||||
else:
|
||||
self.draw_text("No active profile", 320, 60, self.colors['yellow'], 'medium', center=True)
|
||||
|
||||
# API connection status
|
||||
api_status = "API: Connected" if self.api_enabled else "API: Offline"
|
||||
api_color = self.colors['light_green'] if self.api_enabled else self.colors['red']
|
||||
self.draw_text(api_status, 320, 80, api_color, 'tiny', center=True)
|
||||
|
||||
# Device ID display
|
||||
device_text = f"Device: {self.device_id}"
|
||||
self.draw_text(device_text, 320, 95, self.colors['light_gray'], 'tiny', center=True)
|
||||
|
||||
# Menu panel
|
||||
panel_x, panel_y = 120, 140
|
||||
panel_width, panel_height = 400, 220
|
||||
panel_x, panel_y = 120, 120
|
||||
panel_width, panel_height = 400, 240
|
||||
self.draw_panel(panel_x, panel_y, panel_width, panel_height,
|
||||
self.colors['dark_gray'], self.colors['gray'])
|
||||
|
||||
@@ -511,18 +653,27 @@ class ProfileManager:
|
||||
"Create Profile",
|
||||
"Select Profile",
|
||||
"Edit Settings",
|
||||
"Leaderboard",
|
||||
"Profile Stats",
|
||||
"Exit"
|
||||
]
|
||||
|
||||
button_width = 280
|
||||
button_height = 35
|
||||
button_height = 30
|
||||
button_x = panel_x + 60
|
||||
start_y = panel_y + 20
|
||||
start_y = panel_y + 15
|
||||
|
||||
for i, item in enumerate(menu_items):
|
||||
button_y = start_y + i * (button_height + 10)
|
||||
button_y = start_y + i * (button_height + 8)
|
||||
selected = (i == self.selected_index)
|
||||
self.draw_button(item, button_x, button_y, button_width, button_height, selected)
|
||||
|
||||
# Gray out unavailable options
|
||||
if (item == "Edit Settings" or item == "Profile Stats") and not self.active_profile:
|
||||
self.draw_button(f"{item} (No Profile)", button_x, button_y, button_width, button_height, False, disabled=True)
|
||||
elif item == "Leaderboard" and not self.api_enabled:
|
||||
self.draw_button(f"{item} (Offline)", button_x, button_y, button_width, button_height, False, disabled=True)
|
||||
else:
|
||||
self.draw_button(item, button_x, button_y, button_width, button_height, selected)
|
||||
|
||||
# Controls help panel
|
||||
help_panel_y = 380
|
||||
@@ -748,6 +899,219 @@ class ProfileManager:
|
||||
self.draw_text("Left/Right: Adjust • Enter: Save/Back • Escape: Cancel",
|
||||
320, 435, self.colors['light_gray'], 'tiny', center=True)
|
||||
|
||||
def render_leaderboard(self):
|
||||
"""Render leaderboard screen"""
|
||||
# Header
|
||||
self.renderer.fill((0, 0, 640, 80), self.colors['dark_gray'])
|
||||
|
||||
title = f"{self.leaderboard_type.title()} Leaderboard"
|
||||
self.draw_text(title, 320, 15, self.colors['light_blue'], 'title', center=True)
|
||||
|
||||
if not self.api_enabled:
|
||||
self.draw_text("Server offline - No leaderboard available", 320, 50,
|
||||
self.colors['red'], 'medium', center=True)
|
||||
self.draw_button("← Back", 270, 200, 100, 30, True)
|
||||
return
|
||||
|
||||
# Toggle button
|
||||
toggle_text = f"Type: {self.leaderboard_type.title()} (Left/Right to toggle)"
|
||||
toggle_selected = (self.selected_index == 0)
|
||||
self.draw_button(toggle_text, 120, 90, 400, 25, toggle_selected)
|
||||
|
||||
# Refresh button
|
||||
refresh_selected = (self.selected_index == 1)
|
||||
self.draw_button("Refresh", 150, 125, 80, 25, refresh_selected)
|
||||
|
||||
# Back button
|
||||
back_selected = (self.selected_index == 2)
|
||||
self.draw_button("← Back", 250, 125, 80, 25, back_selected)
|
||||
|
||||
# Leaderboard data
|
||||
if not self.leaderboard_data:
|
||||
self.draw_text("No leaderboard data available", 320, 200,
|
||||
self.colors['yellow'], 'medium', center=True)
|
||||
else:
|
||||
# Leaderboard panel
|
||||
panel_height = min(250, len(self.leaderboard_data) * 25 + 40)
|
||||
self.draw_panel(50, 160, 540, panel_height,
|
||||
self.colors['black'], self.colors['gray'])
|
||||
|
||||
# Headers
|
||||
self.draw_text("Rank", 60, 175, self.colors['light_blue'], 'small')
|
||||
self.draw_text("Player", 120, 175, self.colors['light_blue'], 'small')
|
||||
self.draw_text("Score", 350, 175, self.colors['light_blue'], 'small')
|
||||
self.draw_text("Games", 450, 175, self.colors['light_blue'], 'small')
|
||||
|
||||
if self.leaderboard_type == "global":
|
||||
self.draw_text("Device", 520, 175, self.colors['light_blue'], 'small')
|
||||
|
||||
# Leaderboard entries
|
||||
for i, entry in enumerate(self.leaderboard_data):
|
||||
entry_y = 195 + i * 22
|
||||
|
||||
# Highlight current user
|
||||
is_current_user = (self.active_profile and
|
||||
entry.get('user_id') == self.active_profile)
|
||||
text_color = self.colors['light_green'] if is_current_user else self.colors['light_gray']
|
||||
|
||||
# Rank
|
||||
self.draw_text(str(entry.get('rank', i+1)), 60, entry_y, text_color, 'tiny')
|
||||
|
||||
# Player name
|
||||
player_name = entry.get('user_id', 'Unknown')
|
||||
if len(player_name) > 20:
|
||||
player_name = player_name[:17] + "..."
|
||||
self.draw_text(player_name, 120, entry_y, text_color, 'tiny')
|
||||
|
||||
# Score
|
||||
score = entry.get('best_score', 0)
|
||||
self.draw_text(str(score), 350, entry_y, text_color, 'tiny')
|
||||
|
||||
# Games
|
||||
games = entry.get('total_games', 0)
|
||||
self.draw_text(str(games), 450, entry_y, text_color, 'tiny')
|
||||
|
||||
# Device (global leaderboard only)
|
||||
if self.leaderboard_type == "global":
|
||||
device = entry.get('device_id', '')[:8]
|
||||
self.draw_text(device, 520, entry_y, text_color, 'tiny')
|
||||
|
||||
# Instructions
|
||||
self.draw_panel(10, 420, 620, 40, self.colors['black'], self.colors['dark_gray'])
|
||||
self.draw_text("Left/Right: Toggle Type • Enter: Select • Escape: Back",
|
||||
320, 435, self.colors['light_gray'], 'tiny', center=True)
|
||||
|
||||
def render_profile_stats(self):
|
||||
"""Render detailed profile statistics"""
|
||||
if not self.active_profile:
|
||||
self.current_screen = "main_menu"
|
||||
return
|
||||
|
||||
profile = self.profiles[self.active_profile]
|
||||
|
||||
# Header
|
||||
self.renderer.fill((0, 0, 640, 80), self.colors['dark_gray'])
|
||||
title = f"Stats: {profile.name}"
|
||||
self.draw_text(title, 320, 15, self.colors['light_blue'], 'title', center=True)
|
||||
|
||||
# Get additional info from integration system
|
||||
integration_info = self.integration.get_profile_info()
|
||||
|
||||
# Main stats panel
|
||||
self.draw_panel(50, 90, 540, 260, self.colors['dark_gray'], self.colors['gray'])
|
||||
|
||||
# Basic stats
|
||||
stats_y = 105
|
||||
line_height = 20
|
||||
|
||||
self.draw_text("Profile Statistics", 320, stats_y, self.colors['white'], 'large', center=True)
|
||||
stats_y += 30
|
||||
|
||||
# Local stats
|
||||
self.draw_text("Local Statistics:", 60, stats_y, self.colors['light_blue'], 'medium')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text(f"Games Played: {profile.games_played}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text(f"Best Score: {profile.best_score}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text(f"Total Score: {profile.total_score}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text(f"Achievements: {len(profile.achievements)}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
# Creation and last played dates
|
||||
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"
|
||||
|
||||
self.draw_text(f"Created: {created}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text(f"Last Played: {last_played}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
# Server stats (if available)
|
||||
if self.api_enabled and integration_info:
|
||||
stats_y += 10
|
||||
self.draw_text("Server Status:", 60, stats_y, self.colors['light_green'], 'medium')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text(f"Device ID: {integration_info.get('device_id', 'Unknown')}", 70, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
self.draw_text("✓ Profile synced with server", 70, stats_y, self.colors['light_green'], 'small')
|
||||
else:
|
||||
stats_y += 10
|
||||
self.draw_text("Server Status:", 60, stats_y, self.colors['red'], 'medium')
|
||||
stats_y += line_height
|
||||
self.draw_text("✗ Server offline", 70, stats_y, self.colors['red'], 'small')
|
||||
|
||||
# Settings summary
|
||||
stats_y = 105
|
||||
settings_x = 350
|
||||
|
||||
self.draw_text("Current Settings:", settings_x, stats_y + 30, self.colors['light_blue'], 'medium')
|
||||
stats_y += 50
|
||||
|
||||
difficulty = profile.settings.get('difficulty', 'normal').title()
|
||||
self.draw_text(f"Difficulty: {difficulty}", settings_x + 10, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
sound_vol = profile.settings.get('sound_volume', 50)
|
||||
self.draw_text(f"Sound Volume: {sound_vol}%", settings_x + 10, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
music_vol = profile.settings.get('music_volume', 50)
|
||||
self.draw_text(f"Music Volume: {music_vol}%", settings_x + 10, stats_y, self.colors['light_gray'], 'small')
|
||||
stats_y += line_height
|
||||
|
||||
screen_shake = "On" if profile.settings.get('screen_shake', True) else "Off"
|
||||
self.draw_text(f"Screen Shake: {screen_shake}", settings_x + 10, stats_y, self.colors['light_gray'], 'small')
|
||||
|
||||
# Back button
|
||||
back_selected = (self.selected_index == 0)
|
||||
self.draw_button("← Back", 270, 370, 100, 30, back_selected)
|
||||
|
||||
# Instructions
|
||||
self.draw_panel(10, 420, 620, 40, self.colors['black'], self.colors['dark_gray'])
|
||||
self.draw_text("Enter: Back • Escape: Back to Main Menu",
|
||||
320, 435, self.colors['light_gray'], 'tiny', center=True)
|
||||
|
||||
def render_error_dialog(self):
|
||||
"""Render error dialog overlay"""
|
||||
# Semi-transparent overlay
|
||||
overlay_color = sdl2.ext.Color(0, 0, 0, 128) # Black with alpha
|
||||
self.renderer.fill((0, 0, 640, 480), overlay_color)
|
||||
|
||||
# Error dialog box
|
||||
dialog_width = 400
|
||||
dialog_height = 120
|
||||
dialog_x = (640 - dialog_width) // 2
|
||||
dialog_y = (480 - dialog_height) // 2
|
||||
|
||||
# Dialog background with red border
|
||||
self.draw_panel(dialog_x, dialog_y, dialog_width, dialog_height,
|
||||
self.colors['black'], self.colors['red'], border_width=4)
|
||||
|
||||
# Error title
|
||||
self.draw_text("ERROR", dialog_x + dialog_width // 2, dialog_y + 20,
|
||||
self.colors['red'], 'large', center=True)
|
||||
|
||||
# Error message
|
||||
self.draw_text(self.error_message, dialog_x + dialog_width // 2, dialog_y + 50,
|
||||
self.colors['white'], 'medium', center=True)
|
||||
|
||||
# Dismiss instruction
|
||||
self.draw_text("Press any key to continue...", dialog_x + dialog_width // 2, dialog_y + 80,
|
||||
self.colors['light_gray'], 'small', center=True)
|
||||
|
||||
def draw_text(self, text: str, x: int, y: int, color, font_type='medium', center: bool = False):
|
||||
"""Draw text on screen with improved styling"""
|
||||
if not text:
|
||||
@@ -774,10 +1138,14 @@ class ProfileManager:
|
||||
for i in range(border_width):
|
||||
self.renderer.draw_rect((x + i, y + i, width - 2*i, height - 2*i), border_color)
|
||||
|
||||
def draw_button(self, text: str, x: int, y: int, width: int, height: int, selected: bool = False):
|
||||
def draw_button(self, text: str, x: int, y: int, width: int, height: int, selected: bool = False, disabled: bool = False):
|
||||
"""Draw a styled button"""
|
||||
# Button colors based on state
|
||||
if selected:
|
||||
if disabled:
|
||||
bg_color = self.colors['black']
|
||||
border_color = self.colors['dark_gray']
|
||||
text_color = self.colors['dark_gray']
|
||||
elif selected:
|
||||
bg_color = self.colors['blue']
|
||||
border_color = self.colors['light_blue']
|
||||
text_color = self.colors['white']
|
||||
@@ -798,6 +1166,11 @@ class ProfileManager:
|
||||
"""Handle keyboard input for navigation only (no text input)"""
|
||||
key = event.key.keysym.sym
|
||||
|
||||
# If error dialog is shown, dismiss it on any key press
|
||||
if self.show_error:
|
||||
self.show_error = False
|
||||
return
|
||||
|
||||
# Navigation mode only
|
||||
if key == sdl2.SDLK_UP:
|
||||
self.navigate_up()
|
||||
@@ -852,22 +1225,34 @@ class ProfileManager:
|
||||
|
||||
def main():
|
||||
"""Entry point"""
|
||||
print("Starting Profile Manager...")
|
||||
print("Starting Profile Manager with User Profile Integration...")
|
||||
print("Features:")
|
||||
print(" • Profile creation and management")
|
||||
print(" • API server integration for leaderboards")
|
||||
print(" • Device-specific and global leaderboards")
|
||||
print(" • Detailed profile statistics")
|
||||
print(" • Settings management")
|
||||
print("")
|
||||
print("Controls:")
|
||||
print(" Arrow Keys: Navigate menus")
|
||||
print(" Enter/Space: Confirm/Select")
|
||||
print(" Escape: Back/Cancel")
|
||||
print(" Delete/Backspace: Delete")
|
||||
print(" Tab: Menu (reserved)")
|
||||
print(" Keyboard typing: Text input when creating profiles")
|
||||
print(" Left/Right: Adjust settings and toggle options")
|
||||
print("")
|
||||
print("Gamepad Controls (if connected):")
|
||||
print(" D-Pad: Navigate menus")
|
||||
print(" A Button: Confirm/Select")
|
||||
print(" B Button: Back/Cancel")
|
||||
print(" X Button: Delete")
|
||||
print("")
|
||||
|
||||
manager = ProfileManager()
|
||||
print(f"Device ID: {manager.device_id}")
|
||||
print(f"API Server: {'Connected' if manager.api_enabled else 'Offline'}")
|
||||
print("")
|
||||
|
||||
manager.run()
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
|
||||
from engine import maze, sdl2 as engine, controls, graphics, unit_manager, scoring
|
||||
from units import points
|
||||
from user_profile_integration import UserProfileIntegration, get_global_leaderboard
|
||||
from engine.user_profile_integration import UserProfileIntegration, get_global_leaderboard
|
||||
|
||||
|
||||
class MiceMaze(
|
||||
|
||||
Binary file not shown.
@@ -1,112 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Profile Manager with API Integration Demo
|
||||
"""
|
||||
|
||||
import json
|
||||
from user_profile_integration import UserProfileIntegration
|
||||
|
||||
def main():
|
||||
print("=== Profile Manager with API Integration ===")
|
||||
|
||||
# Initialize the integration
|
||||
integration = UserProfileIntegration()
|
||||
|
||||
print(f"\\nDevice ID: {integration.get_device_id()}")
|
||||
print(f"Active Profile: {integration.get_profile_name()}")
|
||||
print(f"API Server: {'Connected' if integration.api_enabled else 'Offline'}")
|
||||
|
||||
while True:
|
||||
print("\\n=== MAIN MENU ===")
|
||||
print("1. Show Profile Info")
|
||||
print("2. View Online Leaderboard")
|
||||
print("3. View All Device Users")
|
||||
print("4. Submit Test Score")
|
||||
print("5. Create New Profile")
|
||||
print("6. Exit")
|
||||
|
||||
try:
|
||||
choice = input("\\nSelect option (1-6): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
# Show profile info
|
||||
info = integration.get_profile_info()
|
||||
if info:
|
||||
print(f"\\n=== PROFILE INFO ===")
|
||||
for key, value in info.items():
|
||||
print(f"{key.replace('_', ' ').title()}: {value}")
|
||||
else:
|
||||
print("No active profile loaded")
|
||||
|
||||
elif choice == "2":
|
||||
# Show leaderboard
|
||||
if not integration.api_enabled:
|
||||
print("API server not available")
|
||||
continue
|
||||
|
||||
print(f"\\n=== LEADERBOARD ===")
|
||||
leaderboard = integration.get_device_leaderboard(10)
|
||||
if leaderboard:
|
||||
for entry in leaderboard:
|
||||
print(f"{entry['rank']}. {entry['user_id']}: {entry['best_score']} pts ({entry['total_games']} games)")
|
||||
else:
|
||||
print("No scores recorded yet")
|
||||
|
||||
elif choice == "3":
|
||||
# Show all users
|
||||
if not integration.api_enabled:
|
||||
print("API server not available")
|
||||
continue
|
||||
|
||||
print(f"\\n=== DEVICE USERS ===")
|
||||
users = integration.get_all_device_users()
|
||||
if users:
|
||||
for user in users:
|
||||
print(f"{user['user_id']}: Best {user['best_score']}, {user['total_scores']} games")
|
||||
else:
|
||||
print("No users registered yet")
|
||||
|
||||
elif choice == "4":
|
||||
# Submit test score
|
||||
if not integration.current_profile:
|
||||
print("No active profile to submit score for")
|
||||
continue
|
||||
|
||||
try:
|
||||
score = int(input("Enter test score: "))
|
||||
result = integration.update_game_stats(score, True)
|
||||
if result:
|
||||
print(f"Score {score} submitted successfully!")
|
||||
else:
|
||||
print("Failed to submit score")
|
||||
except ValueError:
|
||||
print("Invalid score entered")
|
||||
|
||||
elif choice == "5":
|
||||
# Create new profile - simplified for demo
|
||||
name = input("Enter new profile name: ").strip()
|
||||
if not name:
|
||||
print("Name cannot be empty")
|
||||
continue
|
||||
|
||||
success = integration.register_new_user(name)
|
||||
if success or not integration.api_enabled:
|
||||
print(f"Profile '{name}' created successfully!")
|
||||
else:
|
||||
print("Failed to create profile")
|
||||
|
||||
elif choice == "6":
|
||||
print("Goodbye!")
|
||||
break
|
||||
|
||||
else:
|
||||
print("Invalid choice")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\\nGoodbye!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for Mice Game Score API
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
API_BASE_URL = "http://localhost:8000"
|
||||
|
||||
def test_api():
|
||||
"""Test all API endpoints"""
|
||||
print("Testing Mice Game Score API...")
|
||||
print("=" * 50)
|
||||
|
||||
# Test device and user IDs
|
||||
device_id = "DEV-TEST001"
|
||||
user1 = "TestUser1"
|
||||
user2 = "TestUser2"
|
||||
|
||||
try:
|
||||
# 1. Test root endpoint
|
||||
print("\n1. Testing root endpoint:")
|
||||
response = requests.get(f"{API_BASE_URL}/")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
# 2. Test user signup
|
||||
print(f"\n2. Testing user signup for {user1}:")
|
||||
response = requests.post(f"{API_BASE_URL}/signup/{device_id}/{user1}")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
print(f"\n3. Testing user signup for {user2}:")
|
||||
response = requests.post(f"{API_BASE_URL}/signup/{device_id}/{user2}")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
# 4. Test duplicate signup (should fail)
|
||||
print(f"\n4. Testing duplicate signup for {user1} (should fail):")
|
||||
response = requests.post(f"{API_BASE_URL}/signup/{device_id}/{user1}")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
# 5. Test getting users for device
|
||||
print(f"\n5. Testing get users for device {device_id}:")
|
||||
response = requests.get(f"{API_BASE_URL}/users/{device_id}")
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
# 6. Test score submission
|
||||
print(f"\n6. Testing score submission for {user1}:")
|
||||
score_data = {
|
||||
"user_id": user1,
|
||||
"device_id": device_id,
|
||||
"score": 1500,
|
||||
"game_completed": True
|
||||
}
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/score/{device_id}/{user1}",
|
||||
json=score_data
|
||||
)
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
# 7. Test more score submissions
|
||||
scores_to_test = [
|
||||
(user1, 2000, True),
|
||||
(user1, 1200, False),
|
||||
(user2, 1800, True),
|
||||
(user2, 2500, True)
|
||||
]
|
||||
|
||||
print(f"\n7. Testing multiple score submissions:")
|
||||
for user, score, completed in scores_to_test:
|
||||
score_data = {
|
||||
"user_id": user,
|
||||
"device_id": device_id,
|
||||
"score": score,
|
||||
"game_completed": completed
|
||||
}
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/score/{device_id}/{user}",
|
||||
json=score_data
|
||||
)
|
||||
print(f" {user} - Score: {score}, Completed: {completed} -> Status: {response.status_code}")
|
||||
time.sleep(0.1) # Small delay between requests
|
||||
|
||||
# 8. Test score submission for non-registered user (should fail)
|
||||
print(f"\n8. Testing score submission for non-registered user (should fail):")
|
||||
score_data = {
|
||||
"user_id": "NonExistentUser",
|
||||
"device_id": device_id,
|
||||
"score": 1000,
|
||||
"game_completed": True
|
||||
}
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/score/{device_id}/NonExistentUser",
|
||||
json=score_data
|
||||
)
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response: {response.json()}")
|
||||
|
||||
# 9. Test getting user scores
|
||||
print(f"\n9. Testing get scores for {user1}:")
|
||||
response = requests.get(f"{API_BASE_URL}/scores/{device_id}/{user1}")
|
||||
print(f"Status: {response.status_code}")
|
||||
scores = response.json()
|
||||
print(f"Number of scores: {len(scores)}")
|
||||
for score in scores:
|
||||
print(f" Score: {score['score']}, Completed: {score['game_completed']}, Time: {score['timestamp']}")
|
||||
|
||||
# 10. Test leaderboard
|
||||
print(f"\n10. Testing leaderboard for device {device_id}:")
|
||||
response = requests.get(f"{API_BASE_URL}/leaderboard/{device_id}")
|
||||
print(f"Status: {response.status_code}")
|
||||
leaderboard = response.json()
|
||||
print("Leaderboard:")
|
||||
for entry in leaderboard:
|
||||
print(f" Rank {entry['rank']}: {entry['user_id']} - Best Score: {entry['best_score']} ({entry['total_games']} games)")
|
||||
|
||||
# 11. Test final user list
|
||||
print(f"\n11. Final user list for device {device_id}:")
|
||||
response = requests.get(f"{API_BASE_URL}/users/{device_id}")
|
||||
users = response.json()
|
||||
for user in users:
|
||||
print(f" {user['user_id']}: Best Score: {user['best_score']}, Total Games: {user['total_scores']}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("API Testing completed successfully!")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("ERROR: Could not connect to API server.")
|
||||
print("Make sure the API server is running with: python score_api.py")
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_api()
|
||||
+35
-3
@@ -3,7 +3,7 @@
|
||||
"Player1": {
|
||||
"name": "Player1",
|
||||
"created_date": "2024-01-15T10:30:00",
|
||||
"last_played": "2024-01-20T14:45:00",
|
||||
"last_played": "2025-08-21T17:57:06.304649",
|
||||
"games_played": 25,
|
||||
"total_score": 15420,
|
||||
"best_score": 980,
|
||||
@@ -23,7 +23,7 @@
|
||||
"Alice": {
|
||||
"name": "Alice",
|
||||
"created_date": "2024-01-10T09:15:00",
|
||||
"last_played": "2025-08-21T13:00:29.825909",
|
||||
"last_played": "2025-08-21T17:39:28.013833",
|
||||
"games_played": 43,
|
||||
"total_score": 33000,
|
||||
"best_score": 1250,
|
||||
@@ -58,7 +58,39 @@
|
||||
"auto_save": true
|
||||
},
|
||||
"achievements": []
|
||||
},
|
||||
"AA": {
|
||||
"name": "AA",
|
||||
"created_date": "2025-08-21T17:12:50.321070",
|
||||
"last_played": "2025-08-21T17:12:50.321070",
|
||||
"games_played": 0,
|
||||
"total_score": 0,
|
||||
"best_score": 0,
|
||||
"settings": {
|
||||
"difficulty": "normal",
|
||||
"sound_volume": 50,
|
||||
"music_volume": 50,
|
||||
"screen_shake": true,
|
||||
"auto_save": true
|
||||
},
|
||||
"achievements": []
|
||||
},
|
||||
"B0B": {
|
||||
"name": "B0B",
|
||||
"created_date": "2025-08-21T18:03:12.189612",
|
||||
"last_played": "2025-08-21T18:04:57.426796",
|
||||
"games_played": 0,
|
||||
"total_score": 0,
|
||||
"best_score": 0,
|
||||
"settings": {
|
||||
"difficulty": "normal",
|
||||
"sound_volume": 50,
|
||||
"music_volume": 50,
|
||||
"screen_shake": true,
|
||||
"auto_save": true
|
||||
},
|
||||
"achievements": []
|
||||
}
|
||||
},
|
||||
"active_profile": "MAT"
|
||||
"active_profile": "B0B"
|
||||
}
|
||||
Reference in New Issue
Block a user