Letní akce! Lákají tě IT školení C#, Javy a PHP v Brně? Přihlas se a napiš nám do zpráv kód "BRNO 500" pro slevu 500 Kč na libovolný brněnský kurz. Lze kombinovat se slevami uvedenými u školení i použít pro více kurzů. Akce končí 28.7.

Lekce 6 - Python - Aréna s bojovníky

Python Objektově orientované programování Python - Aréna s bojovníky

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

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í. Dnes tedy dáme vše 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.

Potřebujeme napsat nějaký kód pro obsluhu bojovníků a výpis zpráv uživateli. Vytvoříme si objekt Arena, kde se bude zápas odehrávat. Hlavní část programu potom jen založí objekty a o zbytek se bude starat objekt Arena.

Třída bude víceméně jednoduchá, jako atributy bude obsahovat 3 potřebné instance: 2 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

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("{0} {1}".format(self.__bojovnik_1,
                           self.__bojovnik_1.graficky_zivot()))
    print("{0} {1}".format(self.__bojovnik_2,
                           self.__bojovnik_2.graficky_zivot()))

Na začátku voláme další metodu - vycisti_obrazov­ku(). Metody se uvnitř objektu volají touto syntaxí:

self.název_metody

Zbytek je jasný. Obě metody jsou soukromé, budeme je používat jen uvnitř třídy.

Zde je kód privátní metody vycisti_obrazov­ku():

def __vycisti_obrazovku(self):
    import sys as _sys
    import subprocess as _subprocess
    if _sys.platform.startswith("win"):
        _subprocess.call(["cmd.exe", "/C", "cls"])
    else:
        _subprocess.call(["clear"])

Importujeme moduly sys a subprocess nutné k vymazání obrazovky konzole. Podle OS ovlivníme program obsluhující konzoli. 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í vlákno programu na daný počet sekund. S vlákny budeme pracovat až na konci seriálu.

Obě metody vlastně jen vypisují na konzoli, připadá mi zbytečné je zkoušet, přesuneme se tedy 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 by mohla vypadat takto:

def zapas(self):
    print("Vítejte v aréně!")
    print("Dnes se utkají {0} s {1}!".format(self.__bojovnik_1, self.__bojovnik_2))
    print("Zápas může začít...", end=" ")
    input()
    # cyklus s bojem
    while (self.__bojovnik_1.nazivu and self.__bojovnik_2.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())
        print("")

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 na konec programu, vytvořme patřičné instance a zavolejme na aréně metodu zapas():

# 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()
input()

Charakteristiky hrdinů si můžete upravit dle libosti. Program spustíme:

Objektová aréna v Pythonu – simulace zápasu ve stolní hře

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 2 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ívejte se na screenshot výše, Shadow útočil jako poslední i když byl mrtvý. Až potom se vystoupilo z while 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 zde "Zalgoren" má vždy výhodu. Pojďme vnést další prvek náhody a pomocí kostky rozhodněme, který z bojovníků bude začínat. Jelikož jsou bojovníci vždy dva, stačí hodit kostkou a podívat se, zda padlo číslo menší nebo rovné polovině počtu stěn kostky. Tedy např. pokud padne na desetistěnné kostce číslo do 5ti, začíná druhý bojovník, jinak začíná první. Zbývá zamyslet se nad tím, jak do kódu zanést prohazování bojovníků. Jistě by bylo velmi nepřehledné opodmínkovat příkazy ve while cyklu. Jelikož již víme, že 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 včetně podmínky, aby nemohl útočit mrtvý bojovník, by mohla vypadat nějak takto:

