12.x – Резюме к главе 12 и небольшой тест

Добавлено 14 июля 2021 в 22:29

В данной главе мы исследовали суть C++ – объектно-ориентированное программирование! Это самая важная глава в данной серии обучающих статей.

Краткое резюме

Классы позволяют создавать собственные типы данных, которые объединяют как данные, так и функции, которые работают с этими данными. Данные и функции внутри класса называются членами. Члены класса выбираются с помощью оператор . (точка) (или ->, если вы обращаетесь к члену через указатель).

Спецификаторы доступа позволяют указать, кто может получать доступ к членам класса. Доступ к открытым (public) членам может получить кто угодно. Доступ к закрытым (private) членам могут получить только другие члены класса. Защищенные (protected) члены мы рассмотрим позже, когда перейдем к наследованию. По умолчанию все члены классов являются закрытыми, а все члены структур являются открытыми.

Инкапсуляция – это процесс скрытия всех ваших членов данных, из-за чего к ним нельзя получить прямой доступ. Она помогает защитить ваш класс от неправильного использования.

Конструкторы – это специальный тип функций-членов, которые позволяют инициализировать объекты класса. Конструктор, который не принимает параметров (или имеет все параметры по умолчанию), называется конструктором по умолчанию. Конструктор по умолчанию используется, если пользователем не указаны значения инициализации. Для своих классов вы всегда должны предоставлять хотя бы один конструктор.

Списки инициализаторов элементов позволяют инициализировать переменные-члены из конструктора (вместо того, чтобы присваивать значения переменным-членам).

В C++11 инициализация нестатических членов позволяет напрямую указывать значения по умолчанию для переменных-членов при их объявлении.

До C++11 конструкторы не должны вызывать другие конструкторы (код будет компилироваться, но будет работать не так, как вы ожидаете). В C++11 конструкторам разрешено вызывать другие конструкторы (называемые делегирующими конструкторами или цепочкой конструкторов).

Деструкторы – это еще один тип специальных функций-членов, которые позволяют вашему классу выполнять очистку после себя. В них должны выполняться любые процедуры освобождения ресурсов или выключения.

Все функции-члены имеют скрытый указатель *this, который указывает на изменяемый объект класса. В большинстве случаев прямой доступ к этому указателю не требуется. Но вы можете его использовать, если нужно.

Хороший стиль программирования – помещать определения классов в заголовочный файл с тем же именем, что и класс, и определять функции класса в файле .cpp с тем же именем, что и класс. Это также помогает избежать циклических зависимостей.

Функции-члены можно (и нужно) делать константными, если они не изменяют состояние класса. Константные объекты класса могут вызывать только константные функции-члены.

Статические переменные-члены являются общими для всех объектов класса. Хотя к ним можно получить доступ через объект класса, к ним также можно получить доступ напрямую через оператор разрешения области видимости.

Точно так же статические функции-члены – это функции-члены, не имеющие указателя *this. Они могут получать доступ только к статическим переменным-членам.

Дружественные функции – это функции, которые обрабатываются как функции-члены класса (и, таким образом, могут напрямую обращаться к закрытым данным класса). Дружественные классы – это классы, в которых все члены класса считаются дружественными функциями.

Анонимные объекты класса можно создавать для вычисления в выражении или передачи или возврата значения.

Вы также можете делать типы вложенными в класс. Это часто используется с перечислениями, связанными с классом, но при желании может быть выполнено и с другими типами (включая другие классы).

Небольшой тест

Вопрос 1

a) Напишите класс с именем Point2d. Point2d должен содержать две переменные-члены типа double: m_x и m_y, обе по умолчанию равны 0.0. Предоставьте конструктор и функцию печати.

Должна запуститься следующая программа:

#include <iostream>
 
int main()
{
    Point2d first{};
    Point2d second{ 3.0, 4.0 };
    first.print();
    second.print();
 
    return 0;
}

Она должна напечатать:

Point2d(0, 0)
Point2d(3, 4)

#include <iostream>
 
class Point2d
{
private:
    double m_x{};
    double m_y{};
 
public:
    Point2d(double x = 0.0, double y = 0.0)
        : m_x{ x }, m_y{ y }
    {
    }
 
    void print() const
    {
        std::cout << "Point2d(" << m_x << ", " << m_y << ")\n";
    }
};
 
 
int main()
{
   Point2d first{};
   Point2d second{ 3.0, 4.0 };
   first.print();
   second.print();
 
    return 0;
}

b) Теперь добавьте функцию-член с именем distanceTo, которая принимает другой объект Point2d в качестве параметра и вычисляет расстояние между ними. Для двух точек (x1, y1) и (x2, y2) расстояние между ними можно рассчитать как std::sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)). Функция std::sqrt находится в заголовке cmath.

Должна запуститься следующая программа:

#include <iostream>
 
int main()
{
    Point2d first{};
    Point2d second{ 3.0, 4.0 };
    first.print();
    second.print();
    std::cout << "Distance between two points: "
              << first.distanceTo(second) << '\n';
 
    return 0;
}

Она должна напечатать:

Point2d(0, 0)
Point2d(3, 4)
Distance between two points: 5

#include <cmath>
#include <iostream>
 
class Point2d
{
private:
    double m_x{};
    double m_y{};
 
public:
    Point2d(double x = 0.0, double y = 0.0)
        : m_x{ x }, m_y{ y }
    {
    }
 
    void print() const
    {
        std::cout << "Point2d(" << m_x << " , " << m_y << ")\n";
    }
 
    double distanceTo(const Point2d &other) const
    {
        return std::sqrt((m_x - other.m_x)*(m_x - other.m_x) +
                         (m_y - other.m_y)*(m_y - other.m_y));
    }
};
 
int main()
{
    Point2d first{};
    Point2d second{ 3.0, 4.0 };
    first.print();
    second.print();
    std::cout << "Distance between two points: "
              << first.distanceTo(second) << '\n';
 
    return 0;
}

c) Измените функцию distanceTo с функции-члена на функцию, не являющуюся членом, которая принимает в качестве параметров два объекта Point. Также переименуйте ее в distanceFrom.

Должна запуститься следующая программа:

#include <iostream>
 
int main()
{
    Point2d first{};
    Point2d second{ 3.0, 4.0 };
    first.print();
    second.print();
    std::cout << "Distance between two points: "
              << distanceFrom(first, second) << '\n';
 
    return 0;
}

Она должна напечатать:

Point2d(0, 0)
Point2d(3, 4)
Distance between two points: 5

#include <cmath>
#include <iostream>
 
class Point2d
{
private:
    double m_x{};
    double m_y{};
 
public:
    Point2d(double x = 0.0, double y = 0.0)
        : m_x{ x }, m_y{ y }
    {
    }
 
    void print() const
    {
        std::cout << "Point2d(" << m_x << " , " << m_y << ")\n";
    }
 
    friend double distanceFrom(const Point2d &x, const Point2d &y);
 
};
 
