Optional timeouts for wait methods. Various bug fixes.

This commit is contained in:
Storm Dragon 2025-02-24 01:59:47 -05:00
parent 6bddf282a7
commit 4d59a610a0
4 changed files with 67 additions and 27 deletions

View File

@ -89,46 +89,55 @@ class pygstormgames:
self.speech.cleanup()
pyglet.app.exit()
def wait(self, validKeys=None):
"""Wait for key press(es).
def wait(self, validKeys=None, timeout=None):
"""Wait for key press(es) with optional timeout.
Args:
validKeys (list, optional): List of pyglet.window.key values to wait for.
If None, accepts any key press.
timeout (float, optional): Time in seconds to wait for input.
If None, waits indefinitely.
Returns:
tuple: (key, modifiers) that were pressed
tuple: (key, modifiers) that were pressed. Returns (None, None) on timeout.
"""
keyResult = [None, None] # Use list to allow modification in closure
start_time = time.time()
def on_key_press(symbol, modifiers):
if validKeys is None or symbol in validKeys:
keyResult[0] = symbol
keyResult[1] = modifiers
return pyglet.event.EVENT_HANDLED
# Register temporary handler
self.display.window.push_handlers(on_key_press=on_key_press)
# Wait for valid key press
# Wait for valid key press or timeout
while keyResult[0] is None:
self.display.window.dispatch_events()
pyglet.clock.tick()
# Check for timeout
if timeout is not None and time.time() - start_time > timeout:
break
# Clean up
self.display.window.remove_handlers()
return tuple(keyResult)
def wait_for_completion(self, condition, validKeys=None):
"""Wait for either a condition to be met or valid keys to be pressed.
def wait_for_completion(self, condition, validKeys=None, timeout=None):
"""Wait for either a condition to be met, valid keys to be pressed, or timeout.
Args:
condition: Function that returns True when waiting should end
validKeys (list, optional): List of pyglet.window.key values that can interrupt
If None, uses ESCAPE, RETURN, and SPACE
timeout (float, optional): Time in seconds to wait before timing out
If None, waits indefinitely
Returns:
bool: True if interrupted by key press, False if condition was met
bool: True if interrupted by key press or timeout, False if condition was met
"""
if validKeys is None:
validKeys = [pyglet.window.key.ESCAPE,
@ -136,18 +145,24 @@ class pygstormgames:
pyglet.window.key.SPACE]
interrupted = [False] # Use list to allow modification in closure
start_time = time.time()
def on_key_press(symbol, modifiers):
if symbol in validKeys:
interrupted[0] = True
return pyglet.event.EVENT_HANDLED
self.display.window.push_handlers(on_key_press=on_key_press)
while not condition() and not interrupted[0]:
self.display.window.dispatch_events()
pyglet.clock.tick()
# Check for timeout
if timeout is not None and time.time() - start_time > timeout:
interrupted[0] = True
break
self.display.window.remove_handlers()
return interrupted[0]

View File

@ -37,6 +37,8 @@ class Display:
text (list): List of text lines to display
speech (Speech): Speech system for audio output
"""
# Stop bgm if present.
self.game.sound.pause_bgm()
# Store original text with blank lines for copying
self.originalText = text.copy()
@ -152,22 +154,31 @@ class Display:
def get_input(self, prompt="Enter text:", defaultText=""):
"""Display a dialog box for text input.
Args:
prompt (str): Prompt text to display
defaultText (str): Initial text in input box
Returns:
str: User input text, or None if cancelled
"""
app = wx.App(False)
dialog = wx.TextEntryDialog(None, prompt, "Input", defaultText)
dialog.SetValue(defaultText)
# Bring dialog to front and give it focus
dialog.Raise()
dialog.SetFocus()
if dialog.ShowModal() == wx.ID_OK:
userInput = dialog.GetValue()
else:
userInput = None
dialog.Destroy()
# Return focus to game window
self.window.activate()
return userInput
def donate(self, speech):

View File

@ -3,6 +3,7 @@
Handles high score tracking with player names and score management.
"""
import pyglet
import time
class Scoreboard:
@ -93,34 +94,40 @@ class Scoreboard:
def add_high_score(self):
"""Add current score to high scores if it qualifies.
Returns:
bool: True if score was added, False if not
"""
position = self.check_high_score()
if position is None:
return False
# Get player name
self.game.speech.speak("New high score! Enter your name:")
name = self.game.display.get_input("New high score! Enter your name:", "Player")
if name is None: # User cancelled
name = "Player"
# Insert new score at correct position
self.highScores.insert(position - 1, {
'name': name,
'score': self.currentScore
})
# Keep only top 10
self.highScores = self.highScores[:10]
# Save to config
for i, entry in enumerate(self.highScores):
self.game.config.set_value("scoreboard", f"score_{i+1}", str(entry['score']))
self.game.config.set_value("scoreboard", f"name_{i+1}", entry['name'])
# Force window refresh after dialog
self.game.display.window.dispatch_events()
self.game.speech.speak(f"Congratulations {name}! You got position {position} on the scoreboard!")
time.sleep(1)
# Make sure window is still responsive
self.game.display.window.dispatch_events()
return True

View File

@ -236,23 +236,30 @@ class Sound:
self.stop_all_sounds()
if self.currentBgm:
self.currentBgm.pause()
if soundName not in self.sounds:
# Find all matching sound variations
matches = [name for name in self.sounds.keys()
if re.match(f"^{soundName}.*", name)]
if not matches:
return
# Pick a random variation
selected_sound = random.choice(matches)
# Create and configure the player
player = pyglet.media.Player()
player.queue(self.sounds[soundName])
player.queue(self.sounds[selected_sound])
player.volume = self.sfxVolume * self.masterVolume
# Start playback
player.play()
# Make sure to give pyglet enough cycles to start playing
startTime = time.time()
duration = self.sounds[soundName].duration
duration = self.sounds[selected_sound].duration
pyglet.clock.tick()
# Wait for completion or skip
interrupted = self.game.wait_for_completion(
lambda: not player.playing or (time.time() - startTime) >= duration