4. díl - OOP v C++ - Bojovník do arény

C a C++ C++ Objektově orientované programování OOP v C++ - Bojovník do arény

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulém dílu seriálu o C++ jsme si vytvořili svůj první pořádný objekt, byla jím hrací kostka. Tento a příští tutoriál budou 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 ukazatel 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 aréna si přidejme třídu Bojovnik a dodejme ji patřičné atributy. Všechny budou privátní:

#pragma once
#include "Kostka.h"
#include "math.h"
class Bojovnik
{
        /// Jméno bojovníka
        std::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;
public:
        Bojovnik(void);
        ~Bojovnik(void);
};

Třída Kostka musí být samozřejmě 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ý. Pro jednoduchost budu psát vždy jen obsah jednoho souboru (v hlavičkových souborech se jen upraví hlavička funkce).

Bojovnik::Bojovnik(std::string jmeno, int zivot, int utok, int obrana, Kostka &_kostka)
        :kostka(_kostka)
{
        this->jmeno = jmeno;
        this->zivot = zivot;
        this->maxZivot = zivot;
        this->utok = utok;
        this->obrana = obrana;
}

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ý) a že před závorkou { máme :kostka(_kostka), což je jediný způsob, jak inicializovat referenci.

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řetížíme tedy funkci operator<<(), který vypíše jméno bojovníka. Určitě se nám bude hodit metoda, vracející zda je bojovník naživu (tedy typu bool). 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 operator<<(), Nazivu() a GrafickyZivot(). Začněme s operator<<(), tam není co vymýšlet:

std::ostream &operator<<(std::ostream &s, Bojovnik &b)
{
        return s << b.jmeno.c_str();
}

Aby měla tato funkce povoleno získat soukromou proměnnou jmeno, upravíme naši třídu takto:

class Bojovnik
{
        /// Jméno bojovníka
        std::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;
public:
        Bojovnik(std::string jmeno, int zivot, int utok, int obrana, Kostka &kostka);
        ~Bojovnik(void);


        friend std::ostream &operator<<(std::ostream &s, Bojovnik &b);
};

Identifikátor friend si vysvětlíme v některém z následujících článků.

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:

bool 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šší:

bool Nazivu()
{
        return (zivot > 0);
}

Grafický život

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:

std::string GrafickyZivot()
{
        std::string s = '[';
        int celkem = 20;
        double pocet = 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 C++ chápal dělení jako neceločíselné.

Kvůli zaokrouhlení přidáme na začátek souboru kód:

int round(double x)
{
        return ((x - floor(x))<0.5)?floor(x):ceil(x);
}

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. Přidáme koncový znak a řetězec vrátíme.

Vše si vyzkoušíme, přejděme do Source.cpp 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(10);
Bojovnik bojovnik("Zalgoren", 100, 20, 10, kostka);

cout << "Bojovník: " << bojovnik << endl; // test operator<<();
cout << "Naživu: " << bojovnik.Nazivu() << endl; // test Nazivu();
cout << "Život: " << bojovnik.GrafickyZivot().c_str() << endl; // test GrafickyZivot();
OOP v C++

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:

void Bojovnik::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 C++, 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:

void Bojovnik::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(10);
Bojovnik bojovnik("Zalgoren", 100, 20, 10, kostka);

cout << "Bojovník: " << bojovnik << endl; // test ToString();
cout << "Naživu: " << bojovnik.Nazivu() << endl; // test Nazivu();
cout << "Život: " << bojovnik.GrafickyZivot().c_str() << endl; // test GrafickyZivot();

bojovnik.Utoc(bojovnik); // test útoku
cout << "Život po útoku: " << bojovnik.GrafickyZivot().c_str() << endl;
Bojovník v C++ po útoku

Zdá se, že vše funguje, jak má. To by pro dnešek stačilo. Příště našeho bojovníka dokončíme a začneme tvořit arénu. Projekt se zdrojovými kódy je níže ke stažení pro případ, že vám něco nešlo.


 

Stáhnout

Staženo 331x (12.54 MB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

 

Článek pro vás napsal Zdeněk Pavlátka
Avatar
Jak se ti líbí článek?
3 hlasů
Autor se věnuje spoustě zajímavých věcí :)
Aktivity (1)

 

 

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

Avatar
Libor Šimo (libcosenior):18.5.2015 19:19

V cecku pouzivam structury a vypis jednej je jednoduchy, myslel som vypisy instancii v poli, ked ich mam vela. Napr. cislo, nazov, marza, jednotka mnozstva, mnozstvo a umiestnenie materialu.

Odpovědět 18.5.2015 19:19
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):18.5.2015 19:20

Diky, vyskusam. ;-) Zda sa, ze je to podobne ako struktury.

Editováno 18.5.2015 19:21
Odpovědět 18.5.2015 19:20
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):18.5.2015 19:23

Pisem si program na inventarizaciu materialu a potrebujem vypisovat instancie kompletne.

Odpovědět 18.5.2015 19:23
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):19.5.2015 8:11

Je to presne to co som potreboval, díky.

Odpovědět 19.5.2015 8:11
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):22.12.2016 8:29

"Kvůli zaokrouhlení přidáme na začátek souboru kód:"

Ten kód je zbytočný, pretože (by mala byť) includovaná knižnica <cmath> a funkcia round() zaokrúhľuje podľa matematických pravidiel.

Odpovědět 22.12.2016 8:29
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Zdeněk Pavlátka
Tým ITnetwork
Avatar
Odpovídá na Libor Šimo (libcosenior)
Zdeněk Pavlátka:22.12.2016 8:46

V době psaní článku v cmath funkce round nebyla... A od té doby jsem ho neupdatoval 8-|

Odpovědět  +1 22.12.2016 8:46
Kolik jazyků umíš, tolikrát jsi programátor.
Avatar
Odpovídá na Zdeněk Pavlátka
Libor Šimo (libcosenior):23.12.2016 7:03

Ahoj.

class Bojovnik
{
        ...
        /// Instance hrací kostky
        Kostka &kostka;

Nie je mi jasné prečo je deklarácia premennej &kostka, veď to je adresa.
Nemá to byť len

Kostka kostka;

???

Odpovědět 23.12.2016 7:03
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Odpovídá na Libor Šimo (libcosenior)
Petr Štechmüller:23.12.2016 7:53

Ahoj, není to adresa, ale reference. Tím mas zajištěno, ze kostka nikdy nebude null a má to hromadu dalších výhod...

detail: v kódu se s referenci pracuje pres "tečkovou" notaci, zatímco s pointerem se pracuje pres "šipkovou" notaci (->)

Pokud by jsi dostal kostku jako pointer, tak to lze dereferencovat pomoci "*"

Kostka &refKostka = *ptrKostka
Odpovědět 23.12.2016 7:53
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Avatar
Odpovídá na Petr Štechmüller
Libor Šimo (libcosenior):23.12.2016 8:58

Mňa to zaujalo preto, že to funguje aj s aj bez &.

Odpovědět 23.12.2016 8:58
Aj tisícmíľová cesta musí začať jednoduchým krokom.
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 22. Zobrazit vše