DicEx (dicEx v0.1.0)

Copy Markdown View Source

Pixel-art 3D dice roller. The roll itself is computed authoritatively in Elixir; the optional LiveView component (DicExWeb.DiceRoller) visualises the result with Three.js + Rapier physics.

Quick start

iex> DicEx.roll("1d20")
%DicEx.Result{total: 14, ...}

iex> DicEx.roll("3d6 + 2")
%DicEx.Result{total: 13, ...}

iex> DicEx.roll("2d20kh1")      # advantage — keep highest
%DicEx.Result{...}

iex> DicEx.roll("4d6dl1")       # 4d6, drop lowest (ability score)
%DicEx.Result{...}

iex> DicEx.roll("8d6!")         # explode (Sneak Attack / fire)
%DicEx.Result{...}

iex> DicEx.roll("1d20r1")       # reroll natural 1s
%DicEx.Result{...}

Notation

TokenMeaning
NdSRoll N dice with S sides (d4..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 (< <= = >= >), append o for once
+ / -Add / subtract modifiers or pools

Reproducible rolls (tests, replays, anti-cheat)

Seed the default RNG for a reproducible sequence:

DicEx.roll("2d20kh1", seed: 42)

Or thread an explicit RNG module/tuple via the lower-level roll/2 second argument.

A note on :seed

:seed reseeds the calling process's :rand state to produce a reproducible sequence. The prior state is not restored, so in a long-lived process (e.g. a LiveView) a later unseeded roll/2 continues the seeded sequence rather than drawing fresh entropy. Thread :rng explicitly when you need isolation, or re-seed per request.

Summary

Functions

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

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()}

Functions

format(result)

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

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

roll(expression, opts \\ [])

@spec roll(binary(), [roll_opt()]) :: DicEx.Result.t()

Rolls the given expression and returns a DicEx.Result.

Options

  • :seed — integer seed for the default RNG (reproducible sequence).
  • :rng — an explicit RNG module (stateless) or {module, state} tuple.

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 (implies 2 dice if count==1).
  • :disadvantage — boolean, keeps lowest of 2.
  • Plus any option accepted by roll/2 (:seed, :rng).

roll_e(expression, opts \\ [])

@spec roll_e(binary(), [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).