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í.

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áli 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 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 a o 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, warrior_1, warrior_2, die):
        self._warrior_1 = warrior_1
        self._warrior_2 = warrior_2
        self._die = die

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 kole 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 _render(self):
    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()}")

Na začiatku voláme ďalšiu metódu clear_screen(). Metódy sa vo vnútri objektu volajú touto syntaxou:

self.method_name()

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

def _clear_screen(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 _print_message(self, message):
    import time as _time
    print(message)
    _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 fight()

Presunieme sa už k samotnému zápasu. Metóda fight() 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 fight(self):
    print("Welcome to the Arena!")
    print(f"Today {self._warrior_1} will battle against {self._warrior_2}!")
    print("Let the battle begin...", end=" ")
    input()
    # 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())
        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())

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 print_message(), 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 fight():

from rolling_die import RollingDie
from warrior import Warrior
from arena import Arena
# creating objects
die = RollingDie(10)
zalgoren = Warrior("Zalgoren", 100, 20, 10, die)
shadow = Warrior("Shadow", 60, 18, 15, die)
arena = Arena(zalgoren, shadow, 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})"
class Warrior:
    """
    The class represents an arena warrior.
    """

    def __init__(self, name, health, damage, defense, die):
        """
        name - the warrior's name
        health - maximum health
        damage - damage
        defense - defense
        die - a rolling die instance
        """
        self._name = name
        self._health = health
        self._max_health = health
        self._damage = damage
        self._defense = defense
        self._die = die
        self._message = ""

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

    def is_alive(self):
        return self._health > 0

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

    def defend(self, hit):
        injury = hit - (self._defense + self._die.roll())
        if injury > 0:
            message = f"{self._name} defended against the attack but still lost {injury} hp."
            self._health = self._health - injury
            if self._health <= 0:
                self._health = 0
                message = f"{message[:-1]} and died."

        else:
            message = f"{self._name} blocked the hit."
        self._set_message(message)

    def attack(self, enemy):
        hit = self._damage + self._die.roll()
        message = f"{self._name} attacks with a hit worth {hit} hp."
        self._set_message(message)
        enemy.defend(hit)

    def _set_message(self, message):
        self._message = message

    def get_last_message(self):
        return self._message
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):
        print("Welcome to the Arena!")
        print(f"Today {self._warrior_1} will battle against {self._warrior_2}!")
        print("Let the battle begin...", end=" ")
        input()
        # 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())
            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())

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("True!")
else:
    print("False!")

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 fight() vrátane podmienky, aby nemohol útočiť mŕtvy bojovník, vyzerá takto:

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):       # we generate True/False
        (self._warrior_1, self._warrior_2) = (self._warrior_2,    # If True is rolled, we will swap the instances of the fighters.
                                              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())

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 rolling_die import RollingDie
from warrior import Warrior
from arena import Arena
# creating objects
die = RollingDie(10)
zalgoren = Warrior("Zalgoren", 100, 20, 10, die)
shadow = Warrior("Shadow", 60, 18, 15, die)
arena = Arena(zalgoren, shadow, 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})"
class Warrior:
    """
    The class represents an arena warrior.
    """

    def __init__(self, name, health, damage, defense, die):
        """
        name - the warrior's name
        health - maximum health
        damage - damage
        defense - defense
        die - a rolling die instance
        """
        self._name = name
        self._health = health
        self._max_health = health
        self._damage = damage
        self._defense = defense
        self._die = die
        self._message = ""

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

    def is_alive(self):
        return self._health > 0

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

    def defend(self, hit):
        injury = hit - (self._defense + self._die.roll())
        if injury > 0:
            message = f"{self._name} defended against the attack but still lost {injury} hp."
            self._health = self._health - injury
            if self._health <= 0:
                self._health = 0
                message = f"{message[:-1]} and died."

        else:
            message = f"{self._name} blocked the hit."
        self._set_message(message)

    def attack(self, enemy):
        hit = self._damage + self._die.roll()
        message = f"{self._name} attacks with a hit worth {hit} hp."
        self._set_message(message)
        enemy.defend(hit)

    def _set_message(self, message):
        self._message = message

    def get_last_message(self):
        return self._message
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):       # we generate True/False
            (self._warrior_1, self._warrior_2) = (self._warrior_2,    # If True is rolled, we will swap the instances of the fighters.
                                                    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())

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é 0x (9.65 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