5. diel - Bojovník do arény
V predchádzajúcom cvičení, Riešené úlohy k 4. lekcii OOP v Jave, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
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 tutoriál budú totiž venované dokončeniu našej arény. Hraciu kocku už máme, ešte nám chýbajú ď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.
Atribúty
Bojovník sa bude nejako volať a bude mať určitý počet
hp (teda bodov života, napr. 80hp). Budeme uchovávať jeho
maximálny život (bude sa líšiť pri každej inštancii) a
jeho súčasný život, teda napr. zranený bojovník bude mať
40hp z 80. 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štancii objektu
Kostka
. Pri útoku či obrane si vždy hodí kockou ak
útoku/obrane pripočíta padnuté číslo. (Samozrejme by mohol mať každý
bojovník svoju kocku, ale chcel som sa priblížiť stolovej 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í. Napokon 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.
Už vieme, čo budeme robiť, poďme na to!:) K projektu
TahovyBoj
si pridajme triedu Bojovnik
a dodajme ju
patričné atribúty. Všetky budú privátne:
public class Bojovnik { /** * Meno bojovníka */ private String jmeno; /** * Život v HP */ private int zivot; /** * Maximálne zdravie */ private int maximalniZivot; /** * Útok v HP */ private int utok; /** * Obrana v HP */ private int obrana; /** * Inštancia hracej kocky */ private Kostka kostka; /** * Posledná správa */ private String zprava; }
Trieda Kostka
musí samozrejme byť v našom projekte.
Metódy
Poďme pre atribúty vytvoriť konštruktor, nebude to nič ťažké. Komentáre tu vynechám, vy si ich dopíšte podobne, ako u atribútov vyššie. Nebudem ich písať ani pri ďalších metódach, aby sa tutoriál zbytočne nerozťahoval a zostal prehľadný (prípadne sa pozrite do archívu).
public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maximalniZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; }
Všimnite si, že maximálne zdravie si v konštruktore odvodíme a nemáme naň parameter v hlavičke metódy. Predpokladáme, že bojovník je pri vytvorení plne zdravý, stačí nám teda poznať iba jeho život a maximálny život bude rovnaký.
Prejdime k metódam, opäť sa najskôr 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úca, či je bojovník nažive (teda typu
boolean
). 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ť metódy toString()
, jeZivy()
a grafickyZivot()
. Začnime s metódou toString()
, tam
nie je čo vymýšľať:
@Override public String toString() { return jmeno; }
Teraz implementujme metódu jeZivy()
, opäť to nebude nič
ťažké. Stačí skontrolovať, či je život väčší ako 0
a
podľa toho sa zachovať. Mohli by sme ju napísať napríklad takto:
public boolean jeZivy() { 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ší:
public boolean jeZivy() { 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 (pokiaľ nie je na výpis 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:
public String grafickyZivot() { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) zivot / maximalniZivot) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; }
Pripravíme si reťazec grafickyZivot
a vložíme doň úvodný
znak [
. Určíme si celkovú dĺžku ukazovateľa života do
premennej celkem
(napr. 20). Teraz v podstate nepotrebujeme nič
iné, než trojčlenku. Ak maximalniZivot
zodpovedá
celkem
dielikov, premenná zivot
bude zodpovedať
premennej pocetDilku
. Premenná pocetDilku
obsahuje
počet dielikov aktuálneho zdravia.
Matematicky platí, že
pocet = (zivot / maximalniZivot) * celkem
. My ešte doplníme
zaokrúhlenie na celé dieliky a tiež pretypovanie jedného z operandov na
double
, aby Java chápala delenie ako neceločíselné.
Mali by sme ošetriť prípad, keď 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 cyklom for
pripojiť k reťazcu
grafickyZivot
patričný počet znakov a doplniť ich medzerami do
celkovej dĺžky. Doplnenie vykonáme pomocou cyklu for
, ktorý
pridáva medzery do dĺžky celkem
. Pridáme koncový znak a
reťazec vrátime.
Všetko si vyskúšame, prejdime k metóde main()
a vytvorme si
bojovníka (a kocku, pretože tu musíme konštruktoru bojovníka odovzdať).
Následne vypíšme, či je nažive a jeho život graficky:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka kostka = new Kostka(10); Bojovnik bojovnik = new Bojovnik("Zalgoren", 100, 20, 10, kostka); System.out.printf("Bojovník: %s%n", bojovnik); // test toString(); System.out.printf("Nažive: %s%n", bojovnik.jeZivy()); // test jeZivy(); System.out.printf("Život: %s%n", bojovnik.grafickyZivot()); // test grafickyZivot(); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten) + 1; } @Override public String toString() { return String.format("Kostka s %s stěnami", pocetSten); } } {/JAVA_OOP}
{JAVA_OOP} class Bojovnik { private String jmeno; private int zivot; private int maximalniZivot; private int utok; private int obrana; private Kostka kostka; public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maximalniZivot = zivot; this.utok = utok; this.kostka = kostka; } public boolean jeZivy() { return (zivot > 0); } public String grafickyZivot() { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) zivot / maximalniZivot) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; } @Override public String toString() { return jmeno; } } {/JAVA_OOP}
Konzolová aplikácia
Bojovník: Zalgoren
Nažive: true
Život: [####################]
Boj
Dostávame sa k samotnému boju. Implementujeme metódy na ú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:
public 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é zranenie a to tak, že z útoku nepriateľa
odpočítame našu obranu zvýšenú o číslo, ktoré padlo na hracej kocke. Ak
sme zranenie celé neodrazili (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 bojovníka 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 Jave, môžeme si inštancie jednoducho
odovzdávať a volať na nich metódy bez toho, aby došlo k ich skopírovaniu.
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:
public 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:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka kostka = new Kostka(10); Bojovnik bojovnik = new Bojovnik("Zalgoren", 100, 20, 10, kostka); System.out.printf("Bojovník: %s%n", bojovnik); // test toString(); System.out.printf("Nažive: %s%n", bojovnik.jeZivy()); // test jeZivy(); System.out.printf("Život: %s%n", bojovnik.grafickyZivot()); // test GrafickyZivot(); bojovnik.utoc(bojovnik); // test útoku System.out.printf("Život po útoku: %s%n", bojovnik.grafickyZivot()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten) + 1; } @Override public String toString() { return String.format("Kocka s %s stenami", pocetSten); } } {/JAVA_OOP}
{JAVA_OOP} class Bojovnik { private String jmeno; private int zivot; private int maximalniZivot; private int utok; private int obrana; private Kostka kostka; public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maximalniZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public boolean jeZivy() { return (zivot > 0); } public String grafickyZivot() { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) zivot / maximalniZivot) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; } public void branSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; if (zivot <= 0) { zivot = 0; } } } public void utoc(Bojovnik souper) { int uder = utok + kostka.hod(); souper.branSe(uder); } @Override public String toString() { return jmeno; } } {/JAVA_OOP}
Konzolová aplikácia
Bojovník: Zalgoren
Nažive: true
Život: [####################]
Život po útoku: [################## ]
Zdá sa, že všetko funguje, ako má. Prejdime k poslednému bodu dnešného tutoriálu a to k správam.
Správy
Ako už bolo povedané, o útokoch a obrane budeme užívateľov informovať
výpisom na konzole. 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 volaní vrátiť aj
správu. Problém by však nastal v prípade, ak 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 na 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šej správy vo
vnútri triedy mohlo byť niekedy problematické.
K atribútom triedy teda pridáme:
private String zprava;
Teraz si vytvoríme dve metódy. Najprv privátnu metódu
nastavZpravu()
, ktorá berie ako parameter text správy a slúži
na vnútorné účely triedy, kde nastaví správu do privátnej premennej:
private void nastavZpravu(String zprava) { this.zprava = zprava; }
Nič zložité. Podobne jednoduchá bude verejná metóda na vrátenie správy:
public String vratPosledniZpravu() { return zprava; }
O práci so správami obohatíme naše metódy utoc()
a
branSe()
, teraz budú vyzerať takto:
public void utoc(Bojovnik souper) { int uder = utok + kostka.hod(); nastavZpravu(String.format("%s útočí s úderom za %s hp", jmeno, uder)); souper.branSe(uder); } public void branSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.format("%s utrpel poškodenie %s hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zomrel"; } } else { zprava = String.format("%s odrazil útok", jmeno); } nastavZpravu(zprava); }
Všetko si opäť vyskúšame, tentoraz už vytvoríme druhého bojovníka:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kostka kostka = new Kostka(10); Bojovnik bojovnik = new Bojovnik("Zalgoren", 100, 20, 10, kostka); System.out.printf("Život: %s%n", bojovnik.grafickyZivot()); // útok na nášho bojovníka Bojovnik souper = new Bojovnik("Shadow", 60, 18, 15, kostka); souper.utoc(bojovnik); System.out.println(souper.vratPosledniZpravu()); System.out.println(bojovnik.vratPosledniZpravu()); System.out.printf("Život: %s%n", bojovnik.grafickyZivot()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class Kostka { private Random random; private int pocetSten; public Kostka() { pocetSten = 6; random = new Random(); } public Kostka(int pocetSten) { this.pocetSten = pocetSten; random = new Random(); } public int vratPocetSten() { return pocetSten; } public int hod() { return random.nextInt(pocetSten) + 1; } @Override public String toString() { return String.format("Kocka s %s stenami", pocetSten); } } {/JAVA_OOP}
{JAVA_OOP} class Bojovnik { private String jmeno; private int zivot; private int maximalniZivot; private int utok; private int obrana; private Kostka kostka; private String zprava; public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) { this.jmeno = jmeno; this.zivot = zivot; this.maximalniZivot = zivot; this.utok = utok; this.obrana = obrana; this.kostka = kostka; } public boolean jeZivy() { return (zivot > 0); } public String grafickyZivot() { String grafickyZivot = "["; int celkem = 20; double pocetDilku = Math.round(((double) zivot / maximalniZivot) * celkem); if ((pocetDilku == 0) && (jeZivy())) { pocetDilku = 1; } for (int i = 0; i < pocetDilku; i++) { grafickyZivot += "#"; } for (int i = 0; i < celkem - pocetDilku; i++) { grafickyZivot += " "; } grafickyZivot += "]"; return grafickyZivot; } public void utoc(Bojovnik souper) { int uder = utok + kostka.hod(); nastavZpravu(String.format("%s útočí s úderom za %s hp", jmeno, uder)); souper.branSe(uder); } public void branSe(int uder) { int zraneni = uder - (obrana + kostka.hod()); if (zraneni > 0) { zivot -= zraneni; zprava = String.format("%s utrpel poškodenie %s hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zomrel"; } } else { zprava = String.format("%s odrazil útok", jmeno); } nastavZpravu(zprava); } private void nastavZpravu(String zprava) { this.zprava = zprava; } public String vratPosledniZpravu() { return zprava; } @Override public String toString() { return jmeno; } } {/JAVA_OOP}
Konzolová aplikácia
Život: [####################]
Shadow útočí s úderom za 24 hp
Zalgoren utrpel poškodenie 10 hp
Život: [################## ]
Máme kocku aj bojovníka, teraz už chýba len aréna.
V ďalšej lekcii, Java - Aréna s bojovníkmi , si vytvoríme arénu.
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é 1475x (9.44 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java