Треки
/
C++
C++
/
Вправи
/
Тролити тролів
Тролити тролів

Тролити тролів

Навчальна вправа

Вступ

Переліки

Досі ми переважно мали справу з числами та рядками тексту (англ. string). Щоб моделювати реальний світ, нам може знадобитися обмежений набір значень, які може набувати змінна. Іноді потрібен окремий тип із кількома різними значеннями, кожне зі своєю назвою. Наприклад, на фабриці скейтбордів матеріал дошки може бути лише кленом, бамбуком або пластиком.

Можна було б закодувати ці значення цілими числами, але тоді довелося б писати додатковий код, щоб перевіряти, чи не надійшло з системи неприпустиме значення матеріалу. Значення таких магічних чисел важко простежити в коді, і їх легко переплутати. enumerations допомагають писати виразний код і запобігати випадковим помилкам порівняння.

Приклад використання

Конкретна назва такого різновиду переліку - scoped enumeration. Наведений нижче фрагмент показує, як записати enumeration DeckMaterial. Звернімо увагу на ключове слово enum class та на ; у кінці визначення:

enum class DeckMaterial {
    maple,
    bamboo,
    plastic
};

Тепер розгляньмо функцію ціноутворення в магазині скейтбордів і звернімо увагу на оператор розвʼязання області видимості (::), який указує enumerator з enumeration:

double deck_price(double base_price, DeckMaterial material) {
    if(material == DeckMaterial::plastic) {
        return base_price * 0.9;
    }
    return base_price * 1.3;
}

Область видимості

Уявімо, що в нас є другий enumeration для матеріалу коліс:

enum class WheelMaterial {
    steel,
    clay,
    plastic
};

Хоча і колеса, і дошка можуть бути з plastic, їх неможливо переплутати. Це різні типи: DeckMaterial пластик і WheelMaterial пластик. Кожен enumeration має свої enumerators у власній області видимості - власному namespace. Саме тому їх називають scoped enumerations.

Advanced

Переліки без області видимості

Можна подумати, що якщо є назва scoped, то існують і unscoped переліки, і це справді так. Unscoped enumerations стають дедалі менш популярними, бо всі вони поділяють один і той самий глобальний простір імен. Через це не можна мати два unscoped enumerations з однаковими enumerators, як plastic у прикладі вище.

Крім того, unscoped enumerations неявно перетворюються на цілі числа. Погляньмо на приклад нижче, щоб побачити несподіваний результат:

enum CitrusFruits {
    lemons,  // 0
    oranges, //1
};

enum IceCream {
    walnut,  // 0
    apples,  // 1
};

bool comparison{apples == oranges};
// => true

// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!

Якщо потрібно перетворити scoped enumerations на цілі числа, можна скористатися static_cast<int>.

Інструкція switch

Як і в інших мовах, у C++ є інструкція switch. Інструкція switch дає коротший спосіб записати довгі конструкції if ... else if. Щоб створити switch, ми починаємо з ключового слова switch, після якого йде ціле число. Далі оголошуємо кожну з умов за допомогою ключового слова case. Також можна оголосити варіант default, який виконається, якщо жодна з попередніх умов case не збіглася. Кожен варіант має завершуватися інструкцією break (або return).

int price{0};
int adults{3};
int kids{2};

switch (int group_size{adults + kids}) {
    case 1:
        price = 50;
        break;
    case 2:
        price = 70;
        break;
    default:
        price = group_size * 30;
}

Провалювання

Одна важлива особливість конструкції switch полягає в тому, що код продовжує виконуватися, доки його не зупинить інструкція break (або return). Це може призвести до несподіваної поведінки.

int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
    case 1:
        price = 50;
    case 2:
        price = 70;
    default:
        price = group_size * 30;
}
// price will be 30!

Головний випадок використання такого наскрізного виконання - це варіант із кількома мітками. Кілька результатів switch можуть відповідати тому самому фрагменту коду, який треба виконати. Так, наприклад, у застосунку для бронювання функція, яку викликають для групи з 2 і 3 осіб, може бути однією й тією самою:

switch (group_size) {
case 1:
    book_room();
    break;
case 2:
case 3:
    book_apartment(group_size);
    break;
default:
    book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3

Вказівки

Helma, наша подруга, створила невелику онлайн-гру, яка швидко набула популярності. Вона називається HellMath. Невелика спільнота привабила кількох тролів, які роблять гру й форуми досить неприємними. Helma попросила нас попрацювати над новою системою дозволів, щоб відокремити тих, хто створює проблеми.

Форум підтримує три різні дії:

  • Читання
  • Написання
  • Видалення

Є чотири типи акаунтів, кожен зі своїми типовими дозволами:

  • Гості: можуть читати дописи.
  • Користувачі та тролі: можуть читати і писати дописи.
  • Модератори: можуть читати, писати і видаляти дописи, вони мають усі дозволи.

Helma помітила, що блокувати акаунти тролів не має сенсу. Її стратегія - створити в них ілюзію, ніби час витрачено «з користю», але їхні дописи показують лише іншим тролям. У будь-чому, що потребує впорядкування за пріоритетом, тролі стоять останніми в будь-якій послідовності. Коли вони заходять у гру, коло доступних гравців також обмежується іншими тролями.

Таку практику називають тіньовим баном.

1. Налаштуйте типи користувачів і дозволи.

Спершу визначте перелік AccountStatus, який представляє чотири типи акаунтів: troll, guest, user і mod.

Далі визначте перелік Action, який представляє три типи дозволів: read, write і remove.

2. Показуйте тролів лише іншим тролям.

Кожен допис на форумах зберігає AccountStatus автора у своїх метаданих. Переконайтеся, що дописи тролів показуються лише іншим тролям.

Helma потрібна функція display_post, яка приймає два аргументи типу AccountStatus і повертає bool. Перший аргумент - статус автора допису, а другий - статус того, хто його переглядає.

using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true

3. Перевіряйте дозволи для різних користувачів.

Helma потрібен спосіб перевірити, чи дозволена певна дія для користувача.

Реалізуйте функцію permission_check, яка приймає Action як перший аргумент і AccountStatus для перевірки. Вона має повертати bool відповідно до дозволів, перелічених у вступі.

permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true

4. Надавайте доступ до гри та обʼєднуйте гравців у пари.

Щоб справжні гравці в грі відповідали за свої дії, Hellmath не дає доступу гостьовим користувачам. Як уже зазначалося, Helma хоче, щоб тролі тролили інших тролів. Ігрові звʼязки між іншими користувачами не обмежуються.

Реалізуйте функцію valid_player_combination, яка перевіряє, чи можуть двоє гравців приєднатися до однієї гри. Функція має два параметри типу AccountStatus і повертає bool.

valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true

5. Побудуйте чергу з пріоритетами.

Через стрімке зростання гри й форумів Helma тепер має розподіляти обчислювальну потужність і пропускну здатність між користувачами. Щоб реагувати на непередбачувані ситуації, модератори отримують найвищий пріоритет. Гості стоять у черзі після звичайних користувачів, а тролі - після всіх інших.

Реалізуйте функцію has_priority, яка приймає два аргументи типу AccountStatus і повертає true тоді й лише тоді, коли перший акаунт має строго вищий пріоритет за другий.

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
C++ Exercism

Час розпочати Тролити тролів?

Зареєструйтеся на Exercism, щоб вивчати й опановувати C++, а також 19 концепцій100 вправ та справжнє наставництво від людей, і все це безкоштовно.