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()
:
{PYTHON} 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() {/PYTHON}
{PYTHON} 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})" {/PYTHON}
{PYTHON} 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 {/PYTHON}
{PYTHON} 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()) {/PYTHON}
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:
{PYTHON} 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() {/PYTHON}
{PYTHON} 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})" {/PYTHON}
{PYTHON} 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 {/PYTHON}
{PYTHON} 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()) {/PYTHON}
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