Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

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 s bojovníkmi, 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

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 ArenaFight vytvoríme teda triedu Mage, ktorú zdedíme z triedy Warrior. Opäť si pre ňu založíme samostatný súbor a nazveme ho mage.py Obsah súboru bude zatiaľ vyzerať takto (triedu si opäť okomentujte):

from warrior import Warrior

class Mage(Warrior):

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, name, health, damage, defense, die, mana, magic_damage):
    super().__init__(name, health, damage, defense, die)
    self._mana = mana
    self._max_mana = mana
    self._magic_damage = magic_damage

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

gandalf = Mage("Gandalf", 60, 15, 12, die, 30, 45)

Samozrejme musíme doplniť import triedy Mage 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 attack() 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 attack() z predka. Zdedenú metódu prepíšeme tak, aby útok pracoval s manou. 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 attack(). V súbore s mágom (mage.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 attack(self, enemy):
    # Mana isn't full
    if self._mana < self._max_mana:
        self._mana = self._mana + 10
        if self._mana > self._max_mana:
            self._mana = self._max_mana
        hit = self._damage + self._die.roll()
        message = f"{self._name} attacks with a hit worth {hit} hp."
        self._set_message(message)
    # Magic damage
    else:
        hit = self._magic_damage + self._die.roll()
        message = f"{self._name} used magic for {hit} hp."
        self._set_message(message)
        self._mana = 0
    enemy.defend(hit)

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 attack(). 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 attack() tak, ako je definovaná v rodičovskej triede Warrior:

    def attack(self, enemy):
    # Mana isn't full
        if self._mana < self._max_mana:
            self._mana = self._mana + 10
            if self._mana > self._max_mana:
                self._mana = self._max_mana
            super().attack(enemy)
    # Magic damage
        else:
            hit = self._magic_damage + self._die.roll()
            message = f"{self._name} used magic for {hit} hp."
            self._set_message(message)
            self._mana = 0
            enemy.defend(hit)
from rolling_die import RollingDie
from warrior import Warrior
from arena import Arena
from mage import Mage
# creating objects
die = RollingDie(10)
zalgoren = Warrior("Zalgoren", 100, 20, 10, die)
gandalf = Mage("Gandalf", 60, 15, 12, die, 30, 45)
arena = Arena(zalgoren, gandalf, die)
# fight
arena.fight()
class RollingDie:

    def __init__(self, sides_count=6):
        self._sides_count = sides_count

    def __str__(self):
        return str(f"Rolling die with {self._sides_count} sides.")

    def get_sides_count(self):
        return self._sides_count

    def roll(self):
        import random as _random
        return _random.randint(1, self._sides_count)

    def __repr__(self):
        return f"RollingDie({self._sides_count})"
from mage import Mage
class Arena:

    def __init__(self, warrior_1, warrior_2, die):
        self._warrior_1 = warrior_1
        self._warrior_2 = warrior_2
        self._die = die

    def _render(self):
       # commented out due to the compiler
       # self._clear_screen()
        print("-------------- Arena -------------- \n")
        print("Warriors health: \n")
        print(f"{self._warrior_1} {self._warrior_1.health_bar()}")
        print(f"{self._warrior_2} {self._warrior_2.health_bar()}")

    """
    def _clear_screen(self):
        import os as _os
        _os.system('cls' if _os.name == 'nt' else 'clear')
    """

    def _print_message(self, message):
        import time as _time
        print(message)
        # commented out due to the compiler
        _time.sleep(0.75)

    def fight(self):
        import random as _random
        print("Welcome to the Arena!")
        print(f"Today {self._warrior_1} will battle against {self._warrior_2}!")
        print("Let the battle begin...", end=" ")
        input()

        # swapping the warriors
        if _random.randint(0, 1):
            (self._warrior_1, self._warrior_2) = (self._warrior_2,
                                                    self._warrior_1)
        # fight loop
        while self._warrior_1.is_alive() and self._warrior_2.is_alive():
            self._warrior_1.attack(self._warrior_2)
            self._render()
            self._print_message(self._warrior_1.get_last_message())
            self._print_message(self._warrior_2.get_last_message())
            if self._warrior_2.is_alive():
                self._warrior_2.attack(self._warrior_1)
                self._render()
                self._print_message(self._warrior_2.get_last_message())
                self._print_message(self._warrior_1.get_last_message())

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á:

The game works correctly:
-------------- Arena --------------

Warriors health:

Gandalf [####################]                  ]
Zalgoren [#############       ]
Gandalf used magic for 51 hp.
Zalgoren defended against the attack but still lost 33 hp.

Aréna nás však neinformuje o mane mága. Poďme to napraviť. Pridáme mágovi verejnú metódu mana_bar(), 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 health_bar() v triede Warrior. Pripomeňme si, ako vyzerá:

def health_bar(self):
    total = 20
    count  = int(self._health / self._max_health * total)
    if (count == 0 and self.is_alive()):
        count = 1
    return f"[{'#' * count}{' ' * (total - count)}]"

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

def graphical_bar(self, current, maximum):
    total = 20
    count = int(current / maximum * total)
    if (count == 0 and self.is_alive()):
        count = 1
    return f"[{'#' * count}{' ' * (total - count)}]"

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

def health_bar(self):
    return self.graphical_bar(self._health, self._max_health)

Určite sme mohli v tutoriáli s bojovníkom urobiť metódu graphical_bar() 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 v 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 Mage a naimplementujme metódu mana_bar():

def mana_bar(self):
    return self.graphical_bar(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ň v aréne samostatnú metódu print_warrior(). Jej parametrom bude daná inštancia bojovníka:

def _print_warrior(self, warrior):
    print(warrior)
    print(f"Health: {warrior.health_bar()}")

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

def _print_warrior(self, warrior):
    print(warrior)
    print(f"Health: {warrior.health_bar()}")
    if isinstance(warrior, Mage):
        print(f"Mana: {warrior.mana_bar()}")

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

    def _render(self):
       # commented out due to the compiler
       # self._clear_screen()
        print("-------------- Arena -------------- \n")
        print("Warriors: \n")
        self._print_warrior(self._warrior_1)
        self._print_warrior(self._warrior_2)

A máme hotovo :)

Aplikáciu ešte môžeme dodať krajší vzhľad. Vložíme napríklad ASCIIart nadpis Arena, 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:

Modified version of the application:
   __    ____  ____  _  _    __
  /__\  (  _ \( ___)( \( )  /__\
 /(__)\  )   / )__)  )  (  /(__)\
(__)(__)(_)\_)(____)(_)\_)(__)(__)

Warriors:

Zalgoren
Health: ████

Gandalf
Health: ███████
Mana:  █

Gandalf used magic for 48 hp
Zalgoren defended against the attack but still lost 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é 0x (12.65 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