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

Lekce 6 - Bojovník do arény - Zapouzdření

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

Z předchozích lekcí máme svůj první pořádný objekt, byla jím hrací kostka. Tento a příští C++ tutoriál o objektově orientovaném programování budou věnovány zprovoznění naší arény. Hrací kostku již máme, ještě nám chybí další objekt: bojovník. Nejprve si popišme, co má bojovník umět, poté se pustíme do psaní kódu.

Na úvod si prosím vymažte výpisy do konzole v konstruktorech a destruktoru ve třídě Kostka. Pro další práci by se nám tyto výpisy pletly do výstupu.

Atributy

Bojovník bude mít určité životy (zdraví). 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 40 životů z 80-ti. Bojovník má určitý útok a obranu. Když bojovník útočí se sílou 20 na druhého bojovníka s obranou 10, ubere mu 10 životů (výpočet později zdokonalíme). Bojovník bude mít referenci na instanci objektu Kostka. Při útoku či obraně si vždy hodí kostkou a k útoku/obraně se 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 25". 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ény si přidáme třídu Bojovnik a dodejme jí patřičné atributy.

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

Konstruktor a destruktor jsme prozatím smazali. Stejně tak nesmíme zapomenou naincludovat Kostka.h k Bojovníkovi.

Metody

Pojďme pro atributy vytvořit konstruktor, nebude to nic těžkého. Budeme chtít nastavit všechny atributy, které třída má. Přidáme deklaraci do hlavičkového souboru Bojovnik.h a inicializaci 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š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ý. Dále si všimněte inicializace reference. Reference je použita z toho důvodu, abychom v programu měli pouze jednu kostku. Pokud by se nejednalo o referenci (nebo ukazatel), potom by měl každý bojovník vlastní kostku. Jak jsme si řekli v lekci o referencích, reference musí být inicializovaná hned při vytvoření. A jak víme z předchozích lekcí, to je ještě před tím, než se zavolá konstruktor. Proto musíme použít tuto syntaxi. Obecně by všechny atributy u kterých to jde, měly být inicializovány tímto způsobem. Proto si konstruktor ještě dodatečně přepíšeme:

Bojovnik::Bojovnik(float zivot, float utok, float obrana, Kostka &kostka) :
    kostka(kostka), zivot(zivot), max_zivot(zivot), utok(utok), obrana(obrana)
{}

Nyní je to správně podle dobrých praktik.

Přejděme k metodám. Zřejmě budeme potřebovat nějakou metodu nazivu(), která zjistí, jestli bojovník ještě žije. Stejně tak budeme potřebovat metodu utoc(), pomocí které bojovník zaútočí na jiného bojovníka. Nejprve se podíváme na metodu nazivu(). Vyjdeme z toho, jestli má bojovník ještě nějaké životy. Pokud nemá, potom je zřejmě mrtvý.

bool Bojovnik::nazivu()
{
    if (this->zivot > 0)
        return true;
    else
        return false;
}

Protože výraz "this->zivot > 0" vrací logickou hodnotu, můžeme celou metodu přepsat do následující podoby:

bool Bojovnik::nazivu()
{
    return this->zivot > 0;
}

S podobnou a zbytečnou podmínkou se setkávám často i u pokročilejších programátorů, proto na ni schválně upozorňuji. If-else konstrukce v takovém případě vypovídá pouze o nezkušenosti programátora.

Nyní se podíváme na metodu utoc(). Ta na základě obrany, hodu kostky a útoku vypočítá zranění, tak jak jsme si to popsali na začátku.

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 měl být snadný. Vypočítáme si obranu obránce (se započteným hodem kostky), poté útočnou sílu útočníka (opět se započteným hodem kostky) a tyto hodnoty od sebe odečteme - získáváme poškození. Musíme počítat i se situací, kdy je obrana vyšší než útok - proto ona podmínka (pokud by tam nebyla, poté by se obránci doplňovaly životy). Nakonec od životů obránce odečteme poškození a tím jsme skončili.

Viditelnost

