#!/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 textwrap 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 math import numpy as np import time import wx localConfig = configparser.ConfigParser() globalConfig = configparser.ConfigParser() class scoreboard(): 'Handles scores and top 10' def __init__(self, startingScore = 0): read_config() try: localConfig.add_section("scoreboard") except: pass self.score = startingScore self.oldScores = [] for i in range(1, 11): try: self.oldScores.insert(i - 1, localConfig.getint("scoreboard", str(i))) except: pass self.oldScores.insert(i - 1, 0) for i in range(1, 11): if self.oldScores[i - 1] == None: self.oldScores[i - 1] = 0 def __del__(self): self.Update_Scores() try: write_config() except: pass def Decrease_Score(self, points = 1): self.score -= points def Get_High_Score(self, position = 1): return self.oldScores[position - 1] def Get_Score(self): return self.score def Increase_Score(self, points = 1): self.score += points def New_High_Score(self): for i, j in enumerate(self.oldScores): if self.score > j: return i + 1 return None def Update_Scores(self): # Update the scores for i, j in enumerate(self.oldScores): if self.score > j: self.oldScores.insert(i, self.score) break # Only keep the top 10 scores. self.oldScores = self.oldScores[:10] # Update the scoreboard section of the games config file. for i, j in enumerate(self.oldScores): localConfig.set("scoreboard", str(i + 1), str(j)) 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(readGlobal = False): if readGlobal == False: 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 get_input(prompt = "Enter text:", text = ""): 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, interupt=True): if speechProvider == "speechd": if interupt: spd.cancel() spd.say(text) else: if speechProvider == "accessible_output2": s.speak(text, interrupt=True) # Display the text on screen screen = pygame.display.get_surface() font = pygame.font.Font(None, 36) # Wrap the text maxWidth = screen.get_width() - 40 # Leave a 20-pixel margin on each side wrappedText = textwrap.wrap(text, width=maxWidth // font.size('A')[0]) # Render each line textSurfaces = [font.render(line, True, (255, 255, 255)) for line in wrappedText] screen.fill((0, 0, 0)) # Clear screen with black # Calculate total height of text block totalHeight = sum(surface.get_height() for surface in textSurfaces) # Start y-position (centered vertically) currentY = (screen.get_height() - totalHeight) // 2 # Blit each line of text for surface in textSurfaces: textRect = surface.get_rect(center=(screen.get_width() // 2, currentY + surface.get_height() // 2)) screen.blit(surface, textRect) currentY += surface.get_height() pygame.display.flip() def check_for_exit(): 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(): 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((800, 600)) pygame.display.set_caption(gameTitle) # Set 32 channels for sound by default pygame.mixer.pre_init(44100, -16, 2, 1024) 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 {'bottle': 'bottle.ogg'} 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) #lets make a dict with pygame.mixer.Sound() objects {'bottle':} 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 generate_tone(frequency, duration=0.1, sample_rate=44100, volume = 0.2): t = np.linspace(0, duration, int(sample_rate * duration), False) tone = np.sin(2 * np.pi * frequency * t) stereo_tone = np.vstack((tone, tone)).T # Create a 2D array for stereo stereo_tone = (stereo_tone * 32767).astype(np.int16) stereo_tone = (stereo_tone * 32767 * volume).astype(np.int16) # Apply volume stereo_tone = np.ascontiguousarray(stereo_tone) # Ensure C-contiguous array return pygame.sndarray.make_sound(stereo_tone) def x_powerbar(): clock = pygame.time.Clock() screen = pygame.display.get_surface() position = -50 # Start from the leftmost position direction = 1 # Move right initially barHeight = 20 while True: frequency = 440 # A4 note leftVolume = (50 - position) / 100 rightVolume = (position + 50) / 100 tone = generate_tone(frequency) channel = tone.play() channel.set_volume(leftVolume, rightVolume) # Visual representation screen.fill((0, 0, 0)) barWidth = screen.get_width() - 40 # Leave 20px margin on each side pygame.draw.rect(screen, (100, 100, 100), (20, screen.get_height() // 2 - barHeight // 2, barWidth, barHeight)) markerPos = int(20 + (position + 50) / 100 * barWidth) pygame.draw.rect(screen, (255, 0, 0), (markerPos - 5, screen.get_height() // 2 - barHeight, 10, barHeight * 2)) pygame.display.flip() for event in pygame.event.get(): check_for_exit() if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: channel.stop() return position # This will return a value between -50 and 50 position += direction if position > 50: position = 50 direction = -1 elif position < -50: position = -50 direction = 1 clock.tick(40) # Speed of bar def y_powerbar(): clock = pygame.time.Clock() screen = pygame.display.get_surface() power = 0 direction = 1 # 1 for increasing, -1 for decreasing barWidth = 20 while True: frequency = 220 + (power * 5) # Adjust these values to change the pitch range tone = generate_tone(frequency) channel = tone.play() # Visual representation screen.fill((0, 0, 0)) barHeight = screen.get_height() - 40 # Leave 20px margin on top and bottom pygame.draw.rect(screen, (100, 100, 100), (screen.get_width() // 2 - barWidth // 2, 20, barWidth, barHeight)) markerPos = int(20 + (100 - power) / 100 * barHeight) pygame.draw.rect(screen, (255, 0, 0), (screen.get_width() // 2 - barWidth, markerPos - 5, barWidth * 2, 10)) pygame.display.flip() for event in pygame.event.get(): check_for_exit() if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: channel.stop() return power power += direction if power >= 100 or power <= 0: direction *= -1 # Reverse direction at limits clock.tick(40) 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 calculate_volume_and_pan(player_pos, obj_pos): 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) volume = ((max_distance - distance) / max_distance) ** 1.5 # 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): 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 x = sounds[soundName].play(-1) # Apply the volume and pan x.set_volume(volume * left, volume * right) return x def obj_update(x, player_pos, obj_pos): if x is None: return None volume, left, right = calculate_volume_and_pan(player_pos, obj_pos) if volume == 0: x.stop() return None # Apply the volume and pan x.set_volume(volume * left, volume * right) return x def obj_stop(x): # Tries to stop a playing object channel try: x.stop() return None except: return x def play_ambiance(sounds, soundNames, probability, randomLocation = False): # 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 if random.randint(1, 100) > probability: return # Choose a random sound from the list ambianceSound = random.choice(soundNames) channel = sounds[ambianceSound].play() if randomLocation and channel: left_volume = random.random() right_volume = random.random() channel.set_volume(left_volume, right_volume) return channel # Return the channel object for potential further manipulation 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"]) and (not f.lower().startswith("_"))] # 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 pygame.mixer.stop() if pygame.mixer.music.get_busy(): pygame.mixer.music.unpause() else: try: pygame.mixer.music.load("sounds/music_menu.ogg") pygame.mixer.music.set_volume(0.75) pygame.mixer.music.play(-1) except: pass 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_HOME and i != 0: i = 0 try: sounds['menu-move'].play() except: pass if options[i] != "donate": pygame.mixer.music.unpause() if event.key == pygame.K_END and i != len(options) - 1: i = len(options) -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://ko-fi.com/stormux')