Lekce 7 - Java - Aréna s bojovníky
V minulé lekci, Bojovník do arény, 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. Samozřejmě ho nebudeme bušit rovnou do výchozího souboru s
metodou main()
, ale vytvoříme si objekt Arena, kde se bude zápas
odehrávat. V metodě main()
se potom jen založí objekty a o
zbytek se bude starat objekt Arena
. Přidejme k projektu tedy
poslední třídu 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í (komentáře si dopište):
class Arena { private Bojovnik bojovnikA; private Bojovnik bojovnikB; private Kostka kostka; public Arena(Bojovnik bojovnikA, Bojovnik bojovnikB, Kostka kostka) { this.bojovnikA = bojovnikA; this.bojovnikB = bojovnikB; this.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 (nebyli by univerzální). 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:
private void vykresli() { System.out.println("-------------- Aréna -------------- \n"); System.out.println("Zdraví bojovníků: \n"); System.out.printf("%s %s%n", bojovnikA, bojovnikA.grafickyZivot()); System.out.printf("%s %s%n", bojovnikB, bojovnikB.grafickyZivot()); }
Metoda vykresli()
je privátní, budeme ji používat jen
uvnitř třídy.
Další privátní metodou bude výpis zprávy s dramatickou pauzou:
private void vypisZpravu(String zprava) { System.out.println(zprava); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Chyba, nepodařilo se uspat vlákno!"); } }
Kód je zřejmý až na třídu Thread
, která umožňuje práci
s vlákny. My z ní využijeme pouze metodu sleep()
, která uspí
vlákno programu na daný počet milisekund. S vlákny budeme pracovat až na
konci seriálu. Bloky try-catch
prozatím nebudeme řešit, nejsou
zde důležité a budeme je probírat později, spokojíme se s tím, že jsou
zde nutné.
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:
public void zapas() { System.out.println("Vítejte v aréně!"); System.out.printf("Dnes se utkají %s s %s! %n", bojovnikA, bojovnikB); System.out.println("Zápas může začít..."); // cyklus s bojem while (bojovnikA.jeZivy() && bojovnikB.jeZivy()) { bojovnikA.utoc(bojovnikB); vykresli(); vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o obraně bojovnikB.utoc(bojovnikA); vykresli(); vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o obraně System.out.println(); } }
Kód vypíše jednoduché informace a po stisku klávesy přejde do cyklu s
bojem. Jedná se o cyklus while
, 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
vypisZpravu()
, která po výpisu udělá dramatickou pauzu. To
samé provedeme i pro druhého bojovníka.
Přesuňme se do souboru TahovyBoj.java
, vytvořme patřičné
instance a zavolejme na aréně metodu zapas()
:
{JAVA_OOP} {JAVA_MAIN_BLOCK} // vytvoření objektů Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik shadow = new Bojovnik("Shadow", 60, 18, 15, kostka); Arena arena = new Arena(zalgoren, shadow, kostka); // zápas arena.zapas(); {/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; 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 úderem 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 utrpěl poškození %s hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zemřel"; } } 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}
{JAVA_OOP} class Arena { private Bojovnik bojovnikA; private Bojovnik bojovnikB; private Kostka kostka; public Arena(Bojovnik bojovnikA, Bojovnik bojovnikB, Kostka kostka) { this.bojovnikA = bojovnikA; this.bojovnikB = bojovnikB; this.kostka = kostka; } private void vykresli() { System.out.println("-------------- Aréna -------------- \n"); System.out.println("Zdraví bojovníků: \n"); System.out.printf("%s %s%n", bojovnikA, bojovnikA.grafickyZivot()); System.out.printf("%s %s%n", bojovnikB, bojovnikB.grafickyZivot()); } private void vypisZpravu(String zprava) { System.out.println(zprava); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Chyba, nepodařilo se uspat vlákno!"); } } public void zapas() { System.out.println("Vítejte v aréně!"); System.out.printf("Dnes se utkají %s s %s! %n", bojovnikA, bojovnikB); System.out.println("Zápas může začít..."); // cyklus s bojem while (bojovnikA.jeZivy() && bojovnikB.jeZivy()) { bojovnikA.utoc(bojovnikB); vykresli(); vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o obraně bojovnikB.utoc(bojovnikA); vykresli(); vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o obraně System.out.println(); } } } {/JAVA_OOP}
Charakteristiky hrdinů si můžete upravit dle libosti. Program spustíme:
Konzolová aplikace
-------------- Aréna --------------
Zdraví bojovníků:
Zalgoren [###### ]
Shadow [ ]
Shadow útočí úderem za 20 hp
Zalgoren utrpěl poškození 4 hp
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 výstup výše, Shadow útočil jako
poslední i když byl mrtvý. Až potom se vystoupilo z cyklu
while
. 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 5, začíná 2. 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 Javě fungují reference, není pro nás problém udělat si dvě proměnné, ve kterých budou instance bojovníků, nazvěme je jednoduše stejně jako atributy, tedybojovnikA
abojovnikB
. Do těchto proměnných si na začátku dosadíme bojovníky z atributůthis.bojovnikA
athis.bojovnikB
tak, jak potřebujeme. Můžeme tedy při pozitivním hodu kostkou dosadit do proměnnébojovnikA
proměnnouthis.bojovnikB
a naopak, výsledkem bude, že začínat bude ten druhý. Kód cyklu se takto vůbec nezmění a zůstane stále přehledný a jednoduchý.
Změněná verze včetně podmínky, aby nemohl útočit mrtvý bojovník, by mohla vypadat nějak takto:
{JAVA_OOP} class Arena { private Bojovnik bojovnikA; private Bojovnik bojovnikB; private Kostka kostka; public Arena(Bojovnik bojovnikA, Bojovnik bojovnikB, Kostka kostka) { this.bojovnikA = bojovnikA; this.bojovnikB = bojovnikB; this.kostka = kostka; } private void vykresli() { System.out.println("-------------- Aréna -------------- \n"); System.out.println("Zdraví bojovníků: \n"); System.out.printf("%s %s%n", bojovnikA, bojovnikA.grafickyZivot()); System.out.printf("%s %s%n", bojovnikB, bojovnikB.grafickyZivot()); } private void vypisZpravu(String zprava) { System.out.println(zprava); try { Thread.sleep(500); } catch (InterruptedException ex) { System.err.println("Chyba, nepodařilo se uspat vlákno!"); } } public void zapas() { // původní pořadí Bojovnik bojovnikA = this.bojovnikA; Bojovnik bojovnikB = this.bojovnikB; System.out.println("Vítejte v aréně!"); System.out.printf("Dnes se utkají %s s %s! %n%n", bojovnikA, bojovnikB); // prohození bojovníků boolean zacinaBojovnikB = (kostka.hod() <= kostka.vratPocetSten() / 2); if (zacinaBojovnikB) { bojovnikA = this.bojovnikB; bojovnikB = this.bojovnikA; } System.out.printf("Začínat bude bojovník %s! %nZápas může začít...%n", bojovnikA); // cyklus s bojem while (bojovnikA.jeZivy() && bojovnikB.jeZivy()) { bojovnikA.utoc(bojovnikB); vykresli(); vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o obraně if (bojovnikB.jeZivy()) { bojovnikB.utoc(bojovnikA); vykresli(); vypisZpravu(bojovnikB.vratPosledniZpravu()); // zpráva o útoku vypisZpravu(bojovnikA.vratPosledniZpravu()); // zpráva o obraně } System.out.println(); } } } {/JAVA_OOP}
{JAVA_OOP} {JAVA_MAIN_BLOCK} // vytvoření objektů Kostka kostka = new Kostka(10); Bojovnik zalgoren = new Bojovnik("Zalgoren", 100, 20, 10, kostka); Bojovnik shadow = new Bojovnik("Shadow", 60, 18, 15, kostka); Arena arena = new Arena(zalgoren, shadow, kostka); // zápas arena.zapas(); {/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; 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 úderem 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 utrpěl poškození %s hp", jmeno, zraneni); if (zivot <= 0) { zivot = 0; zprava += " a zemřel"; } } 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}
Program vyzkoušejme:
Konzolová aplikace
-------------- Aréna --------------
Zdraví bojovníků:
Zalgoren [######### ]
Shadow [ ]
Zalgoren útočí úderem za 27 hp
Shadow utrpěl poškození 11 hp a zemřel
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, si vysvětlíme dědičnost a polymorfismus.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 1294x (11.9 kB)
Aplikace je včetně zdrojových kódů v jazyce Java