From 4696cfc6217b4ba2259af1c8332b4a7edb71ee16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Wed, 4 May 2022 14:26:32 +0200 Subject: [PATCH] =?UTF-8?q?Koment=C3=A1=C5=99e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 ++++---- lib/ascii.py | 8 ++-- lib/fight.py | 30 ++++++------ lib/game.py | 125 +++++++++++++++++++++++++----------------------- lib/item.py | 10 ++-- lib/lang/cz.yml | 1 + lib/lang/en.yml | 1 + lib/menu.py | 10 ++-- lib/save.py | 17 ++++--- 9 files changed, 113 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 8a0cec0..e7e9208 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,12 @@ ![spaghetti code](https://img.shields.io/badge/spaghetti%20code-certified-success) [![wakatime](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/cd3b8d2e-460d-4a8f-952b-dc3c1b869158.svg)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/cd3b8d2e-460d-4a8f-952b-dc3c1b869158) [![CodeFactor](https://www.codefactor.io/repository/github/hernikplays/texty/badge)](https://www.codefactor.io/repository/github/hernikplays/texty) -# texty -An extremely simple text-adventure game ~~engine(?)~~ player +
+

texty

-## What is this? -I was bored +"Jednoduchý" přehrávač textových RPG her +
-## How do I make a game -Check out the `example.yml` file, all games are in the `YAML` format. +## Co to je +Ročníková práce -## How do I use this -Download/clone the repo and run the `__main__.py` file. You need to have a valid game in a `games` folder. - -## TODO -[Here](https://github.com/hernikplays/texty/projects/1) +## Jak se s tím pracuje +TODO diff --git a/lib/ascii.py b/lib/ascii.py index 34de09b..5f6ef44 100644 --- a/lib/ascii.py +++ b/lib/ascii.py @@ -13,16 +13,16 @@ class AsciiAnimation: Loads art from .asc file """ - with TemporaryDirectory() as tmpdir: # we enter a temporary directory - with ZipFile(f"./assets/{name}.asc","r") as z: # extract the asc file + with TemporaryDirectory() as tmpdir: # Vytvoříme dočasnou složku + with ZipFile(f"./assets/{name}.asc","r") as z: # Extrahujeme asc soubor z.extractall(f"{tmpdir}/ascii/{name}") - for f in listdir(f"{tmpdir}/ascii/{name}"): # read all the files + for f in listdir(f"{tmpdir}/ascii/{name}"): # Přečte soubory if f == "config.yml": with open(f"{tmpdir}/ascii/{name}/{f}",encoding="utf-8") as c: data = yaml.load(c,Loader=yaml.SafeLoader) if(data["speed"] != None): self.speed = data["speed"] - with open(f"{tmpdir}/ascii/{name}/{f}",encoding="utf-8") as f: # add all frames into list + with open(f"{tmpdir}/ascii/{name}/{f}",encoding="utf-8") as f: # Přidá všechny snímky do seznamu self.frames.append(f.read()) def play(self): diff --git a/lib/fight.py b/lib/fight.py index 1d7bc65..f35f261 100644 --- a/lib/fight.py +++ b/lib/fight.py @@ -12,10 +12,10 @@ class FightHandler: self.selected = 0 self.rebind() self.name = name - self.max = hp # starting ENEMY HP - self.hp = self.max # current ENEMY HP - self.enemyDef = defense # ENEMY DEF - self.my = 30 # starting PLAYER HP TODO: maybe make this a variable + self.max = hp # životy nepřítele + self.hp = self.max # AKTUÁLNÍ životy nepřítele + self.enemyDef = defense # Obrana nepřítele + self.my = 30 # životy hráče TODO: maybe make this a variable self.attacks = attacks self.img = img self.lang = lang @@ -73,7 +73,7 @@ class FightHandler: keyboard.add_hotkey("down",self.down) keyboard.add_hotkey("enter",self.make_selection) - def show_inventory(self): # Basically `Game` show_inventory + def show_inventory(self): # Zobrazuje inventář TODO: Možná taky equipovat? system("cls||clear") if len(self.inventory) == 0: FightMenu([self.lang["return"]],f" {self.lang['inside_inv']} \n") @@ -81,36 +81,36 @@ class FightHandler: s = "" for i,item in enumerate(self.inventory): if type(item) is not str: - if(i == len(self.inventory)): # last item + if(i == len(self.inventory)): s += f"- {item.name}" else: s += f"- {item.name}\n" else: - if(i == len(self.inventory)): # last item + if(i == len(self.inventory)): s += f"- {item}" else: s += f"- {item}\n" FightMenu([self.lang["return"]],f" {self.lang['inside_inv']} \n{s}") - def attack(self): + def attack(self): # Provede útok vypočítáním ze statů útoku a obrany p = randrange(len(self.attacks)) name = list(self.attacks[p].keys())[0] enemyAtk = self.attacks[p][name]["atk"] enemyDef = self.enemyDef playerAtk = self.equipped["weapon"].attack playerDef = self.equipped["armor"].defense - self.hp -= playerAtk - enemyDef # enemy takes damage - self.my -= enemyAtk - playerDef # player takes damage - self.message = f"{self.lang['enemydmg'].replace('$atk',str(playerAtk - enemyDef)).replace('$name',self.name)}\n{self.lang['playerdmg'].replace('$atk',str(enemyAtk - playerDef)).replace('$name',self.attacks[p][name]['name'])}" + self.hp -= playerAtk - enemyDef # zásah nepříteli + self.my -= enemyAtk - playerDef # zásah hráči + self.message = f"{self.lang['enemydmg'].replace('$atk',str(playerAtk - enemyDef)).replace('$name',self.name)}\n{self.lang['playerdmg'].replace('$atk',str(enemyAtk - playerDef)).replace('$name',self.attacks[p][name]['name'])}" # Změnit zprávu def defend(self): self.message = self.lang["defended"] -class FightMenu(MenuManager): +class FightMenu(MenuManager): # Upravené menu, které nemá input na konci, protože to jinak buguje def __init__(self,selections:list,additional:str): - self.selected = 0 # current selection - self.selections = selections # available selections - self.additional = additional # additional text to display above the menu + self.selected = 0 + self.selections = selections + self.additional = additional keyboard.add_hotkey("up",self.up) keyboard.add_hotkey("down",self.down) keyboard.add_hotkey("enter",self.make_selection) diff --git a/lib/game.py b/lib/game.py index cd156f0..9949ed3 100644 --- a/lib/game.py +++ b/lib/game.py @@ -11,56 +11,56 @@ from .fight import * from time import sleep from os import system -class Game: # the game class keeps information about the loaded game +class Game: # Hlavní třída, uchovává údaje o hře 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 + self.name = data["meta"]["name"] # Název hry + self.author = data["meta"]["creator"] # Název tvůrce + self.current = "start" # Aktuální node + self.nodes = {} # Seznam všech + self.inventory = [] # Hráčův inventář + self.id = data["meta"]["id"] # "Unikátní" ID hry + self.lang = lang # Řetězce pro vybraný jazyk + self.save = SaveManager(self.id,self.lang) # Systém ukládání + self.equipped = {"weapon":None,"armor":None} # Předměty vybavené hráčem + self.enemies = {} # Seznam všech nepřátel if "equippable" in data["meta"].keys(): - self.equippable = [] # Items that can be equipped by player + self.equippable = [] # Předměty, které si hráč může vybavit 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"])) + self.equippable.append(Item(item[name]["name"],item[name]["atk"],item[name]["def"])) elif "def" in item[name].keys(): - self.equippable.append(Item(name=name,defense=item[name]["def"])) + self.equippable.append(Item(name=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 + self.equippable.append(Item(item[name]["name"],item[name]["atk"])) + if("starter" in item[name].keys()): # Pokud je starter, přidáme hráčí na začátku do inventáře 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 + # Načte nepřátele 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"]: + for k in data["game"]: # načte všechny nody self.nodes.update({k:data["game"][k]}) - def main_menu(self): # displays the main menu + def main_menu(self): # Zobrazí hlavní menu l = self.save.load() if not l: - # New game + # V případě nové hry 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 + if(selection == 0): # Začít self.print_text() - elif(selection == 1): + elif(selection == 1): # Nastavení self.settings_menu() - elif(selection == 2): + elif(selection == 2): # Vypnout print(self.lang['quitting']) exit() - else: # Display continue + else: # V případě uložené hry zobrazí "Pokračovat" 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") @@ -76,7 +76,7 @@ class Game: # the game class keeps information about the loaded game print(self.lang['quitting']) exit() - def settings_menu(self): # displays the settings menu + def settings_menu(self): # Zobrazí nastavení m = MenuManager([self.lang['lang'],self.lang['back']],self.lang['options']) selection = m.selected if(selection == 0): @@ -93,11 +93,13 @@ class Game: # the game class keeps information about the loaded game else: self.main_menu() - def print_text(self): # Prints out the current prompt + def print_text(self): # Zobrazí hráči aktuální node 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 + if "add_item" in self.nodes[self.current].keys(): # V případě, že máme přidat hráči věc do inventáře item = self.nodes[self.current]['add_item'] + for i in self.equippable: + if i.name == item: # Pokud lze vybavit, změnit na instanci třídy Item + item = i if item not in self.inventory: self.inventory.append(item) print(self.inventory) @@ -105,13 +107,13 @@ class Game: # the game class keeps information about the loaded game 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 + animated = re.search(r"(?!{).+(?=})",self.nodes[self.current]["text"]) # Hledá kód pro vložení animovaného textu 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 + self.nodes[self.current]["text"] = self.nodes[self.current]["text"].replace("{"+animated.group(0)+"}","") # Odstraní kód z textu 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 + actions_desc = [] # uchovává text nodu, abychom jej nemuseli hledat v MenuManager + need_item = [] # pomáhá implementovat kontrolu potřebného předmětu for option in self.nodes[self.current]["actions"]: try: actions_desc.append(self.nodes[option]["description"]) @@ -124,45 +126,45 @@ class Game: # the game class keeps information about the loaded game exit(1) m = "" actions_desc.extend([self.lang['inventory'],self.lang['quit']]) - if(all(element == None for element in need_item) is False): + if(all(element == None for element in need_item) is False): # Pokud platí, musíme zkontrolovat, jestli hráč má předmět 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 + while need_item[m.selected] != None and all(element not in self.inventory for element in need_item[m.selected]): # Opakovat, dokud uživatel nevybere platný výběr 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 + # Spustí boj + enemy = self.enemies[self.nodes[self.current]["fight"]] # Získá info o nepříteli 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 + m.rebind() # Znovu nastavuje klávesy, kvůli MenuManageru uvnitř show_inventory, který je maže input() system("cls||clear") keyboard.remove_all_hotkeys() if m.hp < 1: - # Enemy defeated + # Nepřítel byl poražen print(self.lang["defeated"].replace("$enemy",enemy["name"])) - sleep(5) - self.current = self.nodes[self.current]["actions"][0] # move to the first action + sleep(3) + self.current = self.nodes[self.current]["actions"][0] # Přesune na první akci self.print_text() + return else: - # Player defeated + # Hráč byl poražen TODO: Otestovat print(self.lang["defeat"].replace("$enemy",enemy["name"])) - sleep(5) + sleep(3) self.print_text() - return + 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 + if(sel == len(actions_desc)-2): # Zobrazit inventář self.show_inventory() - elif (sel == len(actions_desc)-1): # Save & quit - self.save.currentPrompt = self.current # save the current prompt + elif (sel == len(actions_desc)-1): # Uložit a ukončit + self.save.currentPrompt = self.current self.save.inventory = self.inventory self.save.save() exit(0) @@ -173,37 +175,42 @@ class Game: # the game class keeps information about the loaded game print(self.parse_colors(self.nodes[self.current]["text"])) print("") - def show_inventory(self): + def show_inventory(self): # Zobrazí hráčův inventář if len(self.inventory) == 0: MenuManager([self.lang["return"]],f" {self.lang['inside_inv']} \n") else: s = "" + op = [self.lang["return"]] for i,item in enumerate(self.inventory): - if type(item) is Item: - if(i == len(self.inventory)): # last item - s += f"- {item.name}" + if type(item) is Item: # Pokud je předmět třídy Item, zobrazit zda-li je vybaven nebo ne + if self.equipped["weapon"] == item or self.equipped["armor"] == item: + op.append(f"- {item.name} | {self.lang['equipped']}") else: - s += f"- {item.name}\n" + op.append(f"- {item.name}") else: - if(i == len(self.inventory)): # last item + if(i == len(self.inventory)): # poslední, nepřidávat newline s += f"- {item}" else: s += f"- {item}\n" - MenuManager([self.lang["return"]],f" {self.lang['inside_inv']} \n{s}") + m = MenuManager(op,f" {self.lang['inside_inv']} \n{s}") + if(m.selected != len(op)-1): + # Vybavit + i = op[m.selected] + self.equipped[i.type] = i self.print_text() - def print_animated(self,animid): # prints the first found occurence of an ascii animation + def print_animated(self,animid): # Zobrazí animaci animation = AsciiAnimation() animation.load_ascii(animid) animation.play() print() - def parse_colors(self,text:str) -> str: # Converts color codes into terminal colors + def parse_colors(self,text:str) -> str: # Převádí kód na barvy v terminálu 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 + newText += Fore.RESET # resetovat na konci return newText -def load(file_path,lang): # starts to load the game from YAML +def load(file_path,lang): # Načte hru z YAML souboru try: with open(file_path) as f: data = yaml.load(f,Loader=SafeLoader) @@ -212,4 +219,4 @@ def load(file_path,lang): # starts to load the game from YAML except Exception as e: print(f"{Back.RED}{Fore.WHITE}ERROR{Fore.RESET}{Back.RESET}") print(e) - return None + return None \ No newline at end of file diff --git a/lib/item.py b/lib/item.py index fab4c87..5ae831d 100644 --- a/lib/item.py +++ b/lib/item.py @@ -1,9 +1,9 @@ -class Item: +class Item: # Reprezentuje vybavitelný předmět def __init__(self,name:str,attack:int = 0,defense:int = 0) -> None: - self.name = name - if attack == 0 and defense > 0: + self.name = name # Název, jak je zobrazován hráči + if attack == 0 and defense > 0: # Nastaví typ předmětu self.type = "armor" else: self.type = "weapon" - self.attack = attack - self.defense = defense \ No newline at end of file + self.attack = attack # Stat útoku + self.defense = defense # Stat obrany \ No newline at end of file diff --git a/lib/lang/cz.yml b/lib/lang/cz.yml index de839f9..6770a8c 100644 --- a/lib/lang/cz.yml +++ b/lib/lang/cz.yml @@ -14,6 +14,7 @@ acquire: 'Získal jsi $item' inside_inv: "VÁŠ INVENTÁŘ" return: "Vrátit se" +equipped: "VYBAVENO" lang: 'Jazyk' back: 'Zpět' diff --git a/lib/lang/en.yml b/lib/lang/en.yml index 0219d00..ae72758 100644 --- a/lib/lang/en.yml +++ b/lib/lang/en.yml @@ -14,6 +14,7 @@ acquire: 'You acquired $item' inside_inv: "YOUR INVENTORY" return: "Return" +equipped: "EQUIPPED" lang: 'Language' back: 'Back' diff --git a/lib/menu.py b/lib/menu.py index 34e9e95..4ec4269 100644 --- a/lib/menu.py +++ b/lib/menu.py @@ -3,12 +3,12 @@ import keyboard from colorama import Fore class MenuManager: ''' - Creates text menus controllable with arrow keys + Vytváří navigovatelná textová menu ''' def __init__(self,selections:list,additional:str): - self.selected = 0 # current selection - self.selections = selections # available selections - self.additional = additional # additional text to display above the menu + self.selected = 0 # aktuální výběr + self.selections = selections # Dostupné možnosti + self.additional = additional # Text, který se zobrazí nad menu keyboard.add_hotkey("up",self.up) keyboard.add_hotkey("down",self.down) keyboard.add_hotkey("enter",self.make_selection) @@ -45,7 +45,7 @@ class MenuManager: class HasItemDialogue(MenuManager): ''' - Custom handler for dialogue, that requires to check if the user has an item + Odvozená třída pro kontrolu, zda-li má hráč předmět ''' def __init__(self, selections: list, additional: str,inv:list,need_item:list): system("cls||clear") diff --git a/lib/save.py b/lib/save.py index 29c4f0c..2e58db4 100644 --- a/lib/save.py +++ b/lib/save.py @@ -4,29 +4,28 @@ import yaml from lib.game import Item -class SaveManager: # manages save and configuration files +class SaveManager: # Spravuje ukládání def __init__(self,gid:str,lang): - self.id = gid # game ID - self.currentPrompt = "" # Current prompt - self.inventory = [] # Items in inventory + self.id = gid # ID hry + self.currentPrompt = "" # Aktuální node + self.inventory = [] # Předměty v inventáři self.version = 1 self.lang = lang def load(self): if(path.exists(f"./saves/{self.id}.yml")): with open(f"./saves/{self.id}.yml",encoding="utf-8") as f: - data = yaml.load(f,Loader=yaml.SafeLoader) + data = yaml.load(f,Loader=yaml.SafeLoader) # Načteme z YAMLu self.currentPrompt = data["currentPrompt"] - if(data["version"] < self.version): + if(data["version"] < self.version): # V případě nekompatibility zobrazit varování system("cls||clear") print(self.lang["no_comp"]) sleep(5) inv = [] - for item in data["inventory"]: + for item in data["inventory"]: # Zpracovat inventář (zvlášť pouze text a zvlášť vybavitelné) if type(item) is str: inv.append(item) else: - # Item class inv.append(Item(item["name"],item["atk"],item["def"])) return True return False @@ -37,7 +36,7 @@ class SaveManager: # manages save and configuration files if type(item) is str: inv.append(item) else: - # Item class + # Pro vybavitelné předměty inv.append({"name":item.name,"atk":item.attack,"def":item.defense}) data = {"id":self.id,"currentPrompt":self.currentPrompt,"inventory":self.inventory,"version":1} with open(f"./saves/{self.id}.yml",mode="w",encoding="utf-8") as f: