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

11. diel - Aréna s mágom (dedičnosť a polymorfizmus)

V minulej lekcii, Dedičnosť a polymorfizmus v Pythone , sme si vysvetlili dedičnosť a polymorfizmus.

V dnešnom tutoriále objektovo orientovaného programovania v Pythone si vyskúšame dedičnosť a polymorfizmus v praxi. Bude to opäť na našej hre Ťahový boj, kde z bojovníka zdedíme mága. Tento tutoriál už patrí k tým náročnejším a bude to tak aj pri ďalších. Preto si priebežne precvičujte prácu s objektmi, skúšajte si naše cvičenia a taktiež vymýšľajte svoje vlastné aplikácie, aby ste si zažili základné veci. To, že je tu prítomný celý seriál neznamená, že ho celý zrazu prečítate a pochopíte:) Snažte sa programovať priebežne.

Mág - Objektovo orientované programovanie v Pythone - Objektovo orientované programovanie v Pythone
Než začneme niečo písať, zhodnime sa na tom, čo by mal mág vedieť. Mág bude fungovať rovnako ako bojovník. Okrem života bude mať však aj manu. Spočiatku bude mana plná. V prípade plnej many môže mág vykonať magický útok, ktorý bude mať pravdepodobne vyššie poškodenie, než útok normálny (ale samozrejme záleží na tom, ako si ho nastavíme). Tento útok manu vybije na 0. Každé kolo sa bude mana zvyšovať o 10 a mág bude podnikať len bežný útok. Akonáhle sa mana úplne doplní, opäť bude môcť magický útok použiť. Mana bude zobrazená grafickým ukazovateľom, rovnako ako život.

Do pôvodného projektu TahovyBoj vytvoríme teda triedu Mag, ktorú zdedíme z triedy Bojovnik. Opäť si pre ňu založíme samostatný súbor a nazveme ho mag.py Obsah súboru bude zatiaľ vyzerať takto (triedu si opäť okomentujte):

from bojovnik import Bojovnik

class Mag(Bojovnik):

Konštruktor potomka

Pôvodný konštruktor bojovníka použiť nemôžeme, pretože chceme mať u mága dva parametre navyše (manu a magický útok).

Definujeme si teda konštruktor v potomkovi, ktorý berie parametre potrebné na vytvorenie bojovníka a niekoľko parametrov navyše pre mága.

U potomkov nie je nutné vždy volať konštruktor predka. Náš konštruktor musí mať samozrejme všetky parametre potrebné pre predka plus tie nové, čo má navyše potomok, takže ho volať budeme. Niektoré potom odovzdáme predkovi a niektoré si spracujeme sami. Konštruktor predka sa vykoná pred naším konštruktorom.

V Pythone existuje metóda super(), ktorá zavolá verziu metódy na predkovi. My teda môžeme zavolať konštruktor predka s danými parametrami a potom vykonať naviac inicializáciu pre mága. V Pythone volaní konštruktora predka (metódy super()) píšeme do tela nášho konštruktora.

Konštruktor mága bude teda vyzerať takto:

def __init__(self, jmeno, zivot, utok, obrana, kostka, mana, magicky_utok):
    super().__init__(jmeno, zivot, utok, obrana, kostka)
    self._mana = mana
    self._max_mana = mana
    self._magicky_utok = magicky_utok

Presuňme sa do hlavného programu v main.py a druhého bojovníka (Shadow) zmeňme na mága:

gandalf = Mag("Gandalf", 60, 15, 12, kostka, 30, 45)

Samozrejme musíme doplniť import triedy Mag a zámenu urobiť aj v riadku, kde bojovníka do arény vkladáme.

Polymorfizmus a prepisovanie metód

Bolo by výhodné, keby inštancia triedy Arena mohla s mágom pracovať rovnakým spôsobom ako s bojovníkom. My už vieme, že takémuto mechanizmu hovoríme polymorfizmus. Aréna zavolá na bojovníkovi metódu utoc() so súperom v parametri. Nestará sa o to, či bude útok vykonávať bojovník alebo mág, bude s nimi pracovať rovnako. U mága si teda prepíšeme metódu utoc() z predka. Zdedenú metódu prepíšeme tak, aby útok pracoval s manom. Hlavička metódy ale zostane rovnaká.

V potomkovi môžeme jednoducho a bez okolkov prepísať ľubovoľnú metódu. V Pythone sú všetky metódy - povedané terminológiou jazykov C++, C# - virtuálne.

Teraz sa vráťme k metóde utoc(). V súbore s mágom (mag.py) ju prepíšeme (prekryjeme). Jej správanie nebude nijako zložité. Podľa hodnoty many buď vykonáme bežný útok alebo útok magický. Hodnotu many potom buď zvýšime o 10 alebo naopak znížime na 0 v prípade magického útoku:

def utoc(self, souper):
    # mana není naplněna
    if self._mana < self._max_mana:
        self._mana = self._mana + 10
        if self._mana > self._max_mana:
            self._mana = self._max_mana
        uder = self._utok + self._kostka.hod()
        zprava = f"{self._jmeno} útočí s úderem za {uder} hp."
        self._nastav_zpravu(zprava)
    #magický útok
    else:
        uder = self._magicky_utok + self._kostka.hod()
        zprava = f"{self._jmeno} použil magii za {uder} hp."
        self._nastav_zpravu(zprava)
        self._mana = 0
        souper.bran_se(uder)

Kód je zrozumiteľný. Všimnime si ale obmedzenie many na max_mana. Môže sa nám totiž stať, že túto hodnotu presiahneme. Teraz sa na chvíľu zastavme a zamyslime sa nad kódom. Nemagický útok vyššie v podstate vykonáva pôvodná metóda utoc(). Iste by teda bolo prínosné zavolať podobu metódy na predkovi namiesto toho, aby sme správanie opisovali. Na to opäť použijeme metódu super(), ktorá zavolá metódu utoc() tak, ako je definovaná v rodičovskej triede Bojovnik:

    def utoc(self, souper):
    # mana není naplněna
        if self._mana < self._max_mana:
            self._mana = self._mana + 10
            if self._mana > self._max_mana:
                self._mana = self._max_mana
            super().utoc(souper)
    #magický útok
        else:
            uder = self._magicky_utok + self._kostka.hod()
            zprava = f"{self._jmeno} použil magii za {uder} hp."
            self._nastav_zpravu(zprava)
            self._mana = 0
            souper.bran_se(uder)
from kostka import Kostka
from bojovnik import Bojovnik
from arena import Arena
from mag import Mag
# vytvoření objektů
kostka = Kostka(10)
zalgoren = Bojovnik("Zalgoren", 100, 20, 10, kostka)
gandalf = Mag("Gandalf", 60, 15, 12, kostka, 30, 45)
arena = Arena(zalgoren, gandalf, kostka)
# zápas
arena.zapas()
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})"
from mag import Mag
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())

Opäť vidíme, ako môžeme znovupoužívať kód. S dedičnosťou je spojených naozaj mnoho techník, ako si ušetriť prácu. V našom prípade to ušetrí niekoľko riadkov, ale pri väčšom projekte by to mohlo mať obrovský význam. Ostatné nesúkromné metódy sa automaticky zdedia.

Aplikácia teraz funguje tak, ako má:

Hra funguje správně:
-------------- Aréna --------------

Zdraví bojovníků:

