У цьому розділі ми розглянули спадкування в мові C++. Пора закріпити пройдений матеріал.
Теорія
Спадкування дозволяє моделювати відносини типу «є» між двома об’єктами. Об’єкт, який наслідує, називається дочірнім класом. Об’єкт, якого наслідують, називається батьківським класом.
При спадкуванні дочірній клас наслідує всі члени батьківського класу.
При ініціалізації об’єктів дочірнього класу, спочатку виконується побудова батьківської частини об’єкта, а потім вже дочірньої частини об’єкта. Розглянемо детально:
Спочатку виділяється пам’ять для об’єкта дочірнього класу (достатня порція для 2 частин, з яких складається об’єкт: батьківська і дочірня).
Викликається відповідний конструктор дочірнього класу.
Виконується побудова батьківської частини з використанням відповідного конструктора батьківського класу. Якщо конструктор не вказано, то використовується конструктор за замовчуванням батьківського класу.
Список ініціалізації дочірнього класу ініціалізує члени дочірнього класу.
Виконується тіло конструктора дочірнього класу.
Управління повертається назад в caller.
Звільнення пам’яті (знищення) відбувається в порядку, протилежному побудові: від дочірніх до батьківських класів.
Мова C++ має 3 специфікатори доступу: public, private і protected. Специфікатор protected використовується для дозволу доступу до членів дружнім класам/функціям і дочірнім класам, всім іншим об’єктам — доступ закритий.
Є 3 типи спадкування: public, private і protected. Найбільш поширений тип спадкування — public.
Таблиця специфікаторів доступу і типів спадкування:
| Специфікатор доступу в батьківському класі | Специфікатор доступу при спадкуванні типу public в дочірньому класі | Специфікатор доступу при спадкуванні типу private в дочірньому класі | Специфікатор доступу при спадкуванні типу protected в дочірньому класі |
| public | public | private | protected |
| private | Недоступний | Недоступний | Недоступний |
| protected | protected | private | protected |
Дочірні класи можуть змінювати методи батьківського класу, додавати свій функціонал, змінювати специфікатор доступу успадкованих членів або навіть приховувати методи батьківського класу. Все це виконується в тілі дочірнього класу.
Множинне спадкування дозволяє дочірньому класу мати відразу кілька батьківських класів. Не рекомендується використовувати множинне спадкування, якщо є альтернативні рішення.
Тест
Завдання №1
Для кожної з наступних програм визначте результат виконання. Якщо програма не скомпілюється, то поясніть чому. Запускати код не потрібно, ви повинні визначити результат/помилки програм без допомоги компілятора.
a)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <iostream> class Parent { public: Parent() { std::cout << "Parent()\n"; } ~Parent() { std::cout << "~Parent()\n"; } }; class Child: public Parent { public: Child() { std::cout << "Child()\n"; } ~Child() { std::cout << "~Child()\n"; } }; int main() { Child ch; } |
Відповідь №1.a)
Спочатку ініціалізується батьківська частина об’єкту, а потім вже дочірня. Знищення відбувається в зворотному порядку.
Parent()
Child()
~Child()
~Parent()
b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include <iostream> class Parent { public: Parent() { std::cout << "Parent()\n"; } ~Parent() { std::cout << "~Parent()\n"; } }; class Child: public Parent { public: Child() { std::cout << "Child()\n"; } ~Child() { std::cout << "~Child()\n"; } }; int main() { Child ch; Parent p; } |
Підказка: Локальні змінні знищуються в порядку протилежному визначенню.
Відповідь №1.b)
Спочатку виконується побудова ch:
Parent()
Child()
Потім побудова p:
Parent()
Потім знищення p:
~Parent()
Потім знищення ch:
~Child()
~Parent()
c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> class Parent { private: int m_x; public: Parent(int x): m_x(x) { std::cout << "Parent()\n"; } ~Parent() { std::cout << "~Parent()\n"; } void print() { std::cout << "Parent: " << m_x << '\n'; } }; class Child: public Parent { public: Child(int y): Parent(y) { std::cout << "Child()\n"; } ~Child() { std::cout << "~Child()\n"; } void print() { std::cout << "Child: " << m_x << '\n'; } }; int main() { Child ch(7); ch.print(); } |
Відповідь №1.c)
Не скомпілюється. Метод Child::print() не має доступу до закритого члену m_x.
d)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> class Parent { protected: int m_x; public: Parent(int x): m_x(x) { std::cout << "Parent()\n"; } ~Parent() { std::cout << "~Parent()\n"; } void print() { std::cout << "Parent: " << m_x << '\n'; } }; class Child: public Parent { public: Child(int y): Parent(y) { std::cout << "Child()\n"; } ~Child() { std::cout << "~Child()\n"; } void print() { std::cout << "Child: " << m_x << '\n'; } }; int main() { Child ch(7); ch.print(); } |
Відповідь №1.d)
Результат:
Parent()
Child()
Child: 7
~Child()
~Parent()
e)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include <iostream> class Parent { protected: int m_x; public: Parent(int x): m_x(x) { std::cout << "Parent()\n"; } ~Parent() { std::cout << "~Parent()\n"; } void print() { std::cout << "Parent: " << m_x << '\n'; } }; class Child: public Parent { public: Child(int y): Parent(y) { std::cout << "Child()\n"; } ~Child() { std::cout << "~Child()\n"; } void print() { std::cout << "Child: " << m_x << '\n'; } }; class D2 : public Child { public: D2(int z): Child(z) { std::cout << "D2()\n"; } ~D2() { std::cout << "~D2()\n"; } // Зверніть увагу, тут немає методу print() }; int main() { D2 d(7); d.print(); } |
Відповідь №1.e)
Результат:
Parent()
Child()
D2()
Child: 7
~D2()
~Child()
~Parent()
Завдання №2
a) Створіть класи Apple і Banana, які наслідують клас Fruit. У класі Fruit є дві змінні-члени: name і color.
Наступний код:
|
1 2 3 4 5 6 7 8 9 10 |
int main() { Apple a("red"); Banana b; std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n"; std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n"; return 0; } |
Повинен видавати наступний результат:
My apple is red.
My banana is yellow.
Відповідь №2.a)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> #include <string> class Fruit { private: std::string m_name; std::string m_color; public: Fruit(std::string name, std::string color) : m_name(name), m_color(color) { } std::string getName() { return m_name; } std::string getColor() { return m_color; } }; class Apple: public Fruit { public: Apple(std::string color="red") : Fruit("apple", color) { } }; class Banana : public Fruit { public: Banana() : Fruit("banana", "yellow") { } }; int main() { Apple a("red"); Banana b; std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n"; std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n"; return 0; } |
b) Додайте новий клас GrannySmith, який наслідує клас Apple.
Наступний код:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
int main() { Apple a("red"); Banana b; GrannySmith c; std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n"; std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n"; std::cout << "My " << c.getName() << " is " << c.getColor() << ".\n"; return 0; } |
Повинен видавати наступний результат:
My apple is red.
My banana is yellow.
My Granny Smith apple is green.
Відповідь №2.b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include <iostream> #include <string> class Fruit { private: std::string m_name; std::string m_color; public: Fruit(std::string name, std::string color) : m_name(name), m_color(color) { } std::string getName() { return m_name; } std::string getColor() { return m_color; } }; class Apple: public Fruit { // Попередній конструктор, який ми використовували для Apple, мав фіксоване ім'я ("apple"). // Нам потрібен новий конструктор для GrannySmith, щоб мати можливість задавати ім'я для фрукта protected: Apple(std::string name, std::string color) : Fruit(name, color) { } public: Apple(std::string color="red") : Fruit("apple", color) { } }; class Banana : public Fruit { public: Banana() : Fruit("banana", "yellow") { } }; class GrannySmith : public Apple { public: GrannySmith() : Apple("Granny Smith apple", "green") { } }; int main() { Apple a("red"); Banana b; GrannySmith c; std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n"; std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n"; std::cout << "My " << c.getName() << " is " << c.getColor() << ".\n"; return 0; } |
Завдання №3
Найулюбленіше! Будемо створювати просту гру, в якій ви будете битися з монстрами. Ціль гри — зібрати максимум золота, перш ніж ви помрете або досягнете 20 рівня.
Гра складається з трьох класів: Creature, Player і Monster. Player і Monster наслідують клас Creature.
a) Спочатку створіть клас Creature з наступними членами:
ім’я (std::string);
символ (char);
кількість здоров’я (int);
кількість ушкодження, яке він наносить ворогу під час атаки (int);
кількість золота, яке він має (int).
Створіть повний набір геттерів (по одному на кожну змінну-член класу). Додайте ще три методи:
void reduceHealth(int) — зменшує здоров’я Creature на вказане цілочисельне значення;
bool isDead() — повертає true, якщо здоров’я Creature дорівнює 0 або менше;
void addGold(int) — додає золото Creature-у.
Наступний код:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <string> int main() { Creature o("orc", 'o', 4, 2, 10); o.addGold(5); o.reduceHealth(1); std::cout << "The " << o.getName() << " has " << o.getHealth() << " health and is carrying " << o.getGold() << " gold."; return 0; } |
Повинен видавати наступний результат:
The orc has 3 health and is carrying 15 gold.
Відповідь №3.a)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <string> class Creature { protected: std::string m_name; char m_symbol; int m_health; int m_damage; int m_gold; public: Creature(std::string name, char symbol, int health, int damage, int gold) : m_name(name), m_symbol(symbol), m_health(health), m_damage(damage), m_gold(gold) { } const std::string& getName() { return m_name; } char getSymbol() { return m_symbol; } int getHealth() { return m_health; } int getDamage() { return m_damage; } int getGold() { return m_gold; } void reduceHealth(int health) { m_health -= health; } bool isDead() { return m_health <= 0; } void addGold(int gold) { m_gold += gold; } }; int main() { Creature o("orc", 'o', 4, 2, 10); o.addGold(5); o.reduceHealth(1); std::cout << "The " << o.getName() << " has " << o.getHealth() << " health and is carrying " << o.getGold() << " gold."; return 0; } |
b) Тепер нам потрібно створити клас Player, який наслідує Creature. Player має:
змінну-член level, яка починається з 1;
ім’я (користувач вводить з клавіатури);
символ @;
10 очків здоров’я;
1 очко ушкодження (для початку);
0 золота.
Напишіть метод levelUp(), який збільшує рівень Player-а і його ушкодження на 1. Також напишіть геттер для члену level і метод hasWon(), який повертає true, якщо Player досяг 20 рівня.
Додайте в функцію main() код, який запитує у користувача його ім’я і виводить кількість його здоров’я і золота:
Enter your name: Anton
Welcome, Anton.
You have 10 health and are carrying 0 gold.
Відповідь №3.b)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#include <iostream> #include <string> class Creature { protected: std::string m_name; char m_symbol; int m_health; int m_damage; int m_gold; public: Creature(std::string name, char symbol, int health, int damage, int gold) : m_name(name), m_symbol(symbol), m_health(health), m_damage(damage), m_gold(gold) { } const std::string& getName() { return m_name; } char getSymbol() { return m_symbol; } int getHealth() { return m_health; } int getDamage() { return m_damage; } int getGold() { return m_gold; } void reduceHealth(int health) { m_health -= health; } bool isDead() { return m_health <= 0; } void addGold(int gold) { m_gold += gold; } }; class Player : public Creature { int m_level = 1; public: Player(std::string name) : Creature(name, '@', 10, 1, 0) { } void levelUp() { ++m_level; ++m_damage; } int getLevel() { return m_level; } bool hasWon() { return m_level >= 20; } }; int main() { std::cout << "Enter your name: "; std::string playerName; std::cin >> playerName; Player p(playerName); std::cout << "Welcome, " << p.getName() << ".\n"; std::cout << "You have " << p.getHealth() << " health and are carrying " << p.getGold() << " gold."; return 0; } |
c) Наступний клас Monster також наслідує Creature і у нього немає власних змінних-членів. Але є перерахування Type, яке містить 3 енумератори, вони позначають типи монстрів: DRAGON, ORC і SLIME (вам також потрібен додатковий енумератор MAX_TYPES).
Відповідь №3.c)
|
1 2 3 4 5 6 7 8 9 10 11 |
class Monster : public Creature { public: enum Type { DRAGON, ORC, SLIME, MAX_TYPES }; }; |
d) Кожен тип Монстра має своє ім’я, символ, певну кількість здоров’я, ушкодження і золота:
| Type | Name | Symbol | Health | Damage | Gold |
| DRAGON | dragon | D | 20 | 4 | 100 |
| ORC | orc | o | 4 | 2 | 25 |
| SLIME | slime | s | 1 | 1 | 10 |
Наступний крок — реалізація конструктора класу Monster, за допомогою якого можна створювати монстрів. Цей конструктор повинен приймати перерахування Type в якості параметру, а потім створювати монстра з відповідними таблиці характеристиками.
Це можна реалізувати по-різному. Однак, оскільки всі наші властивості типів монстрів визначені (не випадково), ми можемо використовувати таблицю пошуку. Таблиця пошуку — це масив, який містить усі визначені атрибути (властивості) будь-чого. Ми можемо використовувати таблицю пошуку для перегляду характеристик певного типу монстра по мірі необхідності.
Як це зробити? Нам потрібні всього лише дві речі. По-перше, масив з окремим елементом для кожного типу монстра. По-друге, цей елемент міститиме структуру, в якій знаходитимуться всі визначені значення атрибутів для конкретного типу монстра.
Крок №1: Створіть структуру MonsterData всередині класу Monster. Ця структура повинна мати наступні енумератори: name, symbol, health, damage і gold.
Крок №2: Оголосіть статичний масив цієї структури з іменем monsterData.
Крок №3: Додайте код визначення нашої таблиці пошуку поза тілом класу:
|
1 2 3 4 5 6 |
Monster::MonsterData Monster::monsterData[Monster::MAX_TYPES] { { "dragon", 'D', 20, 4, 100 }, { "orc", 'o', 4, 2, 25 }, { "slime", 's', 1, 1, 10 } }; |
Тепер ми можемо шукати будь-які значення, які нам потрібні! Наприклад, щоб дізнатися кількість золота Dragon, ми можемо використати monsterData[DRAGON].gold.
Використовуйте цю таблицю пошуку для реалізації вашого конструктора:
|
1 |
Monster(Type type): Creature(monsterData[type].name, ...) |
Наступний код:
|
1 2 3 4 5 6 7 8 |
#include <iostream> #include <string> int main() { Monster m(Monster::ORC); std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n"; } |
Повинен видавати наступний результат:
A orc (o) was created.
Відповідь №3.d)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
#include <iostream> #include <string> class Creature { protected: std::string m_name; char m_symbol; int m_health; int m_damage; int m_gold; public: Creature(std::string name, char symbol, int health, int damage, int gold) : m_name(name), m_symbol(symbol), m_health(health), m_damage(damage), m_gold(gold) { } const std::string& getName() { return m_name; } char getSymbol() { return m_symbol; } int getHealth() { return m_health; } int getDamage() { return m_damage; } int getGold() { return m_gold; } void reduceHealth(int health) { m_health -= health; } bool isDead() { return m_health <= 0; } void addGold(int gold) { m_gold += gold; } }; class Player : public Creature { int m_level = 1; public: Player(std::string name) : Creature(name, '@', 10, 1, 0) { } void levelUp() { ++m_level; ++m_damage; } int getLevel() { return m_level; } }; class Monster : public Creature { public: enum Type { DRAGON, ORC, SLIME, MAX_TYPES }; struct MonsterData { const char* name; char symbol; int health; int damage; int gold; }; static MonsterData monsterData[MAX_TYPES]; Monster(Type type) : Creature(monsterData[type].name, monsterData[type].symbol, monsterData[type].health, monsterData[type].damage, monsterData[type].gold) { } }; Monster::MonsterData Monster::monsterData[Monster::MAX_TYPES] { { "dragon", 'D', 20, 4, 100 }, { "orc", 'o', 4, 2, 25 }, { "slime", 's', 1, 1, 10 } }; int main() { Monster m(Monster::ORC); std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n"; return 0; } |
e) Нарешті, додайте статичний метод getRandomMonster() в клас Monster. Цей метод повинен генерувати випадкове число від 0 до MAX_TYPES-1 і повертати (повернення по значенню) певний тип монстра (вам потрібно використати оператор static_cast для конвертації int в Type, щоб передати його конструктору класу Monster).
Ви можете використовувати наступний код для генерації випадкового числа:
|
1 2 3 4 5 6 7 8 9 10 |
#include <cstdlib> // для rand() і srand() #include <ctime> // для time() // Генеруємо рандомне число між min і max int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); // Рівномірно розподіляємо рандомне число в нашому діапазоні return static_cast<int>(rand() * fraction * (max - min + 1) + min); } |
Наступний код:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> #include <string> #include <cstdlib> // для rand() и srand() #include <ctime> // для time() int main() { srand(static_cast<unsigned int>(time(0))); // встановлюємо значення системного годинника в якості стартового значення rand(); // скидаємо перший результат for (int i = 0; i < 10; ++i) { Monster m = Monster::getRandomMonster(); std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n"; } return 0; } |
Повинен згенерувати 10 рандомних монстрів:
A slime (s) was created.
A orc (o) was created.
A slime (s) was created.
A slime (s) was created.
A orc (o) was created.
A orc (o) was created.
A dragon (D) was created.
A slime (s) was created.
A orc (o) was created.
A orc (o) was created.
Відповідь №3.e)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
#include <iostream> #include <string> #include <cstdlib> // для rand() і srand() #include <ctime> // для time() // Генеруємо рандомне число між min і max int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); return static_cast<int>(rand() * fraction * (max - min + 1) + min); } class Creature { protected: std::string m_name; char m_symbol; int m_health; int m_damage; int m_gold; public: Creature(std::string name, char symbol, int health, int damage, int gold) : m_name(name), m_symbol(symbol), m_health(health), m_damage(damage), m_gold(gold) { } char getSymbol() { return m_symbol; } const std::string& getName() { return m_name; } bool isDead() { return m_health <= 0; } int getGold() { return m_gold; } void addGold(int gold) { m_gold += gold; } void reduceHealth(int health) { m_health -= health; } int getHealth() { return m_health; } int getDamage() { return m_damage; } }; class Player : public Creature { int m_level = 1; public: Player(std::string name) : Creature(name, '@', 10, 1, 0) { } void levelUp() { ++m_level; ++m_damage; } int getLevel() { return m_level; } bool hasWon() { return m_level >= 20; } }; class Monster : public Creature { public: enum Type { DRAGON, ORC, SLIME, MAX_TYPES }; struct MonsterData { const char* name; char symbol; int health; int damage; int gold; }; static MonsterData monsterData[MAX_TYPES]; Monster(Type type) : Creature(monsterData[type].name, monsterData[type].symbol, monsterData[type].health, monsterData[type].damage, monsterData[type].gold) { } static Monster getRandomMonster() { int num = getRandomNumber(0, MAX_TYPES - 1); return Monster(static_cast<Type>(num)); } }; Monster::MonsterData Monster::monsterData[Monster::MAX_TYPES] { { "dragon", 'D', 20, 4, 100 }, { "orc", 'o', 4, 2, 25 }, { "slime", 's', 1, 1, 10 } }; int main() { srand(static_cast<unsigned int>(time(0))); // встановлюємо значення системного годинника в якості стартового значення rand(); // скидаємо перший результат for (int i = 0; i < 10; ++i) { Monster m = Monster::getRandomMonster(); std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n"; } return 0; } |
f) Готово, тепер нам потрібно розібратися з логікою нашої гри!
Суть:
Гравець стикається з одним випадково вибраним монстром.
З кожним монстром гравець може або (R)un, або (F)ight.
Якщо гравець вирішує Run, то шанси на вдалу втечу складають 50%.
Якщо гравцеві вдається втекти, то він благополучно переходить до наступного монстру (його здоров’я/ушкодження/золото при цьому не зменшується).
Якщо гравцеві не вдається втекти, то монстр його атакує. Здоров’я гравця зменшується від ушкодження монстром. Потім гравець вибирає свою наступну дію.
Якщо гравець вибирає Fight, то він атакує монстра. Здоров’я монстра зменшується від ушкодження гравцем.
Якщо монстр вмирає, то гравець забирає все золото монстра + збільшує свій level і ушкодження на 1.
Якщо монстр не вмирає, то він атакує гравця. Здоров’я гравця зменшується від ушкодження монстром.
Гра закінчується, якщо гравець загинув (програш) або досяг 20 рівня (виграш).
Якщо гравець вмирає, то програма повинна повідомити гравцеві, який рівень у нього був і скільки золота він мав.
Якщо гравець перемагає, то гра повинна повідомити гравцеві, що він виграв і скільки у нього є золота.
Приклад гри:
Enter your name: Anton
Welcome, Anton
You have encountered a orc (o).
(R)un or (F)ight: r
You successfully fled.
You have encountered a slime (s).
(R)un or (F)ight: f
You hit the slime for 1 damage.
You killed the slime.
You are now level 2.
You found 10 gold.
You have encountered a dragon (D).
(R)un or (F)ight: f
You hit the dragon for 2 damage.
The dragon hit you for 4 damage.
(R)un or (F)ight: f
You hit the dragon for 2 damage.
The dragon hit you for 4 damage.
(R)un or (F)ight: f
You hit the dragon for 2 damage.
The dragon hit you for 4 damage.
You died at level 2 and with 10 gold.
Too bad you can't take it with you!
Підказка: У вас повинні бути наступні 4 функції:
Функція створення Гравця і основний ігровий цикл (в функції main()).
Функція fightMonster(), яка обробляє бій між Гравцем і Монстром, і запитує у гравця, що він хоче зробити: Run чи Fight.
Функція attackMonster(), яка обробляє атаку монстра гравцем, включаючи збільшення рівня гравця.
Функція attackPlayer(), яка обробляє атаку гравця монстром.
Відповідь №3.f)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
#include <iostream> #include <string> #include <cstdlib> // для rand() і srand() #include <ctime> // для time() // Генеруємо рандомне число між min і max int getRandomNumber(int min, int max) { static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0); return static_cast<int>(rand() * fraction * (max - min + 1) + min); } class Creature { protected: std::string m_name; char m_symbol; int m_health; int m_damage; int m_gold; public: Creature(std::string name, char symbol, int health, int damage, int gold) : m_name(name), m_symbol(symbol), m_health(health), m_damage(damage), m_gold(gold) { } char getSymbol() { return m_symbol; } const std::string& getName() { return m_name; } bool isDead() { return m_health <= 0; } int getGold() { return m_gold; } void addGold(int gold) { m_gold += gold; } void reduceHealth(int health) { m_health -= health; } int getHealth() { return m_health; } int getDamage() { return m_damage; } }; class Player : public Creature { int m_level = 1; public: Player(std::string name) : Creature(name, '@', 10, 1, 0) { } void levelUp() { ++m_level; ++m_damage; } int getLevel() { return m_level; } bool hasWon() { return m_level >= 20; } }; class Monster : public Creature { public: enum Type { DRAGON, ORC, SLIME, MAX_TYPES }; struct MonsterData { const char* name; char symbol; int health; int damage; int gold; }; static MonsterData monsterData[MAX_TYPES]; Type m_type; Monster(Type type) : Creature(monsterData[type].name, monsterData[type].symbol, monsterData[type].health, monsterData[type].damage, monsterData[type].gold), m_type(type) { } static Monster getRandomMonster() { int num = getRandomNumber(0, MAX_TYPES - 1); return Monster(static_cast<Type>(num)); } }; Monster::MonsterData Monster::monsterData[Monster::MAX_TYPES] { { "dragon", 'D', 20, 4, 100 }, { "orc", 'o', 6, 2, 25 }, { "slime", 's', 1, 1, 10 } }; // Цей метод обробляє атаку монстра гравцем void attackMonster(Player &p, Monster &m) { // Якщо гравець мертвий, то він не може атакувати монстра if (p.isDead()) return; std::cout << "You hit the " << m.getName() << " for " << p.getDamage() << " damage.\n"; // Здоров'я монстра зменшується від ушкодження гравця m.reduceHealth(p.getDamage()); // Якщо монстр мертвий, то збільшуємо level гравця if (m.isDead()) { std::cout << "You killed the " << m.getName() << ".\n"; p.levelUp(); std::cout << "You are now level " << p.getLevel() << ".\n"; std::cout << "You found " << m.getGold() << " gold.\n"; p.addGold(m.getGold()); } } // Цей метод обробляє атаку гравця монстром void attackPlayer(Monster &m, Player &p) { // Якщо монстр мертвий, то він не може атакувати гравця if (m.isDead()) return; // Здоров'я гравця зменшується від ушкодження монстра p.reduceHealth(m.getDamage()); std::cout << "The " << m.getName() << " hit you for " << m.getDamage() << " damage.\n"; } // Цей метод обробляє весь бій між гравцем і рандомним монстром void fightMonster(Player &p) { // Спочатку генеруємо рандомного монстра Monster m = Monster::getRandomMonster(); std::cout << "You have encountered a " << m.getName() << " (" << m.getSymbol() << ").\n"; // Поки монстр або гравець не мертвий, то бій продовжується while (!m.isDead() && !p.isDead()) { std::cout << "(R)un or (F)ight: "; char input; std::cin >> input; if (input == 'R' || input == 'r') { // 50/50 шанс вдалої втечі if (getRandomNumber(1, 2) == 1) { std::cout << "You successfully fled.\n"; return; // зустрічі з монстром вдалося уникнути } else { // Невдала спроба втечі дає монстру право атакувати std::cout << "You failed to flee.\n"; attackPlayer(m, p); continue; } } if (input == 'F' || input == 'f') { // Спочатку атакує гравець, потім монстр attackMonster(p, m); attackPlayer(m, p); } } } int main() { srand(static_cast<unsigned int>(time(0))); // встановлюємо значення системного годинника в якості стартового значення rand(); // скидаємо перший результат std::cout << "Enter your name: "; std::string playerName; std::cin >> playerName; Player p(playerName); std::cout << "Welcome, " << p.getName() << '\n'; // Якщо гравець не мертвий і ще не виграв, то гра продовжується while (!p.isDead() && !p.hasWon()) fightMonster(p); // До цього моменту гравець або мертвий, або виграв if (p.isDead()) { std::cout << "You died at level " << p.getLevel() << " and with " << p.getGold() << " gold.\n"; std::cout << "Too bad you can't take it with you!\n"; } else { std::cout << "You won the game with " << p.getGold() << " gold!\n"; } return 0; } |
