Dés de jeu de rôle
On tape 2d6+3, 4d6kh3 ou 2d20kl1 et les dés roulent : la notation des jeux de rôle, comprise et calculée. Le bon exercice pour lire une petite syntaxe et tirer au sort sans biais.
- Lire une notation avec une expression régulière
- Tirer un nombre sans biais avec crypto.getRandomValues
- Garder les meilleurs ou les pires dés d'un lancer
Pourquoi des dés de jeu de rôle ? Autour d'une table de Donjons et Dragons, on dit « deux d6 plus trois » ou « un d20 avec avantage ». Cette notation est une petite langue : NdF veut dire N dés à F faces, puis on ajoute ou retire des nombres.
On la découpe avec une expression régulière, terme par terme. On ajoute kh et kl pour garder les meilleurs ou les pires dés : 4d6kh3 lance quatre dés et garde les trois plus hauts, 2d20kl1 donne le désavantage.
Pour tirer chaque dé, on utilise crypto.getRandomValues, et on rejette les valeurs qui fausseraient le résultat. Les joueurs ont le droit à des dés honnêtes.
Pourquoi en JavaScript simple ? Une expression régulière, une boucle, un tri. Les messages d'erreur disent ce qui ne va pas dans la notation.
UN DÉ HONNÊTE
Le modulo seul favorise les petites valeurs : on rejette le haut de la plage.
function de(faces) { const max = Math.floor(2 ** 32 / faces) * faces, b = new Uint32Array(1); do crypto.getRandomValues(b); while (b[0] >= max); return (b[0] % faces) + 1; }LIRE LA NOTATION
Chaque terme : un signe, puis NdF avec un kh ou kl optionnel, ou un simple nombre.
const TERME = /([+-]?)(?:(\d*)d(\d+)(?:k([hl])(\d+))?|(\d+))/gy; function analyser(texte) { const s = texte.toLowerCase().replace(/\s+/g, ''); const termes = []; let pos = 0; while (pos < s.length) { TERME.lastIndex = pos; const m = TERME.exec(s); if (!m) throw new Error('Notation incomprise près de ' + s.slice(pos)); const signe = m[1] === '-' ? -1 : 1; termes.push(m[3] ? { signe, n: Number(m[2] || 1), faces: Number(m[3]), garde: m[4], k: Number(m[5]) } : { signe, valeur: Number(m[6]) }); pos = TERME.lastIndex; } return termes; }LANCER UN TERME
kh3 : on trie du plus haut au plus bas et on garde les trois premiers.
function lancerTerme(t) { const des = Array.from({ length: t.n }, () => de(t.faces)); if (!t.garde) return des.reduce((a, b) => a + b, 0); const tri = [...des].sort((a, b) => t.garde === 'h' ? b - a : a - b); return tri.slice(0, t.k).reduce((a, b) => a + b, 0); }LE TOTAL
const total = texte => analyser(texte) .reduce((somme, t) => somme + t.signe * ('valeur' in t ? t.valeur : lancerTerme(t)), 0);ESSAYER
console.log(total('2d6+3'), total('4d6kh3'), total('1d20-1'));
PIÈGE 1 : Le biais du modulo
Mauvais : nombreAleatoire % 6 + 1 sur un entier de 32 bits. Les plus petites faces sortent un tout petit peu plus souvent.
Correct : rejeter les valeurs au-dessus du plus grand multiple de 6, puis appliquer le modulo.
PIÈGE 2 : Accepter n'importe quoi
Mauvais : lancer 99999d99999 parce que la notation est valide. Le navigateur se fige.
Correct : des limites raisonnables, comme 100 dés et 1000 faces, avec un message clair.
PIÈGE 3 : Ignorer les morceaux incompris
Mauvais : une expression régulière sans le drapeau y qui saute ce qu'elle ne comprend pas. « 2d6x3 » donne un résultat sans rien dire.
Correct : lire terme par terme depuis la position courante, et signaler une erreur au premier caractère inattendu.
- Les dés qui explosent : un 6 se relance et s'ajoute, noté 3d6!.
- Des lancers enregistrés pour chaque personnage, comme « Attaque à l'épée ».
- L'avantage et le désavantage en un clic, sans taper la notation.
- Pour des statistiques, calculez la distribution exacte des résultats au lieu de lancer des milliers de fois.
- Gardez l'historique des lancers dans localStorage pour les retrouver d'une séance à l'autre.
L'analyseur et le tirage vivent dans un fichier pur, testable sans interface.
La notation est un useState, le dernier résultat et l'historique en sont deux autres.
Relancez l'animation des dés en changeant la key du conteneur à chaque lancer.
Chargement des commentaires…