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