Après des semaines d'impatience, tes amis et toi vous retrouvez pour votre toute première partie de Dungeons & Dragons (D&D). Comme c'est la première session de la partie, chaque joueur doit générer un personnage avec lequel jouer. Les capacités du personnage sont déterminées en lançant des dés à 6 faces, mais où sont les dés ? Sous le choc, tu réalises que tes amis attendent de toi que tu leur fournisses les dés ; après tout, c'était ton idée de jouer à D&D ! Pris de panique, tu réalises que tu as oublié d'apporter les dés, et sans eux, pas de partie de D&D. Comme tu as quelques bases en programmation, tu trouves vite une solution : tu vas écrire un programme qui simule des lancers de dés.
Pour une partie de Dungeons & Dragons, chaque joueur commence par générer un personnage avec lequel il peut jouer. Ce personnage possède, entre autres, six caractéristiques : Force, Dextérité, Constitution, Intelligence, Sagesse et Charisme. Les scores de ces six caractéristiques sont déterminés de manière aléatoire. Pour cela, lance quatre dés à 6 faces et note la somme des trois dés les plus élevés. Répète l'opération six fois, une fois par caractéristique.
Les points de vie initiaux de ton personnage sont égaux à 10 + son modificateur de Constitution. Tu obtiens le modificateur de Constitution de ton personnage en soustrayant 10 à sa Constitution, en divisant le résultat par 2, puis en arrondissant à l'inférieur.
Écris un générateur de personnages aléatoire qui suit les règles ci-dessus.
Par exemple, les six lancers de quatre dés peuvent ressembler à ceci :
Comme la Constitution vaut 3, le modificateur de Constitution vaut -4 et les points de vie valent 6.
La plupart des langages de programmation disposent de générateurs (pseudo-)aléatoires, mais peu de langages de programmation sont conçus pour lancer des dés. Un tel langage est Troll.
Dans les langages fonctionnels, la plupart des fonctions sont pures. Cela signifie qu'elles renvoient le même résultat pour le même ensemble d'arguments, et qu'elles ne font rien d'autre. En d'autres termes, les fonctions pures sont déterministes.
Cependant, par définition, une valeur aléatoire est imprévisible et non déterministe. Même le caractère pseudo-aléatoire, c'est-à-dire le fait de renvoyer une séquence de nombres qui semblent aléatoires, n'est pas si facile à obtenir dans un contexte pur. En effet, un générateur de nombres pseudo-aléatoires (PRNG) doit garder une trace de son état interne pour renvoyer le nombre suivant de la séquence.
Lean propose deux approches possibles pour résoudre ce problème :
Utilise une monade qui permet de modifier l'état interne d'un PRNG.
Il existe une fonction directement disponible, IO.rand, qui fait exactement cela.
Utilise des fonctions pures qui prennent un générateur en argument et renvoient non seulement la valeur pseudo-aléatoire produite, mais aussi un générateur mis à jour « amorcé » pour le nombre suivant de la séquence.
Cet exercice utilise la seconde approche. On passe un générateur à chaque fonction censée produire une valeur pseudo-aléatoire, et la fonction doit renvoyer à la fois cette valeur et un générateur mis à jour.
Note qu'un générateur donné est déterministe, c'est-à-dire qu'il produit toujours la même valeur. Pour générer la valeur pseudo-aléatoire suivante dans une séquence, il est nécessaire d'utiliser le générateur mis à jour.
Dans cet exercice, le caractère aléatoire est vérifié par un test du chi carré avec un seuil de signification de p < 0.0001.
Cela signifie que si un score de caractéristique est généré conformément aux instructions, il y a moins de 0,01 % de chances qu'une implémentation correcte échoue au test par hasard.
Note que, d'après les instructions, un score de caractéristique est la somme des trois plus grands résultats sur quatre lancers d'un d6 non biaisé (dé à six faces).
Inscris-toi sur Exercism pour apprendre et maîtriser Lean avec 100 exercices, et un vrai mentorat humain, le tout gratuitement.
On explore comment générer des lancers de dés (en apparence) aléatoires. Tu vas découvrir les graines, les nombres pseudo-aléatoires et quelques pièges courants. Enfin, on verra pourquoi les tests basés sur les propriétés sont parfaits pour tester des fonctions qui renvoient des valeurs aléatoires.