6. díl - OOP v C++ - Dokončení arény

C++ Objektově orientované programování OOP v C++ - Dokončení arény

V minulém dílu jsme začali pracovat na aréně v C++ simulující stolní hru. Naše aréna má však 3 nedostatky.

V cyklu s bojem útočí první bojovník na druhého. Poté však vždy útočí i druhý bojovník, nehledě na to, zda ho první nezabil. Může tedy útočit již jako mrtvý. U prvního bojovníka tento problém není, u druhého musíme před útokem kontrolovat, zda je naživu.

Druhým nedostatkem je, že bojovníci vždy bojují ve stejném pořadí, čili zde "Zalgoren" má vždy výhodu. Pojďme vnést další prvek náhody a pomocí kostky rozhodněme, který z bojovníků bude začínat. Jelikož jsou bojovníci vždy dva, stačí hodit kostkou a podívat se, zda padlo číslo menší nebo rovné polovině počtu stěn kostky. Tedy např. pokud padne na desetistěnné kostce číslo do 5ti, začíná 2. bojovník, jinak začíná první. Zbývá zamyslet se nad tím, jak do kódu zanést prohazování bojovníků. Jistě by bylo velmi nepřehledné opodmínkovat příkazy ve while cyklu. Jelikož již víme, že v C++ můžeme použít ukazatele, není pro nás problém udělat si 2 proměnné, které budou ukazovat na instance bojovníků, nazvěme je jednoduše b1 a b2. Do těchto proměnných si na začátku dosadíme adresy bojovníků bojovnik1 a bojovnik2 tak, jak potřebujeme. Můžeme tedy při pozitivním hodu kostkou dosadit do b1 bojovník2 a naopak, výsledkem bude, že začínat bude ten druhý. Kód cyklu se takto vůbec nezmění a zůstane stále přehledný a jednoduchý, jen místo bojovnik bude b.

Třetí nedostatek je hned na začátku metody Zapas(). Používáme v ní totiž funkci _getch(). Ta funguje na většině kláves bez problémů, ale u některých vrátí hodnotu 254 a při dalším volání vrátí druhou hodnotu. Druhé volání se tím pádem přeskočí. My máme to druhé na konci programu, kdy aplikace čeká na stisk klávesy a ukazuje výsledek zápasu. Pokud by se přeskočila, program by hned skončil a my bychom nic neviděli. Proto musíme zkontrolovat její návratovou hodnotu a pokud se bude rovnat 254, zavolat funkci ještě jednou.

Změněná verze včetně podmínky, aby nemohl útočit mrtvý bojovník a ošetření funkce _getch(), by mohla vypadat nějak takto:

void Arena::Zapas()
{
        // ukazatele
        Bojovnik *b1;
        Bojovnik *b2;
        cout << "Vítejte v aréně!" << endl;
        cout << "Dnes se utkají " << bojovnik1 << " a " << bojovnik2 << "! \n" << endl;
        // prohození bojovníků
        bool zacinaBojovnik2 = (kostka.hod() <= kostka.VratPocetSten() / 2);
        if (zacinaBojovnik2)
        {
                b1 = &bojovnik2;
                b2 = &bojovnik1;
        }
        else
        {
                b1 = &bojovnik1;
                b2 = &bojovnik2;
        }
        cout << "Začínat bude bojovník " << *b1 <<"! \nZápas může začít...";
        int c = _getch();
        if(c == 254)
                _getch();
        // cyklus s bojem
        while (b1->Nazivu() && b2->Nazivu())
        {
                b1->Utoc(*b2);
                Vykresli();
                VypisZpravu(b1->VratPosledniZpravu()); // zpráva o útoku
                VypisZpravu(b2->VratPosledniZpravu()); // zpráva o obraně
                if (b2->Nazivu())
                {
                        b2->Utoc(*b1);
                        Vykresli();
                        VypisZpravu(b2->VratPosledniZpravu()); // zpráva o útoku
                        VypisZpravu(b1->VratPosledniZpravu()); // zpráva o obraně
                }
                cout << endl;
        }
}

Program vyzkoušejme.

Objektová aréna v C++ – simulace zápasu ve stolní hře

Vidíme, že je vše již v pořádku. Ještě si ale arénu ozdobíme.

Místo "-------------- Aréna --------------" použijeme hezčí nápis, který si můžeme vygenerovat např. tady: http://patorjk.com/software/taag .

Dále upravíme výpis zdraví:

std::string Bojovnik::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 += char(129); // znak █
        for (int i = 0; i < celkem-pocet; i++)
                s += ' ';
        return s;
}

Teď přidáme barvy. K tomu použijeme funkce z článku Pokročilá práce s konzolí v C++. Upravíme tedy kód metody Vykresli().

