390 lines
13 KiB
Python
Executable File
390 lines
13 KiB
Python
Executable File
#!/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 math
|
|
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 == 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 obj_play(sounds, soundName, playerPos, objPos):
|
|
distance = playerPos - objPos
|
|
if distance > 9 or distance < -9:
|
|
# The item is out of range, so play it at 0
|
|
left = 0
|
|
right = 0
|
|
elif distance == 0:
|
|
left = 0.9
|
|
right = 0.9
|
|
else:
|
|
angle = math.radians(distance * 5)
|
|
left = math.sqrt(2)/2.0 * (math.cos(angle) + math.sin(angle))
|
|
right = math.sqrt(2)/2.0 * (math.cos(angle) - math.sin(angle))
|
|
if left < 0: left *= -1
|
|
if right < 0: right *= -1
|
|
# x is the channel for the sound
|
|
x = sounds[soundName].play(-1)
|
|
# Apply the position information to the channel
|
|
x.set_volume(left, right)
|
|
# return the channel so that it can be used in the update and stop functions.
|
|
return x
|
|
|
|
def obj_update(x, playerPos, objPos):
|
|
distance = playerPos - objPos
|
|
if distance > 9 or distance < -9:
|
|
left = 0
|
|
right = 0
|
|
elif distance == 0:
|
|
left = 0.9
|
|
right = 0.9
|
|
else:
|
|
angle = math.radians(distance * 5)
|
|
left = math.sqrt(2)/2.0 * (math.cos(angle) + math.sin(angle))
|
|
right = math.sqrt(2)/2.0 * (math.cos(angle) - math.sin(angle))
|
|
if left < 0: left *= -1
|
|
if right < 0: right *= -1
|
|
# Apply the position information to the channel
|
|
x.set_volume(left, right)
|
|
# return the channel
|
|
return x
|
|
|
|
def obj_stop(x):
|
|
# Tries to stop a playing object channel
|
|
try:
|
|
x.stop()
|
|
return None
|
|
except:
|
|
return x
|
|
|
|
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
|
|
pygame.mixer.stop()
|
|
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_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')
|