double distanceFrom(const Point2d &x, const Point2d &y)
{
    return std::sqrt((x.m_x - y.m_x)*(x.m_x - y.m_x) +
                     (x.m_y - y.m_y)*(x.m_y - y.m_y));
}
 
int main()
{
    Point2d first{};
    Point2d second{ 3.0, 4.0 };
    first.print();
    second.print();
    std::cout << "Distance between two points: "
              << distanceFrom(first, second) << '\n';
 
    return 0;
}

Вопрос 2

Напишите деструктор для данного класса:

#include <iostream>
 
class HelloWorld
{
private:
    char *m_data{};
 
public:
    HelloWorld()
    {
        m_data = new char[14];
        const char *init{ "Hello, World!" };
        for (int i = 0; i < 14; ++i)
            m_data[i] = init[i];
    }
 
    ~HelloWorld()
    {
        // замените этот комментарий своей реализацией деструктора
    }
 
    void print() const
    {
        std::cout << m_data << '\n';
    }
 
};
 
int main()
{
    HelloWorld hello{};
    hello.print();
 
    return 0;
}

#include <iostream>
 
class HelloWorld
{
private:
    char *m_data{};
 
public:
    HelloWorld()
    {
        m_data = new char[14];
        const char *init{ "Hello, World!" };
        for (int i = 0; i < 14; ++i)
            m_data[i] = init[i];
    }
 
    ~HelloWorld()
    {
        delete[] m_data;
    }
 
    void print() const
    {
        std::cout << m_data << '\n';
    }
 
};
 
int main()
{
    HelloWorld hello{};
    hello.print();
 
    return 0;
}

Вопрос 3

Давайте создадим генератор случайных монстров. Это должно быть весело.

a) Во-первых, давайте создадим перечисление типов монстров с именем MonsterType. Включите следующие типы монстров: Дракон (Dragon), Гоблин (Goblin), Огр (Ogre), Орк (Orc), Скелет (Skeleton), Тролль (Troll), Вампир (Vampire) и Зомби (Zombie). Добавьте дополнительный перечислитель max_monster_types, чтобы мы могли подсчитать количество перечислителей.

enum class MonsterType
{
    dragon,
    goblin,
    ogre,
    orc,
    skeleton,
    troll,
    vampire,
    zombie,
    max_monster_types
};

b) Теперь давайте создадим класс Monster. У нашего монстра будет 4 атрибута (переменных-члена): тип (MonsterType), имя (std::string), рев (std::string) и количество очков жизни (int). Создайте класс Monster с этими 4 переменными-членами.

#include <string>
 
enum class MonsterType
{
    dragon,
    goblin,
    ogre,
    orc,
    skeleton,
    troll,
    vampire,
    zombie,
    max_monster_types
};
 
class Monster
{
private:
    MonsterType m_type{};
    std::string m_name{};
    std::string m_roar{};
    int m_hitPoints{};
};

c) перечисление MonsterType специфично для класса Monster, поэтому переместите это перечисление внутрь класса как открытое объявление. Когда перечисление находится внутри класса, слово "Monster" в "MonsterType" будет избыточным, его можно удалить.

#include <string>
 
class Monster
{
public:
    enum class Type
    {
        dragon,
        goblin,
        ogre,
        orc,
        skeleton,
        troll,
        vampire,
        zombie,
        max_monster_types
    };
 
private:
 
    Type m_type{};
    std::string m_name{};
    std::string m_roar{};
    int m_hitPoints{};
};

d) Создайте конструктор, который позволит вам инициализировать все переменные-члены.

Должна скомпилироваться следующая программа:

int main()
{
    Monster skeleton{ Monster::Type::skeleton, "Bones", "*rattle*", 4 };
 
    return 0;
}

#include <string>
 
class Monster
{
public:
    enum class Type
    {
        dragon,
        goblin,
        ogre,
        orc,
        skeleton,
        troll,
        vampire,
        zombie,
        max_monster_types
    };
 
private:
 
    Type m_type{};
    std::string m_name{};
    std::string m_roar{};
    int m_hitPoints{};
 
public:
    Monster(Type type, const std::string& name,
            const std::string& roar, int hitPoints)
        : m_type{ type }, m_name{ name },
          m_roar{ roar }, m_hitPoints{ hitPoints }
    {
 
    }
};
 
int main()
{
    Monster skeleton{ Monster::Type::skeleton, "Bones", "*rattle*", 4 };
 
    return 0;
}

e) Теперь мы хотим иметь возможность распечатать информацию о нашем монстре, чтобы можно было проверить ее правильность. Для этого нам нужно написать функцию, которая преобразует Monster::Type в строку. Напишите эту функцию (с именем getTypeString()), а также функцию-член print().

Следующая программа должна компилироваться:

int main()
{
    Monster skeleton{ Monster::Type::skeleton, "Bones", "*rattle*", 4 };
    skeleton.print();
 
    return 0;
}

и печатать:

Bones the skeleton has 4 hit points and says *rattle*

#include <iostream>
#include <string>
#include <string_view>
 
class Monster
{
public:
    enum class Type
    {
        dragon,
        goblin,
        ogre,
        orc,
        skeleton,
        troll,
        vampire,
        zombie,
        max_monster_types
    };
 
private:
 
    Type m_type{};
    std::string m_name{};
    std::string m_roar{};
    int m_hitPoints{};
 
public:
    Monster(Type type, const std::string& name,
            const std::string& roar, int hitPoints)
        : m_type{ type }, m_name{ name },
          m_roar{ roar }, m_hitPoints{ hitPoints }
    {
 
    }
 
    // Мы возвращаем строки, которые известны во время компиляции.
    // Возвращение std::string может привести к значительным затратам
    // во время выполнения.
    std::string_view getTypeString() const
    {
        switch (m_type)
        {
        case Type::dragon: return "dragon";
        case Type::goblin: return "goblin";
        case Type::ogre: return "ogre";
        case Type::orc: return "orc";
        case Type::skeleton: return "skeleton";
        case Type::troll: return "troll";
        case Type::vampire: return "vampire";
        case Type::zombie: return "zombie";
        default: return "???";
        }
    }
 
    void print() const
    {
        std::cout << m_name << " the " << getTypeString()
                  << " has " << m_hitPoints << " hit points and says "
                  << m_roar << '\n';
    }
};
 
int main()
{
    Monster skeleton{ Monster::Type::skeleton, "Bones", "*rattle*", 4 };
    skeleton.print();
 
    return 0;
}

f) Теперь мы можем создать генератор случайных монстров. Давайте посмотрим, как будет работать наш класс MonsterGenerator. В идеале мы попросим его дать нам монстра, и он создаст для нас случайного монстра. Нам не нужно более одного MonsterGenerator. Это хороший кандидат для статического класса (в котором все функции статические). Создайте статический класс MonsterGenerator. Создайте статическую функцию с именем generateMonster(). Она должна возвращать монстра. А пока сделайте так, чтобы она возвращала анонимный объект Monster(Monster::Type::skeleton, "Bones", "*rattle*", 4).

