Consolidated common menu options for game_menu. Now the simplest call is choice = game_menu().
This commit is contained in:
		
							
								
								
									
										182
									
								
								menu.py
									
									
									
									
									
								
							
							
						
						
									
										182
									
								
								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) | ||||
|                         else: | ||||
|                             eval(options[currentIndex] + "()") | ||||
|                         # 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: | ||||
|                             lastSpoken = -1 | ||||
|                             try: | ||||
|                                 pygame.mixer.music.fadeout(500) | ||||
|                                 time.sleep(0.5) | ||||
|                             except: | ||||
|                                 pass | ||||
|  | ||||
|                         return options[currentIndex] | ||||
|                             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 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() | ||||
| @@ -222,17 +298,14 @@ def learn_sounds(sounds): | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|          | ||||
|     return "menu" | ||||
|  | ||||
| def instructions(): | ||||
|     """Display game instructions from file. | ||||
|      | ||||
|     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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user