Quick start
DicEx.roll("1d20") # => %DicEx.Result{total: 14, ...}
DicEx.roll("3d6 + 2") # => %DicEx.Result{total: 13, ...}
DicEx.roll("2d20kh1") # advantage — keep highest
DicEx.roll("4d6dl1") # 4d6, drop lowest
DicEx.roll("8d6!") # explode (fireball)
DicEx.roll("1d20r1") # reroll natural 1s
# safe variant for untrusted/LLM-generated expressions
{:ok, result} = DicEx.roll_e(prompted_by_the_llm)
# programmatic API matching a UI's "count + die + modifier"
DicEx.roll_dice(2, 20, mod: 5, advantage: true)
# reproducible
DicEx.roll("4d6", seed: 42)Notation reference
| Token | Meaning |
|---|---|
NdS | Roll N dice of S sides (dS = 1dS) |
d% | Percentile die (d100) |
kh[n] | Keep highest n (advantage) |
kl[n] | Keep lowest n (disadvantage) |
dh[n] | Drop highest n |
dl[n] | Drop lowest n |
! / !p | Explode / explode & penetrate |
r<op>n | Reroll (< <= = >= >); ro rerolls once |
+ / - | Add / subtract pools or modifiers |
Only +/- compose — there's no *, /, or parentheses. A leading sign
applies to the first term (-1d4+5). One reroll modifier per pool.
Limits
Expressions are bounded so untrusted input can't exhaust the process: at most 100 dice in total, 1000 sides per die and 256 characters. Override per call:
DicEx.roll_e("150d6", max_dice: 200, max_sides: 1000, max_length: 256)A repeating reroll that matches every face (1d6r<=6) and exploding a d1 are
rejected, since they would never finish.
Reproducible rolls
Pass a seed for a reproducible sequence — useful for tests, replays, and anti-cheat audits:
DicEx.roll("2d20kh1", seed: 42)The seeded state is private to the call (DicEx.RNG.Seeded); the calling
process's :rand state is left untouched.
Summary
Functions
Pretty-prints a result as a human readable string, e.g. "2d20kh1+5 = 23".
Capabilities of the bundled renderer, so hosts can feature-detect across
package versions. authoritative_3d: true means the 3D hook accepts
authoritative: true roll events and settles on the supplied faces.
Rolls the given expression and returns a DicEx.Result.
Convenience for a single typed roll — the shape the dragonEx UI emits.
Like roll/2 but returns {:ok, result} or {:error, reason} without
raising. Handy when the expression comes from untrusted input (e.g. a chat
command parsed by an LLM in dragonEx).
Types
@type roll_opt() :: {:rng, module() | {module(), term()}} | {:seed, integer()} | {:max_dice, pos_integer()} | {:max_sides, pos_integer()} | {:max_length, pos_integer()}
Functions
@spec format(DicEx.Result.t()) :: String.t()
Pretty-prints a result as a human readable string, e.g. "2d20kh1+5 = 23".
@spec renderer_capabilities() :: %{authoritative_3d: boolean()}
Capabilities of the bundled renderer, so hosts can feature-detect across
package versions. authoritative_3d: true means the 3D hook accepts
authoritative: true roll events and settles on the supplied faces.
@spec roll(term(), [roll_opt()]) :: DicEx.Result.t()
Rolls the given expression and returns a DicEx.Result.
Options
:seed— integer seed for a reproducible sequence. The seeded state is private to the call; the caller's:randstate is left untouched.:rng— an explicit RNG module (stateless) or{module, state}tuple.:max_dice— maximum total dice across the expression (default100).:max_sides— maximum sides per die (default1000).:max_length— maximum expression length in characters (default256).
Expressions that would never finish (1d6r<=6, 1d1!) are rejected.
@spec roll_dice(pos_integer(), pos_integer(), keyword()) :: DicEx.Result.t()
Convenience for a single typed roll — the shape the dragonEx UI emits.
DicEx.roll_dice(2, 20) # 2d20
DicEx.roll_dice(1, 20, mod: 5) # 1d20 + 5Options
:mod— integer modifier added to the total (+/-).:advantage— boolean, keeps highest of 2 (only whencount == 1).:disadvantage— boolean, keeps lowest of 2 (only whencount == 1). Advantage and disadvantage together cancel out (D&D 5e), rolling one die.- Plus any option accepted by
roll/2(:seed,:rng).
@spec roll_e(term(), [roll_opt()]) :: {:ok, DicEx.Result.t()} | {:error, String.t()}
Like roll/2 but returns {:ok, result} or {:error, reason} without
raising. Handy when the expression comes from untrusted input (e.g. a chat
command parsed by an LLM in dragonEx).