Compare commits
	
		
			41 Commits
		
	
	
		
			devel
			...
			68e72f5d81
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 68e72f5d81 | ||
|  | 80fe2caff3 | ||
|  | 2df86c9c76 | ||
|  | 5fa90f9e84 | ||
|  | 658709ebce | ||
|  | d5c79c0770 | ||
|  | 24f9a126d4 | ||
|  | c316d4e570 | ||
|  | e66655d75f | ||
|  | c5406d5089 | ||
|  | b5b472eebe | ||
|  | 9f03de15b8 | ||
|  | 428a48678d | ||
|  | 9a6d6374f9 | ||
|  | df386cbbd9 | ||
|  | 38522aee78 | ||
|  | 0e9c52f5e1 | ||
|  | fabf48ff42 | ||
|  | 0c73e98876 | ||
|  | 0ef11785ec | ||
|  | 58ab5aa854 | ||
|  | 155ed6ec39 | ||
|  | 68ad08be46 | ||
|  | 536659338e | ||
|  | 37aa764d68 | ||
|  | 84a722bb8e | ||
|  | b897abf0a3 | ||
|  | 678af54346 | ||
|  | d456b8b3b3 | ||
|  | c5c32943e2 | ||
|  | 34d89ca54b | ||
|  | e8bf4f9565 | ||
|  | dd350c0285 | ||
|  | ae93e02e69 | ||
|  | 21c0795ea9 | ||
|  | 67d2315cef | ||
|  | b6afb5450e | ||
|  | 42266d4b6c | ||
|  | 54842bac29 | ||
|  | 08f06699c8 | ||
|  | 7ef11be54c | 
							
								
								
									
										942
									
								
								__init__.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										942
									
								
								__init__.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -0,0 +1,942 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| """Standard initializations and functions shared by all Storm Games. | ||||
|  | ||||
| This module provides core functionality for Storm Games including: | ||||
| - Sound and speech handling | ||||
| - Volume controls | ||||
| - Configuration management | ||||
| - Score tracking | ||||
| - GUI initialization | ||||
| - Game menu systems | ||||
| """ | ||||
|  | ||||
| from sys import exit | ||||
| import configparser | ||||
| import os | ||||
| from os import listdir | ||||
| from os.path import isfile, join | ||||
| from inspect import isfunction | ||||
| from xdg import BaseDirectory | ||||
| from setproctitle import setproctitle | ||||
| import pygame | ||||
| import pyperclip | ||||
| import random | ||||
| import re | ||||
| import requests | ||||
| import textwrap | ||||
| import webbrowser | ||||
| import math | ||||
| import numpy as np | ||||
| import time | ||||
| import wx | ||||
|  | ||||
| # Global variable for speech provider | ||||
| try: | ||||
|     import speechd | ||||
|     spd = speechd.Client() | ||||
|     speechProvider = "speechd" | ||||
| except ImportError: | ||||
|     import accessible_output2.outputs.auto | ||||
|     s = accessible_output2.outputs.auto.Auto() | ||||
|     speechProvider = "accessible_output2" | ||||
| except ImportError: | ||||
|     print("No other speech providers found.") | ||||
|     exit() | ||||
|  | ||||
| # Configuration objects | ||||
| localConfig = configparser.ConfigParser() | ||||
| globalConfig = configparser.ConfigParser() | ||||
|  | ||||
| # Volume control globals | ||||
| bgmVolume = 0.75  # Default background music volume | ||||
| sfxVolume = 1.0   # Default sound effects volume | ||||
| masterVolume = 1.0  # Default master volume | ||||
|  | ||||
| class Scoreboard: | ||||
|     """Handles score tracking and top 10 high scores for games. | ||||
|      | ||||
|     This class manages the scoring system including: | ||||
|     - Score tracking | ||||
|     - High score storage | ||||
|     - Score updates and persistence | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, starting_score=0): | ||||
|         """Initialize a new scoreboard with optional starting score. | ||||
|          | ||||
|         Args: | ||||
|             starting_score (int): Initial score value (default: 0) | ||||
|         """ | ||||
|         read_config() | ||||
|         try: | ||||
|             localConfig.add_section("scoreboard") | ||||
|         except: | ||||
|             pass | ||||
|         self.score = starting_score | ||||
|         self.old_scores = [] | ||||
|         for i in range(1, 11): | ||||
|             try: | ||||
|                 self.old_scores.insert(i - 1, localConfig.getint("scoreboard", str(i))) | ||||
|             except: | ||||
|                 self.old_scores.insert(i - 1, 0) | ||||
|         for i in range(1, 11): | ||||
|             if self.old_scores[i - 1] is None: | ||||
|                 self.old_scores[i - 1] = 0 | ||||
|  | ||||
|     def __del__(self): | ||||
|         """Save scores when object is destroyed.""" | ||||
|         self.update_scores() | ||||
|         try: | ||||
|             write_config() | ||||
|         except: | ||||
|             pass | ||||
|  | ||||
|     def decrease_score(self, points=1): | ||||
|         """Decrease the current score. | ||||
|          | ||||
|         Args: | ||||
|             points (int): Number of points to decrease (default: 1) | ||||
|         """ | ||||
|         self.score -= points | ||||
|  | ||||
|     def get_high_score(self, position=1): | ||||
|         """Get a high score at specified position. | ||||
|          | ||||
|         Args: | ||||
|             position (int): Position in high score list (1-10, default: 1) | ||||
|              | ||||
|         Returns: | ||||
|             int: Score at specified position | ||||
|         """ | ||||
|         return self.old_scores[position - 1] | ||||
|  | ||||
|     def get_score(self): | ||||
|         """Get current score. | ||||
|          | ||||
|         Returns: | ||||
|             int: Current score | ||||
|         """ | ||||
|         return self.score | ||||
|  | ||||
|     def increase_score(self, points=1): | ||||
|         """Increase the current score. | ||||
|          | ||||
|         Args: | ||||
|             points (int): Number of points to increase (default: 1) | ||||
|         """ | ||||
|         self.score += points | ||||
|  | ||||
|     def new_high_score(self): | ||||
|         """Check if current score qualifies as a new high score. | ||||
|          | ||||
|         Returns: | ||||
|             int: Position of new high score (1-10), or None if not a high score | ||||
|         """ | ||||
|         for i, j in enumerate(self.old_scores): | ||||
|             if self.score > j: | ||||
|                 return i + 1 | ||||
|         return None | ||||
|  | ||||
|     def update_scores(self): | ||||
|         """Update the high score list with current score if qualified.""" | ||||
|         # Update the scores | ||||
|         for i, j in enumerate(self.old_scores): | ||||
|             if self.score > j: | ||||
|                 self.old_scores.insert(i, self.score) | ||||
|                 break | ||||
|         # Only keep the top 10 scores | ||||
|         self.old_scores = self.old_scores[:10] | ||||
|         # Update the scoreboard section of the games config file | ||||
|         for i, j in enumerate(self.old_scores): | ||||
|             localConfig.set("scoreboard", str(i + 1), str(j)) | ||||
|  | ||||
| def write_config(write_global=False): | ||||
|     """Write configuration to file. | ||||
|      | ||||
|     Args: | ||||
|         write_global (bool): If True, write to global config, otherwise local (default: False) | ||||
|     """ | ||||
|     if not write_global: | ||||
|         with open(gamePath + "/config.ini", 'w') as configfile: | ||||
|             localConfig.write(configfile) | ||||
|     else: | ||||
|         with open(globalPath + "/config.ini", 'w') as configfile: | ||||
|             globalConfig.write(configfile) | ||||
|  | ||||
| def read_config(read_global=False): | ||||
|     """Read configuration from file. | ||||
|      | ||||
|     Args: | ||||
|         read_global (bool): If True, read global config, otherwise local (default: False) | ||||
|     """ | ||||
|     if not read_global: | ||||
|         try: | ||||
|             with open(gamePath + "/config.ini", 'r') as configfile: | ||||
|                 localConfig.read_file(configfile) | ||||
|         except: | ||||
|             pass | ||||
|     else: | ||||
|         try: | ||||
|             with open(globalPath + "/config.ini", 'r') as configfile: | ||||
|                 globalConfig.read_file(configfile) | ||||
|         except: | ||||
|             pass | ||||
|  | ||||
| def initialize_gui(gameTitle): | ||||
|     """Initialize the game GUI and sound system. | ||||
|      | ||||
|     Args: | ||||
|         gameTitle (str): Title of the game | ||||
|          | ||||
|     Returns: | ||||
|         dict: Dictionary of loaded sound objects | ||||
|     """ | ||||
|     # Check for, and possibly create, storm-games path     | ||||
|     global globalPath | ||||
|     global gamePath | ||||
|     global gameName | ||||
|      | ||||
|     globalPath = BaseDirectory.xdg_config_home + "/storm-games" | ||||
|     gamePath = globalPath + "/" + str.lower(str.replace(gameTitle, " ", "-")) | ||||
|     if not os.path.exists(gamePath): | ||||
|         os.makedirs(gamePath) | ||||
|          | ||||
|     # Seed the random generator to the clock | ||||
|     random.seed() | ||||
|      | ||||
|     # Set game's name | ||||
|     gameName = gameTitle | ||||
|     setproctitle(str.lower(str.replace(gameTitle, " ", ""))) | ||||
|      | ||||
|     # Initialize pygame | ||||
|     pygame.init() | ||||
|     pygame.display.set_mode((800, 600)) | ||||
|     pygame.display.set_caption(gameTitle) | ||||
|      | ||||
|     # 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 files | ||||
|     try: | ||||
|         soundFiles = [f for f in listdir("sounds/")  | ||||
|                       if isfile(join("sounds/", f))  | ||||
|                       and (f.split('.')[1].lower() in ["ogg", "wav"])] | ||||
|     except Exception as e: | ||||
|         print("No sounds found.") | ||||
|         speak("No sounds found.", False) | ||||
|         soundFiles = [] | ||||
|      | ||||
|     # Create dictionary of sound objects | ||||
|     soundData = {} | ||||
|     for f in soundFiles: | ||||
|         soundData[f.split('.')[0]] = pygame.mixer.Sound("sounds/" + f) | ||||
|      | ||||
|     # Play intro sound if available | ||||
|     if 'game-intro' in soundData: | ||||
|         soundData['game-intro'].play() | ||||
|         time.sleep(soundData['game-intro'].get_length()) | ||||
|      | ||||
|     return soundData | ||||
|  | ||||
| def adjust_master_volume(change): | ||||
|     """Adjust the master volume for all sounds. | ||||
|      | ||||
|     Args: | ||||
|         change (float): Amount to change volume by (positive or negative) | ||||
|     """ | ||||
|     global masterVolume | ||||
|     masterVolume = max(0.0, min(1.0, masterVolume + change)) | ||||
|     # Update music volume | ||||
|     if pygame.mixer.music.get_busy(): | ||||
|         pygame.mixer.music.set_volume(bgmVolume * masterVolume) | ||||
|     # Update all sound channels | ||||
|     for i in range(pygame.mixer.get_num_channels()): | ||||
|         channel = pygame.mixer.Channel(i) | ||||
|         if channel.get_busy(): | ||||
|             current_volume = channel.get_volume() | ||||
|             if isinstance(current_volume, (int, float)): | ||||
|                 # Mono audio | ||||
|                 channel.set_volume(current_volume * masterVolume) | ||||
|             else: | ||||
|                 # Stereo audio | ||||
|                 left, right = current_volume | ||||
|                 channel.set_volume(left * masterVolume, right * masterVolume) | ||||
|  | ||||
| def adjust_bgm_volume(change): | ||||
|     """Adjust only the background music volume. | ||||
|      | ||||
|     Args: | ||||
|         change (float): Amount to change volume by (positive or negative) | ||||
|     """ | ||||
|     global bgmVolume | ||||
|     bgmVolume = max(0.0, min(1.0, bgmVolume + change)) | ||||
|     if pygame.mixer.music.get_busy(): | ||||
|         pygame.mixer.music.set_volume(bgmVolume * masterVolume) | ||||
|  | ||||
| def adjust_sfx_volume(change): | ||||
|     """Adjust volume for sound effects only. | ||||
|      | ||||
|     Args: | ||||
|         change (float): Amount to change volume by (positive or negative) | ||||
|     """ | ||||
|     global sfxVolume | ||||
|     sfxVolume = max(0.0, min(1.0, sfxVolume + change)) | ||||
|     # Update all sound channels except reserved ones | ||||
|     for i in range(pygame.mixer.get_num_channels()): | ||||
|         channel = pygame.mixer.Channel(i) | ||||
|         if channel.get_busy(): | ||||
|             current_volume = channel.get_volume() | ||||
|             if isinstance(current_volume, (int, float)): | ||||
|                 # Mono audio | ||||
|                 channel.set_volume(current_volume * sfxVolume * masterVolume) | ||||
|             else: | ||||
|                 # Stereo audio | ||||
|                 left, right = current_volume | ||||
|                 channel.set_volume(left * sfxVolume * masterVolume,  | ||||
|                                  right * sfxVolume * masterVolume) | ||||
|  | ||||
| def adjust_bgm_volume(change): | ||||
|     """Adjust only the background music volume. | ||||
|  | ||||
|     Args: | ||||
|         change (float): Amount to change volume by (positive or negative) | ||||
|     """ | ||||
|     global bgmVolume | ||||
|     bgmVolume = max(0.0, min(1.0, bgmVolume + change)) | ||||
|     if pygame.mixer.music.get_busy(): | ||||
|         pygame.mixer.music.set_volume(bgmVolume * masterVolume) | ||||
|  | ||||
| def play_bgm(music_file): | ||||
|     """Play background music with proper volume settings. | ||||
|      | ||||
|     Args: | ||||
|         music_file (str): Path to the music file to play | ||||
|     """ | ||||
|     try: | ||||
|         pygame.mixer.music.stop() | ||||
|         pygame.mixer.music.load(music_file) | ||||
|         pygame.mixer.music.set_volume(bgmVolume * masterVolume) | ||||
|         pygame.mixer.music.play(-1)  # Loop indefinitely | ||||
|     except Exception as e: | ||||
|         pass | ||||
|  | ||||
| def get_input(prompt="Enter text:", text=""): | ||||
|     """Display a dialog box for text input. | ||||
|      | ||||
|     Args: | ||||
|         prompt (str): Prompt text to display (default: "Enter text:") | ||||
|         text (str): Initial text in input box (default: "") | ||||
|          | ||||
|     Returns: | ||||
|         str: User input text, or None if cancelled | ||||
|     """ | ||||
|     app = wx.App(False) | ||||
|     dialog = wx.TextEntryDialog(None, prompt, "Input", text) | ||||
|     dialog.SetValue(text) | ||||
|     if dialog.ShowModal() == wx.ID_OK: | ||||
|         userInput = dialog.GetValue() | ||||
|     else: | ||||
|         userInput = None | ||||
|     dialog.Destroy() | ||||
|     return userInput | ||||
|  | ||||
| def speak(text, interrupt=True): | ||||
|     """Speak text using the configured speech provider and display on screen. | ||||
|      | ||||
|     Args: | ||||
|         text (str): Text to speak and display | ||||
|         interrupt (bool): Whether to interrupt current speech (default: True) | ||||
|     """ | ||||
|     if speechProvider == "speechd": | ||||
|         if interrupt: | ||||
|             spd.cancel() | ||||
|         spd.say(text) | ||||
|     else: | ||||
|         if speechProvider == "accessible_output2": | ||||
|             s.speak(text, interrupt=interrupt) | ||||
|      | ||||
|     # Display the text on screen | ||||
|     screen = pygame.display.get_surface() | ||||
|     font = pygame.font.Font(None, 36) | ||||
|     # Wrap the text | ||||
|     max_width = screen.get_width() - 40  # Leave a 20-pixel margin on each side | ||||
|     wrapped_text = textwrap.wrap(text, width=max_width // font.size('A')[0]) | ||||
|     # Render each line | ||||
|     text_surfaces = [font.render(line, True, (255, 255, 255)) for line in wrapped_text] | ||||
|     screen.fill((0, 0, 0))  # Clear screen with black | ||||
|     # Calculate total height of text block | ||||
|     total_height = sum(surface.get_height() for surface in text_surfaces) | ||||
|     # Start y-position (centered vertically) | ||||
|     currentY = (screen.get_height() - total_height) // 2 | ||||
|     # Blit each line of text | ||||
|     for surface in text_surfaces: | ||||
|         text_rect = surface.get_rect(center=(screen.get_width() // 2, currentY + surface.get_height() // 2)) | ||||
|         screen.blit(surface, text_rect) | ||||
|         currentY += surface.get_height() | ||||
|     pygame.display.flip() | ||||
|  | ||||
| def check_for_exit(): | ||||
|     """Check if user has pressed escape key. | ||||
|      | ||||
|     Returns: | ||||
|         bool: True if escape was pressed, False otherwise | ||||
|     """ | ||||
|     for event in pygame.event.get(): | ||||
|         if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: | ||||
|             return True | ||||
|         return False | ||||
|     pygame.event.pump() | ||||
|  | ||||
| def exit_game(): | ||||
|     """Clean up and exit the game.""" | ||||
|     if speechProvider == "speechd": | ||||
|         spd.close() | ||||
|     pygame.mixer.music.stop() | ||||
|     pygame.quit() | ||||
|     exit() | ||||
|  | ||||
| def calculate_volume_and_pan(player_pos, obj_pos): | ||||
|     """Calculate volume and stereo panning based on relative positions. | ||||
|      | ||||
|     Args: | ||||
|         player_pos (float): Player's position on x-axis | ||||
|         obj_pos (float): Object's position on x-axis | ||||
|          | ||||
|     Returns: | ||||
|         tuple: (volume, left_vol, right_vol) values between 0 and 1 | ||||
|     """ | ||||
|     distance = abs(player_pos - obj_pos) | ||||
|     max_distance = 12  # Maximum audible distance | ||||
|      | ||||
|     if distance > max_distance: | ||||
|         return 0, 0, 0  # No sound if out of range | ||||
|          | ||||
|     # Calculate volume (non-linear scaling for more noticeable changes) | ||||
|     # Apply masterVolume as the maximum possible volume | ||||
|     volume = (((max_distance - distance) / max_distance) ** 1.5) * masterVolume | ||||
|      | ||||
|     # Determine left/right based on relative position | ||||
|     if player_pos < obj_pos: | ||||
|         # Object is to the right | ||||
|         left = max(0, 1 - (obj_pos - player_pos) / max_distance) | ||||
|         right = 1 | ||||
|     elif player_pos > obj_pos: | ||||
|         # Object is to the left | ||||
|         left = 1 | ||||
|         right = max(0, 1 - (player_pos - obj_pos) / max_distance) | ||||
|     else: | ||||
|         # Player is on the object | ||||
|         left = right = 1 | ||||
|          | ||||
|     return volume, left, right | ||||
|  | ||||
| def obj_play(sounds, soundName, player_pos, obj_pos, loop=True): | ||||
|     """Play a sound with positional audio. | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         soundName (str): Name of sound to play | ||||
|         player_pos (float): Player's position for audio panning | ||||
|         obj_pos (float): Object's position for audio panning | ||||
|         loop (bool): Whether to loop the sound (default: True) | ||||
|          | ||||
|     Returns: | ||||
|         pygame.mixer.Channel: Sound channel object, or None if out of range | ||||
|     """ | ||||
|     volume, left, right = calculate_volume_and_pan(player_pos, obj_pos) | ||||
|     if volume == 0: | ||||
|         return None  # Don't play if out of range | ||||
|          | ||||
|     # Play the sound on a new channel | ||||
|     channel = sounds[soundName].play(-1 if loop else 0) | ||||
|     if channel: | ||||
|         channel.set_volume(volume * left * sfxVolume, | ||||
|             volume * right * sfxVolume) | ||||
|     return channel | ||||
|  | ||||
| def obj_update(channel, player_pos, obj_pos): | ||||
|     """Update positional audio for a playing sound. | ||||
|      | ||||
|     Args: | ||||
|         channel: Sound channel to update | ||||
|         player_pos (float): New player position | ||||
|         obj_pos (float): New object position | ||||
|          | ||||
|     Returns: | ||||
|         pygame.mixer.Channel: Updated channel, or None if sound should stop | ||||
|     """ | ||||
|     if channel is None: | ||||
|         return None | ||||
|          | ||||
|     volume, left, right = calculate_volume_and_pan(player_pos, obj_pos) | ||||
|     if volume == 0: | ||||
|         channel.stop() | ||||
|         return None | ||||
|          | ||||
|     # Apply the volume and pan | ||||
|     channel.set_volume(volume * left * sfxVolume, | ||||
|         volume * right * sfxVolume) | ||||
|     return channel | ||||
|  | ||||
| def obj_stop(channel): | ||||
|     """Stop a playing sound channel. | ||||
|      | ||||
|     Args: | ||||
|         channel: Sound channel to stop | ||||
|          | ||||
|     Returns: | ||||
|         None if stopped successfully, otherwise returns original channel | ||||
|     """ | ||||
|     try: | ||||
|         channel.stop() | ||||
|         return None | ||||
|     except: | ||||
|         return channel | ||||
|  | ||||
| def play_sound(sound, volume=1.0): | ||||
|     """Play a sound with current volume settings applied. | ||||
|      | ||||
|     Args: | ||||
|         sound: pygame Sound object to play | ||||
|         volume: base volume for the sound (0.0-1.0, default: 1.0) | ||||
|          | ||||
|     Returns: | ||||
|         pygame.mixer.Channel: The channel the sound is playing on | ||||
|     """ | ||||
|     channel = sound.play() | ||||
|     if channel: | ||||
|         channel.set_volume(volume * sfxVolume * masterVolume) | ||||
|     return channel | ||||
|  | ||||
| def play_ambiance(sounds, soundNames, probability, randomLocation=False): | ||||
|     """Play random ambient sounds with optional positional audio. | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         soundNames (list): List of possible sound names to choose from | ||||
|         probability (int): Chance to play (1-100) | ||||
|         randomLocation (bool): Whether to randomize stereo position | ||||
|          | ||||
|     Returns: | ||||
|         pygame.mixer.Channel: Sound channel if played, None otherwise | ||||
|     """ | ||||
|     # Check if any of the sounds in the list is already playing | ||||
|     for soundName in soundNames: | ||||
|         if pygame.mixer.find_channel(True) and pygame.mixer.find_channel(True).get_busy(): | ||||
|             return None | ||||
|              | ||||
|     if random.randint(1, 100) > probability: | ||||
|         return None | ||||
|          | ||||
|     # Choose a random sound from the list | ||||
|     ambianceSound = random.choice(soundNames) | ||||
|     channel = sounds[ambianceSound].play() | ||||
|      | ||||
|     if randomLocation and channel: | ||||
|         leftVolume = random.random() * sfxVolume * masterVolume | ||||
|         rightVolume = random.random() * sfxVolume * masterVolume | ||||
|         channel.set_volume(leftVolume, rightVolume) | ||||
|          | ||||
|     return channel | ||||
|  | ||||
| def play_random(sounds, soundName, pause=False, interrupt=False): | ||||
|     """Play a random variation of a sound. | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         soundName (str): Base name of sound (will match all starting with this) | ||||
|         pause (bool): Whether to pause execution until sound finishes | ||||
|         interrupt (bool): Whether to interrupt other sounds | ||||
|     """ | ||||
|     key = [] | ||||
|     for i in sounds.keys(): | ||||
|         if re.match("^" + soundName + ".*", i): | ||||
|             key.append(i) | ||||
|              | ||||
|     if not key:  # No matching sounds found | ||||
|         return | ||||
|          | ||||
|     randomKey = random.choice(key) | ||||
|      | ||||
|     if interrupt: | ||||
|         cut_scene(sounds, randomKey) | ||||
|         return | ||||
|      | ||||
|     channel = sounds[randomKey].play() | ||||
|     if channel: | ||||
|         channel.set_volume(sfxVolume * masterVolume, sfxVolume * masterVolume) | ||||
|      | ||||
|     if pause: | ||||
|         time.sleep(sounds[randomKey].get_length()) | ||||
|  | ||||
| def play_random_positional(sounds, soundName, player_x, object_x): | ||||
|     """Play a random variation of a sound with positional audio. | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         soundName (str): Base name of sound to match | ||||
|         player_x (float): Player's x position | ||||
|         object_x (float): Object's x position | ||||
|          | ||||
|     Returns: | ||||
|         pygame.mixer.Channel: Sound channel if played, None otherwise | ||||
|     """ | ||||
|     keys = [k for k in sounds.keys() if k.startswith(soundName)] | ||||
|     if not keys: | ||||
|         return None | ||||
|      | ||||
|     randomKey = random.choice(keys) | ||||
|     volume, left, right = calculate_volume_and_pan(player_x, object_x) | ||||
|      | ||||
|     if volume == 0: | ||||
|         return None | ||||
|          | ||||
|     channel = sounds[randomKey].play() | ||||
|     if channel: | ||||
|         channel.set_volume(volume * left * sfxVolume, | ||||
|             volume * right * sfxVolume) | ||||
|     return channel | ||||
|  | ||||
| def cut_scene(sounds, soundName): | ||||
|     """Play a sound as a cut scene, stopping other sounds. | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         soundName (str): Name of sound to play | ||||
|     """ | ||||
|     pygame.event.clear() | ||||
|     pygame.mixer.stop() | ||||
|     channel = pygame.mixer.Channel(0) | ||||
|     channel.play(sounds[soundName]) | ||||
|     while pygame.mixer.get_busy(): | ||||
|         event = pygame.event.poll() | ||||
|         if event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_RETURN, pygame.K_SPACE]: | ||||
|             pygame.mixer.stop() | ||||
|         pygame.event.pump() | ||||
|  | ||||
| def play_random_falling(sounds, soundName, player_x, object_x, start_y,  | ||||
|                        currentY=0, max_y=20, existing_channel=None): | ||||
|     """Play or update a falling sound with positional audio and volume based on height. | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         soundName (str): Base name of sound to match | ||||
|         player_x (float): Player's x position | ||||
|         object_x (float): Object's x position | ||||
|         start_y (float): Starting Y position (0-20, higher = quieter start) | ||||
|         currentY (float): Current Y position (0 = ground level) (default: 0) | ||||
|         max_y (float): Maximum Y value (default: 20) | ||||
|         existing_channel: Existing sound channel to update (default: None) | ||||
|          | ||||
|     Returns: | ||||
|         pygame.mixer.Channel: Sound channel for updating position/volume, | ||||
|                             or None if sound should stop | ||||
|     """ | ||||
|     # Calculate horizontal positioning | ||||
|     volume, left, right = calculate_volume_and_pan(player_x, object_x) | ||||
|      | ||||
|     # Calculate vertical fall volume multiplier (0 at max_y, 1 at y=0) | ||||
|     fallMultiplier = 1 - (currentY / max_y) | ||||
|      | ||||
|     # Adjust final volumes | ||||
|     finalVolume = volume * fallMultiplier | ||||
|     finalLeft = left * finalVolume | ||||
|     finalRight = right * finalVolume | ||||
|      | ||||
|     if existing_channel is not None: | ||||
|         if volume == 0:  # Out of audible range | ||||
|             existing_channel.stop() | ||||
|             return None | ||||
|         existing_channel.set_volume(finalLeft * sfxVolume, | ||||
|             finalRight * sfxVolume) | ||||
|         return existing_channel | ||||
|     else:  # Need to create new channel | ||||
|         if volume == 0:  # Don't start if out of range | ||||
|             return None | ||||
|              | ||||
|         # Find matching sound files | ||||
|         keys = [k for k in sounds.keys() if k.startswith(soundName)] | ||||
|         if not keys: | ||||
|             return None | ||||
|              | ||||
|         randomKey = random.choice(keys) | ||||
|         channel = sounds[randomKey].play() | ||||
|         if channel: | ||||
|             channel.set_volume(finalLeft * sfxVolume, | ||||
|                 finalRight * sfxVolume) | ||||
|         return channel | ||||
|  | ||||
| def display_text(text): | ||||
|     """Display and speak text with navigation controls. | ||||
|      | ||||
|     Allows users to: | ||||
|     - Navigate text line by line with arrow keys | ||||
|     - Listen to full text with space | ||||
|     - Copy current line or full text | ||||
|     - Exit with enter/escape | ||||
|      | ||||
|     Args: | ||||
|         text (list): List of text lines to display | ||||
|     """ | ||||
|     currentIndex = 0 | ||||
|     text.insert(0, "Press space to read the whole text. Use up and down arrows to navigate " | ||||
|                   "the text line by line. Press c to copy the current line to the clipboard " | ||||
|                   "or t to copy the entire text. Press enter or escape when you are done reading.") | ||||
|     text.append("End of text.") | ||||
|     speak(text[currentIndex]) | ||||
|      | ||||
|     while True: | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             if event.key in (pygame.K_ESCAPE, pygame.K_RETURN): | ||||
|                 return | ||||
|                  | ||||
|             if event.key == pygame.K_DOWN and currentIndex < len(text) - 1: | ||||
|                 currentIndex += 1 | ||||
|                 speak(text[currentIndex]) | ||||
|                  | ||||
|             if event.key == pygame.K_UP and currentIndex > 0: | ||||
|                 currentIndex -= 1 | ||||
|                 speak(text[currentIndex]) | ||||
|                  | ||||
|             if event.key == pygame.K_SPACE: | ||||
|                 speak(' '.join(text[1:])) | ||||
|                  | ||||
|             if event.key == pygame.K_c: | ||||
|                 try: | ||||
|                     pyperclip.copy(text[currentIndex]) | ||||
|                     speak("Copied " + text[currentIndex] + " to the clipboard.") | ||||
|                 except: | ||||
|                     speak("Failed to copy the text to the clipboard.") | ||||
|                      | ||||
|             if event.key == pygame.K_t: | ||||
|                 try: | ||||
|                     pyperclip.copy(' '.join(text[1:-1])) | ||||
|                     speak("Copied entire message to the clipboard.") | ||||
|                 except: | ||||
|                     speak("Failed to copy the text to the clipboard.") | ||||
|                      | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|  | ||||
| def messagebox(text): | ||||
|     """Display a simple message box with text. | ||||
|      | ||||
|     Shows a message that can be repeated until the user chooses to continue. | ||||
|      | ||||
|     Args: | ||||
|         text (str): Message to display | ||||
|     """ | ||||
|     speak(text + "\nPress any key to repeat or enter to continue.") | ||||
|     while True: | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             if event.key in (pygame.K_ESCAPE, pygame.K_RETURN): | ||||
|                 return | ||||
|             speak(text + "\nPress any key to repeat or enter to continue.") | ||||
|  | ||||
| def instructions(): | ||||
|     """Display game instructions from file. | ||||
|      | ||||
|     Reads and displays instructions from 'files/instructions.txt'. | ||||
|     If file is missing, displays an error message. | ||||
|     """ | ||||
|     try: | ||||
|         with open('files/instructions.txt', 'r') as f: | ||||
|             info = f.readlines() | ||||
|     except: | ||||
|         info = ["Instructions file is missing."] | ||||
|     display_text(info) | ||||
|  | ||||
| def credits(): | ||||
|     """Display game credits from file. | ||||
|      | ||||
|     Reads and displays credits from 'files/credits.txt'. | ||||
|     Adds game name header before displaying. | ||||
|     If file is missing, displays an error message. | ||||
|     """ | ||||
|     try: | ||||
|         with open('files/credits.txt', 'r') as f: | ||||
|             info = f.readlines() | ||||
|         # Add the header | ||||
|         info.insert(0, gameName + ": brought to you by Storm Dragon") | ||||
|     except: | ||||
|         info = ["Credits file is missing."] | ||||
|     display_text(info) | ||||
|  | ||||
| def learn_sounds(sounds): | ||||
|     """Interactive menu for learning game sounds. | ||||
|      | ||||
|     Allows users to: | ||||
|     - Navigate through available sounds | ||||
|     - Play selected sounds | ||||
|     - Return to menu with escape key | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of available sound objects | ||||
|          | ||||
|     Returns: | ||||
|         str: "menu" if user exits with escape | ||||
|     """ | ||||
|     loop = True | ||||
|     pygame.mixer.music.pause() | ||||
|     currentIndex = 0 | ||||
|      | ||||
|     # Get list of available sounds, excluding special sounds | ||||
|     soundFiles = [f for f in listdir("sounds/") | ||||
|                    if isfile(join("sounds/", f)) | ||||
|                    and (f.split('.')[1].lower() in ["ogg", "wav"]) | ||||
|                    and (f.split('.')[0].lower() not in ["game-intro", "music_menu"]) | ||||
|                    and (not f.lower().startswith("_"))] | ||||
|      | ||||
|     # Track last spoken index to avoid repetition | ||||
|     lastSpoken = -1 | ||||
|      | ||||
|     while loop: | ||||
|         if currentIndex != lastSpoken: | ||||
|             speak(soundFiles[currentIndex][:-4]) | ||||
|             lastSpoken = currentIndex | ||||
|              | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             if event.key == pygame.K_ESCAPE: | ||||
|                 return "menu" | ||||
|                  | ||||
|             if event.key == pygame.K_DOWN and currentIndex < len(soundFiles) - 1: | ||||
|                 pygame.mixer.stop() | ||||
|                 currentIndex += 1 | ||||
|                  | ||||
|             if event.key == pygame.K_UP and currentIndex > 0: | ||||
|                 pygame.mixer.stop() | ||||
|                 currentIndex -= 1 | ||||
|                  | ||||
|             if event.key == pygame.K_RETURN: | ||||
|                 try: | ||||
|                     soundName = soundFiles[currentIndex][:-4] | ||||
|                     pygame.mixer.stop() | ||||
|                     sounds[soundName].play() | ||||
|                 except: | ||||
|                     lastSpoken = -1 | ||||
|                     speak("Could not play sound.") | ||||
|                      | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|  | ||||
| def game_menu(sounds, *options): | ||||
|     """Display and handle the main game menu. | ||||
|      | ||||
|     Provides menu navigation with: | ||||
|     - Up/Down arrows for selection | ||||
|     - Home/End for first/last option | ||||
|     - Enter to select | ||||
|     - Escape to exit | ||||
|     - Volume controls for all options | ||||
|      | ||||
|     Args: | ||||
|         sounds (dict): Dictionary of sound objects | ||||
|         *options: Variable list of menu options | ||||
|          | ||||
|     Returns: | ||||
|         str: Selected menu option if not handled internally | ||||
|     """ | ||||
|     loop = True | ||||
|     pygame.mixer.stop() | ||||
|      | ||||
|     if pygame.mixer.music.get_busy(): | ||||
|         pygame.mixer.music.unpause() | ||||
|     else: | ||||
|         try: | ||||
|             play_bgm("sounds/music_menu.ogg") | ||||
|         except: | ||||
|             pass | ||||
|              | ||||
|     currentIndex = 0 | ||||
|     lastSpoken = -1  # Track last spoken index | ||||
|      | ||||
|     while loop: | ||||
|         if currentIndex != lastSpoken: | ||||
|             speak(options[currentIndex]) | ||||
|             lastSpoken = currentIndex | ||||
|              | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             # Volume controls | ||||
|             if event.key == pygame.K_PAGEUP: | ||||
|                 adjust_master_volume(0.1) | ||||
|             elif event.key == pygame.K_PAGEDOWN: | ||||
|                 adjust_master_volume(-0.1) | ||||
|             elif event.key == pygame.K_HOME: | ||||
|                 if currentIndex != 0: | ||||
|                     currentIndex = 0 | ||||
|                     try: | ||||
|                         sounds['menu-move'].play() | ||||
|                     except: | ||||
|                         pass | ||||
|                     if options[currentIndex] != "donate": | ||||
|                         pygame.mixer.music.unpause() | ||||
|                 else: | ||||
|                     adjust_bgm_volume(0.1) | ||||
|             elif event.key == pygame.K_END: | ||||
|                 if currentIndex != len(options) - 1: | ||||
|                     currentIndex = len(options) - 1 | ||||
|                     try: | ||||
|                         sounds['menu-move'].play() | ||||
|                     except: | ||||
|                         pass | ||||
|                     if options[currentIndex] != "donate": | ||||
|                         pygame.mixer.music.unpause() | ||||
|                 else: | ||||
|                     adjust_bgm_volume(-0.1) | ||||
|             elif event.key == pygame.K_INSERT: | ||||
|                 adjust_sfx_volume(0.1) | ||||
|             elif event.key == pygame.K_DELETE: | ||||
|                 adjust_sfx_volume(-0.1) | ||||
|             # Menu navigation | ||||
|             elif event.key == pygame.K_ESCAPE: | ||||
|                 exit_game() | ||||
|             elif event.key == pygame.K_DOWN and currentIndex < len(options) - 1: | ||||
|                 currentIndex += 1 | ||||
|                 try: | ||||
|                     sounds['menu-move'].play() | ||||
|                 except: | ||||
|                     pass | ||||
|                 if options[currentIndex] != "donate": | ||||
|                     pygame.mixer.music.unpause() | ||||
|             elif event.key == pygame.K_UP and currentIndex > 0: | ||||
|                 currentIndex -= 1 | ||||
|                 try: | ||||
|                     sounds['menu-move'].play() | ||||
|                 except: | ||||
|                     pass | ||||
|                 if options[currentIndex] != "donate": | ||||
|                     pygame.mixer.music.unpause() | ||||
|             elif event.key == pygame.K_RETURN: | ||||
|                 try: | ||||
|                     lastSpoken = -1 | ||||
|                     try: | ||||
|                         sounds['menu-select'].play() | ||||
|                         time.sleep(sounds['menu-select'].get_length()) | ||||
|                     except: | ||||
|                         pass | ||||
|                     eval(options[currentIndex] + "()") | ||||
|                 except: | ||||
|                     lastSpoken = -1 | ||||
|                     return options[currentIndex] | ||||
|                      | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|  | ||||
| def donate(): | ||||
|     """Open the donation webpage. | ||||
|      | ||||
|     Pauses background music and opens the Ko-fi donation page. | ||||
|     """ | ||||
|     pygame.mixer.music.pause() | ||||
|     webbrowser.open('https://ko-fi.com/stormux') | ||||
|   | ||||
							
								
								
									
										273
									
								
								libstormgames.py
									
									
									
									
									
								
							
							
						
						
									
										273
									
								
								libstormgames.py
									
									
									
									
									
								
							| @@ -1,273 +0,0 @@ | ||||
| #!/bin/python | ||||
| # -*- coding: utf-8 -*- | ||||
| """Standard initializations and functions shared by all games.""" | ||||
|  | ||||
| from sys import exit | ||||
| import configparser | ||||
| import os | ||||
| from os import listdir | ||||
| from os.path import isfile, join | ||||
| from inspect import isfunction | ||||
| from xdg import BaseDirectory | ||||
| from setproctitle import setproctitle | ||||
| import pygame | ||||
| import pyperclip | ||||
| import random | ||||
| import re | ||||
| import requests | ||||
| import webbrowser | ||||
| # Global variable for speech provider | ||||
| try: | ||||
|     import speechd | ||||
|     spd = speechd.Client() | ||||
|     speechProvider = "speechd" | ||||
| except ImportError: | ||||
|     import accessible_output2.outputs.auto | ||||
|     s = accessible_output2.outputs.auto.Auto() | ||||
|     speechProvider = "accessible_output2" | ||||
| except ImportError: | ||||
|     print("No other speech providers found.") | ||||
|     exit() | ||||
|  | ||||
| import time | ||||
|  | ||||
| localConfig = configparser.ConfigParser() | ||||
| globalConfig = configparser.ConfigParser() | ||||
|  | ||||
| class scoreboard(): | ||||
|     'Handles scores and top 10' | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.oldScores = [] | ||||
|         for i in range(9): | ||||
|             try: | ||||
|                 self.oldScores[i] = read_config("scoreboard", i) | ||||
|             except: | ||||
|                 self.oldScores[i] = 0 | ||||
|  | ||||
|  | ||||
| def write_config(writeGlobal = False): | ||||
|     if writeGlobal == False: | ||||
|         with open(gamePath + "config.ini", 'w') as configfile: | ||||
|             localConfig.write(configfile) | ||||
|     else: | ||||
|         with open(globalPath + "config.ini", 'w') as configfile: | ||||
|             globalConfig.write(configfile) | ||||
|  | ||||
| def read_config(section, value, readGlobal = False): | ||||
|     if readGlobal == False: | ||||
|         with open(gamePath + "config.ini", 'r') as configfile: | ||||
|             return localConfig.read(section, value) | ||||
|     else: | ||||
|         with open(globalPath + "config.ini", 'r') as configfile: | ||||
|             return globalConfig.read(section, value) | ||||
|          | ||||
| def speak(text, interupt = True): | ||||
|     if speechProvider == "speechd": | ||||
|         if interupt == True: spd.cancel() | ||||
|         spd.say(text) | ||||
|     else: | ||||
|         if speechProvider == "accessible_output2": | ||||
|             s.speak(text, interrupt=True) | ||||
|  | ||||
|  | ||||
| def exit_game(): | ||||
|     if speechProvider == "speechd": spd.close() | ||||
|     pygame.mixer.music.stop() | ||||
|     pygame.quit() | ||||
|     exit() | ||||
|  | ||||
| def initialize_gui(gameTitle): | ||||
|     # Check for, and possibly create, storm-games path     | ||||
|     global globalPath | ||||
|     global gamePath | ||||
|     globalPath = BaseDirectory.xdg_config_home + "/storm-games" | ||||
|     gamePath = globalPath + "/" + str.lower(str.replace(gameTitle, " ", "-")) | ||||
|     if not os.path.exists(gamePath): os.makedirs(gamePath) | ||||
|     # Seed the random generator to the clock | ||||
|     random.seed() | ||||
|     # Set game's name | ||||
|     global gameName | ||||
|     gameName = gameTitle | ||||
|     setproctitle(str.lower(str.replace(gameTitle, " ", ""))) | ||||
|     # start pygame | ||||
|     pygame.init() | ||||
|     # start the display (required by the event loop) | ||||
|     pygame.display.set_mode((320, 200)) | ||||
|     pygame.display.set_caption(gameTitle) | ||||
|     # Set 32 channels for sound by default | ||||
|     pygame.mixer.init() | ||||
|     pygame.mixer.set_num_channels(32) | ||||
|     # Reserve the cut scene channel | ||||
|     pygame.mixer.set_reserved(0) | ||||
|     # Load sounds from the sound directory and creates a list like that {'bottle': 'bottle.ogg'} | ||||
|     soundFiles = [f for f in listdir("sounds/") if isfile(join("sounds/", f)) and (f.split('.')[1].lower() in ["ogg","wav"])] | ||||
|     #lets make a dict with pygame.mixer.Sound() objects {'bottle':<soundobject>} | ||||
|     soundData = {} | ||||
|     for f in soundFiles: | ||||
|         soundData[f.split('.')[0]] = pygame.mixer.Sound("sounds/" + f) | ||||
|     soundData['game-intro'].play() | ||||
|     time.sleep(soundData['game-intro'].get_length()) | ||||
|     return soundData | ||||
|  | ||||
| def cut_scene(sounds, soundName): | ||||
|     pygame.event.clear() | ||||
|     pygame.mixer.stop() | ||||
|     c = pygame.mixer.Channel(0) | ||||
|     c.play(sounds[soundName]) | ||||
|     while pygame.mixer.get_busy(): | ||||
|         event = pygame.event.poll() | ||||
|         if event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_RETURN, pygame.K_SPACE]: | ||||
|             pygame.mixer.stop() | ||||
|         pygame.event.pump() | ||||
|  | ||||
| def play_random(sounds, soundName, pause = False, interrupt = False): | ||||
|     key = [] | ||||
|     for i in sounds.keys(): | ||||
|         if re.match("^" + soundName + ".*", i): | ||||
|             key.append(i) | ||||
|     randomKey = random.choice(key) | ||||
|     if interrupt == False: | ||||
|         sounds[randomKey].play() | ||||
|     else: | ||||
|         cut_scene(sounds, randomKey) | ||||
|         # Cut scenes override the pause option | ||||
|         return | ||||
|     if pause == True: | ||||
|         time.sleep(sounds[randomKey].get_length()) | ||||
|      | ||||
| def instructions(): | ||||
|     # Read in the instructions file | ||||
|     try: | ||||
|         with open('files/instructions.txt', 'r') as f: | ||||
|             info = f.readlines() | ||||
|     except: | ||||
|         info = ["Instructions file is missing."] | ||||
|     display_text(info) | ||||
|  | ||||
| def credits(): | ||||
|     # Read in the credits file. | ||||
|     try: | ||||
|         with open('files/credits.txt', 'r') as f: | ||||
|             info = f.readlines() | ||||
|         # Add the header | ||||
|         info.insert(0, gameName + ": brought to you by Storm Dragon") | ||||
|     except: | ||||
|         info = ["Credits file is missing."] | ||||
|     display_text(info) | ||||
|  | ||||
| def display_text(text): | ||||
|     i = 0 | ||||
|     text.insert(0, "Press space to read the whole text. Use up and down arrows to navigate the text line by line. Press c to copy the current line to the clipboard or t to copy the entire text. Press enter or escape when you are done reading.") | ||||
|     text.append("End of text.") | ||||
|     speak(text[i]) | ||||
|     while True: | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             if event.key == pygame.K_ESCAPE or event.key == pygame.K_RETURN: return | ||||
|             if event.key == pygame.K_DOWN and i < len(text) - 1: i = i + 1 | ||||
|             if event.key == pygame.K_UP and i > 0: i = i - 1 | ||||
|             if event.key == pygame.K_SPACE: | ||||
|                 speak(' '.join(text[1:])) | ||||
|             else: | ||||
|                 speak(text[i]) | ||||
|             if event.key == pygame.K_c: | ||||
|                 try: | ||||
|                     pyperclip.copy(text[i]) | ||||
|                     speak("Copied " + text[i] + " to the clipboard.") | ||||
|                 except: | ||||
|                     speak("Failed to copy the text to the clipboard.") | ||||
|             if event.key == pygame.K_t: | ||||
|                 try: | ||||
|                     pyperclip.copy(''.join(text[1:-1])) | ||||
|                     speak("Copied entire message to the clipboard.") | ||||
|                 except: | ||||
|                     speak("Failed to copy the text to the clipboard.") | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|  | ||||
| def learn_sounds(sounds): | ||||
|     loop = True | ||||
|     pygame.mixer.music.pause() | ||||
|     i = 0 | ||||
|     soundFiles = [f for f in listdir("sounds/") if isfile(join("sounds/", f)) and (f.split('.')[1].lower() in ["ogg","wav"]) and (f.split('.')[0].lower() not in ["game-intro", "music_menu"])] | ||||
|     # j keeps track of last spoken index so it isn't voiced on key up. | ||||
|     j = -1 | ||||
|     while loop == True: | ||||
|         if i != j: | ||||
|             speak(soundFiles[i][:-4]) | ||||
|             j = i | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             if event.key == pygame.K_ESCAPE: return "menu" | ||||
|             if event.key == pygame.K_DOWN and i < len(soundFiles) - 1: | ||||
|                 pygame.mixer.stop() | ||||
|                 i = i + 1 | ||||
|             if event.key == pygame.K_UP and i > 0: | ||||
|                 pygame.mixer.stop() | ||||
|                 i = i - 1 | ||||
|             if event.key == pygame.K_RETURN: | ||||
|                 try: | ||||
|                     soundName = soundFiles[i][:-4] | ||||
|                     pygame.mixer.stop() | ||||
|                     sounds[soundName].play() | ||||
|                     continue | ||||
|                 except: | ||||
|                     j = -1 | ||||
|                     speak("Could not play sound.") | ||||
|                     continue | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|  | ||||
| def game_menu(sounds, *options): | ||||
|     loop = True | ||||
|     if pygame.mixer.music.get_busy(): | ||||
|         pygame.mixer.music.unpause() | ||||
|     else: | ||||
|         pygame.mixer.music.load("sounds/music_menu.ogg") | ||||
|         pygame.mixer.music.set_volume(0.75) | ||||
|         pygame.mixer.music.play(-1) | ||||
|     i = 0 | ||||
|     # j keeps track of last spoken index so it isn't voiced on key up. | ||||
|     j = -1 | ||||
|     while loop == True: | ||||
|         if i != j: | ||||
|             speak(options[i]) | ||||
|             j = i | ||||
|         event = pygame.event.wait() | ||||
|         if event.type == pygame.KEYDOWN: | ||||
|             if event.key == pygame.K_ESCAPE: exit_game() | ||||
|             if event.key == pygame.K_DOWN and i < len(options) - 1: | ||||
|                 i = i + 1 | ||||
|                 try: | ||||
|                     sounds['menu-move'].play() | ||||
|                 except: | ||||
|                     pass | ||||
|                 if options[i] != "donate": pygame.mixer.music.unpause() | ||||
|             if event.key == pygame.K_UP and i > 0: | ||||
|                 i = i - 1 | ||||
|                 try: | ||||
|                     sounds['menu-move'].play() | ||||
|                 except: | ||||
|                     pass | ||||
|                 if options[i] != "donate": pygame.mixer.music.unpause() | ||||
|             if event.key == pygame.K_RETURN: | ||||
|                 try: | ||||
|                     j = -1 | ||||
|                     try: | ||||
|                         sounds['menu-select'].play() | ||||
|                         time.sleep(sounds['menu-select'].get_length()) | ||||
|                     except: | ||||
|                         pass | ||||
|                     eval(options[i] + "()") | ||||
|                     continue | ||||
|                 except: | ||||
|                     j = -1 | ||||
|                     return options[i] | ||||
|                     continue | ||||
|         event = pygame.event.clear() | ||||
|         time.sleep(0.001) | ||||
|  | ||||
| def donate(): | ||||
|     pygame.mixer.music.pause() | ||||
|     webbrowser.open('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=stormdragon2976@gmail.com&lc=US&item_name=Donation+to+Storm+Games&no_note=0&cn=¤cy_code=USD&bn=PP-DonationsBF:btn_donateCC_LG.gif:NonHosted') | ||||
							
								
								
									
										7
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| pygame>=2.0.0 | ||||
| pyperclip>=1.8.0 | ||||
| requests>=2.25.0 | ||||
| pyxdg>=0.27 | ||||
| setproctitle>=1.2.0 | ||||
| numpy>=1.19.0 | ||||
| accessible-output2>=0.14 | ||||
							
								
								
									
										2
									
								
								requirements_linux.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								requirements_linux.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| -r requirements.txt | ||||
| python-speechd>=0.11.1 | ||||
		Reference in New Issue
	
	Block a user