Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e1451f69b0 | ||
|
401b2a8527 | ||
|
639198e8de |
509
__init__.py
Executable file → Normal file
509
__init__.py
Executable file → Normal file
@ -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':<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 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 *
|
||||
|
511
libstormgames.py
Executable file
511
libstormgames.py
Executable file
@ -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':<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 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')
|
Loading…
Reference in New Issue
Block a user