Code cleanup and sound consolidation.
This commit is contained in:
120
menu.py
120
menu.py
@ -27,7 +27,7 @@ from .services import PathService, ConfigService
|
||||
|
||||
def game_menu(sounds, playCallback=None, *customOptions):
|
||||
"""Display and handle the main game menu with standard and custom options.
|
||||
|
||||
|
||||
Standard menu structure:
|
||||
1. Play (always first)
|
||||
2. High Scores
|
||||
@ -37,53 +37,53 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
||||
6. Credits (if available)
|
||||
7. Donate
|
||||
8. 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)
|
||||
|
||||
|
||||
Args:
|
||||
sounds (dict): Dictionary of sound objects
|
||||
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
|
||||
"""
|
||||
# Get speech instance
|
||||
speech = Speech.get_instance()
|
||||
|
||||
|
||||
# Start with Play option
|
||||
allOptions = ["play"]
|
||||
|
||||
|
||||
# Add high scores option if scores exist
|
||||
if Scoreboard.has_high_scores():
|
||||
allOptions.append("high_scores")
|
||||
|
||||
|
||||
# Add custom options (other menu items, 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:
|
||||
@ -91,23 +91,23 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
||||
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(allOptions[currentIndex])
|
||||
lastSpoken = currentIndex
|
||||
|
||||
|
||||
event = pygame.event.wait()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
# Check for Alt modifier
|
||||
mods = pygame.key.get_mods()
|
||||
altPressed = mods & pygame.KMOD_ALT
|
||||
|
||||
|
||||
# Volume controls (require Alt)
|
||||
if altPressed:
|
||||
if event.key == pygame.K_PAGEUP:
|
||||
@ -169,9 +169,9 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
||||
time.sleep(sounds['menu-select'].get_length())
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
selectedOption = allOptions[currentIndex]
|
||||
|
||||
|
||||
# Special case for exit_game with fade
|
||||
if selectedOption == "exit_game":
|
||||
exit_game(500 if pygame.mixer.music.get_busy() else 0)
|
||||
@ -202,7 +202,7 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
||||
pygame.mixer.music.pause()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Handle standard options
|
||||
if selectedOption == "instructions":
|
||||
instructions()
|
||||
@ -214,7 +214,7 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
||||
Scoreboard.display_high_scores()
|
||||
elif selectedOption == "donate":
|
||||
donate()
|
||||
|
||||
|
||||
# Unpause music after function returns
|
||||
try:
|
||||
# Check if music is actually paused before trying to unpause
|
||||
@ -248,105 +248,105 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
||||
except:
|
||||
pass
|
||||
return allOptions[currentIndex]
|
||||
|
||||
|
||||
event = pygame.event.clear()
|
||||
time.sleep(0.001)
|
||||
|
||||
def learn_sounds(sounds):
|
||||
"""Interactive menu for learning game sounds.
|
||||
|
||||
|
||||
Allows users to:
|
||||
- Navigate through available sounds with up/down arrows
|
||||
- Navigate between sound categories (folders) using Page Up/Page Down or Left/Right arrows
|
||||
- Play selected sounds with Enter
|
||||
- Return to menu with Escape
|
||||
|
||||
|
||||
Excluded sounds:
|
||||
- Files in folders named 'ambience' (at any level)
|
||||
- Files in any directory starting with '.'
|
||||
- Files starting with 'game-intro', 'music_menu', or '_'
|
||||
|
||||
|
||||
Args:
|
||||
sounds (dict): Dictionary of available sound objects
|
||||
|
||||
|
||||
Returns:
|
||||
str: "menu" if user exits with escape
|
||||
"""
|
||||
# Get speech instance
|
||||
speech = Speech.get_instance()
|
||||
|
||||
|
||||
# Define exclusion criteria
|
||||
excludedPrefixes = ["game-intro", "music_menu", "_"]
|
||||
excludedDirs = ["ambience", "."]
|
||||
|
||||
|
||||
# Organize sounds by directory
|
||||
soundsByDir = {}
|
||||
|
||||
|
||||
# Process each sound key in the dictionary
|
||||
for soundKey in sounds.keys():
|
||||
# Skip if key has any excluded prefix
|
||||
if any(soundKey.lower().startswith(prefix.lower()) for prefix in excludedPrefixes):
|
||||
continue
|
||||
|
||||
|
||||
# Split key into path parts
|
||||
parts = soundKey.split('/')
|
||||
|
||||
|
||||
# Skip if any part of the path is an excluded directory
|
||||
if any(part.lower() == dirName.lower() or part.startswith('.') for part in parts for dirName in excludedDirs):
|
||||
continue
|
||||
|
||||
|
||||
# Determine the directory
|
||||
if '/' in soundKey:
|
||||
directory = soundKey.split('/')[0]
|
||||
else:
|
||||
directory = 'root' # Root directory sounds
|
||||
|
||||
|
||||
# Add to sounds by directory
|
||||
if directory not in soundsByDir:
|
||||
soundsByDir[directory] = []
|
||||
soundsByDir[directory].append(soundKey)
|
||||
|
||||
|
||||
# Sort each directory's sounds
|
||||
for directory in soundsByDir:
|
||||
soundsByDir[directory].sort()
|
||||
|
||||
|
||||
# If no sounds found, inform the user and return
|
||||
if not soundsByDir:
|
||||
speech.speak("No sounds available to learn.")
|
||||
return "menu"
|
||||
|
||||
|
||||
# Get list of directories in sorted order
|
||||
directories = sorted(soundsByDir.keys())
|
||||
|
||||
|
||||
# Start with first directory
|
||||
currentDirIndex = 0
|
||||
currentDir = directories[currentDirIndex]
|
||||
currentSoundKeys = soundsByDir[currentDir]
|
||||
currentSoundIndex = 0
|
||||
|
||||
|
||||
# Display appropriate message based on number of directories
|
||||
if len(directories) > 1:
|
||||
messagebox(f"Starting with {currentDir if currentDir != 'root' else 'root directory'} sounds. Use left and right arrows or page up and page down to navigate categories.")
|
||||
|
||||
|
||||
# Track last spoken to avoid repetition
|
||||
lastSpoken = -1
|
||||
directoryChanged = True # Flag to track if directory just changed
|
||||
|
||||
|
||||
# Flag to track when to exit the loop
|
||||
returnToMenu = False
|
||||
|
||||
|
||||
while not returnToMenu:
|
||||
# Announce current sound
|
||||
if currentSoundIndex != lastSpoken:
|
||||
totalSounds = len(currentSoundKeys)
|
||||
soundName = currentSoundKeys[currentSoundIndex]
|
||||
|
||||
|
||||
# Remove directory prefix if present
|
||||
if '/' in soundName:
|
||||
displayName = '/'.join(soundName.split('/')[1:])
|
||||
else:
|
||||
displayName = soundName
|
||||
|
||||
|
||||
# If directory just changed, include directory name in announcement
|
||||
if directoryChanged:
|
||||
dirDescription = "Root directory" if currentDir == 'root' else currentDir
|
||||
@ -354,24 +354,24 @@ def learn_sounds(sounds):
|
||||
directoryChanged = False # Reset flag after announcement
|
||||
else:
|
||||
announcement = f"{displayName}, {currentSoundIndex + 1} of {totalSounds}"
|
||||
|
||||
|
||||
speech.speak(announcement)
|
||||
lastSpoken = currentSoundIndex
|
||||
|
||||
|
||||
event = pygame.event.wait()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
returnToMenu = True
|
||||
|
||||
|
||||
# Sound navigation
|
||||
elif event.key in [pygame.K_DOWN, pygame.K_s] and currentSoundIndex < len(currentSoundKeys) - 1:
|
||||
pygame.mixer.stop()
|
||||
currentSoundIndex += 1
|
||||
|
||||
|
||||
elif event.key in [pygame.K_UP, pygame.K_w] and currentSoundIndex > 0:
|
||||
pygame.mixer.stop()
|
||||
currentSoundIndex -= 1
|
||||
|
||||
|
||||
# Directory navigation
|
||||
elif event.key in [pygame.K_PAGEDOWN, pygame.K_RIGHT] and currentDirIndex < len(directories) - 1:
|
||||
pygame.mixer.stop()
|
||||
@ -381,7 +381,7 @@ def learn_sounds(sounds):
|
||||
currentSoundIndex = 0
|
||||
directoryChanged = True # Set flag on directory change
|
||||
lastSpoken = -1 # Force announcement
|
||||
|
||||
|
||||
elif event.key in [pygame.K_PAGEUP, pygame.K_LEFT] and currentDirIndex > 0:
|
||||
pygame.mixer.stop()
|
||||
currentDirIndex -= 1
|
||||
@ -390,7 +390,7 @@ def learn_sounds(sounds):
|
||||
currentSoundIndex = 0
|
||||
directoryChanged = True # Set flag on directory change
|
||||
lastSpoken = -1 # Force announcement
|
||||
|
||||
|
||||
# Play sound
|
||||
elif event.key == pygame.K_RETURN:
|
||||
try:
|
||||
@ -400,16 +400,16 @@ def learn_sounds(sounds):
|
||||
except Exception as e:
|
||||
print(f"Error playing sound: {e}")
|
||||
speech.speak("Could not play sound.")
|
||||
|
||||
|
||||
event = pygame.event.clear()
|
||||
pygame.event.pump() # Process pygame's internal events
|
||||
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.
|
||||
"""
|
||||
@ -441,7 +441,7 @@ def credits():
|
||||
|
||||
def donate():
|
||||
"""Open the donation webpage.
|
||||
|
||||
|
||||
Opens the Ko-fi donation page.
|
||||
"""
|
||||
webbrowser.open('https://ko-fi.com/stormux')
|
||||
@ -449,20 +449,20 @@ def donate():
|
||||
|
||||
def exit_game(fade=0):
|
||||
"""Clean up and exit the game properly.
|
||||
|
||||
|
||||
Args:
|
||||
fade (int): Milliseconds to fade out music before exiting.
|
||||
0 means stop immediately (default)
|
||||
"""
|
||||
# Force clear any pending events to prevent hanging
|
||||
pygame.event.clear()
|
||||
|
||||
|
||||
# Stop all mixer channels first
|
||||
try:
|
||||
pygame.mixer.stop()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not stop mixer channels: {e}")
|
||||
|
||||
|
||||
# Get speech instance and handle all providers
|
||||
try:
|
||||
speech = Speech.get_instance()
|
||||
@ -473,7 +473,7 @@ def exit_game(fade=0):
|
||||
print(f"Warning: Could not close speech: {e}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not get speech instance: {e}")
|
||||
|
||||
|
||||
# Handle music based on fade parameter
|
||||
try:
|
||||
if fade > 0 and pygame.mixer.music.get_busy():
|
||||
@ -484,13 +484,13 @@ def exit_game(fade=0):
|
||||
pygame.mixer.music.stop()
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not handle music during exit: {e}")
|
||||
|
||||
|
||||
# Clean up pygame
|
||||
try:
|
||||
pygame.quit()
|
||||
except Exception as e:
|
||||
print(f"Warning: Error during pygame.quit(): {e}")
|
||||
|
||||
|
||||
# Use os._exit for immediate termination
|
||||
import os
|
||||
os._exit(0)
|
||||
|
Reference in New Issue
Block a user