NOVINKA - Online rekvalifikační kurz Python programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 9 - Aréna s bojovníky v Pythonu

V minulé lekci, Bojovník do arény v Pythonu, jsme si vytvořili třídu bojovníka. Hrací kostku máme hotovou z prvních lekcí objektově orientovaného programování.

V dnešním tutoriálu objektově orientovaného programování v Pythonu spojíme všechny části naší hry dohromady a vytvoříme funkční arénu. Tutoriál bude spíše oddechový a pomůže nám zopakovat si práci s objekty.

Aréna

V naší hře Tahový boj teď potřebujeme napsat kód pro obsluhu bojovníků a výpis zpráv uživateli. Vytvoříme si objekt (třídu) Arena, kde se bude zápas odehrávat. Hlavní část programu v main.py potom jen založí objekty a o zbytek se bude starat objekt (instance třídy) Arena.

Třída bude víceméně jednoduchá. Jako atributy bude obsahovat tři potřebné instance: dva bojovníky a hrací kostku. V konstruktoru se tyto atributy naplní z parametrů. Kód třídy bude tedy následující:

class Arena:

    def __init__(self, bojovnik_1, bojovnik_2, kostka):
        self._bojovnik_1 = bojovnik_1
        self._bojovnik_2 = bojovnik_2
        self._kostka = kostka

Metoda na třídě Arena

Zamysleme se nad metodami. Z veřejných metod bude určitě potřeba jen ta k simulaci zápasu. Výstup programu na konzoli uděláme trochu na úrovni a také umožníme třídě Arena, aby přímo ke konzoli přistupovala. Rozhodli jsme se, že výpis bude v kompetenci třídy, jelikož se nám to zde vyplatí. Naopak kdyby výpis prováděli i bojovníci, bylo by to na škodu. Potřebujeme tedy metodu, která vykreslí obrazovku s aktuálními údaji o kole a životy bojovníků. Zprávy o útoku a obraně budeme chtít vypisovat s dramatickou pauzou, aby byl výsledný efekt lepší, uděláme si pro takový typ zprávy ještě pomocnou metodu. Začněme s vykreslením informační 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čátku voláme další metodu vycisti_obrazovku(). Metody se uvnitř objektu volají touto syntaxí:

self.metoda()

Zbytek je jasný. Obě metody jsou soukromé, budeme je používat jen uvnitř třídy. Nyní se podívejme na kód privátní metody vycisti_obrazovku():

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

Metoda používá standardní knihovnu os k zjištění, na jakém operačním systému program spouštíme. Následně provede odpovídající příkaz pro smazání obrazovky pro daný systém.

Takto napsaná metoda funguje pro terminály ve Windows, Linux i macOS. Ujistěte se tedy, že program spouštíte v terminálu a ne například v Run konzoli PyCharm.

Další privátní metodou bude výpis zprávy s dramatickou pauzou:

def _vypis_zpravu(self, zprava):
    import time as _time
    print(zprava)
    _time.sleep(0.75)

Kód je zřejmý až funkci sleep() z modulu time, která uspí program na daný počet sekund.

Metoda zapas()

Přesuneme se již k samotnému zápasu. Metoda zapas() nebude mít žádné parametry a nebude ani nic vracet. Uvnitř bude cyklus, který bude na střídačku volat útoky bojovníků navzájem a vypisovat informační obrazovku a zprávy. Metoda bude vypadat 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é informace a po stisku klávesy přejde do cyklu s bojem. Jedná se o while cyklus, který se opakuje, dokud jsou oba bojovníci naživu.

První bojovník zaútočí na druhého, jeho útok vnitřně zavolá na druhém bojovníkovi obranu. Po útoku vykreslíme obrazovku s informacemi a dále zprávy o útoku a obraně pomocí naší metody vypis_zpravu(), která po výpisu udělá dramatickou pauzu. To samé provedeme i pro druhého bojovníka.

Přesuňme se do main.py, vytvořme patřičné instance a zavolejme na aréně metodu zapas():

