В этой главе мы рассмотрели наследование в языке C++. Пора закрепить пройденный материал.
Теория
Наследование позволяет моделировать отношения типа «является» между двумя объектами. Объект, который наследует, называется дочерним классом. Объект, которого наследуют, называется родительским классом.
При наследовании дочерний класс наследует все члены родительского класса.
При инициализации объектов дочернего класса, сначала выполняется построение родительской части объекта, а затем уже дочерней части объекта. Рассмотрим детально:
Сначала выделяется память для объекта дочернего класса (достаточная порция для двух частей, из которых состоит объект: родительская и дочерняя).
Вызывается соответствующий конструктор дочернего класса.
Выполняется построение родительской части с использованием соответствующего конструктора родительского класса. Если конструктор не указан, то используется конструктор по умолчанию родительского класса.
Список инициализации дочернего класса инициализирует члены дочернего класса.
Выполняется тело конструктора дочернего класса.
Управление возвращается обратно в 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; } |
Не понял только, зачем нам условие победы при достижении 20 лвл, если монстров всего 10, а значит макс лвл 11
Задание 3. Отличное задание на закрепление! Спасибо Юрий!
Я не сразу понял логику игры, поэтому пришлось немного повозиться…
Предположил что количество монстров фиксированное, поэтому пришлось организовать сначала масив монстров, а потом и контейнерный класс для хранения всех монстров, конкретно их здоровья. 🙂
Вот что в итоге вышло. Коенчно игроку пришлось добавить здоровья. И есть много других идей на развитие логики, ну да это может быть позже…
Задание 3. Не сразу понял логику игры. Поэтому получилось немного по другому, но зато пришлось повторить/закрепить прошедший материал.
Решил что в каждой битве игрок имеет только одну битву с монстром, который потом убегает и в следующей битве игрок сталкивается снова со случайным монстром.
В связи с этим, пришлось создать сначала просто массив, а потом контейнерный класс для хранения монстров и их здоровья.
Заодно пришлось поработать с указателями и ссылками, что вышло довольно головняково и возможно несколько запутанно.
Итак:
Разве в задании 2.b не лучше было переопределить, а точнее перегрузить метод (поскольку переопределение методов дочерних классов это уже тема виртуальных функций) getName() для GrannySmith, чтобы тот возвращал "Granny Smith " + Apple::getName() ???
Ведь если класс Apple уже был написан и "протестирован", то безопаснее было бы не менять в нём ничего. Да, разумеется в этом задании масштабы не такие огромные, но всё же нужно приучивать себя писать безопасно (на работе точно не помешает).
сразу так и написал)
Добавил в код и прошёл игры со второго раза, так игра выглядит более логичной.
с кодом вс понятно, но как пройти эту ляцкую игру!)
попытки с 50й прошёл игру, давно так не нервничал, дарксолс нервно курит)
Давайте хвастаться кто до какого лвла дошел 😉
Я дошел до 11 с 4 попытки 😀
Прошёл с 7 попытки, 295 голды.
Прошёл с 6 попытки с 310 золота, видимо более 50 часов геймплея MGQ даром не прошли 🙂 Ох, пока делал эту игру, такая ностальгия напала, как будто делаю и прохожу свою версию "Monster Girl Quest" со слаймами, орками и драконами…
Этот вариант немного отличается от задания.
Здравствуйте ! Вопрос к автору. В какой версии MS Visual Studio компилируются примеры ? Поясню :
при компиляции в 2008 и 2012 версиях дают ошибку :
error C2470: Monster::monsterData: выглядит как определение функции, но без списка параметров; пропускается вероятное тело функции. И компилируются только так :
Без '=' получается ошибка. Это опечатка или новый стантарт инициализации ? Подобные вещи встречаличь и в предыдущих уроках. Желательно упоминать, для каких версий С++ написаны примеры.
Скорее всего у вас используется стандарт до С++11. Это значит что поддержки uniform-инициализации нету. Например: