Added exit_game method that handles cleanup. A few other small modifications.
This commit is contained in:
		
							
								
								
									
										38
									
								
								__init__.py
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								__init__.py
									
									
									
									
									
								
							@@ -150,3 +150,41 @@ class pygstormgames:
 | 
			
		||||
    
 | 
			
		||||
        self.display.window.remove_handlers()
 | 
			
		||||
        return interrupted[0]
 | 
			
		||||
 | 
			
		||||
    def exit_game(self):
 | 
			
		||||
        """Closes the game cleanly"""
 | 
			
		||||
        try:
 | 
			
		||||
            self.sound.cleanup()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Error with sound cleanup: {e}")
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.display.window.remove_handlers()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Error with handler cleanup: {e}")
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.speech.cleanup()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Error with speech cleanup: {e}")
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # Try to close the window explicitly
 | 
			
		||||
            if hasattr(self.display, 'window'):
 | 
			
		||||
                self.display.window.close()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Error closing window: {e}")
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            pyglet.app.exit()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Error while exiting: {e}")
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        # Force exit if pyglet.app.exit() didn't work
 | 
			
		||||
        import sys
 | 
			
		||||
        sys.exit(0)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								display.py
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								display.py
									
									
									
									
									
								
							@@ -57,7 +57,6 @@ class Display:
 | 
			
		||||
        self.currentIndex = 0
 | 
			
		||||
        speech.speak(self.navText[self.currentIndex])
 | 
			
		||||
    
 | 
			
		||||
        # Use the game's existing input handlers
 | 
			
		||||
        validKeys = [
 | 
			
		||||
            pyglet.window.key.ESCAPE, 
 | 
			
		||||
            pyglet.window.key.RETURN,
 | 
			
		||||
@@ -131,25 +130,25 @@ class Display:
 | 
			
		||||
            
 | 
			
		||||
        self.display_text(info, speech)
 | 
			
		||||
 | 
			
		||||
def messagebox(self, text):
 | 
			
		||||
    """Display a simple message box with text.
 | 
			
		||||
    def messagebox(self, text):
 | 
			
		||||
        """Display a simple message box with text.
 | 
			
		||||
    
 | 
			
		||||
    Shows a message that can be repeated until the user chooses to continue.
 | 
			
		||||
        Shows a message that can be repeated until the user chooses to continue.
 | 
			
		||||
        
 | 
			
		||||
    Args:
 | 
			
		||||
        text (str): Message to display
 | 
			
		||||
    """
 | 
			
		||||
    text = text + "\nPress any key to repeat or enter to continue."
 | 
			
		||||
    while True:
 | 
			
		||||
        # Speak the text
 | 
			
		||||
        self.game.speech.speak(text)
 | 
			
		||||
        Args:
 | 
			
		||||
            text (str): Message to display
 | 
			
		||||
        """
 | 
			
		||||
        text = text + "\nPress any key to repeat or enter to continue."
 | 
			
		||||
        while True:
 | 
			
		||||
            # Speak the text
 | 
			
		||||
            self.game.speech.speak(text)
 | 
			
		||||
        
 | 
			
		||||
        # Wait for any key
 | 
			
		||||
        key, _ = self.game.wait()
 | 
			
		||||
            # Wait for any key
 | 
			
		||||
            key, _ = self.game.wait()
 | 
			
		||||
        
 | 
			
		||||
        # Exit if enter/escape pressed
 | 
			
		||||
        if key in (pyglet.window.key.RETURN, pyglet.window.key.ESCAPE):
 | 
			
		||||
            break
 | 
			
		||||
            # Exit if enter/escape pressed
 | 
			
		||||
            if key in (pyglet.window.key.RETURN, pyglet.window.key.ESCAPE):
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
    def get_input(self, prompt="Enter text:", defaultText=""):
 | 
			
		||||
        """Display a dialog box for text input.
 | 
			
		||||
@@ -173,5 +172,11 @@ def messagebox(self, text):
 | 
			
		||||
 | 
			
		||||
    def donate(self, speech):
 | 
			
		||||
        """Open the donation webpage."""
 | 
			
		||||
        speech.speak("Opening donation page.")
 | 
			
		||||
        speech.speak("Opening donation page. Press space or enter to continue.")
 | 
			
		||||
        webbrowser.open('https://ko-fi.com/stormux')
 | 
			
		||||
        validKeys = [
 | 
			
		||||
            pyglet.window.key.ESCAPE, 
 | 
			
		||||
            pyglet.window.key.RETURN,
 | 
			
		||||
            pyglet.window.key.SPACE
 | 
			
		||||
            ]
 | 
			
		||||
        key, _ = self.game.wait(validKeys)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								menu.py
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								menu.py
									
									
									
									
									
								
							@@ -78,11 +78,8 @@ class Menu:
 | 
			
		||||
            if key == pyglet.window.key.ESCAPE:
 | 
			
		||||
                if "exit" in options:
 | 
			
		||||
                    # Handle exit cleanup immediately
 | 
			
		||||
                    self.game.sound.cleanup()
 | 
			
		||||
                    self.game.display.window.remove_handlers()
 | 
			
		||||
                    self.game.speech.cleanup()
 | 
			
		||||
                    pyglet.app.exit()
 | 
			
		||||
                return "exit"
 | 
			
		||||
                    self.game.exit_game()
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
            if key == pyglet.window.key.HOME and self.currentIndex != 0:
 | 
			
		||||
                self.currentIndex = 0
 | 
			
		||||
@@ -126,14 +123,12 @@ class Menu:
 | 
			
		||||
                return selection
 | 
			
		||||
            elif selection == "instructions":
 | 
			
		||||
                # Pause menu music
 | 
			
		||||
                if self.game.sound.currentBgm:
 | 
			
		||||
                    self.game.sound.currentBgm.pause()
 | 
			
		||||
                self.game.sound.pause_bgm()
 | 
			
		||||
            
 | 
			
		||||
                self.game.display.instructions(self.game.speech)
 | 
			
		||||
                
 | 
			
		||||
                # Resume menu music
 | 
			
		||||
                if self.game.sound.currentBgm:
 | 
			
		||||
                    self.game.sound.currentBgm.play()
 | 
			
		||||
                self.game.sound.resume_bgm()
 | 
			
		||||
                
 | 
			
		||||
            elif selection == "learn_sounds":
 | 
			
		||||
                if self.learn_sounds() == "menu":
 | 
			
		||||
@@ -141,30 +136,23 @@ class Menu:
 | 
			
		||||
                
 | 
			
		||||
            elif selection == "credits":
 | 
			
		||||
                # Pause menu music
 | 
			
		||||
                if self.game.sound.currentBgm:
 | 
			
		||||
                    self.game.sound.currentBgm.pause()
 | 
			
		||||
                self.game.sound.pause_bgm()
 | 
			
		||||
            
 | 
			
		||||
                self.game.display.credits(self.game.speech)
 | 
			
		||||
            
 | 
			
		||||
                # Resume menu music
 | 
			
		||||
                if self.game.sound.currentBgm:
 | 
			
		||||
                    self.game.sound.currentBgm.play()
 | 
			
		||||
                self.game.sound.resume_bgm()
 | 
			
		||||
                
 | 
			
		||||
            elif selection == "donate":
 | 
			
		||||
                # Pause menu music
 | 
			
		||||
                if self.game.sound.currentBgm:
 | 
			
		||||
                    self.game.sound.currentBgm.pause()
 | 
			
		||||
                self.game.sound.pause_bgm()
 | 
			
		||||
            
 | 
			
		||||
                self.game.display.donate(self.game.speech)
 | 
			
		||||
            
 | 
			
		||||
                # Resume menu music
 | 
			
		||||
                if self.game.sound.currentBgm:
 | 
			
		||||
                    self.game.sound.currentBgm.play()
 | 
			
		||||
                self.game.sound.resume_bgm()
 | 
			
		||||
 | 
			
		||||
            elif selection == "exit":
 | 
			
		||||
                self.game.sound.cleanup()  # Stop all sounds first
 | 
			
		||||
                self.game.display.window.remove_handlers()  # Remove handlers before closing speech
 | 
			
		||||
                self.game.speech.cleanup()  # Clean up speech last
 | 
			
		||||
                self.game.exit_game()
 | 
			
		||||
 | 
			
		||||
    def learn_sounds(self):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								sound.py
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								sound.py
									
									
									
									
									
								
							@@ -82,6 +82,16 @@ class Sound:
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            print(f"Error playing background music: {e}")
 | 
			
		||||
            
 | 
			
		||||
    def pause_bgm(self):
 | 
			
		||||
        """Pause background music."""
 | 
			
		||||
        if self.currentBgm and self.currentBgm.playing:
 | 
			
		||||
            self.currentBgm.pause()
 | 
			
		||||
 | 
			
		||||
    def resume_bgm(self):
 | 
			
		||||
        """Resume background music from paused state."""
 | 
			
		||||
        if self.currentBgm and not self.currentBgm.playing:
 | 
			
		||||
            self.currentBgm.play()
 | 
			
		||||
 | 
			
		||||
    def play_sound(self, soundName, volume=1.0):
 | 
			
		||||
        """Play a sound effect with volume settings.
 | 
			
		||||
        
 | 
			
		||||
@@ -220,15 +230,7 @@ class Sound:
 | 
			
		||||
            player.cone_orientation = direction
 | 
			
		||||
            
 | 
			
		||||
    def cut_scene(self, soundName):
 | 
			
		||||
        """Play a sound as a cut scene, stopping other sounds and waiting for completion.
 | 
			
		||||
        
 | 
			
		||||
        Args:
 | 
			
		||||
            soundName (str): Name of sound to play
 | 
			
		||||
            
 | 
			
		||||
        The method will block until either:
 | 
			
		||||
        - The sound finishes playing
 | 
			
		||||
        - The user presses ESC/RETURN/SPACE (if window is provided)
 | 
			
		||||
        """
 | 
			
		||||
        """Play a sound as a cut scene, stopping other sounds and waiting for completion."""
 | 
			
		||||
        # Stop all current sounds
 | 
			
		||||
        self.stop_all_sounds()
 | 
			
		||||
        if self.currentBgm:
 | 
			
		||||
@@ -242,38 +244,13 @@ class Sound:
 | 
			
		||||
        player.queue(self.sounds[soundName])
 | 
			
		||||
        player.volume = self.sfxVolume * self.masterVolume
 | 
			
		||||
    
 | 
			
		||||
        # Flag to track if we should continue waiting
 | 
			
		||||
        shouldContinue = True
 | 
			
		||||
        
 | 
			
		||||
        def on_player_eos():
 | 
			
		||||
            nonlocal shouldContinue
 | 
			
		||||
            shouldContinue = False
 | 
			
		||||
            
 | 
			
		||||
        # Set up completion callback
 | 
			
		||||
        player.push_handlers(on_eos=on_player_eos)
 | 
			
		||||
        
 | 
			
		||||
        # Get window from game display
 | 
			
		||||
        window = self.game.display.window
 | 
			
		||||
        
 | 
			
		||||
        # If we have a window, set up key handler for skipping
 | 
			
		||||
        if window:
 | 
			
		||||
            skipKeys = [key.ESCAPE, key.RETURN, key.SPACE]
 | 
			
		||||
            
 | 
			
		||||
            @window.event
 | 
			
		||||
            def on_key_press(symbol, modifiers):
 | 
			
		||||
                nonlocal shouldContinue
 | 
			
		||||
                if symbol in skipKeys:
 | 
			
		||||
                    shouldContinue = False
 | 
			
		||||
                    return True
 | 
			
		||||
                    
 | 
			
		||||
        # Start playback
 | 
			
		||||
        player.play()
 | 
			
		||||
    
 | 
			
		||||
        # Wait for completion or skip
 | 
			
		||||
        while shouldContinue and player.playing:
 | 
			
		||||
            if window:
 | 
			
		||||
                window.dispatch_events()
 | 
			
		||||
            pyglet.clock.tick()
 | 
			
		||||
        # Wait for completion or skip using game's wait_for_completion method
 | 
			
		||||
        interrupted = self.game.wait_for_completion(
 | 
			
		||||
            lambda: not player.playing
 | 
			
		||||
        )
 | 
			
		||||
    
 | 
			
		||||
        # Ensure cleanup
 | 
			
		||||
        player.pause()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								speech.py
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								speech.py
									
									
									
									
									
								
							@@ -15,6 +15,7 @@ class Speech:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        """Initialize speech system with fallback providers."""
 | 
			
		||||
        self._lastSpoken = {"text": None, "time": 0}
 | 
			
		||||
        self._active = False
 | 
			
		||||
        self._speechDelay = 250  # ms delay between identical messages
 | 
			
		||||
        
 | 
			
		||||
        # Try to initialize speech providers in order of preference
 | 
			
		||||
@@ -22,11 +23,13 @@ class Speech:
 | 
			
		||||
            import speechd
 | 
			
		||||
            self._speech = speechd.Client()
 | 
			
		||||
            self._provider = "speechd"
 | 
			
		||||
            self._active = True
 | 
			
		||||
        except ImportError:
 | 
			
		||||
            try:
 | 
			
		||||
                import accessible_output2.outputs.auto
 | 
			
		||||
                self._speech = accessible_output2.outputs.auto.Auto()
 | 
			
		||||
                self._provider = "accessible_output2"
 | 
			
		||||
                self._active = True
 | 
			
		||||
            except ImportError:
 | 
			
		||||
                raise RuntimeError("No speech providers found. Install either speechd or accessible_output2.")
 | 
			
		||||
                
 | 
			
		||||
@@ -60,12 +63,15 @@ class Speech:
 | 
			
		||||
        self._lastSpoken["time"] = currentTime
 | 
			
		||||
        
 | 
			
		||||
        # Handle speech output based on provider
 | 
			
		||||
        if self._provider == "speechd":
 | 
			
		||||
            if interrupt:
 | 
			
		||||
                self._speech.cancel()
 | 
			
		||||
            self._speech.speak(text)
 | 
			
		||||
        else:
 | 
			
		||||
            self._speech.speak(text, interrupt=interrupt)
 | 
			
		||||
        try:
 | 
			
		||||
            if self._provider == "speechd":
 | 
			
		||||
                if interrupt:
 | 
			
		||||
                    self._speech.cancel()
 | 
			
		||||
                self._speech.speak(text)
 | 
			
		||||
            else:
 | 
			
		||||
                self._speech.speak(text, interrupt=interrupt)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
                self._active = False
 | 
			
		||||
            
 | 
			
		||||
        # Update display text
 | 
			
		||||
        self._font.text = text
 | 
			
		||||
@@ -76,6 +82,8 @@ class Speech:
 | 
			
		||||
        
 | 
			
		||||
    def cleanup(self):
 | 
			
		||||
        """Clean up speech system resources."""
 | 
			
		||||
        if not self._active:
 | 
			
		||||
            return
 | 
			
		||||
        if self._provider == "speechd":
 | 
			
		||||
            self._speech.close()
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user