5. diel - Bojovník do arény v Dart
V predchádzajúcom cvičení, Riešené úlohy k 4. lekcii OOP v Dart, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 4. lekcii OOP v Dart , sme si vysvetlili ako fungujú odkazy na objekty, garbage collector a hlboká kópie. Už teda vieme, ako fungujú referencie a ako môžeme s objektmi zaobchádzať. Bude sa nám to hodiť dnes aj nabudúce. Tento a budúci Dart tutoriál budú totiž venované dokončenie našej arény. Hracie kocku už máme, ešte nám chýba ďalšie 2 objekty: bojovník a samotná aréna. Dnes sa budeme venovať bojovníkovi. Najprv si popíšme, čo má bojovník vedieť, potom sa pustíme do písania kódu.
Vlastnosti
Bojovník sa bude nejako menovať a bude mať určitý počet hp (teda života, napr. 80hp). Budeme uchovávať jeho maximálnej život (bude sa líšiť u každej inštancie) a jeho súčasný život, teda napr. Zranený bojovník bude mať 40HP z 80tich. Bojovník má určitý útok a obranu, oboje vyjadrené opäť v hp. Keď bojovník útočí s útokom 20HP na druhého bojovníka s obranou 10hp, uberie mu 10hp života. Bojovník bude mať referenciu na inštanciu objektu Kostka. Pri útoku či obrane si vždy hodí kockou a k útoku / obrane pripočíta padlých číslo. (Samozrejme by mohol mať každý bojovník svoju kocku, ale chcel som sa priblížiť stolové podobe hry a ukázať, ako OOP naozaj simuluje realitu. Bojovníci teda budú zdieľať jednu inštanciu kocky.) Kockou dodáme hre prvok náhody, v realite sa jedná vlastne o šťastie, ako sa útok alebo obrana vydarí. Konečne budeme chcieť, aby bojovníci podávali správy o tom, čo sa deje, pretože inak by z toho užívateľ nič nemal. Správa bude vyzerať napr. "Zalgoren útočí s úderom za 25HP.". Správami sa zatiaľ nebudeme zaťažovať a vrátime sa k nim až nakoniec.
class Bojovnik { /// Jméno bojovníka String _jmeno; /// Život v HP int _zivot; /// Maximální zdraví int _maxZivot; /// Útok v HP int _utok; /// Obrana v HP int _obrana; /// Instance hrací kostky Kostka _kostka; }
Trieda Kocka musí samozrejme byť v našom projekte.
Metódy
Poďme pre vlastnosti vytvoriť konštruktor, nebude to nič ťažké. Komentáre tu vynechám, vy si ich dopíšte podobne, ako u vlastností vyššie. Nebudem ich písať ani u ďalších metód, aby sa tutoriál zbytočne neroztahoval a zostal prehľadný.
Bojovnik(this._jmeno, this._maxZivot, this._utok, this._obrana, this._kostka) { _zivot = _maxZivot; }
Všimnite si, ako je táto skracujúce syntax pre parametre v konstruktoru praktická.
Zdravie si v konstruktoru odvodíme a nemáme na neho parameter v hlavičke
metódy. Predpokladáme, že bojovník je pri vytvorení plne zdravý, stačí
nám teda poznať iba jeho maximálnu život a život bude rovnaký. Zvyšok
vlastností sa inicializuje sám vďaka kľúčovému slovu
this
.
Prejdime k metódam. Opäť sa najprv zamyslime nad tým, čo by mal
bojovník vedieť. Začnime tým jednoduchším, budeme chcieť nejakú textovú
reprezentáciu, aby sme mohli bojovníka vypísať. Prekryjeme teda metódu
toString()
, ktorá vráti meno bojovníka. Určite sa nám bude
hodiť metóda, vracajúci či je bojovník nažive (teda typu
bool
). Aby to bolo trochu zaujímavejšie, budeme chcieť kresliť
život bojovníka do konzoly, nebudeme teda písať, koľko má života, ale
"vykreslíme" ho takto:
[######### ]
Vyššie uvedený život by zodpovedal asi 70%. Doteraz spomínané metódy
nepotrebovali žiadne parametre. Samotný útok a obranu nechajme na neskôr a
poďme si implementovať toString()
, nazivu()
a
grafickyZivot()
. Začnime s toString()
, tam nie je čo
vymýšľať:
@override String toString() { return _jmeno; }
Teraz Implementujte metódu nazivu()
. Opäť to nebude nič
ťažké. Stačí skontrolovať, či je život väčšia ako 0
a
podľa toho sa zachovať. Mohli by sme ju napísať napríklad takto:
bool nazivu() { if (_zivot > 0) return true; else return false; }
Keďže aj samotný výraz (zivot > 0)
je vlastne logická
hodnota, môžeme vrátiť tú a kód sa značne zjednoduší:
bool nazivu() { return _zivot > 0; }
Grafický život
Ako som sa už zmienil, metóda grafickyZivot()
bude
umožňovať vykresliť ukazovateľ života v grafickej podobe. Už vieme, že z
hľadiska objektového návrhu nie je vhodné, aby metóda objektu priamo
vypisovala do konzoly (ak nie je k výpisu objekt určený), preto si znaky
uložíme do reťazca a ten vrátime pre neskoršie vypísanie. Ukážeme si
kód metódy a následne podrobne popíšeme:
String grafickyZivot() { String s = '['; int celkem = 20; int pocet = ((_zivot / _maxZivot) * celkem).round(); if (pocet == 0 && nazivu()) pocet = 1; for (int i = 0; i < pocet; i++) s += '#'; s = s.padRight(celkem + 1); s += ']'; return s; }
Pripravíme si reťazec sa vložíme do neho úvodný znak "["
.
Určíme si celkovú dĺžku ukazovateľa života do premennej spolu (napr.
20
). Teraz v podstate nepotrebujeme nič iné, než trojčlenka. Ak
_maxZivot
zodpovedá celkem
dielikov,
_zivot
bude zodpovedať pocet
dielkam.
pocet
je premenná s počtom dielikov aktuálneho zdravie.
Matematicky platí, že pocet = (zivot / maxZivot) * celkem;
. My
ešte doplníme zaokrúhlení na celé dieliky. Mali by sme ošetriť prípad,
kedy je život taký nízky, že nám vyjde na 0 dielikov, ale bojovník je
stále nažive. V tom prípade vykreslíme 1 dielik, inak by to vyzeralo, že je
už mŕtvy.
Ďalej stačí jednoducho for cyklom pripojiť k reťazcu s patričný počet
znakov a doplniť ich medzerami do celkovej dĺžky. Doplnenie vykonáme
pridaním medzery za reťazec, ktorú niekoľkokrát vynásobíme operátorom
*
, čím sa doplní prázdna časť života. Pridáme koncový znak
a reťazec vrátime.
Celý kód môžeme ešte veľmi zjednodušiť; sami popremýšľajte ako kód funguje:
String grafickyZivot() { int celkem = 20; int pocet = ((_zivot / _maxZivot) * celkem).round(); if (pocet == 0 && nazivu()) pocet = 1; return '[' + '#' * pocet + ' ' * (celkem - pocet) + ']'; }
Všetko si vyskúšame, prejdime do main.dart
a vytvorme si
bojovníka (a kocku, pretože tu musíme konstruktoru bojovníka odovzdať).
Následne výpisy, či je nažive a jeho život graficky:
Kostka kostka = new Kostka(10); Bojovnik bojovnik = new Bojovnik('Zalgoren', 100, 20, 10, kostka); print('Bojovník: $bojovnik'); // test toString(); print('Naživu: ${bojovnik.nazivu()}'); // test nazivu(); print('Život: ${bojovnik.grafickyZivot()}'); // test grafickyZivot()
Výstup programu:
Konzolová aplikácia
Bojovník: Zalgoren
Naživu: true
Život: [####################]
Boj
Dostávame sa k samotnému boju. Implementujeme metódy pre útok a obranu.
Obrana
Začnime obranou. Metóda branSe()
bude umožňovať brániť sa
úderu, ktorého sila bude odovzdaná metóde ako parameter. Metódu si opäť
ukážeme a potom popíšeme:
void branSe(int uder) { int zraneni = uder - (_obrana + _kostka.hod()); if (zraneni > 0) { _zivot -= zraneni; if (_zivot <= 0) { _zivot = 0; } } }
Najprv spočítame skutočné zranenia a to tak, že z útoku nepriateľa
odpočítame našu obranu zvýšenú o číslo, ktoré padlo na hracej kocke. Ak
sme zranenia celej neodrazil (zraneni > 0
), budeme znižovať
náš život. Táto podmienka je dôležitá, keby sme zranenia odrazili a bolo
napr. -2
, bez podmienky by sa život zvýšil. Po znížení
života skontrolujeme, či nie je v zápornej hodnote a prípadne ho dorovnáme
na nulu.
Útok
Metóda utoc()
bude brať ako parameter inštanciu bojovníka,
na ktorého sa útočí. To preto, aby sme na ňom mohli zavolať metódu
branSe()
, ktorá na náš útok zareaguje a zmenší protivníkov
život. Tu vidíme výhody referencií v Dart. Môžeme si inštancie jednoducho
odovzdávať a volať na nich metódy, bez toho aby došlo k ich skopírovanie.
Ako prvý vypočítame úder, podobne ako pri obrane, úder bude náš útok +
hodnota z hracej kocky. Na súperovi následne zavoláme metódu
branSe()
s hodnotou úderu:
void utoc(Bojovnik souper) { int uder = _utok + _kostka.hod(); souper.branSe(uder); }
To by sme mali, poďme si skúsiť v našom ukážkovom programe zaútočiť a potom znova vykresliť život. Pre jednoduchosť nemusíme zakladať ďalšieho bojovníka, ale môžeme zaútočiť sami na seba:
Kostka kostka = new Kostka(10); Bojovnik bojovnik = new Bojovnik('Zalgoren', 100, 20, 10, kostka); print('Bojovník: $bojovnik'); // test toString(); print('Naživu: ${bojovnik.nazivu()}'); // test nazivu(); print('Život: ${bojovnik.grafickyZivot()}'); // test grafickyZivot() bojovnik.utoc(bojovnik); // test útoku print('Život po útoku: ${bojovnik.grafickyZivot()}');
Výstup programu:
Konzolová aplikácia
Bojovník: Zalgoren
Naživu: True
Život: [####################]
Život po útoku: [################## ]
Zdá sa, že všetko funguje, ako má. Prejdime k poslednému bodu tohto tutoriálu a to k správam.
Správy
Ako už bolo povedané, o útokoch a obrane budeme užívateľa informovať
výpisom na konzolu. Výpis nebude vykonávať samotná trieda
Bojovnik
, tá bude len vracať správy ako textové reťazce. Jedna
možnosť by bola nastaviť návratový typ metód utoc()
a
branSe()
na String
a pri ich volanie vrátiť aj
správu. Problém by však nastal v prípade, keď by sme chceli získať
správu od metódy, ktorá už niečo vracia. Metóda samozrejme nemôže
jednoducho vrátiť 2 veci.
Poďme na vec univerzálnejšie, správu budeme ukladať do privátnej
premennej zprava
a urobíme metódy pre jej uloženie a vrátenie.
Samozrejme by sme mohli urobiť premennú verejnú, ale nie je tu dôvod, prečo
umožniť zvonku zápis do správy a tiež by skladanie zložitejšie správy
vnútri triedy mohlo byť niekedy problematické.
K vlastnostiam triedy teda pridáme:
String _zprava;
Teraz si vytvoríme dve metódy. Privátne nastavZpravu()
,
ktorá berie ako parameter text správy a slúži na interné účely triedy,
kde nastaví správu do privátnej premennej:
void _nastavZpravu(String zprava) { _zprava = zprava; }
Nič zložité. Podobne jednoduchá bude verejná metóda pre navrátenie správy:
String vratPosledniZpravu () {
return _zprava; }
O prácu so správami obohatíme naše metódy utoc()
a
branSe()
, teraz budú vyzerať takto:
void utoc(Bojovnik souper) { int uder = _utok + _kostka.hod(); _nastavZpravu('$_jmeno útočí s úderem za $uder hp'); souper.branSe(uder); } void branSe(int uder) { String zprava = ''; int zraneni = uder - (_obrana + _kostka.hod()); if (zraneni > 0) { _zivot -= zraneni; zprava = '$_jmeno utrpěl poškození $zraneni hp'; if (_zivot <= 0) { _zivot = 0; zprava += ' a zemřel'; } } else { zprava = '$_jmeno odrazil útok'; } _nastavZpravu(zprava); }
Všetko si opäť vyskúšame, tentoraz už vytvoríme druhého bojovníka:
Kostka kostka = new Kostka(10); Bojovnik bojovnik = new Bojovnik('Zalgoren', 100, 20, 10, kostka); print('Život: ${bojovnik.grafickyZivot()}'); // test GrafickyZivot(); // útok na našeho bojovníka Bojovnik souper = new Bojovnik('Shadow', 60, 18, 15, kostka); souper.utoc(bojovnik); print(souper.vratPosledniZpravu()); print(bojovnik.vratPosledniZpravu()); print('Život: ${bojovnik.grafickyZivot()}');
Výstup programu:
Konzolová aplikácia
Život: [####################]
Shadow útočí s úderem za 27 hp
Zalgoren utrpěl poškození 13 hp
Život: [################# ]
Máme kocku i bojovníka, teraz už chýba len aréna. Tú si vytvoríme hneď v nasledujúcej lekcii, Aréna s bojovníkmi v Dart .
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é 7x (2.38 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Dart