Code cleanup and sound consolidation.

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

146
utils.py
View File

@ -26,137 +26,137 @@ from .scoreboard import Scoreboard
class Game:
"""Central class to manage all game systems."""
def __init__(self, title):
"""Initialize a new game.
Args:
title (str): Title of the game
"""
self.title = title
# Initialize services
self.pathService = PathService.get_instance().initialize(title)
self.configService = ConfigService.get_instance()
self.configService.set_game_info(title, self.pathService)
self.volumeService = VolumeService.get_instance()
# Initialize game components (lazy loaded)
self._speech = None
self._sound = None
self._scoreboard = None
# Display text instructions flag
self.displayTextUsageInstructions = False
@property
def speech(self):
"""Get the speech system (lazy loaded).
Returns:
Speech: Speech system instance
"""
if not self._speech:
self._speech = Speech.get_instance()
return self._speech
@property
def sound(self):
"""Get the sound system (lazy loaded).
Returns:
Sound: Sound system instance
"""
if not self._sound:
self._sound = Sound("sounds/", self.volumeService)
return self._sound
@property
def scoreboard(self):
"""Get the scoreboard (lazy loaded).
Returns:
Scoreboard: Scoreboard instance
"""
if not self._scoreboard:
self._scoreboard = Scoreboard(self.configService)
return self._scoreboard
def initialize(self):
"""Initialize the game GUI and sound system.
Returns:
Game: Self for method chaining
"""
# Set process title
setproctitle(str.lower(str.replace(self.title, " ", "")))
# Seed the random generator
random.seed()
# Initialize pygame
pygame.init()
pygame.display.set_mode((800, 600))
pygame.display.set_caption(self.title)
# Set up audio system
pygame.mixer.pre_init(44100, -16, 2, 1024)
pygame.mixer.init()
pygame.mixer.set_num_channels(32)
pygame.mixer.set_reserved(0) # Reserve channel for cut scenes
# Enable key repeat for volume controls
pygame.key.set_repeat(500, 100)
# Load sound effects
self.sound
# Play intro sound if available
if 'game-intro' in self.sound.sounds:
self.sound.cut_scene('game-intro')
return self
def speak(self, text, interrupt=True):
"""Speak text using the speech system.
Args:
text (str): Text to speak
interrupt (bool): Whether to interrupt current speech
Returns:
Game: Self for method chaining
"""
self.speech.speak(text, interrupt)
return self
def play_bgm(self, musicFile):
"""Play background music.
Args:
musicFile (str): Path to music file
Returns:
Game: Self for method chaining
"""
self.sound.play_bgm(musicFile)
return self
def display_text(self, textLines):
"""Display text with navigation controls.
Args:
textLines (list): List of text lines
Returns:
Game: Self for method chaining
"""
# Store original text with blank lines for copying
originalText = textLines.copy()
# Create navigation text by filtering out blank lines
navText = [line for line in textLines if line.strip()]
# Add instructions at the start on the first display
if not self.displayTextUsageInstructions:
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.")
navText.insert(0, instructions)
self.displayTextUsageInstructions = True
# Add end marker
navText.append("End of text.")
currentIndex = 0
self.speech.speak(navText[currentIndex])
while True:
event = pygame.event.wait()
if event.type == pygame.KEYDOWN:
# Check for Alt modifier
mods = pygame.key.get_mods()
altPressed = mods & pygame.KMOD_ALT
# Volume controls (require Alt)
if altPressed:
if event.key == pygame.K_PAGEUP:
@ -195,19 +195,19 @@ class Game:
else:
if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
return self
if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
currentIndex += 1
self.speech.speak(navText[currentIndex])
if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
currentIndex -= 1
self.speech.speak(navText[currentIndex])
if event.key == pygame.K_SPACE:
# Join with newlines to preserve spacing in speech
self.speech.speak('\n'.join(originalText[1:-1]))
if event.key == pygame.K_c:
try:
import pyperclip
@ -215,7 +215,7 @@ class Game:
self.speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
except:
self.speech.speak("Failed to copy the text to the clipboard.")
if event.key == pygame.K_t:
try:
import pyperclip
@ -224,10 +224,10 @@ class Game:
self.speech.speak("Copied entire message to the clipboard.")
except:
self.speech.speak("Failed to copy the text to the clipboard.")
pygame.event.clear()
time.sleep(0.001)
def exit(self):
"""Clean up and exit the game."""
if self._speech and self.speech.providerName == "speechd":
@ -241,12 +241,12 @@ class Game:
def check_for_updates(currentVersion, gameName, url):
"""Check for game updates.
Args:
currentVersion (str): Current version string (e.g. "1.0.0")
gameName (str): Name of the game
url (str): URL to check for updates
Returns:
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):
"""Convert version string to comparable tuple.
Args:
versionStr (str): Version string (e.g. "1.0.0")
Returns:
tuple: Version as tuple of integers
"""
@ -277,11 +277,11 @@ def get_version_tuple(versionStr):
def check_compatibility(requiredVersion, currentVersion):
"""Check if current version meets minimum required version.
Args:
requiredVersion (str): Minimum required version string
currentVersion (str): Current version string
Returns:
bool: True if compatible, False otherwise
"""
@ -291,10 +291,10 @@ def check_compatibility(requiredVersion, currentVersion):
def sanitize_filename(filename):
"""Sanitize a filename to be safe for all operating systems.
Args:
filename (str): Original filename
Returns:
str: Sanitized filename
"""
@ -309,12 +309,12 @@ def sanitize_filename(filename):
def lerp(start, end, factor):
"""Linear interpolation between two values.
Args:
start (float): Start value
end (float): End value
factor (float): Interpolation factor (0.0-1.0)
Returns:
float: Interpolated value
"""
@ -322,12 +322,12 @@ def lerp(start, end, factor):
def smooth_step(edge0, edge1, x):
"""Hermite interpolation between two values.
Args:
edge0 (float): Start edge
edge1 (float): End edge
x (float): Value to interpolate
Returns:
float: Interpolated value with smooth step
"""
@ -338,13 +338,13 @@ def smooth_step(edge0, edge1, x):
def distance_2d(x1, y1, x2, y2):
"""Calculate Euclidean distance between two 2D points.
Args:
x1 (float): X coordinate of first point
y1 (float): Y coordinate of first point
x2 (float): X coordinate of second point
y2 (float): Y coordinate of second point
Returns:
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):
"""Generate a tone at the specified frequency.
Args:
frequency (float): Frequency in Hz
duration (float): Duration in seconds (default: 0.1)
sampleRate (int): Sample rate in Hz (default: 44100)
volume (float): Volume from 0.0 to 1.0 (default: 0.2)
Returns:
pygame.mixer.Sound: Sound object with the generated tone
"""
t = np.linspace(0, duration, int(sampleRate * duration), False)
tone = np.sin(2 * np.pi * frequency * t)
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():
"""Sound based horizontal power bar
Returns:
int: Selected position between -50 and 50
"""
clock = pygame.time.Clock()
screen = pygame.display.get_surface()
position = -50 # Start from the leftmost position
direction = 1 # Move right initially
barHeight = 20
while True:
frequency = 440 # A4 note
leftVolume = (50 - position) / 100
@ -390,7 +390,7 @@ def x_powerbar():
tone = generate_tone(frequency)
channel = tone.play()
channel.set_volume(leftVolume, rightVolume)
# Visual representation
screen.fill((0, 0, 0))
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)
pygame.draw.rect(screen, (255, 0, 0), (markerPos - 5, screen.get_height() // 2 - barHeight, 10, barHeight * 2))
pygame.display.flip()
for event in pygame.event.get():
check_for_exit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
channel.stop()
return position # This will return a value between -50 and 50
position += direction
if position > 50:
position = 50
@ -412,27 +412,27 @@ def x_powerbar():
elif position < -50:
position = -50
direction = 1
clock.tick(40) # Speed of bar
def y_powerbar():
"""Sound based vertical power bar
Returns:
int: Selected power level between 0 and 100
"""
clock = pygame.time.Clock()
screen = pygame.display.get_surface()
power = 0
direction = 1 # 1 for increasing, -1 for decreasing
barWidth = 20
while True:
frequency = 220 + (power * 5) # Adjust these values to change the pitch range
tone = generate_tone(frequency)
channel = tone.play()
# Visual representation
screen.fill((0, 0, 0))
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)
pygame.draw.rect(screen, (255, 0, 0), (screen.get_width() // 2 - barWidth, markerPos - 5, barWidth * 2, 10))
pygame.display.flip()
for event in pygame.event.get():
check_for_exit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
channel.stop()
return power
power += direction
if power >= 100 or power <= 0:
direction *= -1 # Reverse direction at limits
clock.tick(40)