IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

7. diel - Aréna s bojovníkmi v C ++

V minulej lekcii, Bojovník do arény - Zapuzdrenie , sme si vytvorili triedu bojovníka. Hracie kocku máme hotovú z prvých lekcií objektovo orientovaného programovania. Dnes dáme všetko dokopy a vytvoríme funkčné arénu. C ++ tutoriál bude skôr oddychový a pomôže nám zopakovať si prácu s objektmi a algoritmické myslenie.

Potrebujeme napísať nejaký kód pre obsluhu bojovníkov a výpis správ užívateľmi. Najskôr si vyriešime, kde vlastne budú bojovníci. V našom prípade by bolo vhodné dať ich do triedy Hrac. Pre začiatok predpokladajme, že každý hráč má iba jedného bojovníka.

Hrac.h

#include <string>
#include "Kostka.h"
#include "Bojovnik.h"
using namespace std;

class Hrac
{
private:
    Bojovnik bojovnik;
    string jmeno;
public:
    Hrac(string jmeno, Kostka &kostka);
    string getJmeno();
    Bojovnik& getBojovnik();
};

Hrac.cpp

#include "Hrac.h"
Hrac::Hrac(string jmeno, Kostka &kostka): bojovnik(100,8,5,kostka)
{
    this->jmeno = jmeno;
}
string Hrac::getJmeno()
{
    return this->jmeno;
}
Bojovnik Hrac::getBojovnik()
{
    return this->bojovnik;
}

Teraz sa pozrieme na arénu. Určite budeme chcieť, aby simulovala súboj bojovníkov. Pre zjednodušenie budeme pracovať s tým, že útočník zaútočí na náhodného iného bojovníka. Na začiatku sme si tiež povedali, že sa bude jednať o ťahovú hru - potrebujeme si pamätať počet vykonaných ťahov. Ďalej budeme chcieť vypísať informácie o bojovníkoch a útoku. Všetky tieto metódy bude potrebovať trieda Arena. Najskôr si v triede Arena vytvoríme atribút typu int, pomenujeme ho tah a v konstruktoru mu nastavíme hodnotu 1. Potom si napíšeme metódou pre výpis informácií o aréne - vypis().

void Arena::vypis()
{
    cout << "-------------- Arena --------------" << endl;
    cout << "Tah: " << this->tah << endl;
    cout << "Zdravi bojovniku:" << endl;
    for (int i = 0; i < this->pocet_hracu; i++)
    {
        cout << "\t" << this->hraci[i]->getJmeno() << ": "; // vypíšeme jméno bojovníka
        if (!this->hraci[i]->getBojovnik().nazivu()) // zjistíme, že bojovník ještě není mrtev
        {
            cout << "mrtev" << endl;  // pokud je bojovník mrtev tak o tom informujeme
            continue; // a pokračujeme na dalšího bojovníka
        }
        cout << "["; // začátek baru se životy
        // vypočítáme kolik procent života bojovník má
        float pocet_zivotu_procent = this->hraci[i]->getBojovnik().getZivot() / this->hraci[i]->getBojovnik().getMaxZivot();
        for (double z = 0; z < 1.0; z += 0.1)
            cout << (z < pocet_zivotu_procent ? '#' : ' '); // vypisujeme procenta života
        // ukončíme bar se životy a vypíšeme info o útoku a obraně
        cout << "] (utok: " << this->hraci[i]->getBojovnik().getUtok() << ", obrana: " << this->hraci[i]->getBojovnik().getObrana() << ")" << endl;
    }
}

Životy budeme zobrazovať iba graficky a to percentuálne. Preto si najskôr vypočítame, koľko percent života bojovník má (premenná pocet_zivotu_procent) a potom iterujeme po 10-tich percentách a vypisujeme # (ak bojovník životy má) alebo medzeru (ak životy stratil) - to zabezpečuje ternárne operátor vo výpise.