Следующая программа должна компилироваться:

int main()
{
    Monster m{ MonsterGenerator::generateMonster() };
    m.print();
 
    return 0;
}

и печатать:

Bones the skeleton has 4 hit points and says *rattle*

#include <iostream>
#include <string>
#include <string_view>
 
class Monster
{
public:
    enum class Type
    {
        dragon,
        goblin,
        ogre,
        orc,
        skeleton,
        troll,
        vampire,
        zombie,
        max_monster_types
    };
 
private:
 
    Type m_type{};
    std::string m_name{};
    std::string m_roar{};
    int m_hitPoints{};
 
public:
    Monster(Type type, const std::string& name,
            const std::string& roar, int hitPoints)
        : m_type{ type }, m_name{ name },
          m_roar{ roar }, m_hitPoints{ hitPoints }
    {
 
    }
 
    std::string_view getTypeString() const
    {
        switch (m_type)
        {
        case Type::dragon: return "dragon";
        case Type::goblin: return "goblin";
        case Type::ogre: return "ogre";
        case Type::orc: return "orc";
        case Type::skeleton: return "skeleton";
        case Type::troll: return "troll";
        case Type::vampire: return "vampire";
        case Type::zombie: return "zombie";
        default: return "???";
        }
    }
 
    void print() const
    {
        std::cout << m_name << " the " << getTypeString()
                  << " has " << m_hitPoints << " hit points and says "
                  << m_roar << '\n';
    }
};
 
class MonsterGenerator
{
public:
    static Monster generateMonster()
    {
        return { Monster::Type::skeleton, "Bones", "*rattle*", 4 };
    }
};
 
int main()
{
    Monster m{ MonsterGenerator::generateMonster() };
    m.print();
 
    return 0;
}

g) Теперь MonsterGenerator должен генерировать случайные атрибуты. Для этого нам понадобится следующая удобная функция:

// Генерируем случайное число от min до max (включительно)
// Предполагается, что srand() уже был вызван
int getRandomNumber(int min, int max)
{
    // static используется для повышения эффективности,
    // поэтому мы вычисляем это значение только один раз
    static constexpr double fraction{ 1.0 / (static_cast<double>(RAND_MAX) + 1.0) };
    // равномерно распределяем случайные числа по нашему диапазону
    return static_cast<int>(std::rand() * fraction * (max - min + 1) + min);
}

Однако, поскольку MonsterGenerator напрямую полагается на эту функцию, давайте поместим ее в класс как статическую функцию.

class MonsterGenerator
{
public:
 
    // Генерируем случайное число от min до max (включительно)
    // Предполагается, что std::srand() уже был вызван
    // Предполагает max - min <= RAND_MAX
    static int getRandomNumber(int min, int max)
    {
        // static используется для повышения эффективности, поэтому
        // мы вычисляем это значение только один раз
        static constexpr double fraction { 1.0 / (RAND_MAX + 1.0) };
        // равномерно распределяем случайные числа по нашему диапазону
        return min + static_cast<int>((max - min + 1) * (std::rand() * fraction));
    }
 
    static Monster generateMonster()
    {
        return { Monster::Type::skeleton, "Bones", "*rattle*", 4 };
    }
};

h) Теперь отредактируйте функцию generateMonster(), чтобы сгенерировать случайный Monster::Type (от 0 до Monster::Type::max_monster_types-1) и случайное количество очков жизни (от 1 до 100). Это должно быть довольно просто. Как только вы это сделаете, определите внутри этой функции два статических фиксированных массива размером 6 (с именами s_names и s_roars) и инициализируйте их: 6 – именами, и 6 – звуками на ваш выбор. Выберите случайное имя из этих массивов.

Следующая программа должна компилироваться:

#include <ctime>   // для time()
#include <cstdlib> // для rand() и srand()
 
int main()
{
    // устанавливаем начальное значение в значение системных часов
    std::srand(static_cast<unsigned int>(std::time(nullptr))); 
    std::rand(); // При использовании Visual Studio отбрасываем первое случайное значение
 
    Monster m{ MonsterGenerator::generateMonster() };
    m.print();
 
    return 0;
}

#include <array>
#include <ctime>   // для time()
#include <cstdlib> // для rand() и srand()
#include <iostream>
#include <string>
#include <string_view>
 
class Monster
{
public:
    enum class Type
    {
        dragon,
        goblin,
        ogre,
        orc,
        skeleton,
        troll,
        vampire,
        zombie,
        max_monster_types
    };
 
private:
 
    Type m_type{};
    std::string m_name{};
    std::string m_roar{};
    int m_hitPoints{};
 
public:
    Monster(Type type, const std::string& name,
           const std::string& roar, int hitPoints)
        : m_type{ type }, m_name{ name },
          m_roar{ roar }, m_hitPoints{ hitPoints }
    {
 
    }
 
    std::string_view getTypeString() const
    {
        switch (m_type)
        {
        case Type::dragon: return "dragon";
        case Type::goblin: return "goblin";
        case Type::ogre: return "ogre";
        case Type::orc: return "orc";
        case Type::skeleton: return "skeleton";
        case Type::troll: return "troll";
        case Type::vampire: return "vampire";
        case Type::zombie: return "zombie";
        default: return "???";
        }
    }
 
    void print() const
    {
        std::cout << m_name << " the " << getTypeString()
                  << " has " << m_hitPoints << " hit points and says "
                  << m_roar << '\n';
    }
};
 
class MonsterGenerator
{
public:
    // Генерируем случайное число от min до max (включительно)
    // Предполагается, что std::srand() уже был вызван
    // Предполагает max - min <= RAND_MAX
    static int getRandomNumber(int min, int max)
    {
        // static используется для повышения эффективности, поэтому
        // мы вычисляем это значение только один раз
        static constexpr double fraction { 1.0 / (RAND_MAX + 1.0) };
        // равномерно распределяем случайные числа по нашему диапазону
        return min + static_cast<int>((max - min + 1) * (std::rand() * fraction));
    }
 
    static Monster generateMonster()
    {
        auto type{ static_cast<Monster::Type>(getRandomNumber(0,
                                       static_cast<int>(Monster::Type::max_monster_types) - 1)) };
        int hitPoints{ getRandomNumber(1, 100) };
 
        // Если ваш компилятор не поддерживает C++17,
        // используйте std::array<const char*, 6>.
        static constexpr std::array s_names{ "Blarg", "Moog", "Pksh",
                                             "Tyrn", "Mort", "Hans" };
        static constexpr std::array s_roars{ "*ROAR*", "*peep*", "*squeal*",
                                             "*whine*", "*hum*", "*burp*"};
 
        // Без приведения компиляторы с высоким уровнем предупреждений жалуются
        // на неявное приведение целочисленного типа со знаком к беззнаковому типу.
        auto name{ s_names[static_cast<std::size_t>(getRandomNumber(0, s_names.size()-1))] };
        auto roar{ s_roars[static_cast<std::size_t>(getRandomNumber(0, s_roars.size()-1))] };
 
        return { type, name, roar, hitPoints };
    }
};
 
int main()
{
    // устанавливаем начальное значение в значение системных часов
    std::srand(static_cast<unsigned int>(std::time(nullptr)));
    std::rand(); // При использовании Visual Studio отбрасываем первое случайное значение
 
    Monster m{ MonsterGenerator::generateMonster() };
    m.print();
 
    return 0;
}

i) Почему мы объявили переменные s_names и s_roars статическими?

Создание статических s_names и s_roars приводит к их однократной инициализации. В противном случае они будут повторно инициализироваться каждый раз при вызове generateMonster().


Вопрос 4

Хорошо, время снова вернуться к картам. Это будет испытание. Давайте перепишем игру блэкджек, которую мы написали в предыдущей главе, используя классы! Вот полный код без классов:

#include <algorithm>
#include <array>
#include <cassert>
#include <ctime>
#include <iostream>
#include <random>
 
enum class CardSuit
{
  SUIT_CLUB,
  SUIT_DIAMOND,
  SUIT_HEART,
  SUIT_SPADE,
 
  MAX_SUITS
};
 
enum class CardRank
{
  RANK_2,
  RANK_3,
  RANK_4,
  RANK_5,
  RANK_6,
  RANK_7,
  RANK_8,
  RANK_9,
  RANK_10,
  RANK_JACK,
  RANK_QUEEN,
  RANK_KING,
  RANK_ACE,
 
  MAX_RANKS
};
 
struct Card
{
  CardRank rank{};
  CardSuit suit{};
};
 
struct Player
{
  int score{};
};
 
using deck_type = std::array<Card, 52>;
using index_type = deck_type::size_type;
 
// Максимальный счет до проигрыша.
constexpr int maximumScore{ 21 };
 
// Минимальный счет, который должен иметь дилер.
constexpr int minimumDealerScore{ 17 };
 
void printCard(const Card& card)
{
  switch (card.rank)
  {
  case CardRank::RANK_2:
    std::cout << '2';
    break;
  case CardRank::RANK_3:
    std::cout << '3';
    break;
  case CardRank::RANK_4:
    std::cout << '4';
    break;
  case CardRank::RANK_5:
    std::cout << '5';
    break;
  case CardRank::RANK_6:
    std::cout << '6';
    break;
  case CardRank::RANK_7:
    std::cout << '7';
    break;
  case CardRank::RANK_8:
    std::cout << '8';
    break;
  case CardRank::RANK_9:
    std::cout << '9';
    break;
  case CardRank::RANK_10:
    std::cout << 'T';
    break;
  case CardRank::RANK_JACK:
    std::cout << 'J';
    break;
  case CardRank::RANK_QUEEN:
    std::cout << 'Q';
    break;
  case CardRank::RANK_KING:
    std::cout << 'K';
    break;
  case CardRank::RANK_ACE:
    std::cout << 'A';
    break;
  default:
    std::cout << '?';
    break;
  }
 
  switch (card.suit)
  {
  case CardSuit::SUIT_CLUB:
    std::cout << 'C';
    break;
  case CardSuit::SUIT_DIAMOND:
    std::cout << 'D';
    break;
  case CardSuit::SUIT_HEART:
    std::cout << 'H';
    break;
  case CardSuit::SUIT_SPADE:
    std::cout << 'S';
    break;
  default:
    std::cout << '?';
    break;
  }
}
 
int getCardValue(const Card& card)
{
  if (card.rank <= CardRank::RANK_10)
  {
    return (static_cast<int>(card.rank) + 2);
  }
 
  switch (card.rank)
  {
  case CardRank::RANK_JACK:
  case CardRank::RANK_QUEEN:
  case CardRank::RANK_KING:
    return 10;
  case CardRank::RANK_ACE:
    return 11;
  default:
    assert(false && "should never happen");
    return 0;
  }
}
 
void printDeck(const deck_type& deck)
{
  for (const auto& card : deck)
  {
    printCard(card);
    std::cout << ' ';
  }
 
  std::cout << '\n';
}
 
deck_type createDeck()
{
  deck_type deck{};
 
  index_type card{ 0 };
 
  auto suits{ static_cast<index_type>(CardSuit::MAX_SUITS) };
  auto ranks{ static_cast<index_type>(CardRank::MAX_RANKS) };
 
  for (index_type suit{ 0 }; suit < suits; ++suit)
  {
    for (index_type rank{ 0 }; rank < ranks; ++rank)
    {
      deck[card].suit = static_cast<CardSuit>(suit);
      deck[card].rank = static_cast<CardRank>(rank);
      ++card;
    }
  }
 
  return deck;
}
 
void shuffleDeck(deck_type& deck)
{
  static std::mt19937 mt{ static_cast<std::mt19937::result_type>(std::time(nullptr)) };
 
  std::shuffle(deck.begin(), deck.end(), mt);
}
 
bool playerWantsHit()
{
  while (true)
  {
    std::cout << "(h) to hit, or (s) to stand: ";
 
    char ch{};
    std::cin >> ch;
 
    switch (ch)
    {
    case 'h':
      return true;
    case 's':
      return false;
    }
  }
}
 
// Возвращает true, если у игрока «перебор». В противном случае - false.
bool playerTurn(const deck_type& deck, index_type& nextCardIndex, Player& player)
{
  while (true)
  {
    std::cout << "You have: " << player.score << '\n';
 
    if (player.score > maximumScore)
    {
      return true;
    }
    else
    {
      if (playerWantsHit())
      {
        player.score += getCardValue(deck[nextCardIndex++]);
      }
      else
      {
        // У игрока нет перебора
        return false;
      }
    }
  }
}
 
// Возвращает true, если у дилера «перебор». В противном случае - false.
bool dealerTurn(const deck_type& deck, index_type& nextCardIndex, Player& dealer)
{
  while (dealer.score < minimumDealerScore)
  {
    dealer.score += getCardValue(deck[nextCardIndex++]);
  }
 
  return (dealer.score > maximumScore);
}
 
bool playBlackjack(const deck_type& deck)
{
  index_type nextCardIndex{ 0 };
 
  Player dealer{ getCardValue(deck[nextCardIndex++]) };
 
  std::cout << "The dealer is showing: " << dealer.score << '\n';
 
  Player player{ getCardValue(deck[nextCardIndex]) + getCardValue(deck[nextCardIndex + 1]) };
  nextCardIndex += 2;
 
  if (playerTurn(deck, nextCardIndex, player))
  {
    return false;
  }
 
  if (dealerTurn(deck, nextCardIndex, dealer))
  {
    return true;
  }
 
  return (player.score > dealer.score);
}
 
int main()
{
  auto deck{ createDeck() };
 
  shuffleDeck(deck);
 
  if (playBlackjack(deck))
  {
    std::cout << "You win!\n";
  }
  else
  {
    std::cout << "You lose!\n";
  }
 
  return 0;
}

Ух ты! С чего нам вообще начать? Не волнуйтесь, мы можем это сделать, но нам понадобится стратегия. Эта программа для блэкджека состоит из четырех частей: логика работы с картами, логика работы с колодой карт, логика раздачи карт из колоды и логика игры. Наша стратегия будет заключаться в том, чтобы поработать над каждой из этих частей отдельно, тестируя каждую часть с помощью небольшой тестовой программы. Таким образом, вместо того, чтобы пытаться переписать всю программу за раз, мы можем сделать это в 4 тестируемых частях.

Начните с копирования исходной программы в вашу среду IDE, а затем закомментируйте всё, кроме строк #include.

a) Начнем с того, что сделаем Card классом, а не структурой. Хорошей новостью является то, что класс Card очень похож на класс Monster из предыдущего вопроса теста. Во-первых, создайте закрытые члены, которые будут содержать ранг и масть (назовите их соответственно m_rank и m_suit). Во-вторых, создайте открытый конструктор для класса Card, чтобы мы могли инициализировать объекты Card. В-третьих, сделайте объекты класса создаваемыми по умолчанию, либо добавив конструктор по умолчанию, либо добавив аргументы по умолчанию в текущий конструктор. Наконец, переместите функции printCard() и getCardValue() внутрь класса как открытые члены (не забудьте сделать их константными!).

Напоминание


При использовании std::array (или std::vector), где элементы принадлежат типу класса, класс вашего элемента должен иметь конструктор по умолчанию, чтобы элементы можно было инициализировать в осмысленное состояние по умолчанию. Если вы его не предоставите, вы получите загадочную ошибку о попытке сослаться на удаленную функцию.

Должна компилироваться следующая тестовая программа:

#include <iostream>
 
// ...
 
int main()
{
  const Card cardQueenHearts{ CardRank::RANK_QUEEN, CardSuit::SUIT_HEART };
  cardQueenHearts.print();
  std::cout << " has the value " << cardQueenHearts.value() << '\n';
 
  return 0;
}

#include <cassert>
#include <iostream>
 
enum class CardSuit
{
  SUIT_CLUB,
  SUIT_DIAMOND,
  SUIT_HEART,
  SUIT_SPADE,
 
  MAX_SUITS
};
 
enum class CardRank
{
  RANK_2,
  RANK_3,
  RANK_4,
  RANK_5,
  RANK_6,
  RANK_7,
  RANK_8,
  RANK_9,
  RANK_10,
  RANK_JACK,
  RANK_QUEEN,
  RANK_KING,
  RANK_ACE,
 
  MAX_RANKS
};
 
class Card
{
private:
  CardRank m_rank{};
  CardSuit m_suit{};
 
public:
  void print() const
  {
    switch (m_rank)
    {
    case CardRank::RANK_2:
      std::cout << '2';
      break;
    case CardRank::RANK_3:
      std::cout << '3';
      break;
    case CardRank::RANK_4:
      std::cout << '4';
      break;
    case CardRank::RANK_5:
      std::cout << '5';
      break;
    case CardRank::RANK_6:
      std::cout << '6';
      break;
    case CardRank::RANK_7:
      std::cout << '7';
      break;
    case CardRank::RANK_8:
      std::cout << '8';
      break;
    case CardRank::RANK_9:
      std::cout << '9';
      break;
    case CardRank::RANK_10:
      std::cout << 'T';
      break;
    case CardRank::RANK_JACK:
      std::cout << 'J';
      break;
    case CardRank::RANK_QUEEN:
      std::cout << 'Q';
      break;
    case CardRank::RANK_KING:
      std::cout << 'K';
      break;
    case CardRank::RANK_ACE:
      std::cout << 'A';
      break;
    default:
      std::cout << '?';
      break;
    }
 
    switch (m_suit)
    {
    case CardSuit::SUIT_CLUB:
      std::cout << 'C';
      break;
    case CardSuit::SUIT_DIAMOND:
      std::cout << 'D';
      break;
    case CardSuit::SUIT_HEART:
      std::cout << 'H';
      break;
    case CardSuit::SUIT_SPADE:
      std::cout << 'S';
      break;
    default:
      std::cout << '?';
      break;
    }
  }
 
  int value() const
  {
    if (m_rank <= CardRank::RANK_10)
    {
      return (static_cast<int>(m_rank) + 2);
    }
 
    switch (m_rank)
    {
    case CardRank::RANK_JACK:
    case CardRank::RANK_QUEEN:
    case CardRank::RANK_KING:
      return 10;
    case CardRank::RANK_ACE:
      return 11;
    default:
      assert(false && "should never happen");
      return 0;
    }
  }
 
  Card() = default;
 
  Card(CardRank rank, CardSuit suit)
      : m_rank{ rank }, m_suit{ suit }
  {
  }
};
 
int main()
{
  const Card cardQueenHearts{ CardRank::RANK_QUEEN, CardSuit::SUIT_HEART };
  cardQueenHearts.print();
  std::cout << " has the value " << cardQueenHearts.value() << '\n';
 
  return 0;
}

b) Хорошо, теперь поработаем над классом Deck. Колода должна содержать 52 карты, поэтому используйте закрытый член std::array, чтобы создать фиксированный массив из 52 карт с именем m_deck. Во-вторых, создайте конструктор, который не принимает параметров и инициализирует m_deck по одному экземпляру каждой карты (измените код из исходной функции createDeck()). В-третьих, переместите printDeck в класс Deck как открытый член. В-четвертых, переместите shuffleDeck в класс как открытый член.

Самая сложная часть этого шага – инициализация колоды с использованием модифицированного кода из исходной функции createDeck(). Следующая подсказка показывает, как это сделать.

m_deck[card] = { static_cast<CardRank>(rank), static_cast<CardSuit>(suit) };

Должна компилироваться следующая тестовая программа:

// ...
 
int main()
{
  Deck deck{};
  deck.print();
  deck.shuffle();
  deck.print();
 
  return 0;
}

#include <array>
#include <cassert>
#include <ctime>
#include <iostream>
#include <random>
 
enum class CardSuit
{
  SUIT_CLUB,
  SUIT_DIAMOND,
  SUIT_HEART,
  SUIT_SPADE,
 
  MAX_SUITS
};
 
enum class CardRank
{
  RANK_2,
  RANK_3,
  RANK_4,
  RANK_5,
  RANK_6,
  RANK_7,
  RANK_8,
  RANK_9,
  RANK_10,
  RANK_JACK,
  RANK_QUEEN,
  RANK_KING,
  RANK_ACE,
 
  MAX_RANKS
};
 
