IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
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í.

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).

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é 96x (527.01 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C++

 

Predchádzajúci článok
Riešené úlohy k 5. lekcii OOP v C ++
Všetky články v sekcii
Objektovo orientované programovanie v C ++
Preskočiť článok
(neodporúčame)
Aréna s bojovníkmi v C ++
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity