Parcours
/
C++
C++
/
Exercices
/
Trolle les trolls
Trolle les trolls

Trolle les trolls

Exercice d'apprentissage

Introduction

Énumérations

Jusqu'ici, on a surtout manipulé des nombres et des strings. Pour modéliser le monde réel, on peut vouloir qu'une variable ne puisse prendre qu'un nombre limité de valeurs. On peut souhaiter un type dédié, avec quelques valeurs distinctes portant des noms distincts. Par exemple, dans une usine de skateboards, où le matériau du plateau ne pourrait être que de l'érable, du bambou ou du plastique.

On pourrait encoder ces valeurs avec des entiers, mais il faudrait ajouter du code pour vérifier que le système ne renvoie pas une valeur invalide pour le matériau. La signification de ces nombres magiques est difficile à suivre dans le code source, et ces valeurs sont faciles à confondre. On peut utiliser les enumerations pour encourager un code expressif et limiter les erreurs de comparaison involontaires.

Exemple d'utilisation

Le terme précis pour ce type d'énumération est scoped enumeration. L'extrait ci-dessous montre comment écrire une enumeration DeckMaterial. Remarque le mot-clé enum class et le ; à la fin de la définition :

enum class DeckMaterial {
    maple,
    bamboo,
    plastic
};

Maintenant, regarde une fonction de tarification dans le magasin de skateboards et remarque l'opérateur de résolution de portée (::) qui désigne un enumerator de l'enumeration :

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

Portée

Imagine que tu aies une seconde enumeration pour le matériau des roues :

enum class WheelMaterial {
    steel,
    clay,
    plastic
};

Bien que les roues et le plateau puissent tous deux être faits de plastic, on ne peut pas les confondre. Ce sont deux types différents : le plastic de DeckMaterial et le plastic de WheelMaterial. Chaque enumeration a ses enumerators dans sa propre portée, son propre namespace. C'est pour cette raison qu'on les appelle des scoped enumerations.

Advanced

Énumérations non portées

Tu te dis peut-être qu'avec un nom comme scoped, il doit aussi exister des énumérations unscoped, et tu as raison. Les Unscoped enumerations sont de moins en moins populaires, parce qu'elles partagent toutes le même espace de noms global. À cause de ce partage, on ne pouvait pas avoir deux unscoped enumerations avec les mêmes enumerators, comme plastic dans l'exemple ci-dessus.

De plus, les unscoped enumerations se convertissent implicitement en entiers. Regarde l'exemple ci-dessous pour un résultat surprenant :

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!

Si tu veux convertir des scoped enumerations en entiers, tu peux utiliser static_cast<int>.

Instructions switch

Comme d'autres langages, C++ propose aussi une instruction switch. Une instruction switch est une façon plus courte d'écrire de longues instructions if ... else if. Pour écrire un switch, on commence par le mot-clé switch, suivi d'un entier. On déclare ensuite chacune des conditions avec le mot-clé case. On peut aussi déclarer un cas default, qui s'exécute quand aucune des conditions case précédentes n'est vérifiée. Chaque cas doit se terminer par une instruction break (ou 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;
}

L'exécution en cascade

Un point important de la construction switch : le code continue de s'exécuter jusqu'à ce qu'une instruction break (ou return) l'arrête. Cela peut mener à un comportement inattendu.

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!

Le principal cas d'usage de cette exécution continue est une instruction portant plusieurs étiquettes. Plusieurs cas d'un switch peuvent correspondre au même morceau de code à exécuter. Ainsi, dans une application de réservation par exemple, la fonction appelée pour une taille de groupe de 2 ou 3 peut être la même :

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

Instructions

Ton amie Helma a créé un petit jeu en ligne qui a rapidement gagné en popularité. Il s'appelle HellMath. La petite communauté a attiré quelques trolls qui rendent le jeu et les forums assez désagréables. Helma t'a demandé de travailler sur un nouveau système de permissions pour séparer les fauteurs de troubles.

Le forum prend en charge trois actions différentes :

  • Read
  • Write
  • Remove

Il existe quatre types de comptes, chacun avec des permissions par défaut différentes :

  • Invités : peuvent lire les publications.
  • Utilisateurs et trolls : peuvent lire et écrire des publications.
  • Modérateurs : peuvent lire, écrire et supprimer des publications, ils ont toutes les permissions.

Helma a remarqué qu'il ne sert à rien de bannir les comptes de trolls. Sa stratégie consiste à leur donner l'illusion que leur temps est « bien investi », mais leurs publications ne sont montrées qu'aux autres trolls. Pour tout ce qui demande un classement par priorité, les trolls passent en dernier dans n'importe quelle séquence. Quand ils entrent dans une partie, le vivier de joueurs disponibles est lui aussi limité aux autres trolls.

Cette pratique s'appelle le shadow-banning : voir shadow-banning.

1. Mets en place les types d'utilisateurs et les permissions.

Commence par définir une énumération AccountStatus pour représenter les quatre types de comptes : troll, guest, user et mod.

Ensuite, définis une énumération Action pour représenter les trois types de permissions : read, write et remove.

2. Ne fais la promotion des trolls qu'auprès des autres trolls.

Chaque publication sur les forums enregistre l'AccountStatus de son auteur dans ses métadonnées. Assure-toi que les publications des trolls ne sont affichées qu'aux autres trolls.

Helma a besoin d'une fonction display_post qui prend deux arguments de type AccountStatus et renvoie un bool. Le premier argument est le statut de l'auteur de la publication, le second est le statut de la personne qui la lit.

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

3. Vérifie les permissions des différents utilisateurs.

Helma a besoin d'un moyen de vérifier si une action donnée est autorisée pour un utilisateur.

Implémente une fonction permission_check qui prend une Action en premier argument et un AccountStatus à vérifier. Elle doit renvoyer un bool conformément aux permissions listées dans l'introduction.

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

4. Accorde l'accès au jeu et associe les joueurs.

Pour que les vrais joueurs restent responsables de leurs actes dans le jeu, Hellmath refuse l'accès aux invités. Comme mentionné plus haut, Helma veut que les trolls trollent les autres trolls. Les connexions entre les autres utilisateurs ne sont pas limitées.

Implémente la fonction valid_player_combination qui vérifie si deux joueurs peuvent rejoindre la même partie. La fonction prend deux paramètres de type AccountStatus et renvoie un bool.

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

5. Mets en place la file de priorité.

Avec la croissance massive du jeu et des forums, Helma doit désormais répartir la puissance de calcul et la bande passante entre les utilisateurs. Pour gérer les urgences, les modérateurs ont la priorité la plus élevée. Les invités sont placés derrière les utilisateurs normaux, et les trolls sont relégués derrière tout le monde.

Implémente la fonction has_priority qui prend deux arguments de type AccountStatus et renvoie true si et seulement si le premier compte a une priorité strictement supérieure à celle du second.

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
C++ Exercism

Prêt à commencer Trolle les trolls ?

Inscris-toi sur Exercism pour apprendre et maîtriser C++ avec 19 concepts100 exercices, et un vrai mentorat humain, le tout gratuitement.