Code cleanup and sound consolidation.
This commit is contained in:
parent
3a478d15d5
commit
a17a4c6f15
26
__init__.py
26
__init__.py
@ -79,7 +79,7 @@ __version__ = '2.0.0'
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
# Services
|
# Services
|
||||||
'ConfigService', 'VolumeService', 'PathService',
|
'ConfigService', 'VolumeService', 'PathService',
|
||||||
|
|
||||||
# Sound
|
# Sound
|
||||||
'Sound',
|
'Sound',
|
||||||
'play_bgm',
|
'play_bgm',
|
||||||
@ -97,32 +97,32 @@ __all__ = [
|
|||||||
'cut_scene',
|
'cut_scene',
|
||||||
'play_random_falling',
|
'play_random_falling',
|
||||||
'calculate_volume_and_pan',
|
'calculate_volume_and_pan',
|
||||||
|
|
||||||
# Speech
|
# Speech
|
||||||
'messagebox',
|
'messagebox',
|
||||||
'speak',
|
'speak',
|
||||||
'Speech',
|
'Speech',
|
||||||
|
|
||||||
# Scoreboard
|
# Scoreboard
|
||||||
'Scoreboard',
|
'Scoreboard',
|
||||||
|
|
||||||
# Input
|
# Input
|
||||||
'get_input', 'check_for_exit', 'pause_game',
|
'get_input', 'check_for_exit', 'pause_game',
|
||||||
|
|
||||||
# Display
|
# Display
|
||||||
'display_text', 'initialize_gui',
|
'display_text', 'initialize_gui',
|
||||||
|
|
||||||
# Menu
|
# Menu
|
||||||
'game_menu', 'learn_sounds', 'instructions', 'credits', 'donate', 'exit_game', 'high_scores', 'has_high_scores',
|
'game_menu', 'learn_sounds', 'instructions', 'credits', 'donate', 'exit_game', 'high_scores', 'has_high_scores',
|
||||||
|
|
||||||
# Game class
|
# Game class
|
||||||
'Game',
|
'Game',
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
'check_for_updates', 'get_version_tuple', 'check_compatibility',
|
'check_for_updates', 'get_version_tuple', 'check_compatibility',
|
||||||
'sanitize_filename', 'lerp', 'smooth_step', 'distance_2d',
|
'sanitize_filename', 'lerp', 'smooth_step', 'distance_2d',
|
||||||
'x_powerbar', 'y_powerbar', 'generate_tone',
|
'x_powerbar', 'y_powerbar', 'generate_tone',
|
||||||
|
|
||||||
# Re-exported functions from pygame, math, random
|
# Re-exported functions from pygame, math, random
|
||||||
'get_ticks', 'delay', 'wait',
|
'get_ticks', 'delay', 'wait',
|
||||||
'sin', 'cos', 'sqrt', 'floor', 'ceil',
|
'sin', 'cos', 'sqrt', 'floor', 'ceil',
|
||||||
@ -141,10 +141,10 @@ def initialize_gui_with_services(gameTitle):
|
|||||||
"""Wrapper around initialize_gui that initializes services."""
|
"""Wrapper around initialize_gui that initializes services."""
|
||||||
# Initialize path service
|
# Initialize path service
|
||||||
pathService.initialize(gameTitle)
|
pathService.initialize(gameTitle)
|
||||||
|
|
||||||
# Connect config service to path service
|
# Connect config service to path service
|
||||||
configService.set_game_info(gameTitle, pathService)
|
configService.set_game_info(gameTitle, pathService)
|
||||||
|
|
||||||
# Call original initialize_gui
|
# Call original initialize_gui
|
||||||
return _originalInitializeGui(gameTitle)
|
return _originalInitializeGui(gameTitle)
|
||||||
|
|
||||||
@ -159,11 +159,11 @@ def scoreboard_init_with_services(self, score=0, configService=None, speech=None
|
|||||||
# Use global services if not specified
|
# Use global services if not specified
|
||||||
if configService is None:
|
if configService is None:
|
||||||
configService = ConfigService.get_instance()
|
configService = ConfigService.get_instance()
|
||||||
|
|
||||||
# Ensure pathService is connected if using defaults
|
# Ensure pathService is connected if using defaults
|
||||||
if not hasattr(configService, 'pathService') and pathService.game_path is not None:
|
if not hasattr(configService, 'pathService') and pathService.game_path is not None:
|
||||||
configService.pathService = pathService
|
configService.pathService = pathService
|
||||||
|
|
||||||
# Call original init with services
|
# Call original init with services
|
||||||
_originalScoreboardInit(self, score, configService, speech)
|
_originalScoreboardInit(self, score, configService, speech)
|
||||||
|
|
||||||
|
22
config.py
22
config.py
@ -13,10 +13,10 @@ from xdg import BaseDirectory
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
"""Configuration management class for Storm Games."""
|
"""Configuration management class for Storm Games."""
|
||||||
|
|
||||||
def __init__(self, gameTitle):
|
def __init__(self, gameTitle):
|
||||||
"""Initialize configuration system for a game.
|
"""Initialize configuration system for a game.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gameTitle (str): Title of the game
|
gameTitle (str): Title of the game
|
||||||
"""
|
"""
|
||||||
@ -24,19 +24,19 @@ class Config:
|
|||||||
self.globalPath = os.path.join(BaseDirectory.xdg_config_home, "storm-games")
|
self.globalPath = os.path.join(BaseDirectory.xdg_config_home, "storm-games")
|
||||||
self.gamePath = os.path.join(self.globalPath,
|
self.gamePath = os.path.join(self.globalPath,
|
||||||
str.lower(str.replace(gameTitle, " ", "-")))
|
str.lower(str.replace(gameTitle, " ", "-")))
|
||||||
|
|
||||||
# Create game directory if it doesn't exist
|
# Create game directory if it doesn't exist
|
||||||
if not os.path.exists(self.gamePath):
|
if not os.path.exists(self.gamePath):
|
||||||
os.makedirs(self.gamePath)
|
os.makedirs(self.gamePath)
|
||||||
|
|
||||||
# Initialize config parsers
|
# Initialize config parsers
|
||||||
self.localConfig = configparser.ConfigParser()
|
self.localConfig = configparser.ConfigParser()
|
||||||
self.globalConfig = configparser.ConfigParser()
|
self.globalConfig = configparser.ConfigParser()
|
||||||
|
|
||||||
# Load existing configurations
|
# Load existing configurations
|
||||||
self.read_local_config()
|
self.read_local_config()
|
||||||
self.read_global_config()
|
self.read_global_config()
|
||||||
|
|
||||||
def read_local_config(self):
|
def read_local_config(self):
|
||||||
"""Read local configuration from file."""
|
"""Read local configuration from file."""
|
||||||
try:
|
try:
|
||||||
@ -44,7 +44,7 @@ class Config:
|
|||||||
self.localConfig.read_file(configFile)
|
self.localConfig.read_file(configFile)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def read_global_config(self):
|
def read_global_config(self):
|
||||||
"""Read global configuration from file."""
|
"""Read global configuration from file."""
|
||||||
try:
|
try:
|
||||||
@ -52,12 +52,12 @@ class Config:
|
|||||||
self.globalConfig.read_file(configFile)
|
self.globalConfig.read_file(configFile)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def write_local_config(self):
|
def write_local_config(self):
|
||||||
"""Write local configuration to file."""
|
"""Write local configuration to file."""
|
||||||
with open(os.path.join(self.gamePath, "config.ini"), 'w') as configFile:
|
with open(os.path.join(self.gamePath, "config.ini"), 'w') as configFile:
|
||||||
self.localConfig.write(configFile)
|
self.localConfig.write(configFile)
|
||||||
|
|
||||||
def write_global_config(self):
|
def write_global_config(self):
|
||||||
"""Write global configuration to file."""
|
"""Write global configuration to file."""
|
||||||
with open(os.path.join(self.globalPath, "config.ini"), 'w') as configFile:
|
with open(os.path.join(self.globalPath, "config.ini"), 'w') as configFile:
|
||||||
@ -71,7 +71,7 @@ globalPath = ""
|
|||||||
|
|
||||||
def write_config(writeGlobal=False):
|
def write_config(writeGlobal=False):
|
||||||
"""Write configuration to file.
|
"""Write configuration to file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
writeGlobal (bool): If True, write to global config, otherwise local (default: False)
|
writeGlobal (bool): If True, write to global config, otherwise local (default: False)
|
||||||
"""
|
"""
|
||||||
@ -84,7 +84,7 @@ def write_config(writeGlobal=False):
|
|||||||
|
|
||||||
def read_config(readGlobal=False):
|
def read_config(readGlobal=False):
|
||||||
"""Read configuration from file.
|
"""Read configuration from file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
readGlobal (bool): If True, read global config, otherwise local (default: False)
|
readGlobal (bool): If True, read global config, otherwise local (default: False)
|
||||||
"""
|
"""
|
||||||
|
60
display.py
60
display.py
@ -23,81 +23,81 @@ displayTextUsageInstructions = False
|
|||||||
|
|
||||||
def initialize_gui(gameTitle):
|
def initialize_gui(gameTitle):
|
||||||
"""Initialize the game GUI and sound system.
|
"""Initialize the game GUI and sound system.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gameTitle (str): Title of the game
|
gameTitle (str): Title of the game
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Dictionary of loaded sound objects
|
dict: Dictionary of loaded sound objects
|
||||||
"""
|
"""
|
||||||
# Initialize path service with game title
|
# Initialize path service with game title
|
||||||
pathService = PathService.get_instance().initialize(gameTitle)
|
pathService = PathService.get_instance().initialize(gameTitle)
|
||||||
|
|
||||||
# Seed the random generator to the clock
|
# Seed the random generator to the clock
|
||||||
random.seed()
|
random.seed()
|
||||||
|
|
||||||
# Set game's name
|
# Set game's name
|
||||||
setproctitle(str.lower(str.replace(gameTitle, " ", "-")))
|
setproctitle(str.lower(str.replace(gameTitle, " ", "-")))
|
||||||
|
|
||||||
# Initialize pygame
|
# Initialize pygame
|
||||||
pygame.init()
|
pygame.init()
|
||||||
pygame.display.set_mode((800, 600))
|
pygame.display.set_mode((800, 600))
|
||||||
pygame.display.set_caption(gameTitle)
|
pygame.display.set_caption(gameTitle)
|
||||||
|
|
||||||
# Set up audio system
|
# Set up audio system
|
||||||
pygame.mixer.pre_init(44100, -16, 2, 1024)
|
pygame.mixer.pre_init(44100, -16, 2, 1024)
|
||||||
pygame.mixer.init()
|
pygame.mixer.init()
|
||||||
pygame.mixer.set_num_channels(32)
|
pygame.mixer.set_num_channels(32)
|
||||||
pygame.mixer.set_reserved(0) # Reserve channel for cut scenes
|
pygame.mixer.set_reserved(0) # Reserve channel for cut scenes
|
||||||
|
|
||||||
# Enable key repeat for volume controls
|
# Enable key repeat for volume controls
|
||||||
pygame.key.set_repeat(500, 100)
|
pygame.key.set_repeat(500, 100)
|
||||||
|
|
||||||
# Load sound files recursively including subdirectories
|
# Load sound files recursively including subdirectories
|
||||||
soundData = {}
|
soundData = {}
|
||||||
try:
|
try:
|
||||||
import os
|
import os
|
||||||
|
|
||||||
soundDir = "sounds/"
|
soundDir = "sounds/"
|
||||||
# Walk through directory tree
|
# Walk through directory tree
|
||||||
for dirPath, dirNames, fileNames in os.walk(soundDir):
|
for dirPath, dirNames, fileNames in os.walk(soundDir):
|
||||||
# Get relative path from soundDir
|
# Get relative path from soundDir
|
||||||
relPath = os.path.relpath(dirPath, soundDir)
|
relPath = os.path.relpath(dirPath, soundDir)
|
||||||
|
|
||||||
# Process each file
|
# Process each file
|
||||||
for fileName in fileNames:
|
for fileName in fileNames:
|
||||||
# Check if file is a valid sound file
|
# Check if file is a valid sound file
|
||||||
if fileName.lower().endswith(('.ogg', '.wav')):
|
if fileName.lower().endswith(('.ogg', '.wav')):
|
||||||
# Full path to the sound file
|
# Full path to the sound file
|
||||||
fullPath = os.path.join(dirPath, fileName)
|
fullPath = os.path.join(dirPath, fileName)
|
||||||
|
|
||||||
# Create sound key (remove extension)
|
# Create sound key (remove extension)
|
||||||
baseName = os.path.splitext(fileName)[0]
|
baseName = os.path.splitext(fileName)[0]
|
||||||
|
|
||||||
# If in root sounds dir, just use basename
|
# If in root sounds dir, just use basename
|
||||||
if relPath == '.':
|
if relPath == '.':
|
||||||
soundKey = baseName
|
soundKey = baseName
|
||||||
else:
|
else:
|
||||||
# Otherwise use relative path + basename, normalized with forward slashes
|
# Otherwise use relative path + basename, normalized with forward slashes
|
||||||
soundKey = os.path.join(relPath, baseName).replace('\\', '/')
|
soundKey = os.path.join(relPath, baseName).replace('\\', '/')
|
||||||
|
|
||||||
# Load the sound
|
# Load the sound
|
||||||
soundData[soundKey] = pygame.mixer.Sound(fullPath)
|
soundData[soundKey] = pygame.mixer.Sound(fullPath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error loading sounds:", e)
|
print("Error loading sounds:", e)
|
||||||
Speech.get_instance().speak("Error loading sounds.", False)
|
Speech.get_instance().speak("Error loading sounds.", False)
|
||||||
soundData = {}
|
soundData = {}
|
||||||
|
|
||||||
# Play intro sound if available
|
# Play intro sound if available
|
||||||
from .sound import cut_scene
|
from .sound import cut_scene
|
||||||
if 'game-intro' in soundData:
|
if 'game-intro' in soundData:
|
||||||
cut_scene(soundData, 'game-intro')
|
cut_scene(soundData, 'game-intro')
|
||||||
|
|
||||||
return soundData
|
return soundData
|
||||||
|
|
||||||
def display_text(text):
|
def display_text(text):
|
||||||
"""Display and speak text with navigation controls.
|
"""Display and speak text with navigation controls.
|
||||||
|
|
||||||
Allows users to:
|
Allows users to:
|
||||||
- Navigate text line by line with arrow keys (skipping blank lines)
|
- Navigate text line by line with arrow keys (skipping blank lines)
|
||||||
- Listen to full text with space
|
- Listen to full text with space
|
||||||
@ -107,20 +107,20 @@ def display_text(text):
|
|||||||
- Alt+PageUp/PageDown: Master volume up/down
|
- Alt+PageUp/PageDown: Master volume up/down
|
||||||
- Alt+Home/End: Background music volume up/down
|
- Alt+Home/End: Background music volume up/down
|
||||||
- Alt+Insert/Delete: Sound effects volume up/down
|
- Alt+Insert/Delete: Sound effects volume up/down
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (list): List of text lines to display
|
text (list): List of text lines to display
|
||||||
"""
|
"""
|
||||||
# Get service instances
|
# Get service instances
|
||||||
speech = Speech.get_instance()
|
speech = Speech.get_instance()
|
||||||
volumeService = VolumeService.get_instance()
|
volumeService = VolumeService.get_instance()
|
||||||
|
|
||||||
# Store original text with blank lines for copying
|
# Store original text with blank lines for copying
|
||||||
originalText = text.copy()
|
originalText = text.copy()
|
||||||
|
|
||||||
# Create navigation text by filtering out blank lines
|
# Create navigation text by filtering out blank lines
|
||||||
navText = [line for line in text if line.strip()]
|
navText = [line for line in text if line.strip()]
|
||||||
|
|
||||||
# Add instructions at the start on the first display
|
# Add instructions at the start on the first display
|
||||||
global displayTextUsageInstructions
|
global displayTextUsageInstructions
|
||||||
if not displayTextUsageInstructions:
|
if not displayTextUsageInstructions:
|
||||||
@ -129,20 +129,20 @@ def display_text(text):
|
|||||||
"or t to copy the entire text. Press enter or escape when you are done reading.")
|
"or t to copy the entire text. Press enter or escape when you are done reading.")
|
||||||
navText.insert(0, instructions)
|
navText.insert(0, instructions)
|
||||||
displayTextUsageInstructions = True
|
displayTextUsageInstructions = True
|
||||||
|
|
||||||
# Add end marker
|
# Add end marker
|
||||||
navText.append("End of text.")
|
navText.append("End of text.")
|
||||||
|
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
speech.speak(navText[currentIndex])
|
speech.speak(navText[currentIndex])
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event = pygame.event.wait()
|
event = pygame.event.wait()
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
# Check for Alt modifier
|
# Check for Alt modifier
|
||||||
mods = pygame.key.get_mods()
|
mods = pygame.key.get_mods()
|
||||||
altPressed = mods & pygame.KMOD_ALT
|
altPressed = mods & pygame.KMOD_ALT
|
||||||
|
|
||||||
# Volume controls (require Alt)
|
# Volume controls (require Alt)
|
||||||
if altPressed:
|
if altPressed:
|
||||||
if event.key == pygame.K_PAGEUP:
|
if event.key == pygame.K_PAGEUP:
|
||||||
@ -160,26 +160,26 @@ def display_text(text):
|
|||||||
else:
|
else:
|
||||||
if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
|
if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
|
||||||
return
|
return
|
||||||
|
|
||||||
if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
|
if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
|
||||||
currentIndex += 1
|
currentIndex += 1
|
||||||
speech.speak(navText[currentIndex])
|
speech.speak(navText[currentIndex])
|
||||||
|
|
||||||
if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
|
if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
|
||||||
currentIndex -= 1
|
currentIndex -= 1
|
||||||
speech.speak(navText[currentIndex])
|
speech.speak(navText[currentIndex])
|
||||||
|
|
||||||
if event.key == pygame.K_SPACE:
|
if event.key == pygame.K_SPACE:
|
||||||
# Join with newlines to preserve spacing in speech
|
# Join with newlines to preserve spacing in speech
|
||||||
speech.speak('\n'.join(originalText[1:-1]))
|
speech.speak('\n'.join(originalText[1:-1]))
|
||||||
|
|
||||||
if event.key == pygame.K_c:
|
if event.key == pygame.K_c:
|
||||||
try:
|
try:
|
||||||
pyperclip.copy(navText[currentIndex])
|
pyperclip.copy(navText[currentIndex])
|
||||||
speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
|
speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
|
||||||
except:
|
except:
|
||||||
speech.speak("Failed to copy the text to the clipboard.")
|
speech.speak("Failed to copy the text to the clipboard.")
|
||||||
|
|
||||||
if event.key == pygame.K_t:
|
if event.key == pygame.K_t:
|
||||||
try:
|
try:
|
||||||
# Join with newlines to preserve blank lines in full text
|
# Join with newlines to preserve blank lines in full text
|
||||||
@ -187,6 +187,6 @@ def display_text(text):
|
|||||||
speech.speak("Copied entire message to the clipboard.")
|
speech.speak("Copied entire message to the clipboard.")
|
||||||
except:
|
except:
|
||||||
speech.speak("Failed to copy the text to the clipboard.")
|
speech.speak("Failed to copy the text to the clipboard.")
|
||||||
|
|
||||||
event = pygame.event.clear()
|
event = pygame.event.clear()
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
|
6
input.py
6
input.py
@ -15,11 +15,11 @@ from .speech import speak
|
|||||||
|
|
||||||
def get_input(prompt="Enter text:", text=""):
|
def get_input(prompt="Enter text:", text=""):
|
||||||
"""Display a dialog box for text input.
|
"""Display a dialog box for text input.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prompt (str): Prompt text to display (default: "Enter text:")
|
prompt (str): Prompt text to display (default: "Enter text:")
|
||||||
text (str): Initial text in input box (default: "")
|
text (str): Initial text in input box (default: "")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: User input text, or None if cancelled
|
str: User input text, or None if cancelled
|
||||||
"""
|
"""
|
||||||
@ -66,7 +66,7 @@ def pause_game():
|
|||||||
|
|
||||||
def check_for_exit():
|
def check_for_exit():
|
||||||
"""Check if user has pressed escape key.
|
"""Check if user has pressed escape key.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if escape was pressed, False otherwise
|
bool: True if escape was pressed, False otherwise
|
||||||
"""
|
"""
|
||||||
|
120
menu.py
120
menu.py
@ -27,7 +27,7 @@ from .services import PathService, ConfigService
|
|||||||
|
|
||||||
def game_menu(sounds, playCallback=None, *customOptions):
|
def game_menu(sounds, playCallback=None, *customOptions):
|
||||||
"""Display and handle the main game menu with standard and custom options.
|
"""Display and handle the main game menu with standard and custom options.
|
||||||
|
|
||||||
Standard menu structure:
|
Standard menu structure:
|
||||||
1. Play (always first)
|
1. Play (always first)
|
||||||
2. High Scores
|
2. High Scores
|
||||||
@ -37,53 +37,53 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
|||||||
6. Credits (if available)
|
6. Credits (if available)
|
||||||
7. Donate
|
7. Donate
|
||||||
8. Exit
|
8. Exit
|
||||||
|
|
||||||
Handles navigation with:
|
Handles navigation with:
|
||||||
- Up/Down arrows for selection
|
- Up/Down arrows for selection
|
||||||
- Home/End for first/last option
|
- Home/End for first/last option
|
||||||
- Enter to select
|
- Enter to select
|
||||||
- Escape to exit
|
- Escape to exit
|
||||||
- Volume controls (with Alt modifier)
|
- Volume controls (with Alt modifier)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sounds (dict): Dictionary of sound objects
|
sounds (dict): Dictionary of sound objects
|
||||||
playCallback (function, optional): Callback function for the "play" option.
|
playCallback (function, optional): Callback function for the "play" option.
|
||||||
If None, "play" is returned as a string like other options.
|
If None, "play" is returned as a string like other options.
|
||||||
*customOptions: Additional custom options to include after play but before standard ones
|
*customOptions: Additional custom options to include after play but before standard ones
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Selected menu option or "exit" if user pressed escape
|
str: Selected menu option or "exit" if user pressed escape
|
||||||
"""
|
"""
|
||||||
# Get speech instance
|
# Get speech instance
|
||||||
speech = Speech.get_instance()
|
speech = Speech.get_instance()
|
||||||
|
|
||||||
# Start with Play option
|
# Start with Play option
|
||||||
allOptions = ["play"]
|
allOptions = ["play"]
|
||||||
|
|
||||||
# Add high scores option if scores exist
|
# Add high scores option if scores exist
|
||||||
if Scoreboard.has_high_scores():
|
if Scoreboard.has_high_scores():
|
||||||
allOptions.append("high_scores")
|
allOptions.append("high_scores")
|
||||||
|
|
||||||
# Add custom options (other menu items, etc.)
|
# Add custom options (other menu items, etc.)
|
||||||
allOptions.extend(customOptions)
|
allOptions.extend(customOptions)
|
||||||
|
|
||||||
# Add standard options in preferred order
|
# Add standard options in preferred order
|
||||||
allOptions.append("learn_sounds")
|
allOptions.append("learn_sounds")
|
||||||
|
|
||||||
# Check for instructions file
|
# Check for instructions file
|
||||||
if os.path.isfile('files/instructions.txt'):
|
if os.path.isfile('files/instructions.txt'):
|
||||||
allOptions.append("instructions")
|
allOptions.append("instructions")
|
||||||
|
|
||||||
# Check for credits file
|
# Check for credits file
|
||||||
if os.path.isfile('files/credits.txt'):
|
if os.path.isfile('files/credits.txt'):
|
||||||
allOptions.append("credits")
|
allOptions.append("credits")
|
||||||
|
|
||||||
# Final options
|
# Final options
|
||||||
allOptions.extend(["donate", "exit_game"])
|
allOptions.extend(["donate", "exit_game"])
|
||||||
|
|
||||||
# Track if music was previously playing
|
# Track if music was previously playing
|
||||||
musicWasPlaying = pygame.mixer.music.get_busy()
|
musicWasPlaying = pygame.mixer.music.get_busy()
|
||||||
|
|
||||||
# Only start menu music if no music is currently playing
|
# Only start menu music if no music is currently playing
|
||||||
if not musicWasPlaying:
|
if not musicWasPlaying:
|
||||||
try:
|
try:
|
||||||
@ -91,23 +91,23 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
|||||||
play_bgm("sounds/music_menu.ogg")
|
play_bgm("sounds/music_menu.ogg")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
loop = True
|
loop = True
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
lastSpoken = -1 # Track last spoken index
|
lastSpoken = -1 # Track last spoken index
|
||||||
|
|
||||||
while loop:
|
while loop:
|
||||||
if currentIndex != lastSpoken:
|
if currentIndex != lastSpoken:
|
||||||
speech.speak(allOptions[currentIndex])
|
speech.speak(allOptions[currentIndex])
|
||||||
lastSpoken = currentIndex
|
lastSpoken = currentIndex
|
||||||
|
|
||||||
event = pygame.event.wait()
|
event = pygame.event.wait()
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
# Check for Alt modifier
|
# Check for Alt modifier
|
||||||
mods = pygame.key.get_mods()
|
mods = pygame.key.get_mods()
|
||||||
altPressed = mods & pygame.KMOD_ALT
|
altPressed = mods & pygame.KMOD_ALT
|
||||||
|
|
||||||
# Volume controls (require Alt)
|
# Volume controls (require Alt)
|
||||||
if altPressed:
|
if altPressed:
|
||||||
if event.key == pygame.K_PAGEUP:
|
if event.key == pygame.K_PAGEUP:
|
||||||
@ -169,9 +169,9 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
|||||||
time.sleep(sounds['menu-select'].get_length())
|
time.sleep(sounds['menu-select'].get_length())
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
selectedOption = allOptions[currentIndex]
|
selectedOption = allOptions[currentIndex]
|
||||||
|
|
||||||
# Special case for exit_game with fade
|
# Special case for exit_game with fade
|
||||||
if selectedOption == "exit_game":
|
if selectedOption == "exit_game":
|
||||||
exit_game(500 if pygame.mixer.music.get_busy() else 0)
|
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()
|
pygame.mixer.music.pause()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Handle standard options
|
# Handle standard options
|
||||||
if selectedOption == "instructions":
|
if selectedOption == "instructions":
|
||||||
instructions()
|
instructions()
|
||||||
@ -214,7 +214,7 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
|||||||
Scoreboard.display_high_scores()
|
Scoreboard.display_high_scores()
|
||||||
elif selectedOption == "donate":
|
elif selectedOption == "donate":
|
||||||
donate()
|
donate()
|
||||||
|
|
||||||
# Unpause music after function returns
|
# Unpause music after function returns
|
||||||
try:
|
try:
|
||||||
# Check if music is actually paused before trying to unpause
|
# Check if music is actually paused before trying to unpause
|
||||||
@ -248,105 +248,105 @@ def game_menu(sounds, playCallback=None, *customOptions):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return allOptions[currentIndex]
|
return allOptions[currentIndex]
|
||||||
|
|
||||||
event = pygame.event.clear()
|
event = pygame.event.clear()
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
|
|
||||||
def learn_sounds(sounds):
|
def learn_sounds(sounds):
|
||||||
"""Interactive menu for learning game sounds.
|
"""Interactive menu for learning game sounds.
|
||||||
|
|
||||||
Allows users to:
|
Allows users to:
|
||||||
- Navigate through available sounds with up/down arrows
|
- Navigate through available sounds with up/down arrows
|
||||||
- Navigate between sound categories (folders) using Page Up/Page Down or Left/Right arrows
|
- Navigate between sound categories (folders) using Page Up/Page Down or Left/Right arrows
|
||||||
- Play selected sounds with Enter
|
- Play selected sounds with Enter
|
||||||
- Return to menu with Escape
|
- Return to menu with Escape
|
||||||
|
|
||||||
Excluded sounds:
|
Excluded sounds:
|
||||||
- Files in folders named 'ambience' (at any level)
|
- Files in folders named 'ambience' (at any level)
|
||||||
- Files in any directory starting with '.'
|
- Files in any directory starting with '.'
|
||||||
- Files starting with 'game-intro', 'music_menu', or '_'
|
- Files starting with 'game-intro', 'music_menu', or '_'
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sounds (dict): Dictionary of available sound objects
|
sounds (dict): Dictionary of available sound objects
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: "menu" if user exits with escape
|
str: "menu" if user exits with escape
|
||||||
"""
|
"""
|
||||||
# Get speech instance
|
# Get speech instance
|
||||||
speech = Speech.get_instance()
|
speech = Speech.get_instance()
|
||||||
|
|
||||||
# Define exclusion criteria
|
# Define exclusion criteria
|
||||||
excludedPrefixes = ["game-intro", "music_menu", "_"]
|
excludedPrefixes = ["game-intro", "music_menu", "_"]
|
||||||
excludedDirs = ["ambience", "."]
|
excludedDirs = ["ambience", "."]
|
||||||
|
|
||||||
# Organize sounds by directory
|
# Organize sounds by directory
|
||||||
soundsByDir = {}
|
soundsByDir = {}
|
||||||
|
|
||||||
# Process each sound key in the dictionary
|
# Process each sound key in the dictionary
|
||||||
for soundKey in sounds.keys():
|
for soundKey in sounds.keys():
|
||||||
# Skip if key has any excluded prefix
|
# Skip if key has any excluded prefix
|
||||||
if any(soundKey.lower().startswith(prefix.lower()) for prefix in excludedPrefixes):
|
if any(soundKey.lower().startswith(prefix.lower()) for prefix in excludedPrefixes):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Split key into path parts
|
# Split key into path parts
|
||||||
parts = soundKey.split('/')
|
parts = soundKey.split('/')
|
||||||
|
|
||||||
# Skip if any part of the path is an excluded directory
|
# 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):
|
if any(part.lower() == dirName.lower() or part.startswith('.') for part in parts for dirName in excludedDirs):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Determine the directory
|
# Determine the directory
|
||||||
if '/' in soundKey:
|
if '/' in soundKey:
|
||||||
directory = soundKey.split('/')[0]
|
directory = soundKey.split('/')[0]
|
||||||
else:
|
else:
|
||||||
directory = 'root' # Root directory sounds
|
directory = 'root' # Root directory sounds
|
||||||
|
|
||||||
# Add to sounds by directory
|
# Add to sounds by directory
|
||||||
if directory not in soundsByDir:
|
if directory not in soundsByDir:
|
||||||
soundsByDir[directory] = []
|
soundsByDir[directory] = []
|
||||||
soundsByDir[directory].append(soundKey)
|
soundsByDir[directory].append(soundKey)
|
||||||
|
|
||||||
# Sort each directory's sounds
|
# Sort each directory's sounds
|
||||||
for directory in soundsByDir:
|
for directory in soundsByDir:
|
||||||
soundsByDir[directory].sort()
|
soundsByDir[directory].sort()
|
||||||
|
|
||||||
# If no sounds found, inform the user and return
|
# If no sounds found, inform the user and return
|
||||||
if not soundsByDir:
|
if not soundsByDir:
|
||||||
speech.speak("No sounds available to learn.")
|
speech.speak("No sounds available to learn.")
|
||||||
return "menu"
|
return "menu"
|
||||||
|
|
||||||
# Get list of directories in sorted order
|
# Get list of directories in sorted order
|
||||||
directories = sorted(soundsByDir.keys())
|
directories = sorted(soundsByDir.keys())
|
||||||
|
|
||||||
# Start with first directory
|
# Start with first directory
|
||||||
currentDirIndex = 0
|
currentDirIndex = 0
|
||||||
currentDir = directories[currentDirIndex]
|
currentDir = directories[currentDirIndex]
|
||||||
currentSoundKeys = soundsByDir[currentDir]
|
currentSoundKeys = soundsByDir[currentDir]
|
||||||
currentSoundIndex = 0
|
currentSoundIndex = 0
|
||||||
|
|
||||||
# Display appropriate message based on number of directories
|
# Display appropriate message based on number of directories
|
||||||
if len(directories) > 1:
|
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.")
|
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
|
# Track last spoken to avoid repetition
|
||||||
lastSpoken = -1
|
lastSpoken = -1
|
||||||
directoryChanged = True # Flag to track if directory just changed
|
directoryChanged = True # Flag to track if directory just changed
|
||||||
|
|
||||||
# Flag to track when to exit the loop
|
# Flag to track when to exit the loop
|
||||||
returnToMenu = False
|
returnToMenu = False
|
||||||
|
|
||||||
while not returnToMenu:
|
while not returnToMenu:
|
||||||
# Announce current sound
|
# Announce current sound
|
||||||
if currentSoundIndex != lastSpoken:
|
if currentSoundIndex != lastSpoken:
|
||||||
totalSounds = len(currentSoundKeys)
|
totalSounds = len(currentSoundKeys)
|
||||||
soundName = currentSoundKeys[currentSoundIndex]
|
soundName = currentSoundKeys[currentSoundIndex]
|
||||||
|
|
||||||
# Remove directory prefix if present
|
# Remove directory prefix if present
|
||||||
if '/' in soundName:
|
if '/' in soundName:
|
||||||
displayName = '/'.join(soundName.split('/')[1:])
|
displayName = '/'.join(soundName.split('/')[1:])
|
||||||
else:
|
else:
|
||||||
displayName = soundName
|
displayName = soundName
|
||||||
|
|
||||||
# If directory just changed, include directory name in announcement
|
# If directory just changed, include directory name in announcement
|
||||||
if directoryChanged:
|
if directoryChanged:
|
||||||
dirDescription = "Root directory" if currentDir == 'root' else currentDir
|
dirDescription = "Root directory" if currentDir == 'root' else currentDir
|
||||||
@ -354,24 +354,24 @@ def learn_sounds(sounds):
|
|||||||
directoryChanged = False # Reset flag after announcement
|
directoryChanged = False # Reset flag after announcement
|
||||||
else:
|
else:
|
||||||
announcement = f"{displayName}, {currentSoundIndex + 1} of {totalSounds}"
|
announcement = f"{displayName}, {currentSoundIndex + 1} of {totalSounds}"
|
||||||
|
|
||||||
speech.speak(announcement)
|
speech.speak(announcement)
|
||||||
lastSpoken = currentSoundIndex
|
lastSpoken = currentSoundIndex
|
||||||
|
|
||||||
event = pygame.event.wait()
|
event = pygame.event.wait()
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
if event.key == pygame.K_ESCAPE:
|
if event.key == pygame.K_ESCAPE:
|
||||||
returnToMenu = True
|
returnToMenu = True
|
||||||
|
|
||||||
# Sound navigation
|
# Sound navigation
|
||||||
elif event.key in [pygame.K_DOWN, pygame.K_s] and currentSoundIndex < len(currentSoundKeys) - 1:
|
elif event.key in [pygame.K_DOWN, pygame.K_s] and currentSoundIndex < len(currentSoundKeys) - 1:
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
currentSoundIndex += 1
|
currentSoundIndex += 1
|
||||||
|
|
||||||
elif event.key in [pygame.K_UP, pygame.K_w] and currentSoundIndex > 0:
|
elif event.key in [pygame.K_UP, pygame.K_w] and currentSoundIndex > 0:
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
currentSoundIndex -= 1
|
currentSoundIndex -= 1
|
||||||
|
|
||||||
# Directory navigation
|
# Directory navigation
|
||||||
elif event.key in [pygame.K_PAGEDOWN, pygame.K_RIGHT] and currentDirIndex < len(directories) - 1:
|
elif event.key in [pygame.K_PAGEDOWN, pygame.K_RIGHT] and currentDirIndex < len(directories) - 1:
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
@ -381,7 +381,7 @@ def learn_sounds(sounds):
|
|||||||
currentSoundIndex = 0
|
currentSoundIndex = 0
|
||||||
directoryChanged = True # Set flag on directory change
|
directoryChanged = True # Set flag on directory change
|
||||||
lastSpoken = -1 # Force announcement
|
lastSpoken = -1 # Force announcement
|
||||||
|
|
||||||
elif event.key in [pygame.K_PAGEUP, pygame.K_LEFT] and currentDirIndex > 0:
|
elif event.key in [pygame.K_PAGEUP, pygame.K_LEFT] and currentDirIndex > 0:
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
currentDirIndex -= 1
|
currentDirIndex -= 1
|
||||||
@ -390,7 +390,7 @@ def learn_sounds(sounds):
|
|||||||
currentSoundIndex = 0
|
currentSoundIndex = 0
|
||||||
directoryChanged = True # Set flag on directory change
|
directoryChanged = True # Set flag on directory change
|
||||||
lastSpoken = -1 # Force announcement
|
lastSpoken = -1 # Force announcement
|
||||||
|
|
||||||
# Play sound
|
# Play sound
|
||||||
elif event.key == pygame.K_RETURN:
|
elif event.key == pygame.K_RETURN:
|
||||||
try:
|
try:
|
||||||
@ -400,16 +400,16 @@ def learn_sounds(sounds):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error playing sound: {e}")
|
print(f"Error playing sound: {e}")
|
||||||
speech.speak("Could not play sound.")
|
speech.speak("Could not play sound.")
|
||||||
|
|
||||||
event = pygame.event.clear()
|
event = pygame.event.clear()
|
||||||
pygame.event.pump() # Process pygame's internal events
|
pygame.event.pump() # Process pygame's internal events
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
|
|
||||||
return "menu"
|
return "menu"
|
||||||
|
|
||||||
def instructions():
|
def instructions():
|
||||||
"""Display game instructions from file.
|
"""Display game instructions from file.
|
||||||
|
|
||||||
Reads and displays instructions from 'files/instructions.txt'.
|
Reads and displays instructions from 'files/instructions.txt'.
|
||||||
If file is missing, displays an error message.
|
If file is missing, displays an error message.
|
||||||
"""
|
"""
|
||||||
@ -441,7 +441,7 @@ def credits():
|
|||||||
|
|
||||||
def donate():
|
def donate():
|
||||||
"""Open the donation webpage.
|
"""Open the donation webpage.
|
||||||
|
|
||||||
Opens the Ko-fi donation page.
|
Opens the Ko-fi donation page.
|
||||||
"""
|
"""
|
||||||
webbrowser.open('https://ko-fi.com/stormux')
|
webbrowser.open('https://ko-fi.com/stormux')
|
||||||
@ -449,20 +449,20 @@ def donate():
|
|||||||
|
|
||||||
def exit_game(fade=0):
|
def exit_game(fade=0):
|
||||||
"""Clean up and exit the game properly.
|
"""Clean up and exit the game properly.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
fade (int): Milliseconds to fade out music before exiting.
|
fade (int): Milliseconds to fade out music before exiting.
|
||||||
0 means stop immediately (default)
|
0 means stop immediately (default)
|
||||||
"""
|
"""
|
||||||
# Force clear any pending events to prevent hanging
|
# Force clear any pending events to prevent hanging
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
|
|
||||||
# Stop all mixer channels first
|
# Stop all mixer channels first
|
||||||
try:
|
try:
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not stop mixer channels: {e}")
|
print(f"Warning: Could not stop mixer channels: {e}")
|
||||||
|
|
||||||
# Get speech instance and handle all providers
|
# Get speech instance and handle all providers
|
||||||
try:
|
try:
|
||||||
speech = Speech.get_instance()
|
speech = Speech.get_instance()
|
||||||
@ -473,7 +473,7 @@ def exit_game(fade=0):
|
|||||||
print(f"Warning: Could not close speech: {e}")
|
print(f"Warning: Could not close speech: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not get speech instance: {e}")
|
print(f"Warning: Could not get speech instance: {e}")
|
||||||
|
|
||||||
# Handle music based on fade parameter
|
# Handle music based on fade parameter
|
||||||
try:
|
try:
|
||||||
if fade > 0 and pygame.mixer.music.get_busy():
|
if fade > 0 and pygame.mixer.music.get_busy():
|
||||||
@ -484,13 +484,13 @@ def exit_game(fade=0):
|
|||||||
pygame.mixer.music.stop()
|
pygame.mixer.music.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not handle music during exit: {e}")
|
print(f"Warning: Could not handle music during exit: {e}")
|
||||||
|
|
||||||
# Clean up pygame
|
# Clean up pygame
|
||||||
try:
|
try:
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Error during pygame.quit(): {e}")
|
print(f"Warning: Error during pygame.quit(): {e}")
|
||||||
|
|
||||||
# Use os._exit for immediate termination
|
# Use os._exit for immediate termination
|
||||||
import os
|
import os
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
108
scoreboard.py
108
scoreboard.py
@ -18,10 +18,10 @@ from .config import localConfig, write_config, read_config
|
|||||||
|
|
||||||
class Scoreboard:
|
class Scoreboard:
|
||||||
"""Handles high score tracking with player names."""
|
"""Handles high score tracking with player names."""
|
||||||
|
|
||||||
def __init__(self, score=0, configService=None, speech=None):
|
def __init__(self, score=0, configService=None, speech=None):
|
||||||
"""Initialize scoreboard.
|
"""Initialize scoreboard.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
score (int): Initial score (default: 0)
|
score (int): Initial score (default: 0)
|
||||||
configService (ConfigService): Config service (default: global instance)
|
configService (ConfigService): Config service (default: global instance)
|
||||||
@ -29,15 +29,15 @@ class Scoreboard:
|
|||||||
"""
|
"""
|
||||||
# Ensure services are properly initialized
|
# Ensure services are properly initialized
|
||||||
self._ensure_services()
|
self._ensure_services()
|
||||||
|
|
||||||
self.configService = configService or ConfigService.get_instance()
|
self.configService = configService or ConfigService.get_instance()
|
||||||
self.speech = speech or Speech.get_instance()
|
self.speech = speech or Speech.get_instance()
|
||||||
self.currentScore = score
|
self.currentScore = score
|
||||||
self.highScores = []
|
self.highScores = []
|
||||||
|
|
||||||
# For backward compatibility
|
# For backward compatibility
|
||||||
read_config()
|
read_config()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to use configService
|
# Try to use configService
|
||||||
self.configService.localConfig.add_section("scoreboard")
|
self.configService.localConfig.add_section("scoreboard")
|
||||||
@ -47,7 +47,7 @@ class Scoreboard:
|
|||||||
localConfig.add_section("scoreboard")
|
localConfig.add_section("scoreboard")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Load existing high scores
|
# Load existing high scores
|
||||||
for i in range(1, 11):
|
for i in range(1, 11):
|
||||||
try:
|
try:
|
||||||
@ -72,15 +72,15 @@ class Scoreboard:
|
|||||||
'name': "Player",
|
'name': "Player",
|
||||||
'score': 0
|
'score': 0
|
||||||
})
|
})
|
||||||
|
|
||||||
# Sort high scores by score value in descending order
|
# Sort high scores by score value in descending order
|
||||||
self.highScores.sort(key=lambda x: x['score'], reverse=True)
|
self.highScores.sort(key=lambda x: x['score'], reverse=True)
|
||||||
|
|
||||||
def _ensure_services(self):
|
def _ensure_services(self):
|
||||||
"""Ensure PathService and ConfigService are properly initialized."""
|
"""Ensure PathService and ConfigService are properly initialized."""
|
||||||
# Get PathService and make sure it has a game name
|
# Get PathService and make sure it has a game name
|
||||||
pathService = PathService.get_instance()
|
pathService = PathService.get_instance()
|
||||||
|
|
||||||
# If no game name yet, try to get from pygame window title
|
# If no game name yet, try to get from pygame window title
|
||||||
if not pathService.gameName:
|
if not pathService.gameName:
|
||||||
try:
|
try:
|
||||||
@ -89,55 +89,55 @@ class Scoreboard:
|
|||||||
pathService.gameName = pygame.display.get_caption()[0]
|
pathService.gameName = pygame.display.get_caption()[0]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Initialize path service if we have a game name but no paths set up
|
# Initialize path service if we have a game name but no paths set up
|
||||||
if pathService.gameName and not pathService.gamePath:
|
if pathService.gameName and not pathService.gamePath:
|
||||||
pathService.initialize(pathService.gameName)
|
pathService.initialize(pathService.gameName)
|
||||||
|
|
||||||
# Get ConfigService and connect to PathService
|
# Get ConfigService and connect to PathService
|
||||||
configService = ConfigService.get_instance()
|
configService = ConfigService.get_instance()
|
||||||
if not hasattr(configService, 'pathService') or not configService.pathService:
|
if not hasattr(configService, 'pathService') or not configService.pathService:
|
||||||
if pathService.gameName:
|
if pathService.gameName:
|
||||||
configService.set_game_info(pathService.gameName, pathService)
|
configService.set_game_info(pathService.gameName, pathService)
|
||||||
|
|
||||||
# Ensure the game directory exists
|
# Ensure the game directory exists
|
||||||
if pathService.gamePath and not os.path.exists(pathService.gamePath):
|
if pathService.gamePath and not os.path.exists(pathService.gamePath):
|
||||||
try:
|
try:
|
||||||
os.makedirs(pathService.gamePath)
|
os.makedirs(pathService.gamePath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error creating game directory: {e}")
|
print(f"Error creating game directory: {e}")
|
||||||
|
|
||||||
def get_score(self):
|
def get_score(self):
|
||||||
"""Get current score."""
|
"""Get current score."""
|
||||||
return self.currentScore
|
return self.currentScore
|
||||||
|
|
||||||
def get_high_scores(self):
|
def get_high_scores(self):
|
||||||
"""Get list of high scores."""
|
"""Get list of high scores."""
|
||||||
return self.highScores
|
return self.highScores
|
||||||
|
|
||||||
def decrease_score(self, points=1):
|
def decrease_score(self, points=1):
|
||||||
"""Decrease the current score."""
|
"""Decrease the current score."""
|
||||||
self.currentScore -= int(points)
|
self.currentScore -= int(points)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def increase_score(self, points=1):
|
def increase_score(self, points=1):
|
||||||
"""Increase the current score."""
|
"""Increase the current score."""
|
||||||
self.currentScore += int(points)
|
self.currentScore += int(points)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_score(self, score):
|
def set_score(self, score):
|
||||||
"""Set the current score to a specific value."""
|
"""Set the current score to a specific value."""
|
||||||
self.currentScore = int(score)
|
self.currentScore = int(score)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def reset_score(self):
|
def reset_score(self):
|
||||||
"""Reset the current score to zero."""
|
"""Reset the current score to zero."""
|
||||||
self.currentScore = 0
|
self.currentScore = 0
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def check_high_score(self):
|
def check_high_score(self):
|
||||||
"""Check if current score qualifies as a high score.
|
"""Check if current score qualifies as a high score.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Position (1-10) if high score, None if not
|
int: Position (1-10) if high score, None if not
|
||||||
"""
|
"""
|
||||||
@ -145,23 +145,23 @@ class Scoreboard:
|
|||||||
if self.currentScore > entry['score']:
|
if self.currentScore > entry['score']:
|
||||||
return i + 1
|
return i + 1
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_high_score(self, name=None):
|
def add_high_score(self, name=None):
|
||||||
"""Add current score to high scores if it qualifies.
|
"""Add current score to high scores if it qualifies.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): Player name (if None, will prompt user)
|
name (str): Player name (if None, will prompt user)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if score was added, False if not
|
bool: True if score was added, False if not
|
||||||
"""
|
"""
|
||||||
# Ensure services are properly set up
|
# Ensure services are properly set up
|
||||||
self._ensure_services()
|
self._ensure_services()
|
||||||
|
|
||||||
position = self.check_high_score()
|
position = self.check_high_score()
|
||||||
if position is None:
|
if position is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get player name
|
# Get player name
|
||||||
if name is None:
|
if name is None:
|
||||||
# Import get_input here to avoid circular imports
|
# Import get_input here to avoid circular imports
|
||||||
@ -169,23 +169,23 @@ class Scoreboard:
|
|||||||
name = get_input("New high score! Enter your name:", "Player")
|
name = get_input("New high score! Enter your name:", "Player")
|
||||||
if name is None: # User cancelled
|
if name is None: # User cancelled
|
||||||
name = "Player"
|
name = "Player"
|
||||||
|
|
||||||
# Insert new score at correct position
|
# Insert new score at correct position
|
||||||
self.highScores.insert(position - 1, {
|
self.highScores.insert(position - 1, {
|
||||||
'name': name,
|
'name': name,
|
||||||
'score': self.currentScore
|
'score': self.currentScore
|
||||||
})
|
})
|
||||||
|
|
||||||
# Keep only top 10
|
# Keep only top 10
|
||||||
self.highScores = self.highScores[:10]
|
self.highScores = self.highScores[:10]
|
||||||
|
|
||||||
# Save to config - try both methods for maximum compatibility
|
# Save to config - try both methods for maximum compatibility
|
||||||
try:
|
try:
|
||||||
# Try new method first
|
# Try new method first
|
||||||
for i, entry in enumerate(self.highScores):
|
for i, entry in enumerate(self.highScores):
|
||||||
self.configService.localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
|
self.configService.localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
|
||||||
self.configService.localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
|
self.configService.localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
|
||||||
|
|
||||||
# Try to write with configService
|
# Try to write with configService
|
||||||
try:
|
try:
|
||||||
self.configService.write_local_config()
|
self.configService.write_local_config()
|
||||||
@ -196,7 +196,7 @@ class Scoreboard:
|
|||||||
localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
|
localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
|
||||||
localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
|
localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
|
||||||
write_config()
|
write_config()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error writing high scores: {e}")
|
print(f"Error writing high scores: {e}")
|
||||||
# If all else fails, try direct old method
|
# If all else fails, try direct old method
|
||||||
@ -204,7 +204,7 @@ class Scoreboard:
|
|||||||
localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
|
localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
|
||||||
localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
|
localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
|
||||||
write_config()
|
write_config()
|
||||||
|
|
||||||
# Announce success
|
# Announce success
|
||||||
try:
|
try:
|
||||||
self.speech.messagebox(f"Congratulations {name}! You got position {position} on the scoreboard!")
|
self.speech.messagebox(f"Congratulations {name}! You got position {position} on the scoreboard!")
|
||||||
@ -212,14 +212,14 @@ class Scoreboard:
|
|||||||
# Fallback to global speak function
|
# Fallback to global speak function
|
||||||
from .speech import speak
|
from .speech import speak
|
||||||
speak(f"Congratulations {name}! You got position {position} on the scoreboard!")
|
speak(f"Congratulations {name}! You got position {position} on the scoreboard!")
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_high_scores():
|
def has_high_scores():
|
||||||
"""Check if the current game has any high scores.
|
"""Check if the current game has any high scores.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if at least one high score exists, False otherwise
|
bool: True if at least one high score exists, False otherwise
|
||||||
"""
|
"""
|
||||||
@ -227,7 +227,7 @@ class Scoreboard:
|
|||||||
# Get PathService to access game name
|
# Get PathService to access game name
|
||||||
pathService = PathService.get_instance()
|
pathService = PathService.get_instance()
|
||||||
gameName = pathService.gameName
|
gameName = pathService.gameName
|
||||||
|
|
||||||
# If no game name, try to get from window title
|
# If no game name, try to get from window title
|
||||||
if not gameName:
|
if not gameName:
|
||||||
try:
|
try:
|
||||||
@ -237,31 +237,31 @@ class Scoreboard:
|
|||||||
pathService.gameName = gameName
|
pathService.gameName = gameName
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Ensure path service is properly initialized
|
# Ensure path service is properly initialized
|
||||||
if gameName and not pathService.gamePath:
|
if gameName and not pathService.gamePath:
|
||||||
pathService.initialize(gameName)
|
pathService.initialize(gameName)
|
||||||
|
|
||||||
# Get the config file path
|
# Get the config file path
|
||||||
configPath = os.path.join(pathService.gamePath, "config.ini")
|
configPath = os.path.join(pathService.gamePath, "config.ini")
|
||||||
|
|
||||||
# If config file doesn't exist, there are no scores
|
# If config file doesn't exist, there are no scores
|
||||||
if not os.path.exists(configPath):
|
if not os.path.exists(configPath):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Ensure config service is properly connected to path service
|
# Ensure config service is properly connected to path service
|
||||||
configService = ConfigService.get_instance()
|
configService = ConfigService.get_instance()
|
||||||
configService.set_game_info(gameName, pathService)
|
configService.set_game_info(gameName, pathService)
|
||||||
|
|
||||||
# Create scoreboard using the properly initialized services
|
# Create scoreboard using the properly initialized services
|
||||||
board = Scoreboard(0, configService)
|
board = Scoreboard(0, configService)
|
||||||
|
|
||||||
# Force a read of local config to ensure fresh data
|
# Force a read of local config to ensure fresh data
|
||||||
configService.read_local_config()
|
configService.read_local_config()
|
||||||
|
|
||||||
# Get high scores
|
# Get high scores
|
||||||
scores = board.get_high_scores()
|
scores = board.get_high_scores()
|
||||||
|
|
||||||
# Check if any score is greater than zero
|
# Check if any score is greater than zero
|
||||||
return any(score['score'] > 0 for score in scores)
|
return any(score['score'] > 0 for score in scores)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -271,7 +271,7 @@ class Scoreboard:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def display_high_scores():
|
def display_high_scores():
|
||||||
"""Display high scores for the current game.
|
"""Display high scores for the current game.
|
||||||
|
|
||||||
Reads the high scores from Scoreboard class.
|
Reads the high scores from Scoreboard class.
|
||||||
Shows the game name at the top followed by the available scores.
|
Shows the game name at the top followed by the available scores.
|
||||||
"""
|
"""
|
||||||
@ -279,7 +279,7 @@ class Scoreboard:
|
|||||||
# Get PathService to access game name
|
# Get PathService to access game name
|
||||||
pathService = PathService.get_instance()
|
pathService = PathService.get_instance()
|
||||||
gameName = pathService.gameName
|
gameName = pathService.gameName
|
||||||
|
|
||||||
# If no game name, try to get from window title
|
# If no game name, try to get from window title
|
||||||
if not gameName:
|
if not gameName:
|
||||||
try:
|
try:
|
||||||
@ -289,30 +289,30 @@ class Scoreboard:
|
|||||||
pathService.gameName = gameName
|
pathService.gameName = gameName
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Ensure path service is properly initialized
|
# Ensure path service is properly initialized
|
||||||
if gameName and not pathService.gamePath:
|
if gameName and not pathService.gamePath:
|
||||||
pathService.initialize(gameName)
|
pathService.initialize(gameName)
|
||||||
|
|
||||||
# Ensure config service is properly connected to path service
|
# Ensure config service is properly connected to path service
|
||||||
configService = ConfigService.get_instance()
|
configService = ConfigService.get_instance()
|
||||||
configService.set_game_info(gameName, pathService)
|
configService.set_game_info(gameName, pathService)
|
||||||
|
|
||||||
# Create scoreboard using the properly initialized services
|
# Create scoreboard using the properly initialized services
|
||||||
board = Scoreboard(0, configService)
|
board = Scoreboard(0, configService)
|
||||||
|
|
||||||
# Force a read of local config to ensure fresh data
|
# Force a read of local config to ensure fresh data
|
||||||
configService.read_local_config()
|
configService.read_local_config()
|
||||||
|
|
||||||
# Get high scores
|
# Get high scores
|
||||||
scores = board.get_high_scores()
|
scores = board.get_high_scores()
|
||||||
|
|
||||||
# Filter out scores with zero points
|
# Filter out scores with zero points
|
||||||
validScores = [score for score in scores if score['score'] > 0]
|
validScores = [score for score in scores if score['score'] > 0]
|
||||||
|
|
||||||
# Prepare the lines to display
|
# Prepare the lines to display
|
||||||
lines = [f"High Scores for {gameName}:"]
|
lines = [f"High Scores for {gameName}:"]
|
||||||
|
|
||||||
# Add scores to the display list
|
# Add scores to the display list
|
||||||
if validScores:
|
if validScores:
|
||||||
for i, entry in enumerate(validScores, 1):
|
for i, entry in enumerate(validScores, 1):
|
||||||
@ -320,13 +320,13 @@ class Scoreboard:
|
|||||||
lines.append(scoreStr)
|
lines.append(scoreStr)
|
||||||
else:
|
else:
|
||||||
lines.append("No high scores yet.")
|
lines.append("No high scores yet.")
|
||||||
|
|
||||||
# Display the high scores
|
# Display the high scores
|
||||||
display_text(lines)
|
display_text(lines)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error displaying high scores: {e}")
|
print(f"Error displaying high scores: {e}")
|
||||||
info = ["Could not display high scores."]
|
info = ["Could not display high scores."]
|
||||||
display_text(info)
|
display_text(info)
|
||||||
|
|
||||||
# For backward compatibility with older code that might call displayHigh_scores
|
# For backward compatibility with older code that might call displayHigh_scores
|
||||||
displayHigh_scores = display_high_scores
|
displayHigh_scores = display_high_scores
|
||||||
|
72
services.py
72
services.py
@ -17,37 +17,37 @@ from .config import gamePath, globalPath, write_config, read_config
|
|||||||
|
|
||||||
class ConfigService:
|
class ConfigService:
|
||||||
"""Configuration management service."""
|
"""Configuration management service."""
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
"""Get or create the singleton instance."""
|
"""Get or create the singleton instance."""
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = ConfigService()
|
cls._instance = ConfigService()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize configuration parsers."""
|
"""Initialize configuration parsers."""
|
||||||
self.localConfig = configparser.ConfigParser()
|
self.localConfig = configparser.ConfigParser()
|
||||||
self.globalConfig = configparser.ConfigParser()
|
self.globalConfig = configparser.ConfigParser()
|
||||||
self.gameTitle = None
|
self.gameTitle = None
|
||||||
self.pathService = None
|
self.pathService = None
|
||||||
|
|
||||||
def set_game_info(self, gameTitle, pathService):
|
def set_game_info(self, gameTitle, pathService):
|
||||||
"""Set game information and initialize configs.
|
"""Set game information and initialize configs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gameTitle (str): Title of the game
|
gameTitle (str): Title of the game
|
||||||
pathService (PathService): Path service instance
|
pathService (PathService): Path service instance
|
||||||
"""
|
"""
|
||||||
self.gameTitle = gameTitle
|
self.gameTitle = gameTitle
|
||||||
self.pathService = pathService
|
self.pathService = pathService
|
||||||
|
|
||||||
# Load existing configurations
|
# Load existing configurations
|
||||||
self.read_local_config()
|
self.read_local_config()
|
||||||
self.read_global_config()
|
self.read_global_config()
|
||||||
|
|
||||||
def read_local_config(self):
|
def read_local_config(self):
|
||||||
"""Read local configuration from file."""
|
"""Read local configuration from file."""
|
||||||
try:
|
try:
|
||||||
@ -66,7 +66,7 @@ class ConfigService:
|
|||||||
self.localConfig.read_dict(globals().get('localConfig', {}))
|
self.localConfig.read_dict(globals().get('localConfig', {}))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def read_global_config(self):
|
def read_global_config(self):
|
||||||
"""Read global configuration from file."""
|
"""Read global configuration from file."""
|
||||||
try:
|
try:
|
||||||
@ -85,7 +85,7 @@ class ConfigService:
|
|||||||
self.globalConfig.read_dict(globals().get('globalConfig', {}))
|
self.globalConfig.read_dict(globals().get('globalConfig', {}))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def write_local_config(self):
|
def write_local_config(self):
|
||||||
"""Write local configuration to file."""
|
"""Write local configuration to file."""
|
||||||
try:
|
try:
|
||||||
@ -104,7 +104,7 @@ class ConfigService:
|
|||||||
write_config(False)
|
write_config(False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Failed to write local config: {e}")
|
print(f"Warning: Failed to write local config: {e}")
|
||||||
|
|
||||||
def write_global_config(self):
|
def write_global_config(self):
|
||||||
"""Write global configuration to file."""
|
"""Write global configuration to file."""
|
||||||
try:
|
try:
|
||||||
@ -127,37 +127,37 @@ class ConfigService:
|
|||||||
|
|
||||||
class VolumeService:
|
class VolumeService:
|
||||||
"""Volume management service."""
|
"""Volume management service."""
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
"""Get or create the singleton instance."""
|
"""Get or create the singleton instance."""
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = VolumeService()
|
cls._instance = VolumeService()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize volume settings."""
|
"""Initialize volume settings."""
|
||||||
self.bgmVolume = 0.75 # Default background music volume
|
self.bgmVolume = 0.75 # Default background music volume
|
||||||
self.sfxVolume = 1.0 # Default sound effects volume
|
self.sfxVolume = 1.0 # Default sound effects volume
|
||||||
self.masterVolume = 1.0 # Default master volume
|
self.masterVolume = 1.0 # Default master volume
|
||||||
|
|
||||||
def adjust_master_volume(self, change, pygameMixer=None):
|
def adjust_master_volume(self, change, pygameMixer=None):
|
||||||
"""Adjust the master volume for all sounds.
|
"""Adjust the master volume for all sounds.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
change (float): Amount to change volume by (positive or negative)
|
change (float): Amount to change volume by (positive or negative)
|
||||||
pygameMixer: Optional pygame.mixer module for real-time updates
|
pygameMixer: Optional pygame.mixer module for real-time updates
|
||||||
"""
|
"""
|
||||||
self.masterVolume = max(0.0, min(1.0, self.masterVolume + change))
|
self.masterVolume = max(0.0, min(1.0, self.masterVolume + change))
|
||||||
|
|
||||||
# Update real-time audio if pygame mixer is provided
|
# Update real-time audio if pygame mixer is provided
|
||||||
if pygameMixer:
|
if pygameMixer:
|
||||||
# Update music volume
|
# Update music volume
|
||||||
if pygameMixer.music.get_busy():
|
if pygameMixer.music.get_busy():
|
||||||
pygameMixer.music.set_volume(self.bgmVolume * self.masterVolume)
|
pygameMixer.music.set_volume(self.bgmVolume * self.masterVolume)
|
||||||
|
|
||||||
# Update all sound channels
|
# Update all sound channels
|
||||||
for i in range(pygameMixer.get_num_channels()):
|
for i in range(pygameMixer.get_num_channels()):
|
||||||
channel = pygameMixer.Channel(i)
|
channel = pygameMixer.Channel(i)
|
||||||
@ -170,29 +170,29 @@ class VolumeService:
|
|||||||
# Stereo audio
|
# Stereo audio
|
||||||
left, right = currentVolume
|
left, right = currentVolume
|
||||||
channel.set_volume(left * self.masterVolume, right * self.masterVolume)
|
channel.set_volume(left * self.masterVolume, right * self.masterVolume)
|
||||||
|
|
||||||
def adjust_bgm_volume(self, change, pygameMixer=None):
|
def adjust_bgm_volume(self, change, pygameMixer=None):
|
||||||
"""Adjust only the background music volume.
|
"""Adjust only the background music volume.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
change (float): Amount to change volume by (positive or negative)
|
change (float): Amount to change volume by (positive or negative)
|
||||||
pygameMixer: Optional pygame.mixer module for real-time updates
|
pygameMixer: Optional pygame.mixer module for real-time updates
|
||||||
"""
|
"""
|
||||||
self.bgmVolume = max(0.0, min(1.0, self.bgmVolume + change))
|
self.bgmVolume = max(0.0, min(1.0, self.bgmVolume + change))
|
||||||
|
|
||||||
# Update real-time audio if pygame mixer is provided
|
# Update real-time audio if pygame mixer is provided
|
||||||
if pygameMixer and pygameMixer.music.get_busy():
|
if pygameMixer and pygameMixer.music.get_busy():
|
||||||
pygameMixer.music.set_volume(self.bgmVolume * self.masterVolume)
|
pygameMixer.music.set_volume(self.bgmVolume * self.masterVolume)
|
||||||
|
|
||||||
def adjust_sfx_volume(self, change, pygameMixer=None):
|
def adjust_sfx_volume(self, change, pygameMixer=None):
|
||||||
"""Adjust volume for sound effects only.
|
"""Adjust volume for sound effects only.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
change (float): Amount to change volume by (positive or negative)
|
change (float): Amount to change volume by (positive or negative)
|
||||||
pygameMixer: Optional pygame.mixer module for real-time updates
|
pygameMixer: Optional pygame.mixer module for real-time updates
|
||||||
"""
|
"""
|
||||||
self.sfxVolume = max(0.0, min(1.0, self.sfxVolume + change))
|
self.sfxVolume = max(0.0, min(1.0, self.sfxVolume + change))
|
||||||
|
|
||||||
# Update real-time audio if pygame mixer is provided
|
# Update real-time audio if pygame mixer is provided
|
||||||
if pygameMixer:
|
if pygameMixer:
|
||||||
# Update all sound channels except reserved ones
|
# Update all sound channels except reserved ones
|
||||||
@ -208,18 +208,18 @@ class VolumeService:
|
|||||||
left, right = currentVolume
|
left, right = currentVolume
|
||||||
channel.set_volume(left * self.sfxVolume * self.masterVolume,
|
channel.set_volume(left * self.sfxVolume * self.masterVolume,
|
||||||
right * self.sfxVolume * self.masterVolume)
|
right * self.sfxVolume * self.masterVolume)
|
||||||
|
|
||||||
def get_bgm_volume(self):
|
def get_bgm_volume(self):
|
||||||
"""Get the current BGM volume with master adjustment.
|
"""Get the current BGM volume with master adjustment.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Current adjusted BGM volume
|
float: Current adjusted BGM volume
|
||||||
"""
|
"""
|
||||||
return self.bgmVolume * self.masterVolume
|
return self.bgmVolume * self.masterVolume
|
||||||
|
|
||||||
def get_sfx_volume(self):
|
def get_sfx_volume(self):
|
||||||
"""Get the current SFX volume with master adjustment.
|
"""Get the current SFX volume with master adjustment.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Current adjusted SFX volume
|
float: Current adjusted SFX volume
|
||||||
"""
|
"""
|
||||||
@ -228,32 +228,32 @@ class VolumeService:
|
|||||||
|
|
||||||
class PathService:
|
class PathService:
|
||||||
"""Path management service."""
|
"""Path management service."""
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
"""Get or create the singleton instance."""
|
"""Get or create the singleton instance."""
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = PathService()
|
cls._instance = PathService()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize path variables."""
|
"""Initialize path variables."""
|
||||||
self.globalPath = None
|
self.globalPath = None
|
||||||
self.gamePath = None
|
self.gamePath = None
|
||||||
self.gameName = None
|
self.gameName = None
|
||||||
|
|
||||||
# Try to initialize from global variables for backward compatibility
|
# Try to initialize from global variables for backward compatibility
|
||||||
global gamePath, globalPath
|
global gamePath, globalPath
|
||||||
if gamePath:
|
if gamePath:
|
||||||
self.gamePath = gamePath
|
self.gamePath = gamePath
|
||||||
if globalPath:
|
if globalPath:
|
||||||
self.globalPath = globalPath
|
self.globalPath = globalPath
|
||||||
|
|
||||||
def initialize(self, gameTitle):
|
def initialize(self, gameTitle):
|
||||||
"""Initialize paths for a game.
|
"""Initialize paths for a game.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gameTitle (str): Title of the game
|
gameTitle (str): Title of the game
|
||||||
"""
|
"""
|
||||||
@ -261,14 +261,14 @@ class PathService:
|
|||||||
self.globalPath = os.path.join(BaseDirectory.xdg_config_home, "storm-games")
|
self.globalPath = os.path.join(BaseDirectory.xdg_config_home, "storm-games")
|
||||||
self.gamePath = os.path.join(self.globalPath,
|
self.gamePath = os.path.join(self.globalPath,
|
||||||
str.lower(str.replace(gameTitle, " ", "-")))
|
str.lower(str.replace(gameTitle, " ", "-")))
|
||||||
|
|
||||||
# Create game directory if it doesn't exist
|
# Create game directory if it doesn't exist
|
||||||
if not os.path.exists(self.gamePath):
|
if not os.path.exists(self.gamePath):
|
||||||
os.makedirs(self.gamePath)
|
os.makedirs(self.gamePath)
|
||||||
|
|
||||||
# Update global variables for backward compatibility
|
# Update global variables for backward compatibility
|
||||||
global gamePath, globalPath
|
global gamePath, globalPath
|
||||||
gamePath = self.gamePath
|
gamePath = self.gamePath
|
||||||
globalPath = self.globalPath
|
globalPath = self.globalPath
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
34
speech.py
34
speech.py
@ -15,26 +15,26 @@ from sys import exit
|
|||||||
|
|
||||||
class Speech:
|
class Speech:
|
||||||
"""Handles text-to-speech functionality."""
|
"""Handles text-to-speech functionality."""
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
"""Get or create the singleton instance."""
|
"""Get or create the singleton instance."""
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = Speech()
|
cls._instance = Speech()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize speech system with available provider."""
|
"""Initialize speech system with available provider."""
|
||||||
# Handle speech delays so we don't get stuttering
|
# Handle speech delays so we don't get stuttering
|
||||||
self.lastSpoken = {"text": None, "time": 0}
|
self.lastSpoken = {"text": None, "time": 0}
|
||||||
self.speechDelay = 250 # ms
|
self.speechDelay = 250 # ms
|
||||||
|
|
||||||
# Try to initialize a speech provider
|
# Try to initialize a speech provider
|
||||||
self.provider = None
|
self.provider = None
|
||||||
self.providerName = None
|
self.providerName = None
|
||||||
|
|
||||||
# Try speechd first
|
# Try speechd first
|
||||||
try:
|
try:
|
||||||
import speechd
|
import speechd
|
||||||
@ -44,7 +44,7 @@ class Speech:
|
|||||||
return
|
return
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Try accessible_output2 next
|
# Try accessible_output2 next
|
||||||
try:
|
try:
|
||||||
import accessible_output2.outputs.auto
|
import accessible_output2.outputs.auto
|
||||||
@ -54,31 +54,31 @@ class Speech:
|
|||||||
return
|
return
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# No speech providers found
|
# No speech providers found
|
||||||
print("No speech providers found.")
|
print("No speech providers found.")
|
||||||
|
|
||||||
def speak(self, text, interrupt=True):
|
def speak(self, text, interrupt=True):
|
||||||
"""Speak text using the configured speech provider and display on screen.
|
"""Speak text using the configured speech provider and display on screen.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str): Text to speak and display
|
text (str): Text to speak and display
|
||||||
interrupt (bool): Whether to interrupt current speech (default: True)
|
interrupt (bool): Whether to interrupt current speech (default: True)
|
||||||
"""
|
"""
|
||||||
if not self.provider:
|
if not self.provider:
|
||||||
return
|
return
|
||||||
|
|
||||||
currentTime = pygame.time.get_ticks()
|
currentTime = pygame.time.get_ticks()
|
||||||
|
|
||||||
# Check if this is the same text within the delay window
|
# Check if this is the same text within the delay window
|
||||||
if (self.lastSpoken["text"] == text and
|
if (self.lastSpoken["text"] == text and
|
||||||
currentTime - self.lastSpoken["time"] < self.speechDelay):
|
currentTime - self.lastSpoken["time"] < self.speechDelay):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update last spoken tracking
|
# Update last spoken tracking
|
||||||
self.lastSpoken["text"] = text
|
self.lastSpoken["text"] = text
|
||||||
self.lastSpoken["time"] = currentTime
|
self.lastSpoken["time"] = currentTime
|
||||||
|
|
||||||
# Proceed with speech
|
# Proceed with speech
|
||||||
if self.providerName == "speechd":
|
if self.providerName == "speechd":
|
||||||
if interrupt:
|
if interrupt:
|
||||||
@ -86,12 +86,12 @@ class Speech:
|
|||||||
self.spd.say(text)
|
self.spd.say(text)
|
||||||
elif self.providerName == "accessible_output2":
|
elif self.providerName == "accessible_output2":
|
||||||
self.ao2.speak(text, interrupt=interrupt)
|
self.ao2.speak(text, interrupt=interrupt)
|
||||||
|
|
||||||
# Display the text on screen
|
# Display the text on screen
|
||||||
screen = pygame.display.get_surface()
|
screen = pygame.display.get_surface()
|
||||||
if not screen:
|
if not screen:
|
||||||
return
|
return
|
||||||
|
|
||||||
font = pygame.font.Font(None, 36)
|
font = pygame.font.Font(None, 36)
|
||||||
# Wrap the text
|
# Wrap the text
|
||||||
maxWidth = screen.get_width() - 40 # Leave a 20-pixel margin on each side
|
maxWidth = screen.get_width() - 40 # Leave a 20-pixel margin on each side
|
||||||
@ -109,7 +109,7 @@ class Speech:
|
|||||||
screen.blit(surface, textRect)
|
screen.blit(surface, textRect)
|
||||||
currentY += surface.get_height()
|
currentY += surface.get_height()
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Clean up speech resources."""
|
"""Clean up speech resources."""
|
||||||
if self.providerName == "speechd":
|
if self.providerName == "speechd":
|
||||||
@ -120,7 +120,7 @@ _speechInstance = None
|
|||||||
|
|
||||||
def speak(text, interrupt=True):
|
def speak(text, interrupt=True):
|
||||||
"""Speak text using the global speech instance.
|
"""Speak text using the global speech instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str): Text to speak and display
|
text (str): Text to speak and display
|
||||||
interrupt (bool): Whether to interrupt current speech (default: True)
|
interrupt (bool): Whether to interrupt current speech (default: True)
|
||||||
|
146
utils.py
146
utils.py
@ -26,137 +26,137 @@ from .scoreboard import Scoreboard
|
|||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
"""Central class to manage all game systems."""
|
"""Central class to manage all game systems."""
|
||||||
|
|
||||||
def __init__(self, title):
|
def __init__(self, title):
|
||||||
"""Initialize a new game.
|
"""Initialize a new game.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
title (str): Title of the game
|
title (str): Title of the game
|
||||||
"""
|
"""
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
# Initialize services
|
# Initialize services
|
||||||
self.pathService = PathService.get_instance().initialize(title)
|
self.pathService = PathService.get_instance().initialize(title)
|
||||||
self.configService = ConfigService.get_instance()
|
self.configService = ConfigService.get_instance()
|
||||||
self.configService.set_game_info(title, self.pathService)
|
self.configService.set_game_info(title, self.pathService)
|
||||||
self.volumeService = VolumeService.get_instance()
|
self.volumeService = VolumeService.get_instance()
|
||||||
|
|
||||||
# Initialize game components (lazy loaded)
|
# Initialize game components (lazy loaded)
|
||||||
self._speech = None
|
self._speech = None
|
||||||
self._sound = None
|
self._sound = None
|
||||||
self._scoreboard = None
|
self._scoreboard = None
|
||||||
|
|
||||||
# Display text instructions flag
|
# Display text instructions flag
|
||||||
self.displayTextUsageInstructions = False
|
self.displayTextUsageInstructions = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speech(self):
|
def speech(self):
|
||||||
"""Get the speech system (lazy loaded).
|
"""Get the speech system (lazy loaded).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Speech: Speech system instance
|
Speech: Speech system instance
|
||||||
"""
|
"""
|
||||||
if not self._speech:
|
if not self._speech:
|
||||||
self._speech = Speech.get_instance()
|
self._speech = Speech.get_instance()
|
||||||
return self._speech
|
return self._speech
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sound(self):
|
def sound(self):
|
||||||
"""Get the sound system (lazy loaded).
|
"""Get the sound system (lazy loaded).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Sound: Sound system instance
|
Sound: Sound system instance
|
||||||
"""
|
"""
|
||||||
if not self._sound:
|
if not self._sound:
|
||||||
self._sound = Sound("sounds/", self.volumeService)
|
self._sound = Sound("sounds/", self.volumeService)
|
||||||
return self._sound
|
return self._sound
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def scoreboard(self):
|
def scoreboard(self):
|
||||||
"""Get the scoreboard (lazy loaded).
|
"""Get the scoreboard (lazy loaded).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Scoreboard: Scoreboard instance
|
Scoreboard: Scoreboard instance
|
||||||
"""
|
"""
|
||||||
if not self._scoreboard:
|
if not self._scoreboard:
|
||||||
self._scoreboard = Scoreboard(self.configService)
|
self._scoreboard = Scoreboard(self.configService)
|
||||||
return self._scoreboard
|
return self._scoreboard
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""Initialize the game GUI and sound system.
|
"""Initialize the game GUI and sound system.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Game: Self for method chaining
|
Game: Self for method chaining
|
||||||
"""
|
"""
|
||||||
# Set process title
|
# Set process title
|
||||||
setproctitle(str.lower(str.replace(self.title, " ", "")))
|
setproctitle(str.lower(str.replace(self.title, " ", "")))
|
||||||
|
|
||||||
# Seed the random generator
|
# Seed the random generator
|
||||||
random.seed()
|
random.seed()
|
||||||
|
|
||||||
# Initialize pygame
|
# Initialize pygame
|
||||||
pygame.init()
|
pygame.init()
|
||||||
pygame.display.set_mode((800, 600))
|
pygame.display.set_mode((800, 600))
|
||||||
pygame.display.set_caption(self.title)
|
pygame.display.set_caption(self.title)
|
||||||
|
|
||||||
# Set up audio system
|
# Set up audio system
|
||||||
pygame.mixer.pre_init(44100, -16, 2, 1024)
|
pygame.mixer.pre_init(44100, -16, 2, 1024)
|
||||||
pygame.mixer.init()
|
pygame.mixer.init()
|
||||||
pygame.mixer.set_num_channels(32)
|
pygame.mixer.set_num_channels(32)
|
||||||
pygame.mixer.set_reserved(0) # Reserve channel for cut scenes
|
pygame.mixer.set_reserved(0) # Reserve channel for cut scenes
|
||||||
|
|
||||||
# Enable key repeat for volume controls
|
# Enable key repeat for volume controls
|
||||||
pygame.key.set_repeat(500, 100)
|
pygame.key.set_repeat(500, 100)
|
||||||
|
|
||||||
# Load sound effects
|
# Load sound effects
|
||||||
self.sound
|
self.sound
|
||||||
|
|
||||||
# Play intro sound if available
|
# Play intro sound if available
|
||||||
if 'game-intro' in self.sound.sounds:
|
if 'game-intro' in self.sound.sounds:
|
||||||
self.sound.cut_scene('game-intro')
|
self.sound.cut_scene('game-intro')
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def speak(self, text, interrupt=True):
|
def speak(self, text, interrupt=True):
|
||||||
"""Speak text using the speech system.
|
"""Speak text using the speech system.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str): Text to speak
|
text (str): Text to speak
|
||||||
interrupt (bool): Whether to interrupt current speech
|
interrupt (bool): Whether to interrupt current speech
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Game: Self for method chaining
|
Game: Self for method chaining
|
||||||
"""
|
"""
|
||||||
self.speech.speak(text, interrupt)
|
self.speech.speak(text, interrupt)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def play_bgm(self, musicFile):
|
def play_bgm(self, musicFile):
|
||||||
"""Play background music.
|
"""Play background music.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
musicFile (str): Path to music file
|
musicFile (str): Path to music file
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Game: Self for method chaining
|
Game: Self for method chaining
|
||||||
"""
|
"""
|
||||||
self.sound.play_bgm(musicFile)
|
self.sound.play_bgm(musicFile)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def display_text(self, textLines):
|
def display_text(self, textLines):
|
||||||
"""Display text with navigation controls.
|
"""Display text with navigation controls.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
textLines (list): List of text lines
|
textLines (list): List of text lines
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Game: Self for method chaining
|
Game: Self for method chaining
|
||||||
"""
|
"""
|
||||||
# Store original text with blank lines for copying
|
# Store original text with blank lines for copying
|
||||||
originalText = textLines.copy()
|
originalText = textLines.copy()
|
||||||
|
|
||||||
# Create navigation text by filtering out blank lines
|
# Create navigation text by filtering out blank lines
|
||||||
navText = [line for line in textLines if line.strip()]
|
navText = [line for line in textLines if line.strip()]
|
||||||
|
|
||||||
# Add instructions at the start on the first display
|
# Add instructions at the start on the first display
|
||||||
if not self.displayTextUsageInstructions:
|
if not self.displayTextUsageInstructions:
|
||||||
instructions = ("Press space to read the whole text. Use up and down arrows to navigate "
|
instructions = ("Press space to read the whole text. Use up and down arrows to navigate "
|
||||||
@ -164,20 +164,20 @@ class Game:
|
|||||||
"or t to copy the entire text. Press enter or escape when you are done reading.")
|
"or t to copy the entire text. Press enter or escape when you are done reading.")
|
||||||
navText.insert(0, instructions)
|
navText.insert(0, instructions)
|
||||||
self.displayTextUsageInstructions = True
|
self.displayTextUsageInstructions = True
|
||||||
|
|
||||||
# Add end marker
|
# Add end marker
|
||||||
navText.append("End of text.")
|
navText.append("End of text.")
|
||||||
|
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
self.speech.speak(navText[currentIndex])
|
self.speech.speak(navText[currentIndex])
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event = pygame.event.wait()
|
event = pygame.event.wait()
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
# Check for Alt modifier
|
# Check for Alt modifier
|
||||||
mods = pygame.key.get_mods()
|
mods = pygame.key.get_mods()
|
||||||
altPressed = mods & pygame.KMOD_ALT
|
altPressed = mods & pygame.KMOD_ALT
|
||||||
|
|
||||||
# Volume controls (require Alt)
|
# Volume controls (require Alt)
|
||||||
if altPressed:
|
if altPressed:
|
||||||
if event.key == pygame.K_PAGEUP:
|
if event.key == pygame.K_PAGEUP:
|
||||||
@ -195,19 +195,19 @@ class Game:
|
|||||||
else:
|
else:
|
||||||
if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
|
if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
|
if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
|
||||||
currentIndex += 1
|
currentIndex += 1
|
||||||
self.speech.speak(navText[currentIndex])
|
self.speech.speak(navText[currentIndex])
|
||||||
|
|
||||||
if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
|
if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
|
||||||
currentIndex -= 1
|
currentIndex -= 1
|
||||||
self.speech.speak(navText[currentIndex])
|
self.speech.speak(navText[currentIndex])
|
||||||
|
|
||||||
if event.key == pygame.K_SPACE:
|
if event.key == pygame.K_SPACE:
|
||||||
# Join with newlines to preserve spacing in speech
|
# Join with newlines to preserve spacing in speech
|
||||||
self.speech.speak('\n'.join(originalText[1:-1]))
|
self.speech.speak('\n'.join(originalText[1:-1]))
|
||||||
|
|
||||||
if event.key == pygame.K_c:
|
if event.key == pygame.K_c:
|
||||||
try:
|
try:
|
||||||
import pyperclip
|
import pyperclip
|
||||||
@ -215,7 +215,7 @@ class Game:
|
|||||||
self.speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
|
self.speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
|
||||||
except:
|
except:
|
||||||
self.speech.speak("Failed to copy the text to the clipboard.")
|
self.speech.speak("Failed to copy the text to the clipboard.")
|
||||||
|
|
||||||
if event.key == pygame.K_t:
|
if event.key == pygame.K_t:
|
||||||
try:
|
try:
|
||||||
import pyperclip
|
import pyperclip
|
||||||
@ -224,10 +224,10 @@ class Game:
|
|||||||
self.speech.speak("Copied entire message to the clipboard.")
|
self.speech.speak("Copied entire message to the clipboard.")
|
||||||
except:
|
except:
|
||||||
self.speech.speak("Failed to copy the text to the clipboard.")
|
self.speech.speak("Failed to copy the text to the clipboard.")
|
||||||
|
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
|
|
||||||
def exit(self):
|
def exit(self):
|
||||||
"""Clean up and exit the game."""
|
"""Clean up and exit the game."""
|
||||||
if self._speech and self.speech.providerName == "speechd":
|
if self._speech and self.speech.providerName == "speechd":
|
||||||
@ -241,12 +241,12 @@ class Game:
|
|||||||
|
|
||||||
def check_for_updates(currentVersion, gameName, url):
|
def check_for_updates(currentVersion, gameName, url):
|
||||||
"""Check for game updates.
|
"""Check for game updates.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
currentVersion (str): Current version string (e.g. "1.0.0")
|
currentVersion (str): Current version string (e.g. "1.0.0")
|
||||||
gameName (str): Name of the game
|
gameName (str): Name of the game
|
||||||
url (str): URL to check for updates
|
url (str): URL to check for updates
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Update information or None if no update available
|
dict: Update information or None if no update available
|
||||||
"""
|
"""
|
||||||
@ -266,10 +266,10 @@ def check_for_updates(currentVersion, gameName, url):
|
|||||||
|
|
||||||
def get_version_tuple(versionStr):
|
def get_version_tuple(versionStr):
|
||||||
"""Convert version string to comparable tuple.
|
"""Convert version string to comparable tuple.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
versionStr (str): Version string (e.g. "1.0.0")
|
versionStr (str): Version string (e.g. "1.0.0")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: Version as tuple of integers
|
tuple: Version as tuple of integers
|
||||||
"""
|
"""
|
||||||
@ -277,11 +277,11 @@ def get_version_tuple(versionStr):
|
|||||||
|
|
||||||
def check_compatibility(requiredVersion, currentVersion):
|
def check_compatibility(requiredVersion, currentVersion):
|
||||||
"""Check if current version meets minimum required version.
|
"""Check if current version meets minimum required version.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
requiredVersion (str): Minimum required version string
|
requiredVersion (str): Minimum required version string
|
||||||
currentVersion (str): Current version string
|
currentVersion (str): Current version string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if compatible, False otherwise
|
bool: True if compatible, False otherwise
|
||||||
"""
|
"""
|
||||||
@ -291,10 +291,10 @@ def check_compatibility(requiredVersion, currentVersion):
|
|||||||
|
|
||||||
def sanitize_filename(filename):
|
def sanitize_filename(filename):
|
||||||
"""Sanitize a filename to be safe for all operating systems.
|
"""Sanitize a filename to be safe for all operating systems.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename (str): Original filename
|
filename (str): Original filename
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Sanitized filename
|
str: Sanitized filename
|
||||||
"""
|
"""
|
||||||
@ -309,12 +309,12 @@ def sanitize_filename(filename):
|
|||||||
|
|
||||||
def lerp(start, end, factor):
|
def lerp(start, end, factor):
|
||||||
"""Linear interpolation between two values.
|
"""Linear interpolation between two values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start (float): Start value
|
start (float): Start value
|
||||||
end (float): End value
|
end (float): End value
|
||||||
factor (float): Interpolation factor (0.0-1.0)
|
factor (float): Interpolation factor (0.0-1.0)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Interpolated value
|
float: Interpolated value
|
||||||
"""
|
"""
|
||||||
@ -322,12 +322,12 @@ def lerp(start, end, factor):
|
|||||||
|
|
||||||
def smooth_step(edge0, edge1, x):
|
def smooth_step(edge0, edge1, x):
|
||||||
"""Hermite interpolation between two values.
|
"""Hermite interpolation between two values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
edge0 (float): Start edge
|
edge0 (float): Start edge
|
||||||
edge1 (float): End edge
|
edge1 (float): End edge
|
||||||
x (float): Value to interpolate
|
x (float): Value to interpolate
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Interpolated value with smooth step
|
float: Interpolated value with smooth step
|
||||||
"""
|
"""
|
||||||
@ -338,13 +338,13 @@ def smooth_step(edge0, edge1, x):
|
|||||||
|
|
||||||
def distance_2d(x1, y1, x2, y2):
|
def distance_2d(x1, y1, x2, y2):
|
||||||
"""Calculate Euclidean distance between two 2D points.
|
"""Calculate Euclidean distance between two 2D points.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
x1 (float): X coordinate of first point
|
x1 (float): X coordinate of first point
|
||||||
y1 (float): Y coordinate of first point
|
y1 (float): Y coordinate of first point
|
||||||
x2 (float): X coordinate of second point
|
x2 (float): X coordinate of second point
|
||||||
y2 (float): Y coordinate of second point
|
y2 (float): Y coordinate of second point
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Distance between points
|
float: Distance between points
|
||||||
"""
|
"""
|
||||||
@ -352,17 +352,17 @@ def distance_2d(x1, y1, x2, y2):
|
|||||||
|
|
||||||
def generate_tone(frequency, duration=0.1, sampleRate=44100, volume=0.2):
|
def generate_tone(frequency, duration=0.1, sampleRate=44100, volume=0.2):
|
||||||
"""Generate a tone at the specified frequency.
|
"""Generate a tone at the specified frequency.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
frequency (float): Frequency in Hz
|
frequency (float): Frequency in Hz
|
||||||
duration (float): Duration in seconds (default: 0.1)
|
duration (float): Duration in seconds (default: 0.1)
|
||||||
sampleRate (int): Sample rate in Hz (default: 44100)
|
sampleRate (int): Sample rate in Hz (default: 44100)
|
||||||
volume (float): Volume from 0.0 to 1.0 (default: 0.2)
|
volume (float): Volume from 0.0 to 1.0 (default: 0.2)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
pygame.mixer.Sound: Sound object with the generated tone
|
pygame.mixer.Sound: Sound object with the generated tone
|
||||||
"""
|
"""
|
||||||
|
|
||||||
t = np.linspace(0, duration, int(sampleRate * duration), False)
|
t = np.linspace(0, duration, int(sampleRate * duration), False)
|
||||||
tone = np.sin(2 * np.pi * frequency * t)
|
tone = np.sin(2 * np.pi * frequency * t)
|
||||||
stereoTone = np.vstack((tone, tone)).T # Create a 2D array for stereo
|
stereoTone = np.vstack((tone, tone)).T # Create a 2D array for stereo
|
||||||
@ -372,17 +372,17 @@ def generate_tone(frequency, duration=0.1, sampleRate=44100, volume=0.2):
|
|||||||
|
|
||||||
def x_powerbar():
|
def x_powerbar():
|
||||||
"""Sound based horizontal power bar
|
"""Sound based horizontal power bar
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Selected position between -50 and 50
|
int: Selected position between -50 and 50
|
||||||
"""
|
"""
|
||||||
|
|
||||||
clock = pygame.time.Clock()
|
clock = pygame.time.Clock()
|
||||||
screen = pygame.display.get_surface()
|
screen = pygame.display.get_surface()
|
||||||
position = -50 # Start from the leftmost position
|
position = -50 # Start from the leftmost position
|
||||||
direction = 1 # Move right initially
|
direction = 1 # Move right initially
|
||||||
barHeight = 20
|
barHeight = 20
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
frequency = 440 # A4 note
|
frequency = 440 # A4 note
|
||||||
leftVolume = (50 - position) / 100
|
leftVolume = (50 - position) / 100
|
||||||
@ -390,7 +390,7 @@ def x_powerbar():
|
|||||||
tone = generate_tone(frequency)
|
tone = generate_tone(frequency)
|
||||||
channel = tone.play()
|
channel = tone.play()
|
||||||
channel.set_volume(leftVolume, rightVolume)
|
channel.set_volume(leftVolume, rightVolume)
|
||||||
|
|
||||||
# Visual representation
|
# Visual representation
|
||||||
screen.fill((0, 0, 0))
|
screen.fill((0, 0, 0))
|
||||||
barWidth = screen.get_width() - 40 # Leave 20px margin on each side
|
barWidth = screen.get_width() - 40 # Leave 20px margin on each side
|
||||||
@ -398,13 +398,13 @@ def x_powerbar():
|
|||||||
markerPos = int(20 + (position + 50) / 100 * barWidth)
|
markerPos = int(20 + (position + 50) / 100 * barWidth)
|
||||||
pygame.draw.rect(screen, (255, 0, 0), (markerPos - 5, screen.get_height() // 2 - barHeight, 10, barHeight * 2))
|
pygame.draw.rect(screen, (255, 0, 0), (markerPos - 5, screen.get_height() // 2 - barHeight, 10, barHeight * 2))
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
check_for_exit()
|
check_for_exit()
|
||||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
|
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
|
||||||
channel.stop()
|
channel.stop()
|
||||||
return position # This will return a value between -50 and 50
|
return position # This will return a value between -50 and 50
|
||||||
|
|
||||||
position += direction
|
position += direction
|
||||||
if position > 50:
|
if position > 50:
|
||||||
position = 50
|
position = 50
|
||||||
@ -412,27 +412,27 @@ def x_powerbar():
|
|||||||
elif position < -50:
|
elif position < -50:
|
||||||
position = -50
|
position = -50
|
||||||
direction = 1
|
direction = 1
|
||||||
|
|
||||||
clock.tick(40) # Speed of bar
|
clock.tick(40) # Speed of bar
|
||||||
|
|
||||||
def y_powerbar():
|
def y_powerbar():
|
||||||
"""Sound based vertical power bar
|
"""Sound based vertical power bar
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Selected power level between 0 and 100
|
int: Selected power level between 0 and 100
|
||||||
"""
|
"""
|
||||||
|
|
||||||
clock = pygame.time.Clock()
|
clock = pygame.time.Clock()
|
||||||
screen = pygame.display.get_surface()
|
screen = pygame.display.get_surface()
|
||||||
power = 0
|
power = 0
|
||||||
direction = 1 # 1 for increasing, -1 for decreasing
|
direction = 1 # 1 for increasing, -1 for decreasing
|
||||||
barWidth = 20
|
barWidth = 20
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
frequency = 220 + (power * 5) # Adjust these values to change the pitch range
|
frequency = 220 + (power * 5) # Adjust these values to change the pitch range
|
||||||
tone = generate_tone(frequency)
|
tone = generate_tone(frequency)
|
||||||
channel = tone.play()
|
channel = tone.play()
|
||||||
|
|
||||||
# Visual representation
|
# Visual representation
|
||||||
screen.fill((0, 0, 0))
|
screen.fill((0, 0, 0))
|
||||||
barHeight = screen.get_height() - 40 # Leave 20px margin on top and bottom
|
barHeight = screen.get_height() - 40 # Leave 20px margin on top and bottom
|
||||||
@ -440,15 +440,15 @@ def y_powerbar():
|
|||||||
markerPos = int(20 + (100 - power) / 100 * barHeight)
|
markerPos = int(20 + (100 - power) / 100 * barHeight)
|
||||||
pygame.draw.rect(screen, (255, 0, 0), (screen.get_width() // 2 - barWidth, markerPos - 5, barWidth * 2, 10))
|
pygame.draw.rect(screen, (255, 0, 0), (screen.get_width() // 2 - barWidth, markerPos - 5, barWidth * 2, 10))
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
check_for_exit()
|
check_for_exit()
|
||||||
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
|
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
|
||||||
channel.stop()
|
channel.stop()
|
||||||
return power
|
return power
|
||||||
|
|
||||||
power += direction
|
power += direction
|
||||||
if power >= 100 or power <= 0:
|
if power >= 100 or power <= 0:
|
||||||
direction *= -1 # Reverse direction at limits
|
direction *= -1 # Reverse direction at limits
|
||||||
|
|
||||||
clock.tick(40)
|
clock.tick(40)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user