Konzolová aplikácia
-------------- Arena --------------
Tah: 1
Zdravi bojovniku:
        Karel: [##########] (utok: 8, obrana: 5)
        Pavel: [##########] (utok: 8, obrana: 5)
        Honza: [##########] (utok: 8, obrana: 5)

Teraz sa presunieme na metódu zapas() - hlavná slučku našej hry. Metóda zapas() nebude mať žiadne parametre a nebude ani nič vracať. Vnútri bude cyklus, ktorý bude na striedačku volať útoky bojovníkov a vypisovať informačnú obrazovku a správy. Najskôr si urobíme pomocné metódy (budú privátne), ktoré neskôr použijeme:

Arena.cpp

bool Arena::existujeVitez()
{
    return this->pocetZivych() == 1;
}

int Arena::pocetZivych()
{
    int zivych = 0;
    // pro každého živého hráče
    for (int i = 0; i < this->pocet_hracu; i++)
        if (this->hraci[i]->getBojovnik().nazivu())
            zivych++; // zvyš počet živých o jeden
    return zivych;
}

Metóda nám zistí, koľko bojovníkov prežilo. Pokiaľ bude živý iba jeden z nich, potom je víťaz a hra môže skončiť. A teraz ku sľubované metóde zapas().

Arena.cpp

void Arena::zapas()
{
    // dokud nezůstane pouze jeden hráč
    while (!this->existujeVitez())
    {
        this->vypis(); // vypíšeme informace o hráčích
        // zkontroluj všechny hráče
        for (int i = 0; i < this->pocet_hracu; i++)
        {
            // pokud není bojovník naživu, potom ho přeskoč
            if (!this->hraci[i]->getBojovnik().nazivu())
                continue;
            // mohlo se stát, že v předchozím kole někoho zabili a tak zůstal poslední bojovník
            // pokud se to stalo, potom hra končí
            if (this->existujeVitez())
                break;
            // spočtení index nejbližšího živého hráče, na kterého budeme útočit
            int utok_na = (i + 1) % this->pocet_hracu;
            while (!this->hraci[utok_na]->getBojovnik().nazivu())
                utok_na = (utok_na + 1) % this->pocet_hracu;
            // útok
            float zraneni = this->hraci[i]->getBojovnik().utoc(this->hraci[utok_na]->getBojovnik());
            // vypsání výsledku souboje
            cout << this->hraci[i]->getJmeno() << " utoci na "
                << this->hraci[utok_na]->getJmeno() << " za " << zraneni << " zraneni" << endl;
        }
        // přesuneme se do dalšího tahu
        this->tah++;
    }
}

Kroky som sa snažil popísať v komentároch, preto ich nemá zmysel popisovať znovu v texte. Najproblematic­kejšie je zrejme spočítané indexu hráča, na ktorého budeme útočiť. Začneme na bojovníkovi na vyššom indexe než je aktuálna bojovník (aby hráč neútočil sám na seba). Potom zisťujeme, či je bojovník živý. Ak nie je, potom sa posunieme na ďalšieho bojovníka v poradí. Čo ale robiť, keď dôjdeme na koniec poľa? Chceli by sme sa vrátiť späť na začiatok (index 0) - to nám zaistí modulo. Ak sa dostaneme na hodnotu 3 (a 3 je počet hráčov), potom modulo automaticky index zníži na 0.

Teraz už to stačí len celé spustiť:

Main.cpp

#include <iostream>
#include "Kostka.h"
#include "Arena.h"
#include "Bojovnik.h"
using namespace std;


int main()
{
    Kostka kostka;
    Arena arena(3, kostka);
    arena.zapas();
    arena.vypis();
    cin.get(); cin.get();
    return 0;
}

Ak ste program skúsili spustiť, bude bežať v nekonečnom cykle, ale nič sa nebude meniť. Ako už bolo povedané, pri volaní metódy sa parametrami a návratová hodnota prekopírujú. To je veľmi dôležité. V metóde hráčov getBojovnik() vraciame typ Bojovnik. To ale znamená, že volajúci program nedostane nášho skutočného bojovníka, ale iba jeho kópiu. Opravíme to tak, že zmeníme návratovú hodnotu na referenciu alebo ukazovateľ - v našom prípade poslúži lepšie referencie.

Hrac.h

class Hrac
{
private:
    Bojovnik bojovnik;
    string jmeno;
public:
    Hrac(string jmeno, Kostka &kostka);
    string getJmeno();
    Bojovnik& getBojovnik();
};

Hrac.cpp

Bojovnik& Hrac::getBojovnik()
{
    return this->bojovnik;
}

Teraz by mal program fungovať podľa našich predstáv.

Konzolová aplikácia
Zadejte jmeno hrace: Karel
Zadejte jmeno hrace: Pavel
Zadejte jmeno hrace: Honza
-------------- Arena --------------
Tah: 1
Zdravi bojovniku:
        Karel: [###########] (utok: 8, obrana: 5)
        Pavel: [###########] (utok: 8, obrana: 5)
        Honza: [###########] (utok: 8, obrana: 5)
Karel utoci na Pavel
Pavel utoci na Honza
Honza utoci na Karel
-------------- Arena --------------
Tah: 2
Zdravi bojovniku:
        Karel: [########## ] (utok: 8, obrana: 5)
        Pavel: [########## ] (utok: 8, obrana: 5)
        Honza: [########## ] (utok: 8, obrana: 5)
Karel utoci na Pavel
Pavel utoci na Honza
Honza utoci na Karel

Možno by sme chceli pri útoku zobraziť, koľko životov bola protihráči ubrané. To vykonáme jednoducho, z metódy Bojovnik.utoc() si môžeme vrátiť zranenia, ktoré bolo spôsobené. Ďalej by sme na konci programu chceli zobraziť informácie o víťazovi. Pre vykonáme opäť jednoducho, pridáme si do triedy Arena ďalšiu metódu, ktorá vypíše informácie o víťazovi. Ako je vidieť, OOP prístup je veľmi praktický a ľahko sa rozširuje.

int main()
{
    Kostka kostka;
    Arena arena(6, kostka);
    arena.zapas();
    arena.vypis();
    arena.vypisViteze();
    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();
    float utoc(Bojovnik &druhy);
    float getZivot();
    float getMaxZivot();
    float getUtok();
    float getObrana();
};
#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;
}

float 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;
    return zraneni;
}

float Bojovnik::getZivot()
{
    return this->zivot;
}

float Bojovnik::getMaxZivot()
{
    return this->max_zivot;
}

float Bojovnik::getUtok()
{
    return this->utok;
}

float Bojovnik::getObrana()
{
    return this->obrana;
}
#ifndef __HRAC__H_
#define __HRAC__H_
#include <string>
#include "Kostka.h"
#include "Bojovnik.h"
using namespace std;

class Hrac
{
private:
    Bojovnik bojovnik;
    string jmeno;
public:
    Hrac(string jmeno, Kostka &kostka);
    string getJmeno();
    Bojovnik& getBojovnik();
};

#endif
#include "Hrac.h"

Hrac::Hrac(string jmeno, Kostka &kostka): bojovnik(100,8,5,kostka)
{
    this->jmeno = jmeno;
}

string Hrac::getJmeno()
{
    return this->jmeno;
}

Bojovnik& Hrac::getBojovnik()
{
    return this->bojovnik;
}
#ifndef __ARENA_H_
#define __ARENA_H_
#include "Hrac.h"
#include "Kostka.h"
class Arena
{
private:
    Hrac** hraci;
    int pocet_hracu;
    int tah;
    bool existujeVitez();
    int pocetZivych();
public:
    Arena(int pocet_hracu, Kostka &kostka);
    ~Arena();
    void vypis();
    void vypisViteze();
    void zapas();
};
#endif
#include <iostream>
#include <cstdlib>
#include "Arena.h"

using namespace std;
bool Arena::existujeVitez()
{
    return this->pocetZivych() == 1;
}

int Arena::pocetZivych()
{
    int zivych = 0;
    // pro každého živého hráče
    for (int i = 0; i < this->pocet_hracu; i++)
        if (this->hraci[i]->getBojovnik().nazivu())
            zivych++; // zvyš počet živých o jeden
    return zivych;
}

Arena::Arena(int pocet_hracu, Kostka &kostka) : tah(1)
{
    this->pocet_hracu = pocet_hracu;
    this->hraci = new Hrac*[pocet_hracu];
        string jmena[] = {"Karel", "Pavel", "Honza", "Petr", "Josef", "Tomas"};
    for (int i = 0; i < pocet_hracu; i++)
    {
        cout << "Zadejte jmeno hrace: " << jmena[i] << endl;
        this->hraci[i] = new Hrac(jmena[i], kostka);
    }
}

Arena::~Arena()
{
    for (int i = 0; i < this->pocet_hracu; i++)
        delete this->hraci[i];
    delete[] this->hraci;
    this->hraci = NULL;
}

void Arena::vypis()
{
    cout << "-------------- Arena --------------" << endl;
    cout << "Tah: " << this->tah << endl;
    cout << "Zdravi bojovniku:" << endl;
    for (int i = 0; i < this->pocet_hracu; i++)
    {
        cout << "\t" << this->hraci[i]->getJmeno() << ": "; // vypíšeme jméno bojovníka
        if (!this->hraci[i]->getBojovnik().nazivu()) // zjistíme, že bojovník ještě není mrtev
        {
            cout << "mrtev" << endl;  // pokud je bojovník mrtev tak o tom informujeme
            continue; // a pokračujeme na dalšího bojovníka
        }
        cout << "["; // začátek baru se životy
        // vypočítáme kolik procent života bojovník má
        float pocet_zivotu_procent = this->hraci[i]->getBojovnik().getZivot() / this->hraci[i]->getBojovnik().getMaxZivot();
        for (double z = 0; z < 1.0; z += 0.1)
            cout << (z < pocet_zivotu_procent ? '#' : ' '); // vypisujeme procenta života
        // ukončíme bar se životy a vypíšeme info o útoku a obraně
        cout << "] (utok: " << this->hraci[i]->getBojovnik().getUtok() << ", obrana: " << this->hraci[i]->getBojovnik().getObrana()  << ")" << endl;
    }
}


void Arena::vypisViteze()
{
    if (!this->existujeVitez())
        return;

    for (int i = 0; i < this->pocet_hracu; i++)
        if (this->hraci[i]->getBojovnik().nazivu())
        {
            cout << endl << "-------------- VITEZ --------------" << endl;
            cout << "Vitezem se stal: " << this->hraci[i]->getJmeno() << " s " << this->hraci[i]->getBojovnik().getZivot() << " zivoty" << endl;
            return;
        }
}


void Arena::zapas()
{
    // dokud nezůstane pouze jeden hráč
    while (!this->existujeVitez())
    {
        this->vypis(); // vypíšeme informace o hráčích
        // zkontroluj všechny hráče
        for (int i = 0; i < this->pocet_hracu; i++)
        {
            // pokud není bojovník naživu, potom ho přeskoč
            if (!this->hraci[i]->getBojovnik().nazivu())
                continue;
            // mohlo se stát, že v předchozím kole někoho zabili a tak zůstal poslední bojovník
            // pokud se to stalo, potom hra končí
            if (this->existujeVitez())
                break;
            // spočtení index nejbližšího živého hráče, na kterého budeme útočit
            int utok_na = (i + 1) % this->pocet_hracu;
            while (!this->hraci[utok_na]->getBojovnik().nazivu())
                utok_na = (utok_na + 1) % this->pocet_hracu;
            // útok
            float zraneni = this->hraci[i]->getBojovnik().utoc(this->hraci[utok_na]->getBojovnik());
            // vypsání výsledku souboje
            cout << this->hraci[i]->getJmeno() << " utoci na "
                << this->hraci[utok_na]->getJmeno() << " za " << zraneni << " zraneni" << endl;
        }
        this->tah++;
    }
}

Gratulujem vám, ak ste sa dostali až sem a tutoriály naozaj čítali a pochopili, máte základy objektového programovania a dokážete tvoriť rozumné aplikácie :)

Tým máme (aspoň zo základu) hotovú našu arénu a nabudúce, v lekcii Konštantný metódy v C ++ , sa pozrieme na konštantnej metódy v C ++.


 

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

 

Predchádzajúci článok
Bojovník do arény - Zapuzdrenie
Všetky články v sekcii
Objektovo orientované programovanie v C ++
Preskočiť článok
(neodporúčame)
Konštantný metódy 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