Pouze tento týden sleva až 80 % na e-learning týkající se C# .NET. Zároveň využij akci až 30 % zdarma při nákupu e-learningu - Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
discount 30 + hiring

Lekce 5 - Bojovník do arény

V předešlém cvičení, Řešené úlohy k 4. lekci OOP v Javě, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

Již tedy víme, jak fungují reference a jak můžeme s objekty zacházet. Bude se nám to hodit dnes i příště. Tento a příští tutoriál budou totiž věnovány dokončení naší arény. Hrací kostku již máme, ještě nám chybí další 2 objekty: bojovník a samotná aréna. Dnes se budeme věnovat bojovníkovi. Nejprve si popišme, co má bojovník umět, poté se pustíme do psaní kódu.

Atributy

Bojovník se bude nějak jmenovat a bude mít určitý počet hp (tedy života, např. 80hp). Budeme uchovávat jeho maximální život (bude se lišit u každé instance) a jeho současný život, tedy např. zraněný bojovník bude mít 40hp z 80ti. Bojovník má určitý útok a obranu, obojí vyjádřené opět v hp. Když bojovník útočí s útokem 20hp na druhého bojovníka s obranou 10hp, ubere mu 10hp života. Bojovník bude mít referenci na instanci objektu Kostka. Při útoku či obraně si vždy hodí kostkou a k útoku/obraně přičte padlé číslo. (Samozřejmě by mohl mít každý bojovník svou kostku, ale chtěl jsem se přiblížit stolní podobě hry a ukázat, jak OOP opravdu simuluje realitu. Bojovníci tedy budou sdílet jednu instanci kostky.) Kostkou dodáme hře prvek náhody, v realitě se jedná vlastně o štěstí, jak se útok nebo obrana vydaří. Konečně budeme chtít, aby bojovníci podávali zprávy o tom, co se děje, protože jinak by z toho uživatel nic neměl. Zpráva bude vypadat např. "Zalgoren útočí s úderem za 25hp.". Zprávami se zatím nebudeme zatěžovat a vrátíme se k nim až nakonec.

Již víme, co budeme dělat, pojďme na to! :) K projektu TahovyBoj si přidejme třídu Bojovnik a dodejme ji patřičné atributy. Všechny budou privátní:

class Bojovnik {
    /** Jméno bojovníka */
    private String jmeno;
    /** Život v HP */
    private int zivot;
    /** Maximální zdraví */
    private int maxZivot;
    /** Útok v HP */
    private int utok;
    /** Obrana v HP */
    private int obrana;
    /** Instance hrací kostky */
    private Kostka kostka;

}

Třída Kostka musí samozřejmě být v našem projektu.

Metody

Pojďme pro atributy vytvořit konstruktor, nebude to nic těžkého. Komentáře zde vynechám, vy si je dopište podobně, jako u atributů výše. Nebudu je psát ani u dalších metod, aby se tutoriál zbytečně neroztahoval a zůstal přehledný.

public Bojovnik(String jmeno, int zivot, int utok, int obrana, Kostka kostka) {
    this.jmeno = jmeno;
    this.zivot = zivot;
    this.maxZivot = zivot;
    this.utok = utok;
    this.obrana = obrana;
    this.kostka = kostka;
}

Všimněte si, že maximální zdraví si v konstruktoru odvodíme a nemáme na něj parametr v hlavičce metody. Předpokládáme, že bojovník je při vytvoření plně zdravý, stačí nám tedy znát pouze jeho život a maximální život bude stejný.

Přejděme k metodám, opět se nejprve zamysleme nad tím, co by měl bojovník umět. Začněme tím jednodušším, budeme chtít nějakou textovou reprezentaci, abychom mohli bojovníka vypsat. Překryjeme tedy metodu toString(), která vrátí jméno bojovníka. Určitě se nám bude hodit metoda, vracející zda je bojovník naživu (tedy typu boolean). Aby to bylo trochu zajímavější, budeme chtít kreslit život bojovníka do konzole, nebudeme tedy psát, kolik má života, ale "vykreslíme" ho takto:

[#########    ]

Výše uvedený život by odpovídal asi 70%. Dosud zmíněné metody nepotřebovaly žádné parametry. Samotný útok a obranu nechme na později a pojďme si implementovat toString(), nazivu() a grafickyZivot(). Začněme s toString(), tam není co vymýšlet:

@Override
public String toString() {
    return jmeno;
}

Nyní implementujme metodu nazivu(), opět to nebude nic těžkého. Stačí zkontrolovat, zda je život větší než 0 a podle toho se zachovat. Mohli bychom ji napsat třeba takto:

public boolean nazivu() {
    if (zivot > 0) {
        return true;
    } else {
        return false;
    }
}

Jelikož i samotný výraz (zivot > 0) je vlastně logická hodnota, můžeme vrátit tu a kód se značně zjednodušší:

public boolean nazivu() {
    return (zivot > 0);
}

Grafický život

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Jak jsem se již zmínil, metoda grafickyZivot() bude umožňovat vykreslit ukazatel života v grafické podobě. Již víme, že z hlediska objektového návrhu není vhodné, aby metoda objektu přímo vypisovala do konzole (pokud není k výpisu objekt určený), proto si znaky uložíme do řetězce a ten vrátíme pro pozdější vypsání. Ukážeme si kód metody a následně podrobně popíšeme:

public String grafickyZivot() {
    String s = "[";
    int celkem = 20;
    double pocet = Math.round(((double)zivot / maxZivot) * celkem);
    if ((pocet == 0) && (nazivu())) {
        pocet = 1;
    }
    for (int i = 0; i < pocet; i++) {
        s += "#";
    }
    for (int i = 0; i < celkem - pocet; i++) {
            s += " ";
    }
    s += "]";
    return s;
}

Připravíme si řetězec s a vložíme do něj úvodní znak "[". Určíme si celkovou délku ukazatele života do proměnné celkem (např. 20). Nyní v podstatě nepotřebujeme nic jiného, než trojčlenku. Pokud maxZivot odpovídá celkem dílků, zivot bude odpovídat pocet dílkům. pocet je proměnná s počtem dílků aktuálního zdraví.

Matematicky platí, že pocet = (zivot / maxZivot) * celkem;. My ještě doplníme zaokrouhlení na celé dílky a také přetypování jednoho z operandů na double, aby Java chápala dělení jako neceločíselné.

Měli bychom ošetřit případ, kdy je život tak nízký, že nám vyjde na 0 dílků, ale bojovník je stále naživu. V tom případě vykreslíme 1 dílek, jinak by to vypadalo, že je již mrtvý.

Dále stačí jednoduše for cyklem připojit k řetězci s patřičný počet znaků a doplnit je mezerami do celkové délky. Doplnění provedeme pomocí for cyklu, který přidává mezery do délky celkem. Přidáme koncový znak a řetězec vrátíme.

Vše si vyzkoušíme, přejděme k metodě main() a vytvořme si bojovníka (a kostku, protože tu musíme konstruktoru bojovníka předat). Následně vypišme, zda je naživu a jeho život graficky:

    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živu: %s\n", bojovnik.nazivu()); // test nazivu();
    System.out.printf("Život: %s\n", bojovnik.grafickyZivot()); // test grafickyZivot();
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);
    }
}
class Bojovnik {
    private String jmeno;
    private int zivot;
    private int maxZivot;
    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.maxZivot = zivot;
    this.utok = utok;
    this.kostka = kostka;
    }

    public boolean nazivu() {
     return (zivot > 0);
    }

    public String grafickyZivot() {
    String s = "[";
    int celkem = 20;
    double pocet = Math.round(((double)zivot / maxZivot) * celkem);
    if ((pocet == 0) && (nazivu())) {
        pocet = 1;
    }
    for (int i = 0; i < pocet; i++) {
        s += "#";
    }
    for (int i = 0; i < celkem - pocet; i++) {
        s += " ";
    }
    s += "]";
    return s;
    }

    @Override
    public String toString() {
    return jmeno;
    }

}

