IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

8. diel - Bojovník do arény v Pythone

V predchádzajúcom cvičení, Riešené úlohy k 6.-7. lekciu OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

Už teda vieme, ako fungujú referencie a ako môžeme s objektmi zaobchádzať. Bude sa nám to hodiť dnes aj nabudúce. Tento a budúci Python tutoriál budú venované dokončeniu našej arény. Hraciu kocku už máme, ešte nám chýbajú ďalšie dva objekty: bojovník a samotná aréna. Dnes sa budeme venovať bojovníkovi. Najprv si popíšme, čo má bojovník vedieť, potom sa pustíme do písania kódu.

Atribúty

Bojovník sa bude nejako volať a bude mať určitý počet hp (teda bodov života, napr. 80hp). Budeme uchovávať jeho maximálny život (bude sa líšiť pri každej inštancii) a jeho súčasný život, teda napr. zranený bojovník bude mať 40hp z 80. Bojovník má určitý útok a obranu, oboje vyjadrené opäť v hp. Keď bojovník útočí s útokom 20hp na druhého bojovníka s obranou 10hp, uberie mu 10hp života. Bojovník bude mať referenciu na inštancii objektu Kostka. Pri útoku či obrane si vždy hodí kockou ak útoku/obrane pripočíta padnuté číslo. (Samozrejme by mohol mať každý bojovník svoju kocku, ale chcel som sa priblížiť stolovej podobe hry a ukázať, ako OOP naozaj simuluje realitu. Bojovníci teda budú zdieľať jednu inštanciu kocky.) Kockou dodáme hre prvok náhody, v realite sa jedná vlastne o šťastie, ako sa útok alebo obrana vydarí. Napokon budeme chcieť, aby bojovníci podávali správy o tom, čo sa deje, pretože inak by z toho užívateľ nič nemal. Správa bude vyzerať napr. "Zalgoren útočí s úderom za 25hp.". Správami sa zatiaľ nebudeme zaťažovať a vrátime sa k nim až nakoniec.

Už vieme, čo budeme robiť, poďme na to! 🙂 Najprv upravíme štruktúru nášho projektu TahovyBoj. Triedu Kostka v súbore kostka.py máme hotovú a ďalej sa jej nebudeme zaoberať, len ju budeme používať. Aby to bolo možné, budeme si ju musieť do všetkých ďalších súborov, kde ju chceme využiť, najskôr importovať. V našom prípade to bude import do nového súboru main.py, ktorý si založíme. V ňom bude hlavný kód programu, teda to, čo budeme písať mimo triedy. Arénu budeme mať v súbore arena.py. Ten si len založíme a necháme nabudúce. Štruktúru projektu tým máme hotovú a nič nám nebráni pustiť sa do bojovníka. Do nového súboru bojovnik.py si teda pridajme triedu Bojovnik. Za moment jej dodáme aj patričné atribúty. Všetky budú privátne. Trieda Bojovnik zatiaľ vyzerá teda takto:

class Bojovnik:
    """
    Třída reprezentující bojovníka do arény.
    """

Metódy

Poďme pre atribúty vytvoriť konštruktor, nebude to nič ťažké. Komentáre tu vynechám, vy si ich dopíšte podobne, ako u atribútov vyššie. Nebudem ich písať ani pri ďalších metódach, aby sa tutoriál zbytočne nerozťahoval a zostal prehľadný (prípadne sa pozrite do archívu):

def __init__(self, jmeno, zivot, utok, obrana, kostka):
    self._jmeno = jmeno
    self._zivot = zivot
    self._max_zivot = zivot
    self._utok = utok
    self._obrana = obrana
    self._kostka = kostka

Všimnime si, že maximálne zdravie si v konštruktore odvodíme a nemáme naň parameter v hlavičke. Predpokladáme, že je bojovník pri vytvorení plne zdravý. Stačí nám teda poznať iba jeho život a maximálny život bude rovnaký.

