9. diel - Aréna s bojovníkmi v Pythone
V minulej lekcii, Bojovník do arény v Pythone , sme si vytvorili triedu bojovníka. Hraciu kocku máme hotovú z prvých lekcií objektovo orientovaného programovania.
V dnešnom tutoriále objektovo orientovaného programovania v Pythone spojíme všetky časti našej hry dohromady a vytvoríme funkčnú arénu. Tutoriál bude skôr oddychový a pomôže nám zopakovať si prácu s objektmi.
Aréna
V našej hre Ťahový boj teraz potrebujeme napísať kód pre obsluhu
bojovníkov a výpis správ užívateľovi. Vytvoríme si objekt (triedu)
Arena
, kde sa bude zápas odohrávať. Hlavná časť programu v
main.py
potom len založí objekty ao zvyšok sa bude starať
objekt (inštancia triedy) Arena
.
Trieda bude viac-menej jednoduchá. Ako atribúty bude obsahovať tri potrebné inštancie: dvoch bojovníkov a hraciu kocku. V konštruktore sa tieto atribúty naplnia z parametrov. Kód triedy bude teda nasledujúci:
class Arena: def __init__(self, bojovnik_1, bojovnik_2, kostka): self._bojovnik_1 = bojovnik_1 self._bojovnik_2 = bojovnik_2 self._kostka = kostka
Metóda na triede Arena
Zamyslime sa nad metódami. Z verejných metód bude určite potrebná len
tá na simuláciu zápasu. Výstup programu na konzole urobíme trochu na
úrovni a tiež umožníme triede Arena
, aby priamo ku konzole
pristupovala. Rozhodli sme sa, že výpis bude v kompetencii triedy, pretože sa
nám to tu oplatí. Naopak keby výpis vykonávali aj bojovníci, bolo by to na
škodu. Potrebujeme teda metódu, ktorá vykreslí obrazovku s aktuálnymi
údajmi o bicykli a životmi bojovníkov. Správy o útoku a obrane budeme
chcieť vypisovať s dramatickou pauzou, aby bol výsledný efekt lepší,
urobíme si pre takýto typ správy ešte pomocnú metódu. Začnime s
vykreslením informačnej obrazovky:
def _vykresli(self): self._vycisti_obrazovku() print("-------------- Aréna -------------- \n") print("Zdraví bojovníků: \n") print(f"{self._bojovnik_1} {self._bojovnik_1.graficky_zivot()}") print(f"{self._bojovnik_2} {self._bojovnik_2.graficky_zivot()}")
Na začiatku voláme ďalšiu metódu vycisti_obrazovku()
.
Metódy sa vo vnútri objektu volajú touto syntaxou:
self.metoda()
Zvyšok je jasný. Obe metódy sú súkromné, budeme ich používať len vo
vnútri triedy. Teraz sa pozrime na kód privátnej metódy
vycisti_obrazovku()
:
def _vycisti_obrazovku(self): import os as _os _os.system('cls' if _os.name == 'nt' else 'clear')
Metóda používa štandardnú knižnicu os
na zistenie, na akom
operačnom systéme program spúšťame. Následne vykoná zodpovedajúci
príkaz na zmazanie obrazovky pre daný systém.
Takto napísaná metóda funguje pre terminály vo Windows, Linux aj macOS. Uistite sa teda, že program spúšťate v termináli a nie napríklad v Run konzole PyCharm.
Ďalšou privátnou metódou bude výpis správy s dramatickou pauzou:
def _vypis_zpravu(self, zprava): import time as _time print(zprava) _time.sleep(0.75)
Kód je zrejmý až funkciu sleep()
z modulu time
,
ktorá uspí program na daný počet sekúnd.
Metóda zapas()
Presunieme sa už k samotnému zápasu. Metóda zapas()
nebude
mať žiadne parametre a nebude ani nič vracať. Vnútri bude cyklus, ktorý
bude na striedačku volať útoky bojovníkov navzájom a vypisovať
informačnú obrazovku a správy. Metóda bude vyzerať takto:
def zapas(self): print("Vítejte v aréně!") print(f"Dnes se utkají {self._bojovnik_1} a {self._bojovnik_2}!") print("Zápas může začít...", end=" ") input() # cyklus s bojem while self._bojovnik_1.je_nazivu() and self._bojovnik_2.je_nazivu(): self._bojovnik_1.utoc(self._bojovnik_2) self._vykresli() self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) self._bojovnik_2.utoc(self._bojovnik_1) self._vykresli() self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu())
Kód vypíše jednoduché informácie a po stlačení klávesy prejde do
cyklu s bojom. Jedná sa o while
cyklus, ktorý sa opakuje, kým
sú obaja bojovníci nažive.
Prvý bojovník zaútočí na druhého, jeho útok vnútorne zavolá na
druhom bojovníkovi obranu. Po útoku vykreslíme obrazovku s informáciami a
ďalej správy o útoku a obrane pomocou našej metódy
vypis_zpravu()
, ktorá po výpise urobí dramatickú pauzu. To
isté urobíme aj pre druhého bojovníka.
Presuňme sa do main.py
, vytvorme patričné inštancie a
zavolajme na aréne metódu zapas()
:
{PYTHON} from kostka import Kostka from bojovnik import Bojovnik from arena import Arena # vytvoření objektů kostka = Kostka(10) zalgoren = Bojovnik("Zalgoren", 100, 20, 10, kostka) shadow = Bojovnik("Shadow", 60, 18, 15, kostka) arena = Arena(zalgoren, shadow, kostka) # zápas arena.zapas() {/PYTHON}
{PYTHON} class Kostka: def __init__(self, pocet_sten=6): self._pocet_sten = pocet_sten def __str__(self): return str(f"Kostka s {self._pocet_sten} stěnami.") def vrat_pocet_sten(self): return self._pocet_sten def hod(self): import random as _random return _random.randint(1, self._pocet_sten) def __repr__(self): return f"Kostka({self._pocet_sten})" {/PYTHON}
{PYTHON} class Bojovnik: """ Třída reprezentující bojovníka do arény. """ def __init__(self, jmeno, zivot, utok, obrana, kostka): """ jmeno - jméno bojovníka zivot - maximální život bojovníka utok - útok bojovníka obrana - obrana bojovníka kostka - instance kostky """ self._jmeno = jmeno self._zivot = zivot self._max_zivot = zivot self._utok = utok self._obrana = obrana self._kostka = kostka self._zprava = "" def __str__(self): return str(self._jmeno) def je_nazivu(self): return self._zivot > 0 def graficky_zivot(self): celkem = 20 pocet = int(self._zivot / self._max_zivot * celkem) if pocet == 0 and self.je_nazivu(): pocet = 1 return f"[{'#' * pocet}{' ' * (celkem - pocet)}]" def bran_se(self, uder): zraneni = uder - (self._obrana + self._kostka.hod()) if zraneni > 0: zprava = f"{self._jmeno} utrpěl poškození {zraneni} hp." self._zivot = self._zivot - zraneni if self._zivot < 0: self._zivot = 0 zprava = f"{zprava[:-1]} a zemřel." else: zprava = f"{self._jmeno} odrazil útok." self._nastav_zpravu(zprava) def utoc(self, souper): uder = self._utok + self._kostka.hod() zprava = f"{self._jmeno} útočí s úderem za {uder} hp." self._nastav_zpravu(zprava) souper.bran_se(uder) def _nastav_zpravu(self, zprava): self._zprava = zprava def vrat_posledni_zpravu(self): return self._zprava {/PYTHON}
{PYTHON} class Arena: def __init__(self, bojovnik_1, bojovnik_2, kostka): self._bojovnik_1 = bojovnik_1 self._bojovnik_2 = bojovnik_2 self._kostka = kostka def _vykresli(self): # zakomentováno kvůli kompileru # self._vycisti_obrazovku() print("-------------- Aréna -------------- \n") print("Zdraví bojovníků: \n") print(f"{self._bojovnik_1} {self._bojovnik_1.graficky_zivot()}") print(f"{self._bojovnik_2} {self._bojovnik_2.graficky_zivot()}") """ def _vycisti_obrazovku(self): import os as _os _os.system('cls' if _os.name == 'nt' else 'clear') """ def _vypis_zpravu(self, zprava): import time as _time print(zprava) # zakomentováno kvůli kompileru _time.sleep(0.75) def zapas(self): print("Vítejte v aréně!") print(f"Dnes se utkají {self._bojovnik_1} a {self._bojovnik_2}!") print("Zápas může začít...", end=" ") input() # cyklus s bojem while self._bojovnik_1.je_nazivu() and self._bojovnik_2.je_nazivu(): self._bojovnik_1.utoc(self._bojovnik_2) self._vykresli() self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) self._bojovnik_2.utoc(self._bojovnik_1) self._vykresli() self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu()) {/PYTHON}
V kompileri vyššie sú zakomentované niektoré riadky kódu, ktoré kompiler nedokáže vykonať. Pokiaľ si budete kód kopírovať, myslite na zakomentovaný kód pre správny chod programu vo vašom IDE.
Charakteristiky hrdinov si môžete upraviť podľa ľubovôle.
Doladenie nedostatkov
Výsledok je celkom pôsobivý. Objekty spolu komunikujú, grafický život ubúda ako má, zážitok umocňuje dramatická pauza. Aréna má však dva nedostatky:
- V cykle s bojom útočí prvý bojovník na druhého. Potom však vždy útočí aj druhý bojovník, nehľadiac na to, či ho prvý nezabil. Môže teda útočiť už ako mŕtvy. Pozrime sa na výpis boja vyššie, Shadow útočil ako posledný aj keď bol mŕtvy. Až potom sa vystúpilo z cyklu. U prvého bojovníka tento problém nie je, u druhého musíme pred útokom kontrolovať, či je nažive.
- Druhým nedostatkom je, že bojovníci vždy bojujú v rovnakom poradí,
čiže Zalgoren má vždy výhodu. Poďme vniesť ďalší prvok náhodného
určenia, ktorý z bojovníkov bude začínať. Keďže sú
bojovníci vždy dvaja, použijeme trik na generovanie náhodnej
True/False
hodnoty:
import random if random.randint(0, 1): print("Pravda!") else: print("Nepravda!")
Funkcia randint()
vygeneruje s danými parametrami buď
0
alebo 1
. To Python ľahko dokáže interpretovať
ako boolean výstup a my to využijeme náhodnej voľbe, ktorý bojovník bude
útočiť ako prvý. Zostáva sa zamyslieť nad tým, ako konkrétne do kódu
prehodenia bojovníkov zaniesť. Iste by bolo veľmi neprehľadné
opodmienkovať príkazy vo while
cykle. Keďže však už vieme,
ako v Pythone fungujú referencie, nie je to pre nás problém. Navyše Python
podporuje súbežné priraďovanie, takže si ani nemusíme
tvoriť zástupné premenné. Jednoducho prehodíme inštancie bojovníkov.
Zmenená verzia metódy zapas()
vrátane podmienky, aby nemohol
útočiť mŕtvy bojovník, vyzerá takto:
def zapas(self): import random as _random print("Vítejte v aréně!") print(f"Dnes se utkají {self._bojovnik_1} a {self._bojovnik_2}!") print("Zápas může začít...", end=" ") input() # prohození bojovníků if _random.randint(0, 1): # generujeme True/False (self._bojovnik_1, self._bojovnik_2) = (self._bojovnik_2, # pokud padlo True, prohodíme instance bojovníků self._bojovnik_1) # cyklus s bojem while self._bojovnik_1.je_nazivu() and self._bojovnik_2.je_nazivu(): self._bojovnik_1.utoc(self._bojovnik_2) self._vykresli() self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) if self._bojovnik_2.je_nazivu(): self._bojovnik_2.utoc(self._bojovnik_1) self._vykresli() self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu())
Bojovníky prehodíme pomocou výrazu so zátvorkami vyššie. Ak by tam neboli, tak by Python vyhodil chybu.
Kód sme na tomto mieste odriadkovali, aby sa nám program tak nerozťahoval do šírky. Odporúčaná dĺžka riadka je 80 znakov.
Vyskúšajme si finálnu podobu kódu:
{PYTHON} from kostka import Kostka from bojovnik import Bojovnik from arena import Arena # vytvoření objektů kostka = Kostka(10) zalgoren = Bojovnik("Zalgoren", 100, 20, 10, kostka) shadow = Bojovnik("Shadow", 60, 18, 15, kostka) arena = Arena(zalgoren, shadow, kostka) # zápas arena.zapas() {/PYTHON}
{PYTHON} class Kostka: def __init__(self, pocet_sten=6): self._pocet_sten = pocet_sten def __str__(self): return str(f"Kostka s {self._pocet_sten} stěnami.") def vrat_pocet_sten(self): return self._pocet_sten def hod(self): import random as _random return _random.randint(1, self._pocet_sten) def __repr__(self): return f"Kostka({self._pocet_sten})" {/PYTHON}
{PYTHON} class Bojovnik: """ Třída reprezentující bojovníka do arény. """ def __init__(self, jmeno, zivot, utok, obrana, kostka): """ jmeno - jméno bojovníka zivot - maximální život bojovníka utok - útok bojovníka obrana - obrana bojovníka kostka - instance kostky """ self._jmeno = jmeno self._zivot = zivot self._max_zivot = zivot self._utok = utok self._obrana = obrana self._kostka = kostka self._zprava = "" def __str__(self): return str(self._jmeno) def je_nazivu(self): return self._zivot > 0 def graficky_zivot(self): celkem = 20 pocet = int(self._zivot / self._max_zivot * celkem) if pocet == 0 and self.je_nazivu(): pocet = 1 return f"[{'#' * pocet}{' ' * (celkem - pocet)}]" def bran_se(self, uder): zraneni = uder - (self._obrana + self._kostka.hod()) if zraneni > 0: zprava = f"{self._jmeno} utrpěl poškození {zraneni} hp." self._zivot = self._zivot - zraneni if self._zivot < 0: self._zivot = 0 zprava = f"{zprava[:-1]} a zemřel." else: zprava = f"{self._jmeno} odrazil útok." self._nastav_zpravu(zprava) def utoc(self, souper): uder = self._utok + self._kostka.hod() zprava = f"{self._jmeno} útočí s úderem za {uder} hp." self._nastav_zpravu(zprava) souper.bran_se(uder) def _nastav_zpravu(self, zprava): self._zprava = zprava def vrat_posledni_zpravu(self): return self._zprava {/PYTHON}
{PYTHON} class Arena: def __init__(self, bojovnik_1, bojovnik_2, kostka): self._bojovnik_1 = bojovnik_1 self._bojovnik_2 = bojovnik_2 self._kostka = kostka def _vykresli(self): # zakomentováno kvůli kompileru # self._vycisti_obrazovku() print("-------------- Aréna -------------- \n") print("Zdraví bojovníků: \n") print(f"{self._bojovnik_1} {self._bojovnik_1.graficky_zivot()}") print(f"{self._bojovnik_2} {self._bojovnik_2.graficky_zivot()}") """ def _vycisti_obrazovku(self): import os as _os _os.system('cls' if _os.name == 'nt' else 'clear') """ def _vypis_zpravu(self, zprava): import time as _time print(zprava) # zakomentováno kvůli kompileru _time.sleep(0.75) def zapas(self): import random as _random print("Vítejte v aréně!") print(f"Dnes se utkají {self._bojovnik_1} a {self._bojovnik_2}!") print("Zápas může začít...", end=" ") input() # prohození bojovníků if _random.randint(0, 1): (self._bojovnik_1, self._bojovnik_2) = (self._bojovnik_2, self._bojovnik_1) # cyklus s bojem while self._bojovnik_1.je_nazivu() and self._bojovnik_2.je_nazivu(): self._bojovnik_1.utoc(self._bojovnik_2) self._vykresli() self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) if self._bojovnik_2.je_nazivu(): self._bojovnik_2.utoc(self._bojovnik_1) self._vykresli() self._vypis_zpravu(self._bojovnik_2.vrat_posledni_zpravu()) self._vypis_zpravu(self._bojovnik_1.vrat_posledni_zpravu()) {/PYTHON}
Vidíme, že všetko je už v poriadku. Gratulujem vám, ak ste sa dostali až sem a tutoriály naozaj čítali a pochopili, máte základy objektového programovania v Pythone a dokážete tvoriť rozumné aplikácie 🙂
V budúcej lekcii, Dedičnosť a polymorfizmus v Pythone , sa pozrieme na objektovo orientované programovanie podrobnejšie. V úvode sme si hovorili, že OOP stojí na pilieroch: zapuzdrenie, dedičnosť a polymorfizmus. Prvé vieme už veľmi dobre a rozumieme reči podčiarkovníkov. Takže tie ďalšie dve zručnosti nás čakajú nabudúce.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 1014x (4.38 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python