Compare commits

...

38 commits
0.0.1 ... main

Author SHA1 Message Date
Matyáš Caras 6e55242fb5
fix: saving equipped items 2022-05-24 09:40:21 +02:00
Matyáš Caras b8ffa7871d Merge branch 'main' of https://github.com/hernikplays/texty 2022-05-17 17:03:28 +02:00
Matyáš Caras dce622c7b3 Fix saves and equipping inside fight (Closes #2) 2022-05-17 17:03:25 +02:00
Matyáš Caras 761a23da8a
Update example.yml 2022-05-16 19:05:26 +00:00
Matyáš Caras a98cc869dd Fix health 2022-05-16 21:00:45 +02:00
Matyáš Caras 17a444d0b5 Trailing spaces 2022-05-16 17:52:20 +02:00
Matyáš Caras d39971b852 Add player health 2022-05-16 17:51:17 +02:00
Matyáš Caras 01077b14bb Czech example 2022-05-16 16:14:21 +02:00
Matyáš Caras b9a3d5c5d4 Prettier code 2022-05-16 15:59:06 +02:00
Matyáš Caras 71e430f61d First release 2022-05-16 15:56:11 +02:00
Matyáš Caras 15ba06b4aa
Update README.md 2022-05-04 14:27:19 +02:00
Matyáš Caras 4696cfc621
Komentáře 2022-05-04 14:26:32 +02:00
Matyáš Caras 7dd83c7ca0
Update example.yml 2022-05-04 10:20:11 +02:00
Matyáš Caras 362caee205
Formatting 2022-05-04 10:18:42 +02:00
Matyáš Caras 860b98f1a9
Complete fighting and squash bugs 2022-05-04 10:16:30 +02:00
Matyáš Caras 04538bae36 Work on fighting 2022-05-02 11:32:40 +02:00
Matyáš Caras f0eae367b1
Update fight.py 2022-04-19 12:38:45 +02:00
Matyáš Caras 1cb78c4b8a
Work on equipping items 2022-04-19 12:36:05 +02:00
Matyáš Caras 8d58c9647a
Update save.py 2022-04-19 08:26:58 +02:00
Matyáš Caras 3e9cd33e98 Fix saving, move anim playing and work on fight 2022-04-17 20:03:52 +02:00
Matyáš Caras 3251dbe4a6 Fix removing item from inventory 2022-04-17 19:02:11 +02:00
Matyáš Caras b347fd7585 Merge branch 'main' of https://github.com/hernikplays/texty 2022-04-17 18:47:02 +02:00
Matyáš Caras 5a530c7d88 Push unpushed changes 2022-04-17 18:46:17 +02:00
Matyáš Caras 322980c704 Remove item 2022-04-12 15:16:38 +02:00
Matyáš Caras 90358c73c7
Update README.md 2022-04-06 14:12:23 +02:00
Matyáš Caras 7361882831
Add showing inventory 2022-04-06 10:03:21 +02:00
Matyáš Caras 997475aef1
Hopefully item checking is complete 2022-04-06 09:42:47 +02:00
Matyáš Caras 111e918c93
Work on item checking 2022-04-05 11:59:11 +02:00
Matyáš Caras efaf74111b Implement menumanager v1 2022-03-24 18:20:39 +01:00
Matyáš Caras 2240dd0c0b
Update README.md 2022-03-23 14:23:41 +01:00
Matyáš Caras 5d87c3a221
Add language 2022-03-23 14:22:12 +01:00
Matyáš Caras a398bbd5dc
Main menu 2022-03-23 14:21:44 +01:00
Matyáš Caras 5ff4eb2b4a
Create menu.py 2022-03-23 14:20:59 +01:00
Matyáš Caras 3a35468cd7
Update README.md 2022-03-23 14:20:29 +01:00
Matyáš Caras 1a02220ffc
Update en.yml 2022-03-23 14:18:37 +01:00
Matyáš Caras e83e273569
Update cz.yml 2022-03-23 14:18:29 +01:00
Matyáš Caras 07260d794e
Update requirements.txt 2022-03-23 14:15:47 +01:00
Matyáš Caras 0d1646e0ff
Start main menu 2022-03-22 11:22:50 +01:00
15 changed files with 770 additions and 196 deletions

9
.vscode/launch.json vendored
View file

@ -5,12 +5,13 @@
"version": "0.2.0",
"configurations": [
{
"name": "Start with example game",
"name": "Python: Start main (verbose)",
"type": "python",
"request": "launch",
"program": "${workspaceRoot}/__main__.py",
"args": ["./example.yml"],
"console": "integratedTerminal"
"program": "${workspaceFolder}/__main__.py",
"args": ["-v"],
"console": "integratedTerminal",
"justMyCode": true,
}
]
}

View file

@ -1,16 +1,16 @@
# texty
An extremely simple text-adventure game ~~engine(?)~~ player
<div align="center">
<h1>texty</h1>
## What is this?
I was bored
"Jednoduchý" přehrávač textových RPG her
![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)
</div>
## 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 with the path to a game in `YAML` format as the first argument.
## Jak se s tím pracuje
TODO
## TODO
- Colors
- Catching errors while parsing YAML
- Maybe a welcome screen instead of the need to enter the path as argument
## Pozor
Kvůli limitacím a/nebo bezpečnostním opatřením systému je na **MacOS** a **Linuxu** nutné spouštět jako sudo!

View file

@ -1,17 +1,61 @@
from sys import argv
from genericpath import isdir
import sys
from lib.game import *
from colorama import init
from colorama import init, Back, Fore
from os import mkdir, listdir, path
def main(): # TODO: Maybe a menu for available text games?
init()
if len(argv)<2:
print("You need to specify a path to a YAML file")
exit(1)
def lang():
lang = "en"
if not (path.exists("./saves/lang")):
mkdir("./saves")
with open("./saves/lang","w") as f:
f.write("en")
else:
game = load(argv[1])
if(game is None):
exit(1)
game.main_menu()
with open("./saves/lang","r") as f:
lang = f.read()
if lang == "cz":
lang = "cz"
data = ""
with open(f"./lib/lang/{lang}.yml",encoding="utf-8") as f:
data = yaml.load(f,Loader=SafeLoader)
return data
def main():
l = lang()
init()
if(not isdir("./games")):
mkdir("./games")
games = []
for file in listdir("./games"):
if file.endswith("yml") or file.endswith("yaml"):
# hledá hry
if len(sys.argv) > 1 and (sys.argv[1] == "-v" or sys.argv[1] == "--verbose"): # nepoužívá try/except pro vypsání celé chybové hlášky (debugování)
g = load(f"./games/{file}",l)
if g is not None:
games.append(g)
else:
try:
# parsuje
g = load(f"./games/{file}",l)
if g is not None:
games.append(g)
except Exception as e:
print(f"{Back.RED}{Fore.WHITE}{l['error_loading']} {file}{Fore.RESET}{Back.RESET}:")
print(e)
print(l['enter'])
input()
# výpis menu
if len(games) < 1:
print(l['no_games'])
else:
names = []
for n in games:
if(n is not None):
names.append(n.name)
m = MenuManager(names,f" TEXTY \n{l['available']}")
games[m.selected].main_menu()
if __name__ == "__main__":
main()
main()

View file

@ -1,28 +0,0 @@
meta: # make sure every key is in lowercase
name: "My Text Adventure" # Game name
id: "examplegame" # An original game ID (please try to be original)
creator: "hernik" # Your name as a creator
game: # here goes all the game logic
start: # the starting point always HAS to be named "start" (lowercase), the order and name of the rest does not matter
text: "&bYou arrive to a small tavern in the middle of nowhere.\nYou are greeted with a non-welcoming look on the faces of all the customers." # here is the text, which gets printed
actions: # here you add a list of actions that are inside of `game`, the user can select them
- wave
- sit
wave:
description: "Wave" # this appears in the selection box, if no description is supplied
text: "&bYou wave at the customers to signal your arrival. &rThey all ignore you and look away."
sit: # if no `actions` are supplied, the game exits
description: "Sit down"
text: "You quietly sit down and check the menu."
actions:
- beer
- nothing
beer:
description: "Order beer"
text: "You order some &ebeer"
add_inventory: "Beer" # add something to inventory
nothing:
description: "Do nothing"
text: "You sit and wait..."

84
games/example.yml Normal file
View file

@ -0,0 +1,84 @@
meta: # make sure every key is in lowercase
name: "My Text Adventure" # Game name
id: "examplegame" # An original game ID (please try to be original)
creator: "hernik" # Your name as a creator
equippable:
- iron_sword:
atk: 3
starter: true
name: "Iron Sword"
- chainmail:
def: 2
name: "Chainmail Armor"
starter: true
enemies:
- john:
name: "John"
hp: 20
def: 1
attacks:
- slash:
atk: 2
name: "Slash"
- boom:
atk: 1
name: "Boom"
game: # here goes all the game logic
start: # the starting point always HAS to be named "start" (lowercase), the order and name of the rest does not matter
text: "&bYou arrive to a small tavern in the middle of nowhere.\nYou are greeted with a non-welcoming look on the faces of all the customers." # here is the text, which gets printed
actions: # here you add a list of actions that are inside of `game`, the user can select them
- wave
- sit
wave:
description: "Wave" # this appears in the selection box, if no description is supplied
text: "&bYou wave at the customers to signal your arrival. &rThey all ignore you and look away."
sit: # if no `actions` are supplied, the game exits
description: "Sit down"
text: "You quietly sit down and check the menu."
actions:
- beer
- nothing
beer:
description: "Order beer"
text: "You order some &ebeer"
add_item: "Beer" # add something to inventory
actions:
- do_something
nothing:
description: "Do nothing"
text: "You sit and wait..."
actions:
- drink
- leave
do_something:
description: "Continue"
text: "You start to feel bored."
actions:
- drink
- leave
drink:
has_item: ["Beer"] # item names are case-sensitive
description: "Drink beer"
text: "You take a sip of your cold &eBeer"
actions:
- leave
leave:
description: "Leave"
text: "You decide to leave. But John Doe blocks your way!"
fight: john
actions:
- af
af:
description: "Continue"
text: "&cJohn&r: I'm really sorry for attacking you, I don't know what I was doing!"
actions:
- idc
- ok
idc:
description: "Leave silently"
text: "You decide to turn around and leave without saying a word."
ok:
description: "Accept apology"
text: "&cJohn&r: Oh, thank you, kind sir!"

88
games/example_cz.yml Normal file
View file

@ -0,0 +1,88 @@
meta: # Klíče musí být malým písmem
name: "Moje Textové Dobrodružství" # Název hry
id: "priklad" # Originální ID hry, používá se pro ukládání
creator: "hernik" # Vaše jméno
equippable: # Zde vložíte předměty, které si hráč může vybavit
- iron_sword:
atk: 3 # atk udává hodnotu poškození
starter: true # Hodnota starter udává, jestli hráč dostane věci na začátku hry
name: "Železný meč"
- wood_sword:
atk: 1
starter: true
name: "Dřevěný meč"
- chainmail:
def: 2 # def udává hodnotu brnění
name: "Kroužková zbroj"
starter: true
enemies: # zde můžete vložit seznam nepřátel, které může hráč potkat
- john:
name: "Josef"
hp: 20
def: 1
attacks: # zde vložíte seznam útoků, je nutný alespoň jeden útok
- slash:
atk: 2
name: "Švih"
- boom:
atk: 1
name: "Bumprásk"
game: # game obsahuje všechnu herní logiku a nody
start: # hra musí začínat vždy u "start" (malým písmem)
text: "&bDorazil jsi do malé krčmy uprostřed pustiny.\nUvnitř tě přivítá nepřátelský pohled zákazníků." # here is the text, which gets printed
actions: # zde přidáš akce, tj. klíče nodů, které může hráč vybrat
- wave
- sit
wave:
description: "Zamávat" # Tento text se zobrazí ve výběru
text: "&bZamáváš všem. &rVšichni tě ignorují."
sit: # V případě, že nejsou zadány žádné akce, hra končí
description: "Posadit se"
text: "Potichu usedneš a podíváš se na jídelní lístek."
actions:
- beer
- nothing
beer:
description: "Objednat pivo"
text: "Objednáš si jedno &epivo"
add_item: "Pivo" # přidá předmět do inventáře
actions:
- do_something
nothing:
description: "Nedělat nic"
text: "Rozhodl ses nic nedělat a tak jen sedíš..."
actions:
- drink
- leave
do_something:
description: "Pokračovat"
text: "Začínáš se nudit."
actions:
- drink
- leave
drink:
has_item: ["Pivo"] # názvy předmětů musí být doslovně, včetně malých a velkých písmen
description: "Napít se"
text: "Napil ses svého &ePiva"
actions:
- leave
leave:
description: "Odejít"
text: "Rozhodl ses odejít. Ale Josef ti zablokoval cestu!"
fight: john # klíč "fight" spouští souboj s postavou, kterou jsi definoval výše
actions:
- af
af:
description: "Pokračovat"
text: "&cJosef&r: Opravdu se omlouvám, nevím, co to do mě vjelo!"
actions:
- idc
- ok
idc:
description: "Mlčky odejít"
text: "Otočil ses na místě a beze slova odešel."
ok:
description: "Přijmout omluvu"
text: "&cJosef&r: Och, děkují, rozumný pane!"

View file

@ -1,6 +1,7 @@
from tempfile import TemporaryDirectory
from time import sleep
from zipfile import ZipFile
from os import listdir
from os import listdir, system
import yaml
class AsciiAnimation:
def __init__(self) -> None:
@ -9,18 +10,26 @@ class AsciiAnimation:
def load_ascii(self,name:str):
"""
Returns ASCII by name
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"]
pass
with open(f"{tmpdir}/ascii/{name}/{f}",encoding="utf-8") as f: # add all frames into list
self.frames.append(f.read())
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):
"""
Plays the animation frame by frame
"""
for frame in self.frames:
system("cls||clear")
print(frame)
sleep(self.speed)

145
lib/fight.py Normal file
View file

@ -0,0 +1,145 @@
import math
from lib.item import Item
from lib.menu import MenuManager
from .ascii import *
from colorama import Fore
import keyboard
from random import randrange
class FightHandler:
def __init__(self,message:str,name:str,hp:int,defense:int,attacks:dict,lang:dict,eq:dict,inv:list,img:str="") -> None:
self.selected = 0
self.rebind()
self.name = name
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
self.message = message
self.equipped = eq
self.inventory = inv
self.show()
def up(self):
if self.selected == 0:
self.selected = 2
else:
self.selected -= 1
system("cls||clear")
self.show()
def down(self):
if self.selected == 2:
self.selected = 0
else:
self.selected += 1
system("cls||clear")
self.show()
def show(self):
system("cls||clear")
p = math.trunc(self.hp/self.max*10)
h = f"{Fore.RED}{Fore.RESET}"*p
j = math.trunc(self.my/30*10)
a = f"{Fore.GREEN}{Fore.RESET}"*j
if str(p).endswith(".5"):
h += ""
if str(a).endswith(".5"):
a += ""
print(self.message)
print(f"{self.lang['you']} {a} {self.my}/30")
print(f"{self.name} {h} {self.hp}/{self.max}")
if self.img != "":
anim = AsciiAnimation()
anim.load_ascii(self.img)
anim.play()
s = [self.lang["attack"],self.lang["defend"],self.lang["inventory"]]
for selection in s:
if(self.selected == s.index(selection)):
print(f"{Fore.RED}{Fore.RESET} {selection}")
else:
print(f" {selection}")
def make_selection(self) -> None:
if self.selected == 0:
self.attack()
elif self.selected == 1:
self.defend()
elif self.selected == 2:
self.show_inventory()
def rebind(self):
keyboard.remove_all_hotkeys()
keyboard.add_hotkey("up",self.up)
keyboard.add_hotkey("down",self.down)
keyboard.add_hotkey("enter",self.make_selection)
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")
else:
op = []
items = []
s = ""
for i,item in enumerate(self.inventory):
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:
op.append(f"- {item.name}")
items.append(item)
else:
if(i == len(self.inventory)): # poslední, nepřidávat newline
s += f"- {item}"
else:
s += f"- {item}\n"
items.append(None)
op.append(self.lang["return"])
m = FightMenu(op,f" {self.lang['inside_inv']} \n{s}")
if(m.selected != len(op)-1):
# Vybavit
i = items[m.selected]
self.equipped[i.type] = i
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 = 0
playerDef = 0
if self.equipped["weapon"] is not None:
playerAtk = self.equipped["weapon"].attack
if self.equipped["armor"] is not None:
playerDef = self.equipped["armor"].defense
c = enemyAtk - playerDef
e = playerAtk - enemyDef
if c < 0:
c = 0
if e < 0:
e = 0
self.hp -= e # zásah nepříteli
self.my -= c # 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): # Upravené menu, které nemá input na konci, protože to jinak buguje
def __init__(self,selections:list,additional:str):
keyboard.remove_all_hotkeys()
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)
self.show_menu()

View file

@ -1,162 +1,226 @@
import yaml
from yaml.loader import SafeLoader
from colorama import Fore, Back, Style
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, path, mkdir
from os import system
class Game: # the game class keeps information about the loaded game
def __init__(self,data:dict):
self.name = data["meta"]["name"]
self.author = data["meta"]["creator"]
self.current = "start"
self.nodes = {}
self.inventory = []
self.save = SaveManager()
self.id = data["meta"]["id"]
for k in data["game"]:
class Game: # Hlavní třída, uchovává údaje o hře
def __init__(self,data:dict,lang):
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 = [] # 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(item[name]["name"],item[name]["atk"],item[name]["def"]))
elif "def" in item[name].keys():
self.equippable.append(Item(name=item[name]["name"],defense=item[name]["def"]))
elif "atk" in item[name].keys():
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 == item[name]["name"])) # V případě, že nenalezne předmět, vrací None
self.inventory.append(i)
self.equipped[i.type] = i
self.save.equipped[i.type]
if "enemies" in data["meta"].keys():
# 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"]: # 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
print(self.name)
print(f"A game by {self.author}")
print("")
print("1 - Start")
print("2 - Options")
print("0 - Quit")
selection = self.make_selection(3)
# 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 == 1): # start new game
if(selection == 0): # Začít
self.print_text()
elif(selection == 2):
elif(selection == 1): # Nastavení
self.settings_menu()
elif(selection == 0):
print("Quitting")
elif(selection == 2): # Vypnout
print(self.lang['quitting'])
exit()
else: # Display continue
print(self.name)
print(f"A game by {self.author}")
print("")
print("1 - Continue")
print("2 - New game")
print("3 - Options")
print("0 - Quit")
selection = self.make_selection(3)
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")
if(selection == 1):
if(selection == 0):
self.current = self.save.currentPrompt
self.inventory = self.save.inventory
self.equipped = self.save.equipped
self.print_text()
elif(selection == 1):
self.print_text()
elif(selection == 2):
self.print_text()
elif(selection == 3):
self.settings_menu()
elif(selection == 0):
print("Quitting")
elif(selection == 3):
print(self.lang['quitting'])
exit()
def settings_menu(self): # displays the settings menu
print("Options")
print("")
print("1 - Language")
print("0 - Back")
selection = self.make_selection(1)
if(selection == 1):
print("Language")
print("")
print("1 - English")
print("2 - Czech")
print("0 - Back")
selection = self.make_selection(2)
def settings_menu(self): # Zobrazí nastavení
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 == 1):
if(selection == 0):
with open("./saves/lang","w") as f:
f.write("en")
elif(selection == 2):
elif(selection == 1):
with open("./saves/lang","w") as f:
f.write("cz")
self.settings_menu()
else:
self.main_menu()
def make_selection(self, length=0) -> int: # this method makes sure a valid selection is made and returns the selection as a number
# TODO: replace with selection by keyboard(?)
l = length # sets the length
if(l == 0): # if no length was set, we get it from nodes
l = len(self.nodes[self.current]["actions"])-1
y = False
selection = 0
# TODO: Check for "has_item"
while y == False: # while the selection is not correct
try:
selection = int(input("Make a selection (number): ")) # ask for selection
except ValueError: # handle wrong input type
print("Not a number selection")
if(selection > l or selection < 0): # if the number is bigger than the length or smaller than 0, print error
print("Invalid selection")
else:
y = True # else return the selection
return selection
def print_text(self): # Prints out the current prompt
def print_text(self): # Zobrazí hráči aktuální node
system("cls||clear")
animated = re.search(r"(?!{).+(?=})",self.nodes[self.current]["text"]) # find the animated text
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)
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"]) # 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
print(self.parse_colors(self.nodes[self.current]["text"]))
print("")
ostring = ""
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()):
for i,option in enumerate(self.nodes[self.current]["actions"]):
ostring+=f"{i} - {self.nodes[option]['description']}\n"
print(ostring)
sel = self.make_selection()
if "add_item" in self.nodes[self.current]: # if there is an add_inventory key in the node,
# add item to inventory
self.inventory.append(self.nodes[self.current]["add_inventory"])
self.current = self.nodes[self.current]["actions"][sel]
self.save.currentPrompt = self.current # save the current prompt
self.print_text()
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"])
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): # Pokud platí, musíme zkontrolovat, jestli hráč má předmět
need_item.extend([None, None])
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]): # 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():
# 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() # Znovu nastavuje klávesy, kvůli MenuManageru uvnitř show_inventory, který je maže
input()
system("cls||clear")
keyboard.remove_all_hotkeys()
self.equipped = m.equipped
self.save.equipped = m.equipped
if m.hp < 1:
# Nepřítel byl poražen
print(self.lang["defeated"].replace("$enemy",enemy["name"]))
sleep(3)
self.current = self.nodes[self.current]["actions"][0] # Přesune na první akci
self.print_text()
else:
# Hráč byl poražen
print(self.lang["defeat"].replace("$enemy",enemy["name"]))
sleep(3)
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): # Zobrazit inventář
self.show_inventory()
elif (sel == len(actions_desc)-1): # Uložit a ukončit
self.save.currentPrompt = self.current
self.save.inventory = self.inventory
self.save.equipped = self.equipped
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 print_animated(self,animid): # prinst the first found occurence of an ascii animation
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 = []
items = []
for i,item in enumerate(self.inventory):
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:
op.append(f"- {item.name}")
items.append(item)
else:
if(i == len(self.inventory)): # poslední, nepřidávat newline
s += f"- {item}"
else:
s += f"- {item}\n"
items.append(None)
op.append(self.lang["return"])
m = MenuManager(op,f" {self.lang['inside_inv']} \n{s}")
if(m.selected != len(op)-1):
# Vybavit
i = items[m.selected]
self.equipped[i.type] = i
self.save.equipped[i.type] = i
self.print_text()
def print_animated(self,animid): # Zobrazí animaci
animation = AsciiAnimation()
animation.load_ascii(animid)
for frame in animation.frames:
system("cls||clear")
print(frame)
sleep(animation.speed)
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): # starts to load the game from YAML
lang = "en"
if not (path.exists("./saves/lang")):
mkdir("./saves")
with open("./saves/lang","w") as f:
f.write("en")
else:
with open("./saves/lang","r") as f:
lang = f.read()
if lang == "cz":
lang = "cz"
try:
with open(file_path) as f:
data = yaml.load(f,Loader=SafeLoader)
g = Game(data)
g.lang = lang
return g
except Exception as e:
print(f"{Back.RED}{Fore.WHITE}An exception has occured while loading the game from the YAML file:{Fore.RESET}{Back.RESET}")
print(e)
return None
def load(file_path,lang): # Načte hru z YAML souboru
with open(file_path, encoding="utf-8") as f:
data = yaml.load(f,Loader=SafeLoader)
g = Game(data,lang)
return g

9
lib/item.py Normal file
View file

@ -0,0 +1,9 @@
class Item: # Reprezentuje vybavitelný předmět
def __init__(self,name:str,attack:int = 0,defense:int = 0) -> None:
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 # Stat útoku
self.defense = defense # Stat obrany

View file

@ -1,3 +1,7 @@
available: 'Dostupné hry:'
no_games: 'Žádné hry nenalezeny'
enter: 'Stiskněte Enter pro pokračování'
yaml_unspecified: 'Musíte specifikovat YAML soubor'
game_by: 'Vytvořil $author'
start: 'Start'
@ -6,6 +10,12 @@ quit: 'Vypnout'
quitting: 'Vypínám'
continue: 'Pokračovat'
new_game: 'Nová hra'
inventory: 'Zobrazit inventář'
acquire: 'Získal jsi $item'
inside_inv: "VÁŠ INVENTÁŘ"
return: "Vrátit se"
equipped: "VYBAVENO"
lang: 'Jazyk'
back: 'Zpět'
@ -16,4 +26,15 @@ selection: 'Vyberte zadáním čísla:'
not_number: 'Nezadali jste číslo'
invalid: 'Neplatný výběr'
error_loading: 'Při načítání YAML souboru nastala chyba:'
attack: "Útočit"
defend: "Bránit se"
enemydmg: "$name dostal zásah za $atk bodů!"
playerdmg: "Nepřítel použil $name a poškodil tě za $atk bodů."
defended: "Rozhodneš se bránit a nedostal jsi tak žádný zásah."
defeated: "Porazil jsi $enemy."
defeat: "$enemy tě zabil. Budeš to muset zkusit znovu."
you: "Ty"
error_loading: 'Při načítání YAML souboru nastala chyba:'
no_action: 'Chyba: žádná akce "$action" nenalezena v souboru hry'
no_comp: "VAROVÁNÍ: Tato uložená hra je pro starší verzi enginu a nemusí být kompatibilní!"

View file

@ -1,3 +1,7 @@
available: 'Available games:'
no_games: 'No games found'
enter: 'Press Enter to continue'
yaml_unspecified: 'You need to specify a path to a YAML file'
game_by: 'A game by $author'
start: 'Start'
@ -6,6 +10,12 @@ quit: 'Quit'
quitting: 'Quitting'
continue: 'Continue'
new_game: 'New game'
inventory: 'Show inventory'
acquire: 'You acquired $item'
inside_inv: "YOUR INVENTORY"
return: "Return"
equipped: "EQUIPPED"
lang: 'Language'
back: 'Back'
@ -16,4 +26,15 @@ selection: 'Make a selection (number):'
not_number: 'Not a number selection'
invalid: 'Invalid selection'
error_loading: 'An exception has occured while loading the game from the YAML file:'
attack: "Attack"
defend: "Defend"
enemydmg: "$name took $atk damage!"
playerdmg: "The enemy used $name to damage you by $atk"
defended: "You decide to defend yourself and take no damage."
defeated: "You have defeated $enemy."
defeat: "$enemy has slain you. You'll have to try again."
you: "You"
error_loading: 'An exception has occured while loading the game from the YAML file'
no_action: 'Error: No action "$action" found in the game file.'
no_comp: "WARNING: This save is for an older version of the engine and may not be compatible!"

80
lib/menu.py Normal file
View file

@ -0,0 +1,80 @@
from os import system
import keyboard
from colorama import Fore
class MenuManager:
'''
Vytváří navigovatelná textová menu
'''
def __init__(self,selections:list,additional:str):
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)
self.show_menu()
input()
def make_selection(self) -> None:
keyboard.remove_all_hotkeys()
def up(self):
if self.selected == 0:
self.selected = len(self.selections)-1
else:
self.selected -= 1
system("cls||clear")
self.show_menu()
def down(self):
if self.selected == len(self.selections)-1:
self.selected = 0
else:
self.selected += 1
system("cls||clear")
self.show_menu()
def show_menu(self):
system("cls||clear")
print(self.additional)
for selection in self.selections:
if(self.selected == self.selections.index(selection)):
print(f"{Fore.RED}->{Fore.RESET} {selection}")
else:
print(f" {selection}")
class HasItemDialogue(MenuManager):
'''
Odvozená třída pro kontrolu, zda-li hráč předmět
'''
def __init__(self, selections: list, additional: str,inv:list,need_item:list):
system("cls||clear")
self.inventory = inv
self.need_items = need_item
super().__init__(selections, additional)
def show_menu(self):
print(self.additional)
for i,selection in enumerate(self.selections):
if(self.need_items[i] != None and all(element not in self.inventory for element in self.need_items[i])):
c = ""
for i,item in enumerate(self.need_items[i]):
if item not in self.inventory:
if i == len(self.need_items)-self.need_items.count(None)-1: # last item, don't add a comma
c+=f"{item} "
else:
c+=f"{item}, "
# user does not have the needed item
if(self.selected == i):
print(f"{Fore.RED}-> {Fore.CYAN}{selection}{Fore.RESET} (Need {c})")
else:
print(f" {Fore.CYAN}{selection}{Fore.RESET}")
else:
# we don't need to change color for an item user doesn't have
if(self.selected == i):
print(f"{Fore.RED}->{Fore.RESET} {selection}")
else:
print(f" {selection}")
def make_selection(self) -> int:
keyboard.remove_all_hotkeys()

View file

@ -1,23 +1,58 @@
from os import path
from time import sleep
from os import path, system
import yaml
class SaveManager: # manages save and configuration files
def __init__(self):
self.id = "" # game ID
self.currentPrompt = "" # Current prompt
self.lang = "" # Selected language
self.inventory = [] # Items in inventory
from lib.game import Item
class SaveManager: # Spravuje ukládání
def __init__(self,gid:str,lang):
self.id = gid # ID hry
self.currentPrompt = "" # Aktuální node
self.inventory = [] # Předměty v inventáři
self.equipped = {"weapon":None,"armor":None} # Výbava
self.version = 2
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"]
self.inventory = data["inventory"]
if(data["version"] < self.version): # V případě nekompatibility zobrazit varování
system("cls||clear")
print(self.lang["no_comp"])
sleep(5)
self.inventory = []
for item in data["inventory"]: # Zpracovat inventář (zvlášť pouze text a zvlášť vybavitelné)
if type(item) is str:
self.inventory.append(item)
else:
i = Item(item["name"],item["atk"],item["def"])
self.inventory.append(i)
# Přidat stejnou kopii jako vybavenou pokud je vybavena
if(data["equipped"]["weapon"] is not None):
if(data["equipped"]["weapon"]["name"] == i.name):
self.equipped["weapon"] = i
if(data["equipped"]["armor"] is not None):
if(data["equipped"]["armor"]["name"] == i.name):
self.equipped["armor"] = i
return True
return False
def save(self):
data = {"id":self.id,"currentPrompt":self.currentPrompt,"inventory":self.inventory,"lang":self.lang}
inv = []
for item in self.inventory:
if type(item) is str:
inv.append(item)
else:
# Pro vybavitelné předměty
inv.append({"name":item.name,"atk":item.attack,"def":item.defense})
# Zpracovat vybavené předměty
if(self.equipped["weapon"] is not None):
self.equipped["weapon"] = {"name":self.equipped["weapon"].name,"atk":self.equipped["weapon"].attack}
if(self.equipped["armor"] is not None):
self.equipped["armor"] = {"name":self.equipped["armor"].name,"def":self.equipped["armor"].defense}
data = {"id":self.id,"currentPrompt":self.currentPrompt,"inventory":inv,"version":self.version,"equipped":self.equipped}
with open(f"./saves/{self.id}.yml",mode="w",encoding="utf-8") as f:
yaml.dump(data,f)
yaml.dump(data,f)

View file

@ -1,2 +1,3 @@
pyyaml==6.0
colorama==0.4.4
colorama==0.4.4
keyboard==0.13.5