Prejdime k metódam a opäť sa najskôr zamyslime nad tým, čo by mal bojovník vedieť. Začnime tým jednoduchším, budeme chcieť nejakú textovú reprezentáciu, aby sme mohli bojovníka vypísať. Prekryjeme teda metódu __str__(), ktorá vráti meno bojovníka. Určite sa nám bude hodiť metóda vracajúca, či je bojovník nažive. Aby to bolo trochu zaujímavejšie, budeme chcieť kresliť život bojovníka do konzoly, nebudeme teda písať, koľko má života, ale „vykreslíme“ ho takto:

[#########    ]

Vyššie uvedený život by zodpovedal asi 70%. Doteraz spomínané metódy nepotrebovali žiadne parametre. Samotný útok a obranu nechajme na neskôr a poďme si implementovať metódy __str__(), je_nazivu() a graficky_zivot(). Začnime sa __str__(), tam nie je čo vymýšľať:

def __str__(self):
    return str(self._jmeno)

Teraz implementujme metódu je_nazivu(), opäť to nebude nič ťažké. Stačí skontrolovať, či je život väčší ako 0 a podľa toho sa zachovať:

def je_nazivu(self):
    if self._zivot > 0:
       return True
    else:
       return False

Keďže aj samotný výraz (self._zivot > 0) je vlastne logická hodnota, môžeme vrátiť tú a kód sa značne zjednoduší:

def je_nazivu(self):
    return self._zivot > 0

Grafický život

Ako sme už spomínali, metóda graficky_zivot() umožní vykresliť ukazovateľ života v grafickej podobe. Už vieme, že z hľadiska objektového návrhu nie je vhodné, aby metóda objektu priamo vypisovala do konzoly (pokiaľ nie je na výpis objekt určený), preto si znaky uložíme do reťazca a ten vrátime pre neskoršie vypísanie. Pozrime sa na kód metódy a následne si ho podrobne popíšeme:

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)}]"

Určíme si celkovú dĺžku ukazovateľa života do premennej celkem (napríklad 20). Teraz v podstate nepotrebujeme nič iné, než trojčlenku. Pokiaľ max_zivot zodpovedá celkem dielikom, zivot bude zodpovedať pocet dielikom. Premenná pocet je počet dielikov aktuálneho zdravia.

Matematicky platí, že pocet = (zivot / maxZivot) * celkem. My ešte doplníme pretypovanie na celé číslo.

Mali by sme ošetriť prípad, keď je život taký nízky, že nám vyjde na 0 dielikov, ale bojovník je stále nažive. V tom prípade vykreslíme jeden dielik, inak by to vyzeralo, že je už mŕtvy. Ďalej využijeme formátovanie a replikovanie.

Všetko si vyskúšame. Prejdime do súboru main.py a importujme si triedy Bojovnik a Kostka:

from bojovnik import Bojovnik
from kostka import Kostka

Ďalej si vytvoríme inštanciu bojovníka (a kocky, pretože tu musíme odovzdať konštruktoru bojovníka). Následne vypíšeme, či je nažive a jeho život graficky:

from kostka import Kostka
from bojovnik import Bojovnik

kostka = Kostka(10)
bojovnik = Bojovnik("Zalgoren", 100, 20, 10, kostka)
print(f"Bojovník: {bojovnik}")  # test __str__()
print(f"Naživu: {bojovnik.je_nazivu()}")  # test naživu
print(f"Život: {bojovnik.graficky_zivot()}")  # test graficky_zivot()
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

    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)}]"
class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self,pocet_sten=6):
        self._pocet_sten = pocet_sten

    def vrat_pocet_sten(self):
        """
        Vrátí počet stěn kostky.
        """
        return self._pocet_sten

    def hod(self):
        """
        Vykoná hod kostkou a vrátí číslo od 1 do
        počtu stěn.
        """
        import random as _random
        return _random.randint(1, self._pocet_sten)

    def __str__(self):
        """
        Vrací textovou reprezentaci kostky.
        """
        return f"Kostka({self._pocet_sten})"

