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.
#include <iostream> #include "Kostka.h" #include "Arena.h" #include "Bojovnik.h" using namespace std; 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 137x (9.41 kB)
Aplikace je včetně zdrojových kódů v jazyce C++