Klikni pro editaci
  • App
    • main.py
    • kostka.py
    • bojovnik.py
    • arena.py
  • 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())
    
    • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

    V kompileru výše jsou zakomentovány některé řádky kódu, které kompiler nedokáže vykonat. Pokud si budete kód kopírovat, myslete na zakomentovaný kód pro správný chod programu ve vašem IDE.

    Charakteristiky hrdinů si můžete upravit dle libosti.

    Doladění nedostatků

    Výsledek je docela působivý. Objekty spolu komunikují, grafický život ubývá jak má, zážitek umocňuje dramatická pauza. Aréna má však dva nedostatky:

    • V cyklu s bojem útočí první bojovník na druhého. Poté však vždy útočí i druhý bojovník, nehledě na to, zda ho první nezabil. Může tedy útočit již jako mrtvý. Podívejme se na výpis boje výše, Shadow útočil jako poslední i když byl mrtvý. Až potom se vystoupilo z cyklu. U prvního bojovníka tento problém není, u druhého musíme před útokem kontrolovat, zda je naživu.
    • Druhým nedostatkem je, že bojovníci vždy bojují ve stejném pořadí, čili Zalgoren má vždy výhodu. Pojďme vnést další prvek náhodného určení, který z bojovníků bude začínat. Jelikož jsou bojovníci vždy dva, použijeme trik ke generování náhodné True/False hodnoty:
    import random
    
    if random.randint(0, 1):
        print("Pravda!")
    else:
        print("Nepravda!")

    Funkce randint() vygeneruje s danými parametry buď 0 nebo 1. To Python snadno dokáže interpretovat jako boolean výstup a my toho využijeme náhodné volbě, který bojovník bude útočit jako první. Zbývá se zamyslet nad tím, jak konkrétně do kódu prohození bojovníků zanést. Jistě by bylo velmi nepřehledné opodmínkovat příkazy ve while cyklu. Jelikož však už víme, jak v Pythonu fungují reference, není to pro nás problém. Navíc Python podporuje souběžné přiřazování, takže si ani nemusíme tvořit zástupné proměnné. Jednoduše prohodíme instance bojovníků.

    Změněná verze metody zapas() včetně podmínky, aby nemohl útočit mrtvý bojovník, vypadá 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 prohodíme pomocí výrazu se závorkami výše. Pokud by tam nebyly, tak by Python vyhodil chybu.

    Kód jsme na tomto místě odřádkovali, aby se nám program tolik neroztahoval do šířky. Doporučená délka řádku je 80 znaků.

    Vyzkoušejme si finální podobu kódu:

    Klikni pro editaci
    • App
      • main.py
      • kostka.py
      • bojovnik.py
      • arena.py
    • 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())
      
      • Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.

      Vidíme, že je vše již v pořádku. Gratuluji vám, pokud jste se dostali až sem a tutoriály opravdu četli a pochopili, máte základy objektového programování v Pythonu a dokážete tvořit rozumné aplikace 🙂

      V příští lekci, Dědičnost a polymorfismus v Pythonu, se podíváme na objektově orientované programování podrobněji. V úvodu jsme si říkali, že OOP stojí na pilířích: zapouzdření, dědičnost a polymorfismus. První umíme již velmi dobře a rozumíme řeči podtržítek. Takže ty další dvě dovednosti nás čekají příště.


       

      Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

      Stáhnout

      Stažením následujícího souboru souhlasíš s licenčními podmínkami

      Staženo 1100x (4.38 kB)
      Aplikace je včetně zdrojových kódů v jazyce Python

       

      Jak se ti líbí článek?
      Před uložením hodnocení, popiš prosím autorovi, co je špatněZnaků 0 z 50-500
      Předchozí článek
      Bojovník do arény v Pythonu
      Všechny články v sekci
      Objektově orientované programování v Pythonu
      Přeskočit článek
      (nedoporučujeme)
      Dědičnost a polymorfismus v Pythonu
      Článek pro vás napsal gcx11
      Avatar
      Uživatelské hodnocení:
      605 hlasů
      (^_^)
      Aktivity