Pozrime sa na výstup:

Zalgoren žije!
Bojovník: Zalgoren
Naživu: True
Život: [####################]

Boj

Dostávame sa k samotnému boju. Implementujeme metódy na útok a obranu.

Obrana

Začnime obranou. Vrátime sa do súboru bojovnik.py a do triedy Bojovnik pridáme obrannú metódu. Metóda bran_se() bude umožňovať brániť sa úderu, ktorého sila bude odovzdaná metóde ako parameter. Metódu si opäť ukážeme a potom popíšeme:

def bran_se(self, uder):
    zraneni = uder - (self._obrana + self._kostka.hod())
    if zraneni > 0:
        self._zivot = self._zivot - zraneni
        if self._zivot < 0:
            self._zivot = 0

Najprv spočítame skutočné zranenie a to tak, že z útoku nepriateľa odpočítame našu obranu zvýšenú o číslo, ktoré padlo na hracej kocke. Ak sme zranenie celé neodrazili (zraneni > 0), budeme znižovať náš život. Táto podmienka je dôležitá, keby sme zranenia odrazili a bolo napríklad -2, bez podmienky by sa život zvýšil. Po znížení života skontrolujeme, či nie je v zápornej hodnote a prípadne ho dorovnáme na nulu.

Útok

Metóda utoc() bude brať ako parameter inštanciu bojovníka, na ktorého sa útočí. To preto, aby sme na ňom mohli zavolať metódu bran_se(), ktorá na náš útok zareaguje a zmenší protivníkov život. Tu vidíme výhody referencií v Pythone, môžeme si inštancie jednoducho odovzdávať a volať na nich metódy bez toho, aby došlo k ich skopírovaniu. Ako prvý vypočítame úder. Podobne ako pri obrane bude úder tvoriť náš útok + hodnota z hracej kocky. Na súperovi následne zavoláme metódu bran_se() s hodnotou úderu:

def utoc(self, souper):
    uder = self._utok + self._kostka.hod()
    souper.bran_se(uder)

To by sme mali, poďme si skúsiť v našom hlavnom programe (v main.py) zaútočiť a potom znova vykresliť život. Pre jednoduchosť nebudeme zakladať ďalšieho bojovníka, ale zaútočíme sami na seba:

from kostka import Kostka
from bojovnik import Bojovnik

kostka = Kostka(10)
bojovnik = Bojovnik("Zalgoren", 100, 20, 10, kostka)
print(f"Bojovník: {bojovnik}")  # test __str__()
print(f"Naživu: {bojovnik.je_nazivu()}")  # test naživu
print(f"Život: {bojovnik.graficky_zivot()}")  # test graficky_zivot()
bojovnik.utoc(bojovnik)
print(f"Život po útoku: {bojovnik.graficky_zivot()}")
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

    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 "[{0}{1}]".format("#"*pocet, " "*(celkem-pocet))

    def bran_se(self, uder):
        zraneni = uder - (self._obrana + self._kostka.hod())
        if zraneni > 0:
            self._zivot = self._zivot - zraneni
            if self._zivot < 0:
                self._zivot = 0

    def utoc(self, souper):
        uder = self._utok + self._kostka.hod()
        souper.bran_se(uder)
class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self,pocet_sten=6):
        self._pocet_sten = pocet_sten

    def vrat_pocet_sten(self):
        """
        Vrátí počet stěn kostky.
        """
        return self._pocet_sten

    def hod(self):
        """
        Vykoná hod kostkou a vrátí číslo od 1 do
        počtu stěn.
        """
        import random as _random
        return _random.randint(1, self._pocet_sten)

    def __str__(self):
        """
        Vrací textovou reprezentaci kostky.
        """
        return f"Kostka({self._pocet_sten})"