class Card
{
private:
  CardRank m_rank{};
  CardSuit m_suit{};
 
public:
  void print() const
  {
    switch (m_rank)
    {
    case CardRank::RANK_2:
      std::cout << '2';
      break;
    case CardRank::RANK_3:
      std::cout << '3';
      break;
    case CardRank::RANK_4:
      std::cout << '4';
      break;
    case CardRank::RANK_5:
      std::cout << '5';
      break;
    case CardRank::RANK_6:
      std::cout << '6';
      break;
    case CardRank::RANK_7:
      std::cout << '7';
      break;
    case CardRank::RANK_8:
      std::cout << '8';
      break;
    case CardRank::RANK_9:
      std::cout << '9';
      break;
    case CardRank::RANK_10:
      std::cout << 'T';
      break;
    case CardRank::RANK_JACK:
      std::cout << 'J';
      break;
    case CardRank::RANK_QUEEN:
      std::cout << 'Q';
      break;
    case CardRank::RANK_KING:
      std::cout << 'K';
      break;
    case CardRank::RANK_ACE:
      std::cout << 'A';
      break;
    default:
      std::cout << '?';
      break;
    }
 
    switch (m_suit)
    {
    case CardSuit::SUIT_CLUB:
      std::cout << 'C';
      break;
    case CardSuit::SUIT_DIAMOND:
      std::cout << 'D';
      break;
    case CardSuit::SUIT_HEART:
      std::cout << 'H';
      break;
    case CardSuit::SUIT_SPADE:
      std::cout << 'S';
      break;
    default:
      std::cout << '?';
      break;
    }
  }
 
  int value() const
  {
    if (m_rank <= CardRank::RANK_10)
    {
      return (static_cast<int>(m_rank) + 2);
    }
 
    switch (m_rank)
    {
    case CardRank::RANK_JACK:
    case CardRank::RANK_QUEEN:
    case CardRank::RANK_KING:
      return 10;
    case CardRank::RANK_ACE:
      return 11;
    default:
      assert(false && "should never happen");
      return 0;
    }
  }
 
  Card() = default;
 
  Card(CardRank rank, CardSuit suit)
      : m_rank{ rank }, m_suit{ suit }
  {
  }
};
 
class Deck
{
public:
  using array_type = std::array<Card, 52>;
  using index_type = array_type::size_type;
 
private:
  array_type m_deck{};
 
public:
  void print() const
  {
    for (const auto& card : m_deck)
    {
      card.print();
      std::cout << ' ';
    }
 
    std::cout << '\n';
  }
 
  void shuffle()
  {
    static std::mt19937 mt{ static_cast<std::mt19937::result_type>(std::time(nullptr)) };
 
    std::shuffle(m_deck.begin(), m_deck.end(), mt);
  }
 
  Deck()
  {
    index_type card{ 0 };
 
    auto suits{ static_cast<index_type>(CardSuit::MAX_SUITS) };
    auto ranks{ static_cast<index_type>(CardRank::MAX_RANKS) };
 
    for (index_type suit{ 0 }; suit < suits; ++suit)
    {
      for (index_type rank{ 0 }; rank < ranks; ++rank)
      {
        m_deck[card] = { static_cast<CardRank>(rank), static_cast<CardSuit>(suit) };
        ++card;
      }
    }
  }
};
 
int main()
{
  Deck deck;
  deck.print();
  deck.shuffle();
  deck.print();
 
  return 0;
}

c) Теперь нам нужен способ отслеживать, какая карта будет сдана следующей (в исходной программе для этого был nextCardIndex). Сначала добавьте член с именем m_cardIndex в Deck и инициализируйте его значением 0. Создайте открытую функцию-член с именем dealCard(), которая должна возвращать константную ссылку на текущую карту и продвигать m_cardIndex к следующему индексу. shuffle() также следует обновить, чтобы сбрасывать m_cardIndex (поскольку, если вы перемешаете колоду, вы начнете раздачу снова с верха колоды).

Должна скомпилироваться следующая тестовая программа:

// ...
 
int main()
{
  Deck deck{};
  
  deck.shuffle();
  deck.print();
  
  std::cout << "The first card has value: " << deck.dealCard().value() << '\n';
  std::cout << "The second card has value: " << deck.dealCard().value() << '\n';
 
  return 0;
}

#include <array>
#include <cassert>
#include <ctime>
#include <iostream>
#include <random>
 
enum class CardSuit
{
  SUIT_CLUB,
  SUIT_DIAMOND,
  SUIT_HEART,
  SUIT_SPADE,
 
  MAX_SUITS
};
 
enum class CardRank
{
  RANK_2,
  RANK_3,
  RANK_4,
  RANK_5,
  RANK_6,
  RANK_7,
  RANK_8,
  RANK_9,
  RANK_10,
  RANK_JACK,
  RANK_QUEEN,
  RANK_KING,
  RANK_ACE,
 
  MAX_RANKS
};
 
class Card
{
private:
  CardRank m_rank{};
  CardSuit m_suit{};
 
public:
  void print() const
  {
    switch (m_rank)
    {
    case CardRank::RANK_2:
      std::cout << '2';
      break;
    case CardRank::RANK_3:
      std::cout << '3';
      break;
    case CardRank::RANK_4:
      std::cout << '4';
      break;
    case CardRank::RANK_5:
      std::cout << '5';
      break;
    case CardRank::RANK_6:
      std::cout << '6';
      break;
    case CardRank::RANK_7:
      std::cout << '7';
      break;
    case CardRank::RANK_8:
      std::cout << '8';
      break;
    case CardRank::RANK_9:
      std::cout << '9';
      break;
    case CardRank::RANK_10:
      std::cout << 'T';
      break;
    case CardRank::RANK_JACK:
      std::cout << 'J';
      break;
    case CardRank::RANK_QUEEN:
      std::cout << 'Q';
      break;
    case CardRank::RANK_KING:
      std::cout << 'K';
      break;
    case CardRank::RANK_ACE:
      std::cout << 'A';
      break;
    default:
      std::cout << '?';
      break;
    }
 
    switch (m_suit)
    {
    case CardSuit::SUIT_CLUB:
      std::cout << 'C';
      break;
    case CardSuit::SUIT_DIAMOND:
      std::cout << 'D';
      break;
    case CardSuit::SUIT_HEART:
      std::cout << 'H';
      break;
    case CardSuit::SUIT_SPADE:
      std::cout << 'S';
      break;
    default:
      std::cout << '?';
      break;
    }
  }
 
  int value() const
  {
    if (m_rank <= CardRank::RANK_10)
    {
      return (static_cast<int>(m_rank) + 2);
    }
 
    switch (m_rank)
    {
    case CardRank::RANK_JACK:
    case CardRank::RANK_QUEEN:
    case CardRank::RANK_KING:
      return 10;
    case CardRank::RANK_ACE:
      return 11;
    default:
      assert(false && "should never happen");
      return 0;
    }
  }
 
  Card() = default;
 
  Card(CardRank rank, CardSuit suit)
      : m_rank{ rank }, m_suit{ suit }
  {
  }
};
 
class Deck
{
public:
  using array_type = std::array<Card, 52>;
  using index_type = array_type::size_type;
 
private:
  array_type m_deck{};
  index_type m_cardIndex{ 0 };
 
public:
  void print() const
  {
    for (const auto& card : m_deck)
    {
      card.print();
      std::cout << ' ';
    }
 
    std::cout << '\n';
  }
 
