This repository has been archived on 2022-12-03. You can view files and clone it, but cannot push or open issues or pull requests.
texty/lib/game.py
2022-05-04 10:16:30 +02:00

216 lines
10 KiB
Python

import yaml
from yaml.loader import SafeLoader
from colorama import Fore, Back
import re
from lib.item import Item
from lib.menu import HasItemDialogue, MenuManager
from .save import SaveManager
from .ascii import AsciiAnimation
from .fight import *
from time import sleep
from os import system
class Game: # the game class keeps information about the loaded game
def __init__(self,data:dict,lang):
self.name = data["meta"]["name"] # Game name
self.author = data["meta"]["creator"] # Game creator
self.current = "start" # Current prompt
self.nodes = {} # All nodes
self.inventory = [] # Player's inventory
self.id = data["meta"]["id"] # Game ID
self.lang = lang # Language strings
self.save = SaveManager(self.id,self.lang) # saving
self.equipped = {"weapon":None,"armor":None} # Items equipped by player
self.enemies = {} # Enemies
if "equippable" in data["meta"].keys():
self.equippable = [] # Items that can be equipped by player
for item in data["meta"]["equippable"]:
name = list(item.keys())[0]
if "def" in item[name].keys() and "atk" in item[name].keys():
self.equippable.append(Item(name,item[name]["atk"],item[name]["def"]))
elif "def" in item[name].keys():
self.equippable.append(Item(name=name,defense=item[name]["def"]))
elif "atk" in item[name].keys():
self.equippable.append(Item(name,item[name]["atk"]))
if("starter" in item[name].keys()): # if starter, equip and add to inventory
if item[name]["starter"]:
i = next((x for x in self.equippable if x.name == list(item.keys())[0]))
self.inventory.append(i)
self.equipped[i.type] = i
if "enemies" in data["meta"].keys():
# Load enemies
for en in data["meta"]["enemies"]:
name = list(en.keys())[0]
self.enemies[name] = {"name":en["name"],"hp":en["hp"],"attacks":en["attacks"],"def":en["def"]}
for k in data["game"]:
self.nodes.update({k:data["game"][k]})
def main_menu(self): # displays the main menu
l = self.save.load()
if not l:
# New game
m = MenuManager([self.lang['start'],self.lang['options'],self.lang['quit']],f"{self.name}\n{self.lang['game_by'].replace('$author',self.author)}")
selection = m.selected
system("cls||clear")
if(selection == 0): # start new game
self.print_text()
elif(selection == 1):
self.settings_menu()
elif(selection == 2):
print(self.lang['quitting'])
exit()
else: # Display continue
m = MenuManager([self.lang['continue'],self.lang['new_game'],self.lang['options'],self.lang['quit']],f"{self.name}\n{self.lang['game_by'].replace('$author',self.author)}")
selection = m.selected
system("cls||clear")
if(selection == 0):
self.current = self.save.currentPrompt
self.inventory = self.save.inventory
self.print_text()
elif(selection == 1):
self.print_text()
elif(selection == 2):
self.settings_menu()
elif(selection == 3):
print(self.lang['quitting'])
exit()
def settings_menu(self): # displays the settings menu
m = MenuManager([self.lang['lang'],self.lang['back']],self.lang['options'])
selection = m.selected
if(selection == 0):
m = MenuManager(["English","Česky",self.lang['back']],self.lang['lang'])
selection = m.selected
system("cls||clear")
if(selection == 0):
with open("./saves/lang","w") as f:
f.write("en")
elif(selection == 1):
with open("./saves/lang","w") as f:
f.write("cz")
self.settings_menu()
else:
self.main_menu()
def print_text(self): # Prints out the current prompt
system("cls||clear")
if "add_item" in self.nodes[self.current].keys(): # if there is an add_inventory key in the node,
# add item to inventory
item = self.nodes[self.current]['add_item']
if item not in self.inventory:
self.inventory.append(item)
print(self.inventory)
system("clear||cls")
print(f"{self.lang['acquire'].replace('$item',f'{Fore.CYAN}{item}{Fore.RESET}')}")
sleep(3)
system("clear||cls")
animated = re.search(r"(?!{).+(?=})",self.nodes[self.current]["text"]) # find the animated text
if(animated != None):
self.print_animated(animated.group(0))
self.nodes[self.current]["text"] = self.nodes[self.current]["text"].replace("{"+animated.group(0)+"}","") # remove the animated text from the text prompt
if("actions" in self.nodes[self.current].keys()):
actions_desc = [] # has descriptions of text prompts, so that we don't need to find them in MenuManager
need_item = [] # helps implement a check for needing an item
for option in self.nodes[self.current]["actions"]:
try:
actions_desc.append(self.nodes[option]["description"])
if "has_item" in self.nodes[option].keys():
need_item.append(self.nodes[option]["has_item"])
else:
need_item.append(None)
except Exception:
print(f"{Back.RED}{Fore.WHITE}{self.lang['no_action'].replace('$action',option)}{Fore.RESET}")
exit(1)
m = ""
actions_desc.extend([self.lang['inventory'],self.lang['quit']])
if(all(element == None for element in need_item) is False):
need_item.extend([None, None])
# we need to check if user has item
m = HasItemDialogue(actions_desc,self.parse_colors(self.nodes[self.current]["text"]),self.inventory,need_item)
while need_item[m.selected] != None and all(element not in self.inventory for element in need_item[m.selected]): # until user selects an available prompt, re-prompt again
m = HasItemDialogue(actions_desc,self.parse_colors(self.nodes[self.current]["text"]),self.inventory,need_item)
if m.selected <= len(actions_desc)-3 and "has_item" in self.nodes[self.nodes[self.current]["actions"][m.selected]].keys():
for item in need_item[m.selected]:
self.inventory.remove(item)
elif "fight" in self.nodes[self.current].keys():
# Initiate a fight
enemy = self.enemies[self.nodes[self.current]["fight"]] # TODO: Complete after fight actions
m = FightHandler(self.nodes[self.current]["text"],enemy["name"],enemy["hp"],enemy["def"],enemy["attacks"],self.lang,self.equipped,self.inventory)
input()
while m.hp > 0 and m.my > 0:
m.show()
m.rebind() # rebind due to MenuManager in show_inventory
input()
system("cls||clear")
keyboard.remove_all_hotkeys()
if m.hp < 1:
# Enemy defeated
print(self.lang["defeated"].replace("$enemy",enemy["name"]))
sleep(5)
self.current = self.nodes[self.current]["actions"][0] # move to the first action
self.print_text()
return
else:
# Player defeated
print(self.lang["defeat"].replace("$enemy",enemy["name"]))
sleep(5)
self.print_text()
return
else:
m = MenuManager(actions_desc,self.parse_colors(self.nodes[self.current]["text"]))
sel = m.selected
if(sel == len(actions_desc)-2): # show inventory
self.show_inventory()
elif (sel == len(actions_desc)-1): # Save & quit
self.save.currentPrompt = self.current # save the current prompt
self.save.inventory = self.inventory
self.save.save()
exit(0)
else:
self.current = self.nodes[self.current]["actions"][sel]
self.print_text()
else:
print(self.parse_colors(self.nodes[self.current]["text"]))
print("")
def show_inventory(self):
if len(self.inventory) == 0:
MenuManager([self.lang["return"]],f" {self.lang['inside_inv']} \n")
else:
s = ""
for i,item in enumerate(self.inventory):
if type(item) is Item:
if(i == len(self.inventory)): # last item
s += f"- {item.name}"
else:
s += f"- {item.name}\n"
else:
if(i == len(self.inventory)): # last item
s += f"- {item}"
else:
s += f"- {item}\n"
MenuManager([self.lang["return"]],f" {self.lang['inside_inv']} \n{s}")
self.print_text()
def print_animated(self,animid): # prints the first found occurence of an ascii animation
animation = AsciiAnimation()
animation.load_ascii(animid)
animation.play()
print()
def parse_colors(self,text:str) -> str: # Converts color codes into terminal colors
newText = text.replace("&b",Fore.CYAN).replace("&c",Fore.RED).replace("&e", Fore.YELLOW).replace("&a",Fore.GREEN).replace("&9",Fore.BLUE).replace("&r",Fore.RESET).replace("&f",Fore.WHITE).replace("&5",Fore.MAGENTA).replace("\n",Fore.RESET + "\n") # replace color codes and newlines with colorama
newText += Fore.RESET # reset color at the end of the text
return newText
def load(file_path,lang): # starts to load the game from YAML
try:
with open(file_path) as f:
data = yaml.load(f,Loader=SafeLoader)
g = Game(data,lang)
return g
except Exception as e:
print(f"{Back.RED}{Fore.WHITE}ERROR{Fore.RESET}{Back.RESET}")
print(e)
return None