Všetko funguje, ako má:

Zalgoren se zranil!
Bojovník: Zalgoren
Naživu: True
Život: [####################]
Život po útoku: [#################   ]

Prejdime k poslednému bodu dnešného tutoriálu a to k správam.

Správy

Ako už bolo povedané, o útokoch a obrane budeme užívateľov informovať výpisom na konzole. Výpis nebude vykonávať samotná trieda Bojovnik, tá bude len vracať správy ako textové reťazce. Jedna možnosť by bola pri volaní metód utoc() a bran_se() vrátiť aj správu. Problém by však nastal v prípade, ak by sme chceli získať správu od metódy, ktorá už niečo vracia. Metóda samozrejme nemôže jednoducho vrátiť dve veci.

Poďme na vec univerzálnejšie, správu budeme ukladať do privátnej premennej zprava a napíšeme metódy na jej uloženie a vrátenie. Samozrejme by sme mohli urobiť premennú verejnú (a fungovalo by to rovnako), ale nie je dôvod, prečo umožniť zvonku zápis do správy. Skladanie zložitejšie správy vo vnútri triedy by tiež mohlo byť niekedy problematické.

Vždy je lepšie byť "prehnane" dôsledný a využívať zapuzdrenie všade, kde je to možné.

Do metódy konštruktora bojovníka __init__() teda pridáme:

self._zprava = ""

Teraz si v triede Bojovník vytvoríme dve metódy. Privátnu metódu _nastav_zpravu(), ktorá berie ako parameter text správy a slúži na vnútorné účely triedy, kde nastaví správu do privátnej premennej:

def _nastav_zpravu(self, zprava):
    self._zprava = zprava

Nič zložité. Podobne jednoduchá bude verejná metóda na vrátenie správy:

def vrat_posledni_zpravu(self):
    return self._zprava

O práci so správami obohatíme naše metódy utoc() a bran_se(). Teraz budú vyzerať takto:

    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)

Všetko si opäť vyskúšame v main.py Tentokrát už vytvoríme druhého bojovníka:

from kostka import Kostka
from bojovnik import Bojovnik

kostka = Kostka(10)
bojovnik = Bojovnik("Zalgoren", 100, 20, 10, kostka)
print("Život: {0}".format(bojovnik.graficky_zivot())) #test graficky_zivot()
#útok na našeho bojovníka
souper = Bojovnik("Shadow", 60, 18, 15, kostka)
souper.utoc(bojovnik)
print(souper.vrat_posledni_zpravu())
print(bojovnik.vrat_posledni_zpravu())
print("Život: {0}".format(bojovnik.graficky_zivot()))
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
class Kostka:
    """
    Třída reprezentuje hrací kostku.
    """

    def __init__(self,pocet_sten=6):
        self._pocet_sten = pocet_sten

    def vrat_pocet_sten(self):
        """
        Vrátí počet stěn kostky.
        """
        return self._pocet_sten

    def hod(self):
        """
        Vykoná hod kostkou a vrátí číslo od 1 do
        počtu stěn.
        """
        import random as _random
        return _random.randint(1, self._pocet_sten)

    def __str__(self):
        """
        Vrací textovou reprezentaci kostky.
        """
        return f"Kostka({self._pocet_sten})"

Pozrime sa na výsledok:

Shadow útočí!
Život: [####################]
Shadow útočí s úderem za 24 hp.
Zalgoren utrpěl poškození 7 hp.
Život: [##################  ]

To je k dnešnej lekcii všetko:-)

V ďalšej lekcii, Aréna s bojovníkmi v Pythone . dokončíme arénu simulujúcu zápas dvoch bojovníkov.


 

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é 1114x (5.25 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python

 

Predchádzajúci článok
Riešené úlohy k 6.-7. lekciu OOP v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Aréna s bojovníkmi v Pythone
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
1 hlasov
(^_^)
Aktivity