Konzolová aplikace
Bojovník: Zalgoren
Naživu: true
Život: [####################]

Boj

Dostáváme se k samotnému boji. Implementujeme metody pro útok a obranu.

Obrana

Začněme obranou. Metoda branSe() bude umožňovat bránit se úderu, jehož síla bude předána metodě jako parametr. Metodu si opět ukážeme a poté popíšeme:

public void branSe(int uder) {
    int zraneni = uder - (obrana + kostka.hod());
    if (zraneni > 0) {
        zivot -= zraneni;
        if (zivot <= 0) {
            zivot = 0;
        }
    }
}

Nejprve spočítáme skutečné zranění a to tak, že z útoku nepřítele odečteme naši obranu zvýšenou o číslo, které padlo na hrací kostce. Pokud jsme zranění celé neodrazili (zraneni > 0), budeme snižovat náš život. Tato podmínka je důležitá, kdybychom zranění odrazili a bylo např. -2, bez podmínky by se život zvýšil. Po snížení života zkontrolujeme, zda není v záporné hodnotě a případně ho dorovnáme na nulu.

Útok

Metoda utoc() bude brát jako parametr instanci bojovníka, na kterého se útočí. To proto, abychom na něm mohli zavolat metodu branSe(), která na náš útok zareaguje a zmenší protivníkův život. Zde vidíme výhody referencí v Javě, můžeme si instance jednoduše předávat a volat na nich metody, aniž by došlo k jejich zkopírování. Jako první vypočteme úder, podobně jako při obraně, úder bude náš útok + hodnota z hrací kostky. Na soupeři následně zavoláme metodu branSe() s hodnotou úderu:

public void utoc(Bojovnik souper) {
    int uder = utok + kostka.hod();
    souper.branSe(uder);
}

To bychom měli, pojďme si zkusit v našem ukázkovém programu zaútočit a poté znovu vykreslit život. Pro jednoduchost nemusíme zakládat dalšího bojovníka, ale můžeme zaútočit sami na sebe:

    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živu: %s\n", bojovnik.nazivu()); // test Nazivu();
    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());
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);
    }
}
class Bojovnik {
    private String jmeno;
    private int zivot;
    private int maxZivot;
    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.maxZivot = zivot;
    this.utok = utok;
    this.obrana = obrana;
    this.kostka = kostka;
    }

    public boolean nazivu() {
    return (zivot > 0);
    }

    public String grafickyZivot() {
    String s = "[";
    int celkem = 20;
    double pocet = Math.round(((double)zivot / maxZivot) * celkem);
    if ((pocet == 0) && (nazivu())) {
        pocet = 1;
    }
    for (int i = 0; i < pocet; i++) {
        s += "#";
    }
    for (int i = 0; i < celkem - pocet; i++) {
        s += " ";
    }
    s += "]";
    return s;
    }

    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;
    }

}


Konzolová aplikace
Bojovník: Zalgoren
Naživu: true
Život: [####################]
Život po útoku: [##################  ]

Zdá se, že vše funguje, jak má. Přejděme k poslednímu bodu dnešního tutoriálu a to ke zprávám.

Zprávy

Jak již bylo řečeno, o útocích a obraně budeme uživatele informovat výpisem na konzoli. Výpis nebude provádět samotná třída Bojovnik, ta bude jen vracet zprávy jako textové řetězce. Jedna možnost by byla nastavit návratový typ metod utoc() a branSe() na String a při jejich volání vrátit i zprávu. Problém by však nastal v případě, když bychom chtěli získat zprávu od metody, která již něco vrací. Metoda samozřejmě nemůže jednoduše vrátit 2 věci.

Pojďme na věc univerzálněji, zprávu budeme ukládat do privátní proměnné zprava a uděláme metody pro její uložení a navrácení. Samozřejmě bychom mohli udělat proměnnou veřejnou, ale není zde důvod, proč umožnit zvenčí zápis do zprávy a také by skládání složitější zprávy uvnitř třídy mohlo být někdy problematické.

K atributům třídy tedy přidáme:

private String zprava;

Nyní si vytvoříme dvě metody. Privátní nastavZpravu(), která bere jako parametr text zprávy a slouží pro vnitřní účely třídy, kde nastaví zprávu do privátní proměnné:

private void nastavZpravu(String zprava) {
    this.zprava = zprava;
}

Nic složitého. Podobně jednoduchá bude veřejná metoda pro navrácení zprávy:

public String vratPosledniZpravu() {
    return zprava;
}

O práci se zprávami obohatíme naše metody utoc() a branSe(), nyní budou vypadat takto:

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);
}

Vše si opět vyzkoušíme, tentokrát již vytvoříme druhého bojovníka:

    Kostka kostka = new Kostka(10);
    Bojovnik bojovnik = new Bojovnik("Zalgoren", 100, 20, 10, kostka);

    System.out.printf("Život: %s\n", bojovnik.grafickyZivot()); // test GrafickyZivot();

    // útok na našeho 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());
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);
    }
}
class Bojovnik {
    private String jmeno;
    private int zivot;
    private int maxZivot;
    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.maxZivot = zivot;
    this.utok = utok;
    this.obrana = obrana;
    this.kostka = kostka;
    }

    public boolean nazivu() {
    return (zivot > 0);
    }

    public String grafickyZivot() {
    String s = "[";
    int celkem = 20;
    double pocet = Math.round(((double)zivot / maxZivot) * celkem);
    if ((pocet == 0) && (nazivu())) {
        pocet = 1;
    }
    for (int i = 0; i < pocet; i++) {
        s += "#";
    }
    for (int i = 0; i < celkem - pocet; i++) {
        s += " ";
    }
    s += "]";
    return s;
    }

    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;
    }
}

Konzolová aplikace
Život: [####################]
Shadow útočí s úderem za 24 hp
Zalgoren utrpěl poškození 10 hp
Život: [##################  ]

Máme kostku i bojovníka, teď již chybí jen aréna.

Tu si vytvoříme hned v příští lekci, Java - Aréna s bojovníky.


 

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 1225x (4.45 kB)
Aplikace je včetně zdrojových kódů v jazyce Java

 

Předchozí článek
Řešené úlohy k 4. lekci OOP v Javě
Všechny články v sekci
Objektově orientované programování v Javě
Přeskočit článek
(nedoporučujeme)
Java - Aréna s bojovníky
Článek pro vás napsal David Čápka
Avatar
Uživatelské hodnocení:
83 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 13 let. Má rád Nirvanu, sushi a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity

 

 

Komentáře
Zobrazit starší komentáře (38)

Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:17.3.2021 13:05

Možná s tím nikdo neměl problémy, ale můžete se nad tím zamyslet - v poslední části tohoto článku - Zprávy
se v úvodu píše, že výpis nebude mít na starosti samotný bojovník. No a potom následuje postup, že přidáme nový atribut a metody.
Ale dovedu si představit, že by se někdo mohl zeptat kam je přidáme (do které třídy).

Ono to potom vyjde docela najevo, ale může to někoho zmást. Tak to berte jako podnět k zamyšlení, jestli to tam nezkonkretizovat.

Odpovědět
17.3.2021 13:05
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
SARNOVSKÝ Petr:28.11.2021 6:06

Ahoj,
při kontrolním výpisu bojovníka

System.out.printf("Bojovník: %s\n", bojovnik); // test toString();

mi to hlásí chybu:
java: no suitable method found for println(java.lan­g.String,Arena­.Bojovnik)

Nevíte někdo proč?
Děkuji

 
Odpovědět
28.11.2021 6:06
Avatar
Odpovídá na SARNOVSKÝ Petr
SARNOVSKÝ Petr:28.11.2021 15:06

...musel jsem to upravit na

System.out.printf("Bojovník: %s\n", bojovnik); // test toString();
 
Odpovědět
28.11.2021 15:06
Avatar
Odpovídá na SARNOVSKÝ Petr
SARNOVSKÝ Petr:28.11.2021 15:06

...aha :-D

System.out.printf("Bojovník: " + bojovnik); // test toString();
 
Odpovědět
28.11.2021 15:06
Avatar
SARNOVSKÝ Petr:28.11.2021 22:25

Už to mám. Použil jsem špatný příkaz.

 
Odpovědět
28.11.2021 22:25
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Jaroslav Drobek:11. února 9:20

Najednou je tady hlavička třídy Bojovník bez modifikátoru public..co se tím mění?

 
Odpovědět
11. února 9:20
Avatar
popo49
Brigádník
Avatar
Odpovídá na Jaroslav Drobek
popo49:11. února 19:24

Nic.
pokus u třídy chybí modifikátor - tak se třída automaticky bere jako public.

U atributů je to naopak - tam by se automaticky nastavili na private.

 
Odpovědět
11. února 19:24
Avatar
Dominik Bican:28. února 22:43

Mám dotaz, není metoda nastavZpravu(zpra­va) trochu nadbytečná?
V rámci metody branSe(int uder) vlastně ten atribut zprava průběžně vyplňujem a pak mi přijde na konci té metody zbytečné volat tu metodu pro nastavení zprávy, kde jí dáváme jako parametr již vyplněnou zprávu. Takže nastavujeme do zprávy to, co v ní už vlastně je.

Stejně tak by v metodě utoc(Bojovnik souper) nemusela být volána metoda pro nastavení zprávy, ale rovnou by se ten požadovaný text rovnou přiřadil zprávě, jako to bylo u metody branSe.

Díky za odpověď od zkušeného :-)

 
Odpovědět
28. února 22:43
Avatar
Odpovídá na Jaroslav Drobek
Zdeněk Plicka:4. března 23:16

Neni to tak ze bez modifikatoru pristupu je videt pouze v ramci package?

Odpovědět
4. března 23:16
Nejprve je třeba naučit se plazit
Avatar
Ondřej Raška:8. srpna 15:02

Citace: "Nebudu je psát ani u dalších metod, aby se tutoriál zbytečně neroztahoval a zůstal přehledný."

Ach jo. Tento přístup mě v lekcích OOP vytáčí. Kratší != přehlednější. Pokud budu někomu něco ukazovat na číselné ose a nakreslím ji 2mm dlouhou a pak do ní zaznačím 20 bodů, tak jsem ušetřil hodně místa, přehledné to ale asi nebude.

 
Odpovědět
8. srpna 15:02
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 48. Zobrazit vše