Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 7 - Aréna s bojovníky v C++

V minulé lekci, Bojovník do arény - Zapouzdření, jsme si vytvořili třídu bojovníka.

Hrací kostku máme hotovou z prvních lekcí objektově orientovaného programování. Dnes dáme vše dohromady a vytvoříme funkční arénu. C++ tutoriál bude spíše oddechový a pomůže nám zopakovat si práci s objekty a algoritmické myšlení.

Potřebujeme napsat nějaký kód pro obsluhu bojovníků a výpis zpráv uživateli. Nejdříve si vyřešíme, kde vlastně budou bojovníci. V našem případě by bylo vhodné dát je do třídy Hrac. Pro začátek předpokládejme, že každý hráč má pouze jednoho 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;
}

Nyní se podíváme na arénu. Určitě budeme chtít, aby simulovala souboj bojovníků. Pro zjednodušení budeme pracovat s tím, že útočník zaútočí na náhodného jiného bojovníka. Na začátku jsme si také řekli, že se bude jednat o tahovou hru - potřebujeme si pamatovat počet provedených tahů. Dále budeme chtít vypsat informace o bojovnících a útoku. Všechny tyto metody bude potřebovat třída Arena. Nejdříve si ve třídě Arena vytvoříme atribut typu int, pojmenujeme jej tah a v konstruktoru mu nastavíme hodnotu 1. Poté si napíšeme metodou pro výpis informací o aréně - 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 zobrazovat pouze graficky a to procentuálně. Proto si nejdříve vypočítáme, kolik procent života bojovník má (proměnná pocet_zivotu_procent) a poté iterujeme po 10-ti procentech a vypisujeme # (pokud bojovník životy má) nebo mezeru (pokud životy ztratil) - to zajišťuje ternární operátor ve výpisu.

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

Nyní se přesuneme na metodu zapas() - hlavní smyčku naší hry. Metoda zapas() nebude mít žádné parametry a nebude ani nic vracet. Uvnitř bude cyklus, který bude na střídačku volat útoky bojovníků a vypisovat informační obrazovku a zprávy. Nejdříve si uděláme pomocné metody (budou privátní), které později 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;
}

Metoda nám zjistí, kolik bojovníků přežilo. Pokud bude živý jenom jeden z nich, potom je vítěz a hra může skončit. A teď ke slibované metodě 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 jsem se snažil popsat v komentářích, proto je nemá smysl popisovat znovu v textu. Nejproblematičtější je zřejmě spočtení indexu hráče, na kterého budeme útočit. Začneme na bojovníkovi na vyšším indexu než je aktuální bojovník (aby hráč neútočil sám na sebe). Poté zjišťujeme, jestli je bojovník živý. Pokud není, pak se posuneme na dalšího bojovníka v pořadí. Co ale dělat, když dojdeme na konec pole? Chtěli bychom se vrátit zpět na počátek (index 0) - to nám zajistí modulo. Pokud se dostaneme na hodnotu 3 (a 3 je počet hráčů), potom modulo automaticky index sníží na 0.

Teď již to stačí jen celé spustit:

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

Pokud jste program zkusili spustit, bude běžet v nekonečném cyklu, ale nic se nebude měnit. Jak již bylo řečeno, při volání metody se parametry a návratová hodnota překopírují. To je velice důležité. V metodě hráče getBojovnik() vracíme typ Bojovnik. To ale znamená, že volající program nedostane našeho skutečného bojovníka, ale pouze jeho kopii. Opravíme to tak, že změníme návratovou hodnotu na referenci nebo ukazatel - v našem případě poslouží lépe reference.

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

Nyní by měl program fungovat podle našich představ.

Konzolová aplikace
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žná bychom chtěli při útoku zobrazit, kolik životů byla protihráči ubráno. To provedeme jednoduše, z metody Bojovnik.utoc() si můžeme vrátit zranění, které bylo způsobeno. Dále bychom na konci programu chtěli zobrazit informace o vítězi. Pro provedeme opět jednoduše, přidáme si do třídy Arena další metodu, která vypíše informace o vítězi. Jak je vidět, OOP přístup je velice praktický a lehce se rozšiřuje.

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

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

Tím máme (alespoň ze základu) hotovou naši arénu.

Příště, v lekci Konstantní metody v C++, se podíváme na konstantní metody v C++.


 

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

 

Předchozí článek
Bojovník do arény - Zapouzdření
Všechny články v sekci
Objektově orientované programování v C++
Přeskočit článek
(nedoporučujeme)
Konstantní metody v C++
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
25 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity