Le pitch
Dice Roller est une web-app de lancer de dés pour rôlistes. On entre une formule (2d6+3, 1d20, 4d8kh3…) et on obtient instantanément le résultat, avec le détail des dés lancés.
Ce qui la distingue : une notation riche (keep/drop, relances, tri, répétitions, tags) et un système de vérification “provably fair” qui permet d’auditer n’importe quel lancer.
Essayer : presque.cool/dice/
Pourquoi ce projet
Tous les lanceurs de dés que je trouvais en ligne avaient le même problème : ils étaient laids, ou lourds, ou les deux. J’aimais bien celui de Google — simple, rapide — mais je voulais quelque chose de plus complet pour mes sessions de JDR.
C’était aussi le bon candidat pour ma première vraie application React. Un projet avec un périmètre clair, un besoin réel, et assez de complexité technique pour apprendre sans se noyer.
Trois objectifs :
- Apprendre React — mon premier projet sérieux avec le framework.
- Créer un outil que j’utiliserais vraiment — rapide, lisible, adapté aux sessions de jeu.
- Aller au-delà du basique — supporter une notation riche et résoudre le problème de la confiance dans les résultats en ligne.
Le cœur technique : un parser de notation
La plupart des lanceurs de dés se contentent de XdY+N. Dice Roller va plus loin :
- Dés standard :
d4,d6,d8,d10,d12,d20,d100 - Modificateurs :
+3,-2, et combinaisons (2d8+1d6+2) - Keep/Drop :
kh3(garder les 3 meilleurs),dl1(retirer le plus bas) - Relances :
r(relancer les 1),ro>=5(relancer une fois si ≥5) - Tri :
sa(croissant),sd(décroissant) - Répétition :
(2d6+3)@10pour lancer 10 fois la même formule - Tags :
#attaquepour annoter un lancer
Pour parser tout cela, j’ai implémenté un parser récursif descendant. C’était ma première fois — je ne savais pas exactement où j’allais au départ, mais l’approche s’est imposée naturellement face à la complexité des expressions imbriquées.
Le parser produit un AST léger (une liste de “parts” : dés ou modificateurs numériques), avec validation stricte : types de dés autorisés, bornes sur le nombre de dés (≤100 par terme), messages d’erreur explicites quand l’utilisateur tape k3 au lieu de kh3.
Le problème de la confiance : “provably fair”
En JDR en ligne, il y a toujours ce doute : “Est-ce que le dé était vraiment aléatoire, ou l’app m’a donné un résultat arrangé ?”
Dice Roller intègre un système de vérification :
- Génération d’une entropie locale (8 bytes via
crypto.getRandomValues) - Création d’un nonce (timestamp)
- Calcul d’un hash SHA-256 à partir de l’entropie, du nonce et d’un secret applicatif
- Utilisation d’un PRNG déterministe (Mulberry32) pour générer les résultats
- Stockage d’un hash final dans l’historique
N’importe quel lancer peut être recalculé et vérifié côté client. Ce n’est pas une sécurité “anti-triche” absolue (pour cela il faudrait un serveur), mais cela répond au besoin réel : pouvoir prouver qu’un résultat n’a pas été modifié après coup.
Performance : Web Workers et heuristiques
Le parsing peut devenir coûteux avec des notations comme 100d20. La stratégie :
- Compter rapidement le nombre total de dés via regex
- Au-dessus d’un seuil (50 dés), basculer le parsing dans un Web Worker
- En cas d’échec (worker indisponible, timeout), fallback vers parsing synchrone
Cela garantit que l’interface reste fluide même avec des formules complexes.
Architecture : séparation des responsabilités
Le projet suit une structure en trois couches :
Services (métier pur) :
- Parser de notation → AST
- Moteur d’exécution → résultats détaillés
- Crypto → entropie, hash, PRNG
Hooks (orchestration UI) :
- Gestion de l’état de lancer (
isRolling,lastRoll) - Décision d’utiliser ou non le Web Worker
UI (composants) :
- Input avec autocomplétion
- Affichage des résultats (total + détail des dés gardés/retirés)
- Historique filtrable
- Modales (aide, settings, vérification)
Cette séparation a un avantage clé : le cœur métier est testable et réutilisable indépendamment de React.
UX : rapidité et lisibilité
L’objectif était “one-input, résultat immédiat”. Quelques choix :
- Autocomplétion intelligente : suggestions de notation, navigation clavier (↑/↓/Tab/Enter)
- Presets : sauvegarder un lancer fréquent pour le retrouver dans l’autocomplétion
- Historique filtrable : recherche textuelle ou numérique (
>15,<=10,15-20) - Affichage détaillé : total visible immédiatement, détail des dés (gardés, retirés) accessible
L’autocomplétion est désactivée sur mobile pour éviter les conflits avec le clavier virtuel — un compromis pragmatique.
Stack technique
- React + TypeScript
- Vite comme bundler (avec SWC)
- Zustand pour le state management — persistance localStorage (paramètres) + IndexedDB (historique)
- Tailwind CSS en mode CSS-first (tokens via variables CSS, couleurs OKLCH)
- PWA — installable, offline
- i18n maison (français/anglais)
- Web Worker optionnel pour les notations lourdes
- Compression — Brotli + Gzip sur les assets
- CSP — Content-Security-Policy avec hashes auto-générés
Évolutions
| Date | Version | Description |
|---|---|---|
| Oct 2025 | 1.0.0 | Version initiale — Lanceur de dés avec parser de notation, système provably fair et PWA |
Bilan
Durée : environ 1 mois (octobre 2025).
Ce que j’ai appris :
- Construire une application React de bout en bout — mon premier projet sérieux avec le framework
- Implémenter un parser récursif descendant — une première, plus accessible que je ne le pensais
- Intégrer de la cryptographie côté client pour un cas d’usage concret
Ce que je referais pareil :
- Partir d’un besoin personnel réel — cela motive et donne une direction claire
- Séparer strictement le métier de l’UI dès le départ
Ce que je changerais :
- Ajouter une visualisation 3D pour voir les dés rouler. L’expérience serait plus immersive, et ce serait l’occasion d’explorer Three.js ou une lib similaire.
The pitch
Dice Roller is a web app for tabletop RPG players. Enter a formula (2d6+3, 1d20, 4d8kh3…) and instantly get the result, with details of each die rolled.
What sets it apart: rich notation support (keep/drop, rerolls, sorting, repetitions, tags) and a “provably fair” verification system that allows auditing any roll.
Try it: presque.cool/dice/
Why this project
Every dice roller I found online had the same problem: they were ugly, slow, or both. I liked Google’s — simple, fast — but I wanted something more complete for my RPG sessions.
It was also the perfect candidate for my first real React application. A project with a clear scope, a real need, and enough technical complexity to learn without drowning.
Three goals:
- Learn React — my first serious project with the framework.
- Create a tool I’d actually use — fast, readable, suited for game sessions.
- Go beyond the basics — support rich notation and solve the trust problem with online results.
The technical core: a notation parser
Most dice rollers only handle XdY+N. Dice Roller goes further:
- Standard dice:
d4,d6,d8,d10,d12,d20,d100 - Modifiers:
+3,-2, and combinations (2d8+1d6+2) - Keep/Drop:
kh3(keep highest 3),dl1(drop lowest 1) - Rerolls:
r(reroll 1s),ro>=5(reroll once if ≥5) - Sorting:
sa(ascending),sd(descending) - Repetition:
(2d6+3)@10to roll the same formula 10 times - Tags:
#attackto annotate a roll
To parse all this, I implemented a recursive descent parser. It was my first time — I didn’t know exactly where I was going at first, but the approach naturally emerged when facing the complexity of nested expressions.
The parser produces a lightweight AST (a list of “parts”: dice or numeric modifiers), with strict validation: allowed dice types, bounds on dice count (≤100 per term), explicit error messages when users type k3 instead of kh3.
The trust problem: “provably fair”
In online RPGs, there’s always that doubt: “Was the die truly random, or did the app give me a rigged result?”
Dice Roller includes a verification system:
- Generation of local entropy (8 bytes via
crypto.getRandomValues) - Creation of a nonce (timestamp)
- Calculation of a SHA-256 hash from entropy, nonce, and an application secret
- Use of a deterministic PRNG (Mulberry32) to generate results
- Storage of a final hash in history
Any roll can be recalculated and verified client-side. It’s not absolute “anti-cheat” security (that would require a server), but it addresses the real need: being able to prove a result wasn’t modified after the fact.
Performance: Web Workers and heuristics
Parsing can become expensive with notations like 100d20. The strategy:
- Quickly count total dice via regex
- Above a threshold (50 dice), move parsing to a Web Worker
- On failure (worker unavailable, timeout), fallback to synchronous parsing
This ensures the interface stays smooth even with complex formulas.
Architecture: separation of concerns
The project follows a three-layer structure:
Services (pure business logic):
- Notation parser → AST
- Execution engine → detailed results
- Crypto → entropy, hash, PRNG
Hooks (UI orchestration):
- Roll state management (
isRolling,lastRoll) - Decision to use Web Worker or not
UI (components):
- Input with autocomplete
- Result display (total + kept/dropped dice details)
- Filterable history
- Modals (help, settings, verification)
This separation has a key advantage: the business core is testable and reusable independently of React.
UX: speed and readability
The goal was “one input, instant result”. Some choices:
- Smart autocomplete: notation suggestions, keyboard navigation (↑/↓/Tab/Enter)
- Presets: save a frequent roll to find it in autocomplete
- Filterable history: text or numeric search (
>15,<=10,15-20) - Detailed display: total immediately visible, dice details (kept, dropped) accessible
Autocomplete is disabled on mobile to avoid conflicts with the virtual keyboard — a pragmatic compromise.
Tech stack
- React + TypeScript
- Vite as bundler (with SWC)
- Zustand for state management — localStorage (settings) + IndexedDB (history) persistence
- Tailwind CSS in CSS-first mode (tokens via CSS variables, OKLCH colors)
- PWA — installable, offline
- i18n custom (French/English)
- Web Worker optional for heavy notations
- Compression — Brotli + Gzip on assets
- CSP — Content-Security-Policy with auto-generated hashes
Timeline
| Date | Version | Description |
|---|---|---|
| Oct 2025 | 1.0.0 | Initial version — Dice roller with notation parser, provably fair system and PWA |
Takeaways
Duration: about 1 month (October 2025).
What I learned:
- Building a React application end-to-end — my first serious project with the framework
- Implementing a recursive descent parser — a first, more accessible than I thought
- Integrating client-side cryptography for a concrete use case
What I’d do the same:
- Start from a real personal need — it motivates and gives clear direction
- Strictly separate business logic from UI from the start
What I’d change:
- Add 3D visualization to see the dice roll. The experience would be more immersive, and it would be an opportunity to explore Three.js or a similar library.