void Arena::Vykresli()
{
        system("cls");
        set_text_color(Colors::black, Colors::dark_green);
        cout << "\n      ##     #######   ######## ####     ##     ##    \n"
                  "     ####   /##////## /##///// /##/##   /##    ####   \n"
                  "    ##//##  /##   /## /##      /##//##  /##   ##//##  \n"
                  "   ##  //## /#######  /####### /## //## /##  ##  //## \n"
                  "  ##########/##///##  /##////  /##  //##/## ##########\n"
                  " /##//////##/##  //## /##      /##   //####/##//////##\n"
                  " /##     /##/##   //##/########/##    //###/##     /##\n"
                  " //      // //     // //////// //      /// //      // \n" << endl;
        reset_text_color();

        cout << "Zdraví bojovníků: \n" << endl;
        cout << bojovnik1 << " ";

        set_text_color(Colors::dark_red, Colors::red);
        setlocale(LC_ALL,"C"); // používání určitých znaků - např. █
        cout << bojovnik1.GrafickyZivot().c_str() << endl << endl;
        setlocale(LC_ALL,"Czech_Czech Republic.1250"); // dikritika
        reset_text_color();

        cout << bojovnik2 << " ";

        set_text_color(Colors::dark_red, Colors::red);
        setlocale(LC_ALL,"C");
        cout << bojovnik2.GrafickyZivot().c_str() << endl << endl;
        setlocale(LC_ALL,"Czech_Czech Republic.1250");
        reset_text_color();
}

Abychom mohli funkce využívat, musíme na začátek souboru Arena.cpp přidat:

enum Colors{
        black, dark_blue, dark_green, dark_cyan, dark_red, dark_magenta, dark_yellow, grey,
        dark_grey, blue, green, cyan, red, magenta, yellow, white
};
void set_text_color(int background_color, int foreground_color)
{
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), background_color*16+foreground_color);
}
void reset_text_color()
{
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}

Případně to můžeme oddělit do zvláštního hlavičkového souboru. Můj výtvor vypadá takto:

barevná aréna

Gratuluji vám, pokud jste se dostali až sem a tutoriály opravdu četli a pochopili, máte základy objektového programování a dokážete tvořit rozumné aplikace :)

Příště se podíváme na objektově orientované programování podrobněji. V úvodu jsme si říkali, že OOP stojí na pilířích: zapouzdření, dědičnost a polymorfismus. První umíme již velmi dobře a modifikátor private je nám známý. Další dva nás čekají někdy příště.


 

Stáhnout

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

 

  Aktivity (1)

Článek pro vás napsal Zdeněk Pavlátka
Avatar
Autor se věnuje spoustě zajímavých věcí :)

Jak se ti líbí článek?
Celkem (4 hlasů) :
55555


 



 

 

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

Avatar
Libor Šimo (libcosenior):

Ako informácia fajn, ale čo s tým, keď nemá kto spraviť tutoriál. ;)

Odpovědět 21.5.2015 14:01
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Jan Vargovský
Redaktor
Avatar
Jan Vargovský:

To bylo obecne. Ofc ze jazyk tam má nějaké specifické veci. Ale na ty přijdeš hned...

 
Odpovědět 21.5.2015 14:24
Avatar
Libor Šimo (libcosenior):

