Code cleanup and sound consolidation.
This commit is contained in:
146
utils.py
146
utils.py
@ -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)
|
||||
|
Reference in New Issue
Block a user