def zapas(self):
    import random as _random
    print("Vítejte v aréně!")
    print("Dnes se utkají {0} s {1}!".format(self.__bojovnik_1,
                                             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.nazivu and self.__bojovnik_2.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.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())
        print("")

Závorky u prohazování bojovníků nám nevadí. Pokud by tam nebyly, tak by Python vyhodil chybu. Avšak my se musíme se usměrňovat, aby se nám program tolik neroztahoval do šířky. Doporučená délka řádku je totiž 80 znaků.

Program vyzkoušejme:

Objektová aréna v Pythonu – simulace zápasu ve stolní hře

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


 

Stáhnout

Staženo 296x (6.34 kB)
Aplikace je včetně zdrojových kódů v jazyce python

 

 

Článek pro vás napsal gcx11
Avatar
Jak se ti líbí článek?
9 hlasů
(^_^)
Aktivity (5)

 

 

Komentáře
Zobrazit starší komentáře (2)

Avatar
gcx11
Redaktor
Avatar
gcx11:17.6.2014 16:56

Pokud přetypováváš float na int, tak se ti odtrhne desetinná část čísla. Operátor // ti vrátí v podstatě to samé.

 
Odpovědět  +1 17.6.2014 16:56
Avatar
secretcode
Člen
Avatar
secretcode:1.2.2016 14:13

Niesom si istý, ale myslím že keď pred nasledujúci kód

bojovnik_2.utoc(self.__bojovnik_1)

nevložím

self.__bojovnik_2.utoc(self.__bojovnik_1)

tak kód nebude fungovať.

V tom spodnom kóde v článku je to už prepísané, ale v hornej časti článku je to zapísané v kóde bez self.

 
Odpovědět 1.2.2016 14:13
Avatar
gcx11
Redaktor
Avatar
Odpovídá na secretcode
gcx11:2.2.2016 8:52

Ahoj, díky za upozornění, mám v tom chybu. Spíš přespat než vložit. Protože protože proměnné bojovnik1 resp. bojovnik2 nejsou definované, tak by Python vyhodil NameError.

 
Odpovědět 2.2.2016 8:52
Avatar
Daniel Martinek:2.12.2016 20:50

Ahoj, me osobne z nejakeho duvodu tvuj zpusob vycisteni obrazovky nesel:

def __vycisti_obrazovku(self):
    import sys as _sys
    import subprocess as _subprocess
    if _sys.platform.startswith("win"):
        _subprocess.call(["cmd.exe", "/C", "cls"])
    else:
        _subprocess.call(["clear"])

Proto jsem pouzil tohle, pro windows:

def __vycisti_obrazovku(self):
        import os
        def cls():
            os.system ("CLS")
 
Odpovědět 2.12.2016 20:50
Avatar
d.svarc
Člen
Avatar
d.svarc:11.12.2016 20:52

Ahoj, díky za velmi krásné vysvětlení objektů v Pythonu - používám ho k výuce studentů na semináři programování.
Jen si dovolím:

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

nahradit první řádek za:

def __vypis_zpravu(self, zprava):

jinak to vyhodí TypeError: __vypis_zpravu() bere 1 poziční argument a předáváme dva!

 
Odpovědět  +1 11.12.2016 20:52
Avatar
j.c.
Člen
Avatar
j.c.:13. března 10:25

co udělá toto jsem pochopil, ale prosím, mohl by to někdo česky popsat?
(self.__bojov­nik1, self.__bojovnik2) = (self.__bojov­nik2, self.__bojovnik1)

prohození polí v poli to není ;) Díky za vysvětlení.

 
Odpovědět 13. března 10:25
Avatar
Martin Petrovaj
Překladatel
Avatar
Odpovídá na j.c.
Martin Petrovaj:13. března 14:06

Jedná sa o paralelné priradenie, jedna z užitočných a skôr unikátnejších vlastností Pythonu :-)

Zápis

prem1, prem2, prem3, … = hodnota1, hodnota2, hodnota3, …

nám umožňuje dať na jeden riadok ľubovoľný počet priradení, ak sa nám to práve hodí (pozor - nepreháňať to, inak sa výrazne zhorší čitateľnosť kódu). Jediné čo to robí je to, že sa postupne vezme každý jeden pár premenná - hodnota a vykoná sa príslušné priradenie.

Paralelné priradenie sa ale často a zmysluplnejšie využíva aj na prehodenie dvoch premenných (bez potreby nejakej tretej, pomocnej premennej). Naivne by sme mohli s cieľom vymeniť obsah dvoch premenných skúsiť napísať nasledovný kód:

a = 4
b = 6
a = b
b = a
print(f"a: {a} \nb: {b}")

Uvidíme však, že v oboch premenných sa bude nachádzať hodnota 6. Je to kvôli tomu, že v priebehu takéhoto "prehadzovania" si jednu hodnotu nevyhnutne prepíšeme a prichádzame o ňu. Dá sa to vyriešiť treťou, pomocnou premennou:

a = 4
b = 6
c = a   # pomocná premenná na uloženie hodnoty a, aby sme o ňu neprišli
a = b
b = c

Python nám tento problém umožňuje vyriešiť paralelným priradením bez potreby ďalších premenných - z pohľadu používateľa (nás) sa všetky priradenia vykonajú "naraz":

a = 4
b = 6
a, b = b, a     # do "a" priraď "b" a do "b" priraď "a"

Dúfam, že už to je trochu zrozumiteľnejšie :-) Mimochodom, tie zátvorky okolo toho, ako sú použité v článku, potrebné nie sú

Odpovědět  +1 13. března 14:06
if (this.motto == "") { throw new NotImplementedException(); }
Avatar
j.c.
Člen
Avatar
Odpovídá na Martin Petrovaj
j.c.:13. března 14:21

díky moc :)
Já bych určitě použil proměnnou navíc ;)
Takto si to budu pamatovat.

 
Odpovědět 13. března 14:21
Avatar
možná zítra:8. července 17:00

Ve funkci __vypis_zpravu ti chybí za self proměnná zpráva (def __vypis_zpravu(sel­f, zprava)). Zdrojový kód jsem nekontroloval, ale předpokládám, že tam to máš opraveno.

 
Odpovědět 8. července 17:00
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na možná zítra
David Čápka:8. července 17:31

Díky, opraveno :)

Odpovědět 8. července 17:31
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 12. Zobrazit vše