6. diel - Java - Aréna s bojovníkmi
V minulej lekcii, Bojovník do arény, sme si vytvorili triedu bojovníka. Hraciu kocku máme hotovú z prvých lekcií objektovo orientovaného programovania.
Dnes teda dáme všetko 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.
Potrebujeme napísať nejaký kód na obsluhu bojovníkov a výpis správ
používateľovi. Samozrejme ho nehodíme rovno do východiskového súboru s
metódou main()
, ale vytvoríme si objekt Arena
, kde
sa bude zápas odohrávať. V metóde main()
sa potom len založia
objekty a o zvyšok sa bude starať objekt Arena
. Pridajme k
projektu teda poslednú triedu Arena
.
Trieda bude viac-menej jednoduchá, ako atribúty bude obsahovať 3 potrebné inštancie: 2 bojovníkov a hraciu kocku. V konštruktore sa tieto atribúty naplnia z parametrov. Kód triedy bude teda nasledujúci (komentáre si dopíšte):
class Arena { private Warrior warrior1; private Warrior warrior2; private RollingDie die; public Arena(Warrior warrior1, Warrior warrior2, RollingDie die) { this.warrior1 = warrior1; this.warrior2 = warrior2; this.die = die; } }
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, ak by výpis vykonávali aj bojovníci, bolo by to
na škodu (neboli by univerzálni). Potrebujeme teda metódu, ktorá vykreslí
obrazovku s aktuálnymi údajmi o kole a taktiež životy 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:
private void render() { System.out.println("-------------- Arena -------------- \n"); System.out.println("Warriors health: \n"); System.out.printf("%s %s%n", warrior1, warrior1.healthBar()); System.out.printf("%s %s%n", warrior2, warrior2.healthBar()); }
Metóda render()
je privátna, budeme ju používať len vo
vnútri triedy.
Ďalšou privátnou metódou bude výpis správy s dramatickou pauzou:
private void printMessage(String message) { System.out.println(message); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Unable to put the thread to sleep"); } }
Kód je zrejmý až na triedu Thread
, ktorá umožňuje prácu s
vláknami. My z nej využijeme iba metódu sleep()
, ktorá uspí
vlákno programu na daný počet milisekúnd. S vláknami budeme pracovať až
na konci seriálu. Bloky try-catch
zatiaľ nebudeme riešiť, nie
sú tu dôležité a budeme ich preberať neskôr, uspokojíme sa s tým, že
sú tu nutné.
Obe metódy vlastne len vypisujú na konzole, pripadá mi zbytočné ich
skúšať, presunieme sa teda už k samotnému zápasu. Metóda
fight()
nebude mať žiadne parametre a nebude ani nič vracať.
Vnútri bude cyklus, ktorý bude striedavo volať útoky bojovníkov navzájom a
vypisovať informačnú obrazovku a správy. Metóda by mohla vyzerať
takto:
public void fight() { System.out.println("Welcome to the Arena!"); System.out.printf("Today %s will battle against %s! %n", warrior1, warrior2); System.out.println("Let the battle begin..."); // fight loop while (warrior1.isAlive() && warrior2.isAlive()) { warrior1.attack(warrior2); render(); printMessage(warrior1.getLastMessage()); // attack message printMessage(warrior2.getLastMessage()); // defense message warrior2.attack(warrior1); render(); printMessage(warrior2.getLastMessage()); // attack message printMessage(warrior1.getLastMessage()); // defense message System.out.println(); } }
Kód vypíše jednoduché informácie a prejde do cyklu s bojom. Ide o cyklus
while
, 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
printMessage()
, ktorá po výpise urobí dramatickú pauzu. To
isté urobíme aj pre druhého bojovníka.
Presuňme sa do súboru ArenaFight.java
, vytvorme patričné
inštancie a zavolajme na aréne metódu fight()
:
{JAVA_OOP} {JAVA_MAIN_BLOCK} // creating objects RollingDie die = new RollingDie(10); Warrior zalgoren = new Warrior("Zalgoren", 100, 20, 10, die); Warrior shadow = new Warrior("Shadow", 60, 18, 15, die); Arena arena = new Arena(zalgoren, shadow, die); // fight arena.fight(); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } public int roll() { return random.nextInt(sidesCount) + 1; } @Override public String toString() { return String.format("Rolling die with %s sides", sidesCount); } } {/JAVA_OOP}
{JAVA_OOP} public class Warrior { private String name; private int health; private int maxHealth; private int damage; private int defense; private RollingDie die; private String message; public Warrior(String name, int health, int damage, int defense, RollingDie die) { this.name = name; this.health = health; this.maxHealth = health; this.damage = damage; this.defense = defense; this.die = die; } public boolean isAlive() { return (health > 0); } public String healthBar() { String healthBar = "["; int total = 20; double count = Math.round(((double)health / maxHealth) * total); if ((count == 0) && (isAlive())) { count = 1; } for (int i = 0; i < count; i++) { healthBar += "#"; } for (int i = 0; i < total - count; i++) { healthBar += " "; } healthBar += "]"; return healthBar; } public void attack(Warrior enemy) { int hit = damage + die.roll(); setMessage(String.format("%s attacks with a hit worth %s hp", name, hit)); enemy.defend(hit); } public void defend(int hit) { int injury = hit - (defense + die.roll()); if (injury > 0) { health -= injury; message = String.format("%s defended against the attack but still lost %s hp", name, injury); if (health <= 0) { health = 0; message += " and died"; } } else { message = String.format("%s blocked the hit", name); } setMessage(message); } private void setMessage(String message) { this.message = message; } public String getLastMessage() { return message; } @Override public String toString() { return name; } } {/JAVA_OOP}
{JAVA_OOP} public class Arena { private Warrior warrior1; private Warrior warrior2; private RollingDie die; public Arena(Warrior warrior1, Warrior warrior2, RollingDie die) { this.warrior1 = warrior1; this.warrior2 = warrior2; this.die = die; } private void render() { System.out.println("-------------- Arena -------------- \n"); System.out.println("Warriors health: \n"); System.out.printf("%s %s%n", warrior1, warrior1.healthBar()); System.out.printf("%s %s%n", warrior2, warrior2.healthBar()); } private void printMessage(String message) { System.out.println(message); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Unable to put the thread to sleep"); } } public void fight() { System.out.println("Welcome to the Arena!"); System.out.printf("Today %s will battle against %s! %n", warrior1, warrior2); System.out.println("Let the battle begin..."); // fight loop while (warrior1.isAlive() && warrior2.isAlive()) { warrior1.attack(warrior2); render(); printMessage(warrior1.getLastMessage()); // attack message printMessage(warrior2.getLastMessage()); // defense message warrior2.attack(warrior1); render(); printMessage(warrior2.getLastMessage()); // attack message printMessage(warrior1.getLastMessage()); // defense message System.out.println(); } } } {/JAVA_OOP}
Charakteristiky hrdinov si môžete upraviť podľa ľubovôle. Program spustíme:
Konzolová aplikácia
-------------- Arena --------------
Warriors health:
Zalgoren [###### ]
Shadow [ ]
Shadow attacks with a hit worth 20 hp
Zalgoren blocked the hit but still lost 4 hp
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 2 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. Pozrite sa na výstup vyššie, Shadow útočil ako
posledný aj keď bol mŕtvy. Až potom sa vystúpilo z cyklu
while
. 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 tu
Zalgoren
má vždy výhodu. Poďme vniesť ďalší prvok náhody a pomocou kocky rozhodneme, ktorý z bojovníkov bude začínať. Keďže sú bojovníci vždy dvaja, stačí hodiť kockou a pozrieť sa, či padlo číslo menšie alebo rovné polovici počtu stien kocky. Teda napr. ak padne na desaťstennej kocke číslo do 5, začína 2. bojovník, inak začína prvý. Zostáva zamyslieť sa nad tým, ako do kódu zaniesť prehadzovanie bojovníkov. Iste by bolo veľmi neprehľadné opodmienkovať príkazy vo while cykle. Keďže už vieme, že v Jave fungujú referencie, nie je pre nás problém urobiť si dve premenné, v ktorých budú inštancie bojovníkov, nazvime ich jednoducho rovnako ako atribúty, tedawarrior1
awarrior2
. Do týchto premenných si na začiatku dosadíme bojovníkov z atribútovthis.warrior1
athis.warrior2
tak, ako potrebujeme. Môžeme teda pri pozitívnom hode kockou dosadiť do premennejwarrior1
premennúthis.warrior2
a naopak. Výsledkom bude, že začínať bude ten druhý. Kód cyklu sa takto vôbec nezmení a zostane stále prehľadný a jednoduchý.
Zmenená verzia vrátane podmienky, aby nemohol útočiť mŕtvy bojovník, by mohla vyzerať nejako takto:
{JAVA_OOP} public class Arena { private Warrior warrior1; private Warrior warrior2; private RollingDie die; public Arena(Warrior warrior1, Warrior warrior2, RollingDie die) { this.warrior1 = warrior1; this.warrior2 = warrior2; this.die = die; } private void render() { System.out.println("-------------- Arena -------------- \n"); System.out.println("Warriors health: \n"); System.out.printf("%s %s%n", warrior1, warrior1.healthBar()); System.out.printf("%s %s%n", warrior2, warrior2.healthBar()); } private void printMessage(String message) { System.out.println(message); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Unable to put the thread to sleep"); } } public void fight() { // The original order Warrior warrior1 = this.warrior1; Warrior warrior2 = this.warrior2; System.out.println("Welcome to the Arena!"); System.out.printf("Today %s will battle against %s! %n", warrior1, warrior2); // swapping the warriors boolean warrior2Starts = (die.roll() <= die.getSidesCount() / 2); if (warrior2Starts) { warrior1 = this.warrior2; warrior2 = this.warrior1; } System.out.printf("%s goes first!%nLet the battle begin...%n", warrior1); // fight loop while (warrior1.isAlive() && warrior2.isAlive()) { warrior1.attack(warrior2); render(); printMessage(warrior1.getLastMessage()); // attack message printMessage(warrior2.getLastMessage()); // defense message if (warrior2.isAlive()) { warrior2.attack(warrior1); render(); printMessage(warrior2.getLastMessage()); // attack message printMessage(warrior1.getLastMessage()); // defense message } System.out.println(); } } } {/JAVA_OOP}
{JAVA_OOP} {JAVA_MAIN_BLOCK} // creating objects RollingDie die = new RollingDie(10); Warrior zalgoren = new Warrior("Zalgoren", 100, 20, 10, die); Warrior shadow = new Warrior("Shadow", 60, 18, 15, die); Arena arena = new Arena(zalgoren, shadow, die); // fight arena.fight(); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} import java.util.Random; public class RollingDie { private Random random; private int sidesCount; public RollingDie() { sidesCount = 6; random = new Random(); } public RollingDie(int sidesCount) { this.sidesCount = sidesCount; random = new Random(); } public int getSidesCount() { return sidesCount; } public int roll() { return random.nextInt(sidesCount) + 1; } @Override public String toString() { return String.format("Rolling die with %s sides", sidesCount); } } {/JAVA_OOP}
{JAVA_OOP} public class Warrior { private String name; private int health; private int maxHealth; private int damage; private int defense; private RollingDie die; private String message; public Warrior(String name, int health, int damage, int defense, RollingDie die) { this.name = name; this.health = health; this.maxHealth = health; this.damage = damage; this.defense = defense; this.die = die; } public boolean isAlive() { return (health > 0); } public String healthBar() { String healthBar = "["; int total = 20; double count = Math.round(((double)health / maxHealth) * total); if ((count == 0) && (isAlive())) { count = 1; } for (int i = 0; i < count; i++) { healthBar += "#"; } for (int i = 0; i < total - count; i++) { healthBar += " "; } healthBar += "]"; return healthBar; } public void attack(Warrior enemy) { int hit = damage + die.roll(); setMessage(String.format("%s attacks with a hit worth %s hp", name, hit)); enemy.defend(hit); } public void defend(int hit) { int injury = hit - (defense + die.roll()); if (injury > 0) { health -= injury; message = String.format("%s defended against the attack but still lost %s hp", name, injury); if (health <= 0) { health = 0; message += " and died"; } } else { message = String.format("%s blocked the hit", name); } setMessage(message); } private void setMessage(String message) { this.message = message; } public String getLastMessage() { return message; } @Override public String toString() { return name; } } {/JAVA_OOP}
Program vyskúšajme:
Konzolová aplikácia
-------------- Aréna --------------
Warriors health:
Zalgoren [######### ]
Shadow [ ]
Zalgoren attacks with a hit worth 27 hp
Shadow defended against the attack but still lost 11 hp and died
Vidíme, že všetko je už v poriadku. Gratulujem vám, ak ste sa dostali
až sem a tutoriály ste naozaj čítali a pochopili, máte základy
objektového programovania a dokážete tvoriť rozumné aplikácie
V budúcej lekcii, Dedičnosť a polymorfizmus, si vysvetlíme dedičnosť a polymorfizmus.
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é 31x (6.3 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java