Gandalf [####################]                  ]
Zalgoren [#############       ]
Gandalf použil magii za 51 hp.
Zalgoren utrpěl poškození 33 hp.

Aréna nás však neinformuje o mane mága. Poďme to napraviť. Pridáme mágovi verejnú metódu graficka_mana(), ktorá bude obdobne ako pri živote vracať reťazec s grafickým ukazovateľom many.

Aby sme nemuseli logiku so zložením ukazovateľa písať dvakrát, upravíme metódu graficky_zivot() v triede Bojovnik. Pripomeňme si, ako vyzerá:

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

Vidíme, že nie je okrem premenných zivot a max_zivot na živote nijako závislá. Metódu premenujeme na graficky_ukazatel() a dáme jej dva parametre: aktuálnu hodnotu a maximálnu hodnotu. Premenné zivot a max_zivot v tele metódy potom nahradíme za aktualni a maximalni:

def graficky_ukazatel(self, aktualni, maximalni):
    celkem = 20
    pocet = int(aktualni / maximalni * celkem)
    if (pocet == 0 and self.je_nazivu()):
        pocet = 1
    return f"[{'#' * pocet}{' ' * (celkem - pocet)}]"

Metódu graficky_zivot() v triede Bojovnik naimplementujeme znova, bude nám v nej stačiť jediný riadok a to zavolanie metódy graficky_ukazatel() s príslušnými parametrami:

def graficky_zivot(self):
    return self.graficky_ukazatel(self._zivot, self._max_zivot)

Určite sme mohli v tutoriáli s bojovníkom urobiť metódu graficky_ukazatel() rovno. Chceli sme však ukázať, ako sa riešia prípady, keď potrebujeme vykonať podobnú funkčnosť viackrát. S takouto parametrizáciou sa prax stretneme často, pretože nikdy presne nevieme, čo budeme v budúcnosti od nášho programu požadovať.

Teraz môžeme vykresľovať ukazovateľ tak, ako sa nám to hodí. Presuňme sa do triedy Mag a naimplementujme metódu graficka_mana():

def graficka_mana(self):
    return self.graficky_ukazatel(self._mana, self._max_mana)

Jednoduché, že? Teraz je mág hotový, zostáva len naučiť arénu zobrazovať manu v prípade, že je bojovník mág. Presuňme sa teda do triedy Arena.

Rozpoznanie typu objektu

Keďže sa nám teraz vykreslenie bojovníka skomplikovalo, urobíme si naň samostatnú metódu vypis_bojovnika(). Jej parametrom bude daná inštancia bojovníka:

def _vypis_bojovnika(self, bojovnik):
    print(bojovnik)
    print(f"Život: {bojovnik.graficky_zivot()}")

Teraz poďme reagovať na to, či je bojovník mág. Už sme si povedali, že na to slúži funkcia isinstance():

def _vypis_bojovnika(self, bojovnik):
    print(bojovnik)
    print(f"Život: {bojovnik.graficky_zivot()}")
    if isinstance(bojovnik, Mag):
        print(f"Mana: {bojovnik.graficka_mana()}")

To by sme mali, vypis_bojovnika() budeme volať v metóde vykresli(), ktorá bude vyzerať takto:

    def _vykresli(self):
       # zakomentováno kvůli kompileru
       # self._vycisti_obrazovku()
        print("-------------- Aréna -------------- \n")
        print("Bojovníci: \n")
        self._vypis_bojovnika(self._bojovnik_1)
        self._vypis_bojovnika(self._bojovnik_2)

A máme hotovo:)

Aplikáciu ešte môžeme dodať krajší vzhľad. Vložíme napríklad ASCIIart nadpis Aréna, ktorý vytvoríme ASCII generátorom. Metódu na vykreslenie ukazovateľa upravíme tak, aby vykresľovala plný obdĺžnik miesto # (ten napíšeme pomocou klávesov Alt + 2 1 9). Výsledok bude vyzerať takto:

Upravená podoba aplikace:
   __    ____  ____  _  _    __
  /__\  (  _ \( ___)( \( )  /__\
 /(__)\  )   / )__)  )  (  /(__)\
(__)(__)(_)\_)(____)(_)\_)(__)(__)

Bojovníci:

Zalgoren
Život: ████

Gandalf
Život: ███████
Mana:  █

Gandalf použil mágiu za 48 hp
Zalgoren utrpel poškodenie 33 hp

Kód je k dispozícii v prílohe. Pokiaľ ste niečomu nerozumeli, skúste si článok prečítať viackrát alebo pomalšie, sú to dôležité praktiky.

V nasledujúcom kvíze, Kvíz - Dedičnosť a polymorfizmus v Pythone, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

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

 

Predchádzajúci článok
Dedičnosť a polymorfizmus v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Kvíz - Dedičnosť a polymorfizmus v Pythone
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
(^_^)
Aktivity