Napríklad chcem spraviť databázu materiálu v spoločnosti.
Mám základnú triedu Polozka a tu chcem zdediť do triedy DataMaterialu.
Napísal som to takto, ale je to zle. :(
Polozka.h

#ifndef POLOZKA_H
#define POLOZKA_H

#include <iostream>

using namespace std;

class Polozka
{
private:
    /// vojenske cislo materialu
    string vcm;
    /// nazov materialu
    string material;
    /// vyrobne cislo/marza/suprava
    string vc;
    /// merna jednotka
    string mj;
    /// mnozstvo materialu
    float pocet;
    /// umiestnenie materialu pod poradovym cislom miesta
    int miesto;
public:
    /// Vrati mnozstvo materialu
    float VratMnozstvo();
    /// Vrati umiestnenie materialu
    int VratMiesto();
    /// Nastavi nove mnozstvo
    void NastavMnozstvo(float value);
    /// Natavi nove umiestnenie
    void NastavMiesto(int value);
    /// Konstruktor
    Polozka(string vcm, string material, string vc, string mj, float pocet, int miesto);
    /// Destruktor
    ~Polozka(void);

    /** Vypise hodnoty instancie ako text */
    friend std::ostream &operator<<(std::ostream &os, Polozka &x);
};

#endif // POLOZKA_H

Polozka.cpp

#include "Polozka.h"

Polozka::Polozka(string vcm, string material, string vc, string mj, float pocet, int miesto)
{
    //ctor
    this->vcm = vcm;
    this->material = material;
    this->vc = vc;
    this->mj = mj;
    this->pocet = pocet;
    this->miesto = miesto;
}

Polozka::~Polozka()
{
    //dtor
}

std::ostream &operator<<(std::ostream &os, Polozka &x)
{
    return os << x.vcm << " " << x.material << " " << x.vc << " " << x.mj << " " << x.pocet << " " << x.miesto << endl;
}

float Polozka::VratMnozstvo()
{
    return pocet;
}

int Polozka::VratMiesto()
{
    return miesto;
}

void Polozka::NastavMnozstvo(float value)
{
    pocet = value;
}

void Polozka::NastavMiesto(int value)
{
    miesto = value;
}

DataMaterialu.h

#ifndef DATAMATERIALU_H
#define DATAMATERIALU_H
#include "Polozka.h"

class DataMaterialu: public Polozka
{
    public:
        DataMaterialu();
        ~DataMaterialu();
    protected:
    private:
};

#endif // DATAMATERIALU_H

DataMaterialu.cpp

#include "DataMaterialu.h"

DataMaterialu::DataMaterialu(string vcm, string material, string vc, string mj, float pocet, int miesto)
: Polozka(vcm, material, vc, mj, pocet, miesto)
{
    //ctor
}

DataMaterialu::~DataMaterialu()
{
    //dtor
}
Odpovědět 21.5.2015 14:40
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Odpovídá na Jan Vargovský
Lukáš Hruda (Luckin):

To byl jenom ptiklad, tech rozdilu je tam spousta. Nebo ani ne tak rozdilu, jako veci, ktere jsou v C++ navic, at uz vyhod ci problemu. C++ poskytuje nekolik typu dedicnosti- jednoducha/vi­cenasobna dedicnost, soukroma/chra­nena/verejna dedicnost, virtualni dedicnost. K jednotlivym typum se vetsinou vaze nejake specificke chovani, napriklad pristupovani ke konstruktorum neprimych predku u virtualni dedicnosti, ktere je potreba znat. Pak je take potreba vedet, jak se pri dedicnosti chovaji destruktory. Tohle rozhodne neni tema na jeden clanek, spis na jednu celou serii clanku.

 
Odpovědět  +1 21.5.2015 15:01
Avatar
Libor Šimo (libcosenior):

Ako to tu čítam, tak si predsa len ten program na inventarizáciu napíšem v čistom céčku. Tam viem o čo ide a ako sa to píše.
Chcel som to písať v c++ práve preto, aby som sa viac zoznámil s OOP.
Veď ide len o konzolovú app.

Odpovědět 21.5.2015 15:10
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
 
Odpovědět 21.5.2015 17:46
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
Jan Vargovský:

Máš v definici bezparametrický konstruktor, ale implementuješ parametrický. Ono je celkem zbytečné implementovat další třídu, která jí v žádném směru nerozšiřuje, jen kvůli toho, že chceš mít jiný. (To jde vyřešit třeba typedefem, když už bys to fakt nutně potřeboval). Jak tam přetižuješ ten operátor, tak autor tam dával to klíčové slovo friend, aby se dostal k private/protected datovým složkám třídy, u tebe je to zbytečné, vzhledem k tomu, že tam máš gettry.

 
Odpovědět 21.5.2015 18:04
Avatar
Odpovídá na Lukáš Hruda (Luckin)
Libor Šimo (libcosenior):

Chybové hlášky:
D:\cplusplus\E­VID\DataMateri­alu.cpp|3|error: prototype for 'DataMaterialu::Da­taMaterialu(std::strin­g, std::string, std::string, std::string, float, int)' does not match any in class 'DataMaterialu'|

D:\cplusplus\E­VID\DataMateri­alu.h|6|error: candidates are: DataMaterialu::Da­taMaterialu(con­st DataMaterialu&)|

D:\cplusplus\E­VID\DataMateri­alu.h|8|error: DataMaterialu::Da­taMaterialu()|

Neskompiluje sa to.
Dávam to sem len pre objasnenie. Momentálne nebudem kódiť v c++.

Odpovědět 22.5.2015 7:16
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Odpovídá na Jan Vargovský
Libor Šimo (libcosenior):

Trieda Položka obsahuje všetky položky materiálu a má za úlohu možnosť nastaviť položky v konstruktore, vypísať všetky položky jedným príkazom, gettery a settery sú na vyhladávanie a zmenu, ale samotné vyhladávanie sa už má riešiť v triede DataMateriálu, kde má byť pole položiek a k tomu potrebné metódy: vyhladanie podľa jednotlivých položiek, výpis požadovaných položiek ...

Odpovědět 22.5.2015 7:21
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Samuel Bachar:

Tak som nakonci 5 dielu :) ... do whilu som dal tiež podmienku nad 5 útočí Jozef ...pod 5 útočí Fero .

 
Odpovědět 9.10.2015 1:25
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 25. Zobrazit vše