Nyní máme bojovníky, kteří mezi sebou mohou bojovat. Co když ale jeden z hráčů chce podvádět a bude chtít protihráči odebrat více životů? Pokud by to byl programátor, který našeho bojovníka používá (například protože je v knihovně), tak může, protože jsme mu dovolili volně měnit počet životů. Jedním ze základních pilířů OOP je tzv. zapouzdření, tedy uchovávat si atributy pro sebe a ven vystavovat jen metody. To zařizuje ona magická část public: na začátku třídy.

Upravme si třídu bojovníka následovně:

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šimněte si použití private: na začátku třídy. Všechny atributy (a metody) následující za touto konstrukcí nebudou viditelné z vnějšku. Po této úpravě například nemůžeme provést 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 mít přístup ani k jednomu z atributů, protože jsou privátní. Možná by se ale někomu hodilo vědět, kolik má bojovník ještě životů. Na to se používají tzv. gettery a settery. Jedná se metody které začínají get nebo set následující názvem atributu. V principu to jsou plnohodnotné metody, které vrací nebo nastavují hodnotu pro privátní atribut. Gettery a settery z nich dělá pouze konvence.

Například pro životy by vypadaly metody následovně:

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

Pomocí metody getZivot() si nyní můžeme zjistit život bojovníka a to i když je atribut privátní. Naopak pomocí setteru můžeme život nastavit. Všimněte si, že není možné nastavit život na hodnotu nižší než 0 kvůli podmínce v settery. Setter pro životy mít v našem případě nechceme, protože zranění se řeší v metodě utoc(), ale chtěl jsem jej ukázat také z toho důvodu, že můžeme dodat validaci vstupu. Pokud by byl atribut veřejně přístupný, pak tuto validaci nemáme jak zařídit a bojovníkovi by opravdu někdo mohl nastavit záporné zdraví.

K atributům a metodám, které jsou označeny jako public, tedy můžeme přistupovat z vnějšku. Naopak atributy a metody označené jako private jsou zabezpečené a máme jistotu, že jediný, kdo je vidí, jsme my. K těmto metodám a atributům můžeme přistupovat z jiných metod ve stejné třídě (i z veřejných) a přes ukazatel this. To je princip zapouzdření - chránit si svá vlastní data před změnou.

Souboj

Nyní můžeme provést takový malý souboj. V main.cpp si vytvoříme dva bojovníky a budou mezi sebou bojovat až do doby, kdy jeden z nich nezemře (nezapomeneme 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;
}

Konvence

Určitě jste si všimli, že metody a atributy jsou psány jiným stylem (například velikost písma, mezery apod.). V C++ (na rozdíl třeba od Javy nebo C#), nejsou dány pravidla, jak se má kód psát. Vývojáři se dokonce ani neshodli, jak psát složené závorky - jestli za název metody (jak to dělá například Java) nebo pod metodu (jak to dělá C#). Osobně dodržuji konvenci uvedenou v seriálu, tedy názvy atributů a proměnných jsou v tzv. snake-case notaci: malým písmenem a oddělené podtržítkem (int nejaka_promenna, string nazev_hrace). Metody píšu v tzv. camelCase notaci (nazevNejakeMetody(), hod()). Konstanty potom v ALL_CAPS notaci (MAX_POCET_HRACU, POCET_LEVELU). Co se závorek týče, používám tzv. Allmanův styl (složené závorky pod názvem metody), různé konvence můžete najít na Wikipedii. V tomto není C++ sjednocené a je na každém programátorovi, aby si vybral svůj styl.

To by bylo pro tuto lekci vše. V projektu jsme upravili viditelnosti i pro ostatní třídy a popřípadě doplnili gettery a settery. Pokud chcete mít jistotu, že pracujeme nad stejným kódem, stáhněte si prosím zdrojové kódy dole pod článkem.

V příští lekci s ním budeme pokračovat.

A co nás příště čeká? V lekci Aréna s bojovníky v C++ si napíšeme nějakou základní funkcionalitu arény, aby již program něco dělal.


 

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 96x (527.01 kB)
Aplikace je včetně zdrojových kódů v jazyce C++

 

Předchozí článek
Řešené úlohy k 5. lekci OOP v C++
Všechny články v sekci
Objektově orientované programování v C++
Přeskočit článek
(nedoporučujeme)
Aréna s bojovníky v C++
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
30 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity