6. diel - Bojovník do arény - Zapuzdrenie
V predchádzajúcom cvičení, Riešené úlohy k 5. lekcii OOP v C ++, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V minulej lekcii, Riešené úlohy k 5. lekcii OOP v C ++ , sme si vysvetlili kľúčové slovo
this
. Z predchádzajúcich lekcií máme tiež svoj prvý poriadny
objekt, bola ním hracia kocka. Tento a budúci C ++ tutoriál o objektovo
orientovanom programovaní budú venované sprevádzkovanie našej arény.
Hracie kocku už máme, ešte nám chýba ďalší objekt: bojovník. Najprv si
popíšme, čo má bojovník vedieť, potom sa pustíme do písania kódu.
Na úvod si prosím vymažte výpisy do konzoly v konstruktoru a deštruktory v triede Kostka. Pre ďalšiu prácu by sa nám tieto výpisy plietli do výstupu.
Atribúty
Bojovník bude mať určité životy (zdravie). Budeme uchovávať jeho maximálnej život (bude sa líšiť u každej inštancie) a jeho súčasný život, teda napr. Zranený bojovník bude mať 40 životov z 80-tich. Bojovník má určitý útok a obranu. Keď bojovník útočí so silou 20 na druhého bojovníka s obranou 10, uberie mu 10 životov (výpočet neskôr zdokonalíte). Bojovník bude mať referenciu na inštanciu objektu Kostka. Pri útoku či obrane si vždy hodí kockou a k útoku / obrane sa pripočíta padlých číslo. Samozrejme by mohol mať každý bojovník svoju kocku, ale chcel som sa priblížiť stolové 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 šťastí, ako sa útok alebo obrana vydarí. Konečne 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 25". 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 arény si pridáme triedu Bojovnik a dodajme jej patričné atribúty.
Bojovnik.h
#ifndef __BOJOVNIK_H_ #define __BOJOVNIK_H_ #include <string> #include "Kostka.h" using namespace std; class Bojovnik { public: float zivot; float max_zivot; float utok; float obrana; Kostka &kostka; }; #endif
Konštruktor a destruktor sme zatiaľ zmazali. Rovnako tak nesmieme zabudnú naincludovat Kostka.h k bojovníka.
Metódy
Poďme pre atribúty vytvoriť konštruktor, nebude to nič ťažké. Budeme chcieť nastaviť všetky atribúty, ktoré trieda má. Pridáme deklaráciu do hlavičkového súboru Bojovnik.h a inicializácii do implementačného:
Bojovnik.cpp
Bojovnik::Bojovnik(float zivot, float utok, float obrana, Kostka &kostka) : kostka(kostka) { this->zivot = zivot; this->max_zivot = zivot; this->utok = utok; this->obrana = obrana; }
Všimnite si, že maximálna zdravie si v konstruktoru odvodíme a nemáme na neho 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ý. Ďalej si všimnite inicializácia referencie. Referencie je použitá z toho dôvodu, aby sme v programe mali iba jednu kocku. Ak by nešlo o referenciu (alebo ukazovateľ), potom by mal každý bojovník vlastné kocku. Ako sme si povedali v lekcii o referenciách, referencie musí byť inicializovaná hneď pri vytvorení. A ako vieme z predchádzajúcich lekcií, to je ešte pred tým, než sa zavolá konštruktor. Preto musíme použiť túto syntax. Vo všeobecnosti by všetky atribúty u ktorých to ide, mali byť inicializované týmto spôsobom. Preto si konštruktor ešte dodatočne prepíšeme:
Bojovnik::Bojovnik(float zivot, float utok, float obrana, Kostka &kostka) : kostka(kostka), zivot(zivot), max_zivot(zivot), utok(utok), obrana(obrana) {}
Teraz je to správne podľa dobrých praktík.
Prejdime k metódam. Zrejme budeme potrebovať nejakú metódu nazive (), ktorá zistí, či bojovník ešte žije. Rovnako tak budeme potrebovať metódu útočí (), pomocou ktorej bojovník zaútočí na iného bojovníka. Najprv sa pozrieme na metódu nazive (). Vyjdeme z toho, či má bojovník ešte nejaké životy. Pokiaľ nemá, potom je zrejme mŕtvy.
bool Bojovnik::nazivu() { if (this->zivot > 0) return true; else return false; }
Pretože výraz " this->zivot > 0
" vracia logickú
hodnotu, môžeme celú metódu prepísať do nasledujúcej podoby:
bool Bojovnik::nazivu() { return this->zivot > 0; }
S podobnou a zbytočnú podmienkou sa stretávam často aj u pokročilejších programátorov, preto na ňu schválne upozorňujem. If-else konštrukcia v takom prípade vypovedá len o neskúsenosti programátora.
Teraz sa pozrieme na metódu útočí (). Tá na základe obrany, hodu kocky a útoku vypočíta zranenia, tak ako sme si to popísali na začiatku.
void Bojovnik::utoc(Bojovnik & druhy) { float obrana_druhy = druhy.obrana + druhy.kostka.hod(); float utok_prvni = this->utok + this->kostka.hod(); float zraneni = utok_prvni - obrana_druhy; if (zraneni < 0) zraneni = 0; druhy.zivot -= zraneni; }
Výpočet by mal byť jednoduchý. Vypočítame si obranu obranca (so započteným hodom kocky), potom útočnú silu útočníka (opäť sa započteným hodom kocky) a tieto hodnoty od seba odčítame - získavame poškodenie. Musíme počítať aj so situáciou, kedy je obrana vyššia ako útok - preto ona podmienka (ak by tam nebola, potom by sa obrancovia dopĺňali životy). Nakoniec od životov obrancu odpočítame poškodenia a tým sme skončili.
Viditeľnosť
Teraz máme bojovníkmi, ktorí medzi sebou môžu bojovať. Čo keď ale
jeden z hráčov chce podvádzať a bude chcieť protihráči odobrať viac
životov? Ak by to bol programátor, ktorý nášho bojovníka používa
(napríklad pretože je v knižnici), tak môže, pretože sme mu dovolili
voľne meniť počet životov. Jedným zo základných pilierov OOP je tzv.
Zapuzdrenie, teda uchovávať si atribúty pre seba a von
vystavovať len metódy. To zariaďuje ona magická časť public:
na začiatku triedy.
Upravme si triedu bojovníka nasledovne:
Bojovnik.h
class Bojovnik { private: float zivot; float max_zivot; float utok; float obrana; Kostka &kostka; public: Bojovnik(float zivot, float utok, float obrana, Kostka &kostka); bool nazivu(); void utoc(Bojovnik &druhy); };
Všimnite si použitie private:
na začiatku triedy. Všetky
atribúty (a metódy) nasledujúce za touto konštrukciou nebudú viditeľné
zvonku. Po tejto úprave napríklad nemôžeme vykonať tento kód:
Bojovnik ja(100, 8, 4, &kostka); Bojovnik protovnik(100, 8, 4, &kostka); ja.zivoty = 99999; // Haha, jsem téměř nesmrtelný ja.obrana = 99999; // Haha, jsem nezranitelný protivnik.utok = 0; // Haha, neublížíš ani mouše protivník.zivoty = 1; // Haha, jsi slaboch
Programátor nebude mať prístup ani k jednému z atribútov, pretože sú privátne. Možno by sa ale niekomu hodilo vedieť, koľko má bojovník ešte životov. Na to sa používajú tzv. Getter a setter. Jedná sa metódy ktoré začínajú get alebo set nasledujúce názvom atribútu. V princípe to sú plnohodnotné metódy, ktoré vracia alebo nastavujú hodnotu pre privátne atribút. Getter a setter z nich robí iba konvencie.
Napríklad pre životy by vyzerali metódy nasledovne:
Bojovnik.h
class Bojovnik { private: float zivot; float max_zivot; float utok; float obrana; Kostka &kostka; public: Bojovnik(float zivot, float utok, float obrana, Kostka &kostka); bool nazivu(); void utoc(Bojovnik &druhy); float getZivot(); // getter void setZivot(float zivot); // setter };
Bojovnik.cpp
float Bojovnik::getZivot() { return this->zivot; } void Bojovnik::setZivot(float zivot) { if (zivot < 0) // validace return; this->zivot = zivot; }
Pomocou metódy getZivot()
si teraz môžeme zistiť život
bojovníka a to aj keď je atribút privátne. Naopak pomocou Setter môžeme
život nastaviť. Všimnite si, že nie je možné nastaviť život na hodnotu
nižšiu ako 0 kvôli podmienke v setter. Setter pre životy mať v našom
prípade nechceme, pretože zranení sa rieši v metóde útokoch (),
ale chcel som ho ukázať aj z toho dôvodu, že môžeme dodať validáciu
vstupu. Pokiaľ by bol atribút verejne prístupný, potom túto validáciu
nemáme ako zariadiť a bojovníkovi by naozaj niekto mohol nastaviť záporné
zdravie.
K atribútom a metódam, ktoré sú označené ako public, teda môžeme pristupovať z vonka. Naopak atribúty a metódy označené ako private sú zabezpečené a máme istotu, že jediný, kto ich vidí, sme my. K týmto metódam a atribútom môžeme pristupovať z iných metód v rovnakej triede (aj z verejných) a cez ukazovateľ this. To je princíp zapuzdrenie - chrániť si svoje vlastné dáta pred zmenou.
Súboj
Teraz môžeme urobiť taký malý súboj. V main.cpp si vytvoríme dva bojovníkmi a budú medzi sebou bojovať až do doby, keď jeden z nich nezomrie (nezabudneme includovat Bojovnik.h).
#include <iostream> #include "Kostka.h" #include "Bojovnik.h" using namespace std; int main() { Kostka kostka; Bojovnik prvni(100, 8, 4, kostka); Bojovnik druhy(100, 8, 4, kostka); while (prvni.nazivu() && druhy.nazivu()) { prvni.utoc(druhy); if (druhy.nazivu()) druhy.utoc(prvni); } if (prvni.nazivu()) cout << "Prvni bojovnik vyhral s " << prvni.getZivot() << " zivoty" << endl; else cout << "Druhy bojovnik vyhral s " << druhy.getZivot() << " zivoty" << endl; cin.get(); return 0; }
#ifndef __KOSTKA_H__ #define __KOSTKA_H__ using namespace std; class Kostka { private: int pocet_sten; public: Kostka(); Kostka(int pocet_sten); int hod(); int getPocetSten(); }; #endif
#include <iostream> #include <cstdlib> #include <ctime> #include "Kostka.h" using namespace std; Kostka::Kostka() : Kostka(6) { } Kostka::Kostka(int pocet_sten) { this->pocet_sten = pocet_sten; srand((unsigned int)time(NULL)); } int Kostka::hod() { return rand() % this->pocet_sten + 1; } int Kostka::getPocetSten() { return this->pocet_sten; }
#ifndef __BOJOVNIK_H_ #define __BOJOVNIK_H_ #include <string> #include "Kostka.h" using namespace std; class Bojovnik { private: float zivot; float max_zivot; float utok; float obrana; Kostka &kostka; public: Bojovnik(float zivot, float utok, float obrana, Kostka &kostka); bool nazivu(); void utoc(Bojovnik &druhy); float getZivot(); }; #endif
#include "Bojovnik.h" Bojovnik::Bojovnik(float zivot, float utok, float obrana, Kostka &kostka) : kostka(kostka), zivot(zivot), max_zivot(zivot), utok(utok), obrana(obrana) {} bool Bojovnik::nazivu() { return this->zivot > 0; } void Bojovnik::utoc(Bojovnik & druhy) { float obrana_druhy = druhy.obrana + druhy.kostka.hod(); float utok_prvni = this->utok + this->kostka.hod(); float zraneni = utok_prvni - obrana_druhy; if (zraneni < 0) zraneni = 0; druhy.zivot -= zraneni; } float Bojovnik::getZivot() { return this->zivot; }
Konvencie
Určite ste si všimli, že metódy a atribúty sú písané iným štýlom
(napríklad veľkosť písma, medzery a pod.). V C ++ (na rozdiel napríklad od
Javy alebo C #), nie sú dané pravidlá, ako sa má kód písať. Vývojári sa
dokonca ani nezhodli, ako písať zložené zátvorky - či za názov metódy
(ako to robí napríklad Java) alebo pod metódu (ako to robí C #). Osobne
dodržiavam konvencii uvedenú v seriáli, teda názvy atribútov a premenných
sú v tzv. Snake-case notáciu: malým písmenom a oddelené podčiarknikom
(int nejaka_promenna
, string nazev_hrace
). Metódy
píšem v tzv. CamelCase notáciu (nazevNejakeMetody()
,
hod()
). Konštanty potom v ALL_CAPS notáciu
(MAX_POCET_HRACU
, POCET_LEVELU
). Čo sa zátvoriek
týka, používam tzv. Allmanův štýl (zložené zátvorky pod názvom
metódy), rôzne konvencie môžete nájsť na Wikipédii. V tomto nie je
C ++ zjednotenej a je na každom programátorovi, aby si vybral svoj štýl.
To by bolo pre túto lekciu všetko. V projekte sme upravili viditeľnosti i pre ostatné triedy a prípadne doplnili Getter a setter. Ak chcete mať istotu, že pracujeme nad rovnakým kódom, stiahnite si prosím zdrojové kódy dole pod článkom.
V budúcej lekcii s ním budeme pokračovať. A čo nás nabudúce čaká? V lekcii Aréna s bojovníkmi v C ++ si napíšeme nejakú základnú funkcionalitu arény, aby už program niečo robil.
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é 99x (527.01 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C++