From fe772cbb1e924b99be38b168a8c0d49009433201 Mon Sep 17 00:00:00 2001
From: Storm Dragon <stormdragon2976@gmail.com>
Date: Sat, 15 Mar 2025 04:22:44 -0400
Subject: [PATCH] Consolidated common menu options for game_menu. Now the
 simplest call is choice = game_menu().

---
 menu.py | 182 +++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 120 insertions(+), 62 deletions(-)

diff --git a/menu.py b/menu.py
index c2c0183..1877e8f 100644
--- a/menu.py
+++ b/menu.py
@@ -13,32 +13,41 @@ Provides functionality for:
 import pygame
 import time
 import webbrowser
+import os
 from sys import exit
 from os.path import isfile
 from os import listdir
 from os.path import join
 from inspect import isfunction
-from .speech import Speech
+from .speech import messagebox, Speech
 from .sound import adjust_master_volume, adjust_bgm_volume, adjust_sfx_volume, play_bgm
 from .display import display_text
 from .services import PathService
 
-def game_menu(sounds, *options):
-    """Display and handle the main game menu.
+def game_menu(sounds, playCallback=None, *customOptions):
+    """Display and handle the main game menu with standard and custom options.
     
-    Provides menu navigation with:
+    Standard menu structure:
+    1. Play (always first)
+    2. Custom options (high scores, etc.)
+    3. Learn Sounds
+    4. Instructions (if available)
+    5. Credits (if available)
+    6. Donate
+    7. Exit
+    
+    Handles navigation with:
     - Up/Down arrows for selection
     - Home/End for first/last option
     - Enter to select
     - Escape to exit
-    - Volume controls (with Alt modifier):
-        - Alt+PageUp/PageDown: Master volume up/down
-        - Alt+Home/End: Background music volume up/down
-        - Alt+Insert/Delete: Sound effects volume up/down
+    - Volume controls (with Alt modifier)
         
     Args:
         sounds (dict): Dictionary of sound objects
-        *options: Variable list of menu option names (strings)
+        playCallback (function, optional): Callback function for the "play" option.
+            If None, "play" is returned as a string like other options.
+        *customOptions: Additional custom options to include after play but before standard ones
         
     Returns:
         str: Selected menu option or "exit" if user pressed escape
@@ -46,24 +55,45 @@ def game_menu(sounds, *options):
     # Get speech instance
     speech = Speech.get_instance()
     
-    loop = True
-    pygame.mixer.stop()
+    # Start with Play option
+    allOptions = ["play"]
     
-    if pygame.mixer.music.get_busy():
-        pygame.mixer.music.unpause()
-    else:
+    # Add custom options (high scores, etc.)
+    allOptions.extend(customOptions)
+    
+    # Add standard options in preferred order
+    allOptions.append("learn_sounds")
+    
+    # Check for instructions file
+    if os.path.isfile('files/instructions.txt'):
+        allOptions.append("instructions")
+        
+    # Check for credits file
+    if os.path.isfile('files/credits.txt'):
+        allOptions.append("credits")
+        
+    # Final options
+    allOptions.extend(["donate", "exit_game"])
+    
+    # Track if music was previously playing
+    musicWasPlaying = pygame.mixer.music.get_busy()
+    
+    # Only start menu music if no music is currently playing
+    if not musicWasPlaying:
         try:
             from .sound import play_bgm
             play_bgm("sounds/music_menu.ogg")
         except:
             pass
             
+    loop = True
+    pygame.mixer.stop()
     currentIndex = 0
     lastSpoken = -1  # Track last spoken index
     
     while loop:
         if currentIndex != lastSpoken:
-            speech.speak(options[currentIndex])
+            speech.speak(allOptions[currentIndex])
             lastSpoken = currentIndex
             
         event = pygame.event.wait()
@@ -98,24 +128,24 @@ def game_menu(sounds, *options):
                             sounds['menu-move'].play()
                         except:
                             pass
-                        if options[currentIndex] != "donate":
+                        if allOptions[currentIndex] != "donate":
                             pygame.mixer.music.unpause()
                 elif event.key == pygame.K_END:
-                    if currentIndex != len(options) - 1:
-                        currentIndex = len(options) - 1
+                    if currentIndex != len(allOptions) - 1:
+                        currentIndex = len(allOptions) - 1
                         try:
                             sounds['menu-move'].play()
                         except:
                             pass
-                        if options[currentIndex] != "donate":
+                        if allOptions[currentIndex] != "donate":
                             pygame.mixer.music.unpause()
-                elif event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(options) - 1:
+                elif event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(allOptions) - 1:
                     currentIndex += 1
                     try:
                         sounds['menu-move'].play()
                     except:
                         pass
-                    if options[currentIndex] != "donate":
+                    if allOptions[currentIndex] != "donate":
                         pygame.mixer.music.unpause()
                 elif event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
                     currentIndex -= 1
@@ -123,7 +153,7 @@ def game_menu(sounds, *options):
                         sounds['menu-move'].play()
                     except:
                         pass
-                    if options[currentIndex] != "donate":
+                    if allOptions[currentIndex] != "donate":
                         pygame.mixer.music.unpause()
                 elif event.key == pygame.K_RETURN:
                     try:
@@ -134,20 +164,74 @@ def game_menu(sounds, *options):
                         except:
                             pass
                         
+                        selectedOption = allOptions[currentIndex]
+                        
                         # Special case for exit_game with fade
-                        if options[currentIndex] == "exit_game":
+                        if selectedOption == "exit_game":
                             exit_game(500 if pygame.mixer.music.get_busy() else 0)
+                        # Special case for play option
+                        elif selectedOption == "play":
+                            if playCallback:
+                                # If a play callback is provided, call it directly
+                                try:
+                                    pygame.mixer.music.fadeout(500)
+                                    time.sleep(0.5)
+                                except:
+                                    pass
+                                playCallback()
+                            else:
+                                # Otherwise return "play" to the caller
+                                return "play"
+                        # Handle standard options directly
+                        elif selectedOption in ["instructions", "credits", "learn_sounds", "donate"]:
+                            # Pause music before calling the selected function
+                            try:
+                                pygame.mixer.music.pause()
+                            except:
+                                pass
+                                
+                            # Handle standard options
+                            if selectedOption == "instructions":
+                                instructions()
+                            elif selectedOption == "credits":
+                                credits()
+                            elif selectedOption == "learn_sounds":
+                                learn_sounds(sounds)
+                            elif selectedOption == "donate":
+                                donate()
+                            
+                            # Unpause music after function returns
+                            try:
+                                # Check if music is actually paused before trying to unpause
+                                if not pygame.mixer.music.get_busy():
+                                    pygame.mixer.music.unpause()
+                                # If music is already playing, don't try to restart it
+                            except:
+                                # Only start fresh music if no music is playing at all
+                                if not pygame.mixer.music.get_busy():
+                                    try:
+                                        from .sound import play_bgm
+                                        play_bgm("sounds/music_menu.ogg")
+                                    except:
+                                        pass
+                        # Return custom options to the calling function
                         else:
-                            eval(options[currentIndex] + "()")
-                    except:
+                            lastSpoken = -1
+                            try:
+                                pygame.mixer.music.fadeout(500)
+                                time.sleep(0.5)
+                            except:
+                                pass
+                            return selectedOption
+                    except Exception as e:
+                        print(f"Error handling menu selection: {e}")
                         lastSpoken = -1
                         try:
                             pygame.mixer.music.fadeout(500)
                             time.sleep(0.5)
                         except:
                             pass
-
-                        return options[currentIndex]
+                        return allOptions[currentIndex]
                     
         event = pygame.event.clear()
         time.sleep(0.001)
@@ -169,12 +253,6 @@ def learn_sounds(sounds):
     # Get speech instance
     speech = Speech.get_instance()
     
-    loop = True
-    try:
-        pygame.mixer.music.pause()
-    except:
-        pass
-
     currentIndex = 0
     
     # Get list of available sounds, excluding special sounds
@@ -187,7 +265,10 @@ def learn_sounds(sounds):
     # Track last spoken index to avoid repetition
     lastSpoken = -1
     
-    while loop:
+    # Flag to track when to exit the loop
+    returnToMenu = False
+    
+    while not returnToMenu:
         if currentIndex != lastSpoken:
             speech.speak(soundFiles[currentIndex][:-4])
             lastSpoken = currentIndex
@@ -195,12 +276,7 @@ def learn_sounds(sounds):
         event = pygame.event.wait()
         if event.type == pygame.KEYDOWN:
             if event.key == pygame.K_ESCAPE:
-                try:
-                    pygame.mixer.music.unpause()
-                except:
-                    pass
-
-                return "menu"
+                returnToMenu = True
                 
             if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(soundFiles) - 1:
                 pygame.mixer.stop()
@@ -221,6 +297,8 @@ def learn_sounds(sounds):
                     
         event = pygame.event.clear()
         time.sleep(0.001)
+        
+    return "menu"
 
 def instructions():
     """Display game instructions from file.
@@ -228,11 +306,6 @@ def instructions():
     Reads and displays instructions from 'files/instructions.txt'.
     If file is missing, displays an error message.
     """
-    try:
-        pygame.mixer.music.pause()
-    except:
-        pass
-
     try:
         with open('files/instructions.txt', 'r') as f:
             info = f.readlines()
@@ -240,11 +313,6 @@ def instructions():
         info = ["Instructions file is missing."]
     display_text(info)
 
-    try:
-        pygame.mixer.music.unpause()
-    except:
-        pass
-
 def credits():
     """Display game credits from file.
 
@@ -252,11 +320,6 @@ def credits():
     Adds game name header before displaying.
     If file is missing, displays an error message.
     """
-    try:
-        pygame.mixer.music.pause()
-    except:
-        pass
-
     try:
         with open('files/credits.txt', 'r') as f:
             info = f.readlines()
@@ -269,18 +332,13 @@ def credits():
 
     display_text(info)
 
-    try:
-        pygame.mixer.music.unpause()
-    except:
-        pass
-
 def donate():
     """Open the donation webpage.
     
-    Pauses background music and opens the Ko-fi donation page.
+    Opens the Ko-fi donation page.
     """
-    pygame.mixer.music.pause()
     webbrowser.open('https://ko-fi.com/stormux')
+    messagebox("The donation page has been opened in your browser.")
 
 def exit_game(fade=0):
     """Clean up and exit the game properly.