From e1451f69b078a54aed2aa542c2f2159f6fb6651f Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 27 Jul 2024 21:51:12 -0400 Subject: [PATCH] Started work on making this more library like. --- __init__.py | 509 +--------------------------------------------- libstormgames.py | 511 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 512 insertions(+), 508 deletions(-) mode change 100755 => 100644 __init__.py create mode 100755 libstormgames.py diff --git a/__init__.py b/__init__.py old mode 100755 new mode 100644 index d2bac2c..77661ce --- a/__init__.py +++ b/__init__.py @@ -1,511 +1,4 @@ #!/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 - -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 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') +from .libstormgames import * diff --git a/libstormgames.py b/libstormgames.py new file mode 100755 index 0000000..d2bac2c --- /dev/null +++ b/libstormgames.py @@ -0,0 +1,511 @@ +#!/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 + +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 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')