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

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():

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

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:

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

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

 

Predchádzajúci článok
Bojovník do arény v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
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