A dice rolling library for use in games. Specifically to be used for games using dice notation.
This package provides a small, safe parser and pure roll function. The API is intentionally split:
- Parse(notation string) (ParsedDice, error) — parses dice notation into a value.
- RollParsed(pd ParsedDice, rng *rand.Rand) (RollResult, error) — rolls the parsed dice using a provided RNG. Pass nil to use the package default RNG.
Supported notation (subset):
- NdS, dS (e.g.
3d6,d20) - Fate dice:
NF(e.g.4Fproduces values in -1..1 per die) - Modifiers:
+ - x /(x is multiplication;*is accepted and normalized tox) - Exploding:
!after sides, e.g.2d6! - Exploding:
!after sides, e.g.2d6! - Penetrating explosions:
!pafter sides, e.g.1d6!p(each extra exploded roll contributes face-1) - Keep/Drop:
kh/kl/dh/dlork/dshorthand, with a count. Examples:4d6kh3,4d6d1,4d6k3. - Percentile:
d%is accepted asd100. - Rerolls:
r/rowith comparators and per-die cap. Examples:r1(reroll faces equal to 1 until they are different)ro1(reroll once if equal to 1)- comparator forms:
r<2,r>=5,r!=3 - ranges:
rA-Bwill reroll while face ∈ [A,B], e.g.r1-3rerolls faces 1,2,3 - lists:
rA,B,Crerolls when face is any of the listed values, e.g.r1,3,5 - per-die cap: append
#Nto limit rerolls per die, e.g.r1#2orr1-3#2 - safety: the unsafe operator
r!=N(reroll while face != N) is only allowed when paired with a per-die cap#Nor when used as a one-time rerollro!=N. Unboundedr!=Nwithout#Nwill be rejected at parse time.
- Success counting:
>=,>,<=,<,=after the dice term, e.g.10d10>=8.
To avoid resource exhaustion or accidental infinite loops the parser/runner enforces reasonable limits:
- Max dice count: 1000
- Max sides per die: 1,000,000
- Max total RNG calls per roll (including explosions): 10,000
Exploding on a 1-sided die is rejected to prevent infinite exploding loops.
Basic roll:
pd, err := dice.Parse("3d6")
if err != nil { /* handle */ }
res, err := dice.RollParsed(pd, nil) // use default RNG
// res.Rolls -> per-die results
// res.Total -> sumDeterministic tests (seed RNG):
// rand/v2 PCG-based RNG for deterministic results
rng := rand.New(rand.NewPCG(600, 601))
pd, _ := dice.Parse("4d6kh3")
res, _ := dice.RollParsed(pd, rng)
// res contains deterministic resultsExploding dice and keep-highest example:
pd, _ := dice.Parse("4d6!kh3")
res, _ := dice.RollParsed(pd, nil)Penetrating explosion example:
// deterministic PCG seed used for example reproducibility
rng := rand.New(rand.NewPCG(600, 727))
pd, _ := dice.Parse("1d6!p")
res, _ := dice.RollParsed(pd, rng)
// With a roll sequence of 6, 6, 3 this produces: 6 + (6-1) + (3-1) = 13// reroll ones until not 1
pd, _ := dice.Parse("4d6r1")
res, _ := dice.RollParsed(pd, nil)
// reroll once (ro)
pd2, _ := dice.Parse("4d6ro1")
res2, _ := dice.RollParsed(pd2, nil)
// success counting: count how many d10 results are >= 8
pd3, _ := dice.Parse("10d10>=8")
res3, _ := dice.RollParsed(pd3, nil)
// res3.Successes contains the number of successesAfter the dS portion (and optional ! for exploding) tokens may appear in any order. Supported trailing tokens are:
- Keep/Drop:
kordoptionally followed byh/land a count, e.g.kh3ord2. - Reroll:
rorroplus comparator and value. Comparator can be=,!=,<,<=,>,>=. Example:r<2,ro!=3,r1(shorthand forr=1). Optionally append per-die cap#N(e.g.r1#2). - Reroll:
rorroplus comparator, value, range, or list. Comparator can be=,!=,<,<=,>,>=. Examples:r<2,ro!=3,r1(shorthand forr=1). Ranges (rA-B) and lists (rA,B,C) are supported:r1-3,r1,3,5. Optionally append per-die cap#N(e.g.r1#2,r1-3#2).r!=Nis only allowed withroor#N(see safety note above). - Success operator:
>=N,>N,<=N,<N,=N(counts successes among kept dice). - Arithmetic modifier:
+N,-N,xN.
Examples of equivalent orderings:
4d6kh3r1==4d6r1kh32d6!r<2+1==2d6!+1r<2
- Rerolls are applied to the raw die face before explosions. Exploding is evaluated on the final face after rerolls.
- All RNG calls (initial, rerolls, explosions) are counted toward MaxTotalRolls. If that limit is exceeded RollParsed returns an error.
- Parse errors now include the original notation in messages for easier debugging.
The parser is intentionally conservative and we welcome contributions — please open an issue or PR. Implemented extensions include reroll comparator predicates (r/ro with =, !=, <, <=, >, >=), per-die reroll caps using #N (e.g. r1#2), and success thresholds (>=, >, <=, <, =).
If you want additional features (compound/penetrating explosions, multi-term expression parsing, advanced reroll predicates, a CLI/HTTP API, or analytic helpers), open an issue or submit a PR — we can extend the parser and the pure roller incrementally.