DicEx (dicEx v0.3.0)

Copy Markdown View Source

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

TokenMeaning
NdSRoll 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
! / !pExplode / explode & penetrate
r<op>nReroll (< <= = >= >); 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

roll_opt()

@type roll_opt() ::
  {:rng, module() | {module(), term()}}
  | {:seed, integer()}
  | {:max_dice, pos_integer()}
  | {:max_sides, pos_integer()}
  | {:max_length, pos_integer()}

Functions

format(result)

@spec format(DicEx.Result.t()) :: String.t()

Pretty-prints a result as a human readable string, e.g. "2d20kh1+5 = 23".

renderer_capabilities()

@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.

roll(expression, opts \\ [])

@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 :rand state is left untouched.
  • :rng — an explicit RNG module (stateless) or {module, state} tuple.
  • :max_dice — maximum total dice across the expression (default 100).
  • :max_sides — maximum sides per die (default 1000).
  • :max_length — maximum expression length in characters (default 256).

Expressions that would never finish (1d6r<=6, 1d1!) are rejected.

roll_dice(count, sides, opts \\ [])

@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 + 5

Options

  • :mod — integer modifier added to the total (+/-).
  • :advantage — boolean, keeps highest of 2 (only when count == 1).
  • :disadvantage — boolean, keeps lowest of 2 (only when count == 1). Advantage and disadvantage together cancel out (D&D 5e), rolling one die.
  • Plus any option accepted by roll/2 (:seed, :rng).

roll_e(expression, opts \\ [])

@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).