From 86ca9332a72de2c98273c255511fc1cd167c66a2 Mon Sep 17 00:00:00 2001 From: ClementHVT Date: Thu, 10 Apr 2025 10:21:44 +0200 Subject: [PATCH] FEAT [Leaderboard] - Add infinite mode points counter : using DB it's displayed in the Leaderboard too --- src/Database/InfiniteModeDB.py | 39 ++++++------------ src/Database/LeaderboardDB.py | 43 +++++--------------- src/Menu/Leaderboard.py | 73 ++++++++++++++++++++++++---------- src/game.py | 5 --- src/handler.py | 31 ++++++++++++++- 5 files changed, 103 insertions(+), 88 deletions(-) diff --git a/src/Database/InfiniteModeDB.py b/src/Database/InfiniteModeDB.py index cd71da8..627840d 100644 --- a/src/Database/InfiniteModeDB.py +++ b/src/Database/InfiniteModeDB.py @@ -5,24 +5,20 @@ import os class InfiniteModeDB: def __init__(self, db_file="game.db"): """ - Initialize database connection for infinite game mode points management + Initialize database connection for infinite game mode points management. Args: - db_file: SQLite database file path + db_file: SQLite database file path. """ - # Create database directory if it doesn't exist os.makedirs( os.path.dirname(db_file) if os.path.dirname(db_file) else ".", exist_ok=True ) - self.conn = sqlite3.connect(db_file) self.cursor = self.conn.cursor() self._create_tables() - self.clear_InfiniteModeDB() def _create_tables(self): - print("Creating infinite mode table if it doesn't exist") - """Create required tables if they don't exist""" + """Create required tables if they don't exist.""" self.cursor.execute( """ CREATE TABLE IF NOT EXISTS InfiniteMode ( @@ -32,24 +28,14 @@ class InfiniteModeDB: """ ) self.conn.commit() - def get_all(self): - """ - Get all scores from the table - Returns: - List of tuples containing player name and score - """ + def get_all(self): + """Get all scores from the table.""" self.cursor.execute("SELECT * FROM InfiniteMode") return self.cursor.fetchall() def add_score(self, player_name, score): - """ - Add a new score to the InfiniteMode - - Args: - player_name: Name of the player - score: Score to be added - """ + """Add a new score to the InfiniteMode.""" self.cursor.execute( "INSERT INTO InfiniteMode (player_name, score) VALUES (?, ?)", (player_name, score), @@ -57,13 +43,14 @@ class InfiniteModeDB: self.conn.commit() def clear_InfiniteModeDB(self): - """ - Clear all scores from the leaderboard - """ - self.cursor.execute("DELETE FROM InfiniteMode") - self.conn.commit() + """Clear all scores from the InfiniteMode table.""" + try: + self.cursor.execute("DELETE FROM InfiniteMode") + self.conn.commit() + except sqlite3.Error as e: + print(f"Error clearing InfiniteMode table: {e}") def close(self): - """Close database connection""" + """Close database connection.""" if self.conn: self.conn.close() \ No newline at end of file diff --git a/src/Database/LeaderboardDB.py b/src/Database/LeaderboardDB.py index 32fb447..896bc4a 100644 --- a/src/Database/LeaderboardDB.py +++ b/src/Database/LeaderboardDB.py @@ -5,23 +5,20 @@ import os class LeaderboardDB: def __init__(self, db_file="game.db"): """ - Initialize database connection for infinite game mode points management + Initialize database connection for leaderboard management. Args: - db_file: SQLite database file path + db_file: SQLite database file path. """ - # Create database directory if it doesn't exist os.makedirs( os.path.dirname(db_file) if os.path.dirname(db_file) else ".", exist_ok=True ) - self.conn = sqlite3.connect(db_file) self.cursor = self.conn.cursor() self._create_tables() def _create_tables(self): - print("Ensuring the Leaderboard table has the correct schema") - # Create the table with the correct schema + """Ensure the Leaderboard table has the correct schema.""" self.cursor.execute( """ CREATE TABLE IF NOT EXISTS Leaderboard ( @@ -32,36 +29,16 @@ class LeaderboardDB: """ ) self.conn.commit() - def get_all(self): - """ - Get all scores from the table limited at 10 rows - Returns: - List of tuples containing player name and score - """ - self.cursor.execute("SELECT score, date FROM Leaderboard LIMIT 10") - return self.cursor.fetchall() - - def get_top_3_scores(self): - """ - Get top 3 scores from the leaderboard - - Returns: - List of tuples containing player name and score - """ + def get_top_10_scores(self): + """Get top 10 scores from the leaderboard.""" self.cursor.execute( - "SELECT score, date FROM Leaderboard ORDER BY score DESC LIMIT 3" + "SELECT score, date FROM Leaderboard ORDER BY score DESC LIMIT 10" ) return self.cursor.fetchall() def add_score(self, player_name, score): - """ - Add a new score to the InfiniteMode - - Args: - player_name: Name of the player - score: Score to be added - """ + """Add a new score to the leaderboard.""" self.cursor.execute( "INSERT INTO Leaderboard (player_name, score) VALUES (?, ?)", (player_name, score), @@ -69,13 +46,11 @@ class LeaderboardDB: self.conn.commit() def clear_leaderboard(self): - """ - Clear all scores from the leaderboard - """ + """Clear all scores from the leaderboard.""" self.cursor.execute("DELETE FROM Leaderboard") self.conn.commit() def close(self): - """Close database connection""" + """Close database connection.""" if self.conn: self.conn.close() \ No newline at end of file diff --git a/src/Menu/Leaderboard.py b/src/Menu/Leaderboard.py index c66fa1c..3af13ff 100644 --- a/src/Menu/Leaderboard.py +++ b/src/Menu/Leaderboard.py @@ -11,11 +11,12 @@ from src.Database.LevelDB import LevelDB class Leaderboard: """This class represents the leaderboard menu for the game.""" - def __init__(self, WIDTH, HEIGHT, font, db_path="game.db"): + def __init__(self, WIDTH, HEIGHT, font, leaderboard_db, db_path="game.db"): self.WIDTH = WIDTH self.HEIGHT = HEIGHT self.font = font self.db_path = db_path + self.leaderboard_db = leaderboard_db self.levels = self.get_available_levels() self.level_tabs = [f"Level {level}" for level in self.levels] @@ -58,8 +59,23 @@ class Leaderboard: for i, level in enumerate(self.levels): self.scores[i] = self.get_level_scores(str(level)) - # TO DO: Load scores for infinite mode - self.scores[len(self.levels)] = [] + # Load scores for infinite mode + try: + # Get the TOP 10 scores for infinite mode + all_scores = self.leaderboard_db.get_top_10_scores() + + # Format the scores for display + formatted_scores = [] + for score, date in all_scores: + date_obj = datetime.strptime(date, "%Y-%m-%d %H:%M:%S") + formatted_date = date_obj.strftime("%d/%m/%Y") + formatted_scores.append((formatted_date, score)) + + # Assign the formatted scores to the infinite mode tab + self.scores[len(self.levels)] = formatted_scores + except Exception as e: + print(f"Error loading infinite mode scores: {e}") + self.scores[len(self.levels)] = [] def get_level_scores(self, level_id): """Get the top 10 scores for a specific level from the database.""" @@ -104,6 +120,9 @@ class Leaderboard: def draw(self, surface): """Draw the leaderboard on the given surface.""" + # Refresh scores to ensure the latest data is displayed + self.load_scores() + self.bg_manager.draw(surface) # Draw a semi-transparent panel @@ -159,7 +178,7 @@ class Leaderboard: (self.WIDTH // 2 - no_scores_text.get_width() // 2, y_pos + 40), ) else: - for i, (date, time, collected, total) in enumerate(scores_for_tab): + for i, score_data in enumerate(scores_for_tab): row_bg = (30, 30, 60, 150) if i % 2 == 0 else (40, 40, 80, 150) row_rect = pygame.Rect(self.WIDTH // 2 - 200, y_pos - 5, 400, 30) row_surface = pygame.Surface( @@ -169,28 +188,40 @@ class Leaderboard: surface.blit(row_surface, row_rect) # Rank - rank_text = self.font.render(f"{i+1}.", True, (255, 255, 255)) + rank_text = self.font.render(f"{i + 1}.", True, (255, 255, 255)) surface.blit(rank_text, (header_positions[0], y_pos)) - # Date - date_text = self.font.render(date, True, (255, 255, 255)) - surface.blit(date_text, (header_positions[1], y_pos)) + if self.current_tab == len(self.levels): # Infinite mode + date, score = score_data + # Date + date_text = self.font.render(date, True, (255, 255, 255)) + surface.blit(date_text, (header_positions[1], y_pos)) - # Time - time_text = self.font.render( - self.format_time(time), True, (255, 255, 255) - ) - surface.blit(time_text, (header_positions[2], y_pos)) + # Time (score) + score_text = self.font.render(str(score), True, (255, 255, 255)) + surface.blit(score_text, (header_positions[2], y_pos)) + else: # Level scores + date, time, collected, total = score_data - # Collected items - collected_color = (255, 255, 255) - if collected == total: - collected_color = (0, 255, 0) + # Date + date_text = self.font.render(date, True, (255, 255, 255)) + surface.blit(date_text, (header_positions[1], y_pos)) - collected_text = self.font.render( - f"{collected}/{total}", True, collected_color - ) - surface.blit(collected_text, (header_positions[3], y_pos)) + # Time + time_text = self.font.render( + self.format_time(time), True, (255, 255, 255) + ) + surface.blit(time_text, (header_positions[2], y_pos)) + + # Collected items + collected_color = (255, 255, 255) + if collected == total: + collected_color = (0, 255, 0) + + collected_text = self.font.render( + f"{collected}/{total}", True, collected_color + ) + surface.blit(collected_text, (header_positions[3], y_pos)) y_pos += 40 diff --git a/src/game.py b/src/game.py index 3c1fd70..4f4989b 100644 --- a/src/game.py +++ b/src/game.py @@ -138,13 +138,8 @@ def start_infinite_mode(game_resources): game_resources.infinite_mode = True # Open the temporary database - print("Creating leaderboard database") game_resources.infinite_mode_db = InfiniteModeDB() - # Open the leaderboard database - print("Creating leaderboard database") - game_resources.leaderboard_db = LeaderboardDB() - # Generate the first level first_level = infinite_manager.start_infinite_mode() diff --git a/src/handler.py b/src/handler.py index 73f6f85..cb24cbb 100644 --- a/src/handler.py +++ b/src/handler.py @@ -4,6 +4,7 @@ import sys from pygame.locals import * import numpy as np +from src.Database.LeaderboardDB import LeaderboardDB from src.Database.LevelDB import LevelDB from src.Entity.Enemy import Enemy from src.Menu.LevelSelectMenu import LevelSelectMenu @@ -68,8 +69,9 @@ def initialize_game_resources(): level_select_menu = None editor_select_menu = None level_file = "map/levels/1.json" + leaderboard_db = LeaderboardDB() leaderboard = Leaderboard( - game_resources.WIDTH, game_resources.HEIGHT, game_resources.font + game_resources.WIDTH, game_resources.HEIGHT, game_resources.font, leaderboard_db ) return ( @@ -88,7 +90,8 @@ def initialize_game_resources(): leaderboard, projectiles, joysticks, - editor_select_menu, # Added editor_select_menu to the return tuple + editor_select_menu, + leaderboard_db, ) @@ -498,6 +501,11 @@ def handle_exits( speedrun_timer.save_time(collected_coins, total_coins) if hasattr(game_resources, "infinite_mode") and game_resources.infinite_mode: # Infinite mode: load the next level without going back to menu + if hasattr(game_resources, "infinite_mode_db"): + # Zeldo : add 100 points + game_resources.infinite_mode_db.add_score("player", 100) + # Add coins points also + game_resources.infinite_mode_db.add_score("player", P1.coins * 10) result = handle_exit_collision(exit, game_resources, level_file) return {"action": "continue_infinite", "result": result} else: @@ -545,6 +553,7 @@ def draw_ui_elements(displaysurface, P1, FramePerSec, font, speedrun_timer=None) def handle_death_screen( + P1, displaysurface, death_timer, dt, @@ -555,6 +564,7 @@ def handle_death_screen( game_resources, WIDTH, HEIGHT, + leaderboard_db, ): """Handle player death screen""" # Fill background @@ -590,6 +600,20 @@ def handle_death_screen( "projectiles": projectiles, } else: + if hasattr(game_resources, "infinite_mode_db"): + # Save score to database + game_resources.infinite_mode_db.add_score("player", P1.coins * 10) + # Get all scores from the database + all_scores = game_resources.infinite_mode_db.get_all() + game_resources.infinite_mode_db.clear_InfiniteModeDB() + game_resources.infinite_mode_db.close() + # Calculate total points, add them to leaderboard table + if(leaderboard_db): + total = 0 + for i in range(len(all_scores)): + total += all_scores[i][1] + leaderboard_db.add_score("player", total) + # Return to menu if hasattr(game_resources, "infinite_mode"): game_resources.infinite_mode = False @@ -626,6 +650,7 @@ def handler(): projectiles, joysticks, editor_select_menu, + leaderboard_db, ) = initialize_game_resources() # Initialize editor variables @@ -859,6 +884,7 @@ def handler(): elif current_state == DEATH_SCREEN: # Handle death screen death_result = handle_death_screen( + P1, displaysurface, death_timer, dt, @@ -869,6 +895,7 @@ def handler(): game_resources, game_resources.WIDTH, game_resources.HEIGHT, + leaderboard_db ) death_timer = death_result["death_timer"]