Code cleanup and sound consolidation.

This commit is contained in:
Storm Dragon
2025-03-22 17:34:35 -04:00
parent 3a478d15d5
commit a17a4c6f15
10 changed files with 732 additions and 986 deletions

120
menu.py
View File

@ -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)