  void shuffle()
  {
    static std::mt19937 mt{ static_cast<std::mt19937::result_type>(std::time(nullptr)) };
 
    std::shuffle(m_deck.begin(), m_deck.end(), mt);
 
    m_cardIndex = 0;
  }
 
  const Card& dealCard()
  {
    assert(m_cardIndex < m_deck.size());
    
    return m_deck[m_cardIndex++];
  }
 
  Deck()
  {
    index_type card{ 0 };
 
    auto suits{ static_cast<index_type>(CardSuit::MAX_SUITS) };
    auto ranks{ static_cast<index_type>(CardRank::MAX_RANKS) };
 
    for (index_type suit{ 0 }; suit < suits; ++suit)
    {
      for (index_type rank{ 0 }; rank < ranks; ++rank)
      {
        m_deck[card] = { static_cast<CardRank>(rank), static_cast<CardSuit>(suit) };
        ++card;
      }
    }
  }
};
 
int main()
{
  Deck deck{};
  
  deck.shuffle();
  deck.print();
  
  std::cout << "The first card has value: " << deck.dealCard().value() << '\n';
  std::cout << "The second card has value: " << deck.dealCard().value() << '\n';
 
  return 0;
}

d) Теперь переходим к классу игрока Player. Поскольку playerTurn и dealerTurn очень отличаются друг от друга, мы оставим их как функции, не являющиеся членами. Сделайте Player классом и добавьте функцию-член drawCard, которая раздает игроку одну карту из колоды, увеличивая счет игрока. Нам также понадобится функция-член для доступа к счету игрока. Для удобства добавьте функцию-член с именем isBust(), которая возвращает значение true, если счет игрока превышает максимальное значение (maximumScore).

Должен компилироваться следующий код:

// ...
 
int main()
{
  Deck deck{};
  
  deck.shuffle();
  deck.print();
  
  Player player{};
  Player dealer{};
 
  player.drawCard(deck);
  dealer.drawCard(deck);
 
  std::cout << "The player drew a card with value: " << player.score() << '\n';
  std::cout << "The dealer drew a card with value: " << dealer.score() << '\n';
 
  return 0;
}

#include <array>
#include <cassert>
#include <ctime>
#include <iostream>
#include <random>
 
// Максимальный счет до проигрыша.
constexpr int maximumScore{ 21 };
 
// Минимальный балл, который должен быть у дилера.
constexpr int minimumDealerScore{ 17 };
 
enum class CardSuit
{
  SUIT_CLUB,
  SUIT_DIAMOND,
  SUIT_HEART,
  SUIT_SPADE,
 
  MAX_SUITS
};
 
enum class CardRank
{
  RANK_2,
  RANK_3,
  RANK_4,
  RANK_5,
  RANK_6,
  RANK_7,
  RANK_8,
  RANK_9,
  RANK_10,
  RANK_JACK,
  RANK_QUEEN,
  RANK_KING,
  RANK_ACE,
 
  MAX_RANKS
};
 
class Card
{
private:
  CardRank m_rank{};
  CardSuit m_suit{};
 
public:
  void print() const
  {
    switch (m_rank)
    {
    case CardRank::RANK_2:
      std::cout << '2';
      break;
    case CardRank::RANK_3:
      std::cout << '3';
      break;
    case CardRank::RANK_4:
      std::cout << '4';
      break;
    case CardRank::RANK_5:
      std::cout << '5';
      break;
    case CardRank::RANK_6:
      std::cout << '6';
      break;
    case CardRank::RANK_7:
      std::cout << '7';
      break;
    case CardRank::RANK_8:
      std::cout << '8';
      break;
    case CardRank::RANK_9:
      std::cout << '9';
      break;
    case CardRank::RANK_10:
      std::cout << 'T';
      break;
    case CardRank::RANK_JACK:
      std::cout << 'J';
      break;
    case CardRank::RANK_QUEEN:
      std::cout << 'Q';
      break;
    case CardRank::RANK_KING:
      std::cout << 'K';
      break;
    case CardRank::RANK_ACE:
      std::cout << 'A';
      break;
    default:
      std::cout << '?';
      break;
    }
 
    switch (m_suit)
    {
    case CardSuit::SUIT_CLUB:
      std::cout << 'C';
      break;
    case CardSuit::SUIT_DIAMOND:
      std::cout << 'D';
      break;
    case CardSuit::SUIT_HEART:
      std::cout << 'H';
      break;
    case CardSuit::SUIT_SPADE:
      std::cout << 'S';
      break;
    default:
      std::cout << '?';
      break;
    }
  }
 
  int value() const
  {
    if (m_rank <= CardRank::RANK_10)
    {
      return (static_cast<int>(m_rank) + 2);
    }
 
    switch (m_rank)
    {
    case CardRank::RANK_JACK:
    case CardRank::RANK_QUEEN:
    case CardRank::RANK_KING:
      return 10;
    case CardRank::RANK_ACE:
      return 11;
    default:
      assert(false && "should never happen");
      return 0;
    }
  }
 
  Card() = default;
 
  Card(CardRank rank, CardSuit suit)
      : m_rank{ rank }, m_suit{ suit }
  {
  }
};
 
class Deck
{
public:
  using array_type = std::array<Card, 52>;
  using index_type = array_type::size_type;
 
private:
  array_type m_deck{};
  index_type m_cardIndex{ 0 };
 
public:
  void print() const
  {
    for (const auto& card : m_deck)
    {
      card.print();
      std::cout << ' ';
    }
 
    std::cout << '\n';
  }
 
  void shuffle()
  {
    static std::mt19937 mt{ static_cast<std::mt19937::result_type>(std::time(nullptr)) };
 
    std::shuffle(m_deck.begin(), m_deck.end(), mt);
 
    m_cardIndex = 0;
  }
 
  const Card& dealCard()
  {
    assert(m_cardIndex < m_deck.size());
 
    return m_deck[m_cardIndex++];
  }
 
  Deck()
  {
    index_type card{ 0 };
 
    auto suits{ static_cast<index_type>(CardSuit::MAX_SUITS) };
    auto ranks{ static_cast<index_type>(CardRank::MAX_RANKS) };
 
    for (index_type suit{ 0 }; suit < suits; ++suit)
    {
      for (index_type rank{ 0 }; rank < ranks; ++rank)
      {
        m_deck[card] = { static_cast<CardRank>(rank), static_cast<CardSuit>(suit) };
        ++card;
      }
    }
  }
};
 
class Player
{
private:
  int m_score{};
 
public:
  void drawCard(Deck& deck)
  {
    m_score += deck.dealCard().value();
  }
 
  int score() const
  {
    return m_score;
  }
 
  bool isBust() const
  {
    return (m_score > maximumScore);
  }
};
 
int main()
{
  Deck deck{};
  
  deck.shuffle();
  deck.print();
  
  Player player{};
  Player dealer{};
 
  player.drawCard(deck);
  dealer.drawCard(deck);
 
  std::cout << "The player drew a card with value: " << player.score() << '\n';
  std::cout << "The dealer drew a card with value: " << dealer.score() << '\n';
 
  return 0;
}

e) Почти готово! Теперь просто исправьте оставшуюся программу, чтобы использовать классы, которые вы написали выше. Поскольку большинство функций перенесено в классы, вы можете отказаться от них.

#include <algorithm>
#include <array>
#include <cassert>
#include <ctime>
#include <iostream>
#include <random>
 
// Максимальный счет до проигрыша.
constexpr int maximumScore{ 21 };
 
// Минимальный счет, который должен иметь дилер.
constexpr int minimumDealerScore{ 17 };
 
enum class CardSuit
{
  SUIT_CLUB,
  SUIT_DIAMOND,
  SUIT_HEART,
  SUIT_SPADE,
 
  MAX_SUITS
};
 
enum class CardRank
{
  RANK_2,
  RANK_3,
  RANK_4,
  RANK_5,
  RANK_6,
  RANK_7,
  RANK_8,
  RANK_9,
  RANK_10,
  RANK_JACK,
  RANK_QUEEN,
  RANK_KING,
  RANK_ACE,
 
  MAX_RANKS
};
 
class Card
{
private:
  CardRank m_rank{};
  CardSuit m_suit{};
 
public:
  void print() const
  {
    switch (m_rank)
    {
    case CardRank::RANK_2:
      std::cout << '2';
      break;
    case CardRank::RANK_3:
      std::cout << '3';
      break;
    case CardRank::RANK_4:
      std::cout << '4';
      break;
    case CardRank::RANK_5:
      std::cout << '5';
      break;
    case CardRank::RANK_6:
      std::cout << '6';
      break;
    case CardRank::RANK_7:
      std::cout << '7';
      break;
    case CardRank::RANK_8:
      std::cout << '8';
      break;
    case CardRank::RANK_9:
      std::cout << '9';
      break;
    case CardRank::RANK_10:
      std::cout << 'T';
      break;
    case CardRank::RANK_JACK:
      std::cout << 'J';
      break;
    case CardRank::RANK_QUEEN:
      std::cout << 'Q';
      break;
    case CardRank::RANK_KING:
      std::cout << 'K';
      break;
    case CardRank::RANK_ACE:
      std::cout << 'A';
      break;
    default:
      std::cout << '?';
      break;
    }
 
    switch (m_suit)
    {
    case CardSuit::SUIT_CLUB:
      std::cout << 'C';
      break;
    case CardSuit::SUIT_DIAMOND:
      std::cout << 'D';
      break;
    case CardSuit::SUIT_HEART:
      std::cout << 'H';
      break;
    case CardSuit::SUIT_SPADE:
      std::cout << 'S';
      break;
    default:
      std::cout << '?';
      break;
    }
  }
 
  int value() const
  {
    if (m_rank <= CardRank::RANK_10)
    {
      return (static_cast<int>(m_rank) + 2);
    }
 
    switch (m_rank)
    {
    case CardRank::RANK_JACK:
    case CardRank::RANK_QUEEN:
    case CardRank::RANK_KING:
      return 10;
    case CardRank::RANK_ACE:
      return 11;
    default:
      assert(false && "should never happen");
      return 0;
    }
  }
 
  Card() = default;
 
  Card(CardRank rank, CardSuit suit)
      : m_rank{ rank }, m_suit{ suit }
  {
  }
};
 
class Deck
{
public:
  using array_type = std::array<Card, 52>;
  using index_type = array_type::size_type;
 
private:
  array_type m_deck{};
  index_type m_cardIndex{ 0 };
 
public:
  void print() const
  {
    for (const auto& card : m_deck)
    {
      card.print();
      std::cout << ' ';
    }
 
    std::cout << '\n';
  }
 
  void shuffle()
  {
    static std::mt19937 mt{ static_cast<std::mt19937::result_type>(std::time(nullptr)) };
 
    std::shuffle(m_deck.begin(), m_deck.end(), mt);
 
    m_cardIndex = 0;
  }
 
  const Card& dealCard()
  {
    assert(m_cardIndex < m_deck.size());
 
    return m_deck[m_cardIndex++];
  }
 
  Deck()
  {
    index_type card{ 0 };
 
    auto suits{ static_cast<index_type>(CardSuit::MAX_SUITS) };
    auto ranks{ static_cast<index_type>(CardRank::MAX_RANKS) };
 
    for (index_type suit{ 0 }; suit < suits; ++suit)
    {
      for (index_type rank{ 0 }; rank < ranks; ++rank)
      {
        m_deck[card] = { static_cast<CardRank>(rank), static_cast<CardSuit>(suit) };
        ++card;
      }
    }
  }
};
 
class Player
{
private:
  int m_score{};
 
public:
  void drawCard(Deck& deck)
  {
    m_score += deck.dealCard().value();
  }
 
  int score() const
  {
    return m_score;
  }
 
  bool isBust() const
  {
    return (m_score > maximumScore);
  }
};
 
bool playerWantsHit()
{
  while (true)
  {
    std::cout << "(h) to hit, or (s) to stand: ";
 
    char ch{};
    std::cin >> ch;
 
    switch (ch)
    {
    case 'h':
      return true;
    case 's':
      return false;
    }
  }
}
 
// Возвращает true, если у игрока «перебор». В противном случае - false.
bool playerTurn(Deck& deck, Player& player)
{
  while (true)
  {
    std::cout << "You have: " << player.score() << '\n';
 
    if (player.isBust())
    {
      return true;
    }
    else
    {
      if (playerWantsHit())
      {
        player.drawCard(deck);
      }
      else
      {
        // у игрока нет перебора.
        return false;
      }
    }
  }
}
 
// Возвращает true, если у дилера «перебор». В противном случае - false.
bool dealerTurn(Deck& deck, Player& dealer)
{
  while (dealer.score() < minimumDealerScore)
  {
    dealer.drawCard(deck);
  }
 
  return dealer.isBust();
}
 
bool playBlackjack(Deck& deck)
{
  Player dealer{};
  dealer.drawCard(deck);
 
  std::cout << "The dealer is showing: " << dealer.score() << '\n';
 
  Player player{};
  player.drawCard(deck);
  player.drawCard(deck);
 
  if (playerTurn(deck, player))
  {
    return false;
  }
 
  if (dealerTurn(deck, dealer))
  {
    return true;
  }
 
  return (player.score() > dealer.score());
}
 
int main()
{
  Deck deck{};
 
  deck.shuffle();
 
  if (playBlackjack(deck))
  {
    std::cout << "You win!\n";
  }
  else
  {
    std::cout << "You lose!\n";
  }
 
  return 0;
}

Теги

C++ / CppfriendLearnCppstaticДеструктор / Destructor / dtor (программирование)Для начинающихКласс (программирование)Конструктор / Constructor / ctor (программирование)ОбучениеОбъектно-ориентированное программирование (ООП)Программирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.