dice.py¶
A Pythonic definition of a DSL for dice expressions.
3 * D6 + 2
This creates an object with methods for rolling the specified dice, or computing the min, max, mean, and standard deviation.
Implementation¶
A module to model polyhedral dice.
While this isn’t hugely useful for TTRPG design or literary work, it does do some predictions of the expected distribution of a few simple dice mechanics.
Used as a library¶
In Rogue-like games, Gold is generally uniform distribution
between \(2l\) and \(16l\) for a given level, \(l\).
This module defines a UniformValue class for this.
>>> import random
>>> random.seed(42)
>>> level = 1
>>> gold_u = UniformValue(2 * level, 16 * level)
>>> gold_u.min, gold_u.max, 2*level, 16*level
(2, 16, 2, 16)
>>> gold_u.roll()
12
A slightly more colorful approach is to use a more normal distribution.
>>> level = 1
>>> gold_1 = (3 * level) * D6 - level
>>> gold_1.min, gold_1.max, 2*level, 16*level
(2, 17, 2, 16)
>>> str(gold_1)
'3D6-1'
>>> repr(gold_1)
'3 * D6 -1'
>>> level = 2
>>> gold_2 = (3 * level) * D6 - level
>>> gold_2.min, gold_2.max, 2*level, 16*level
(4, 34, 4, 32)
>>> level = 3
>>> gold_3 = (3 * level) * D6 - level
>>> gold_3.min, gold_3.max, 2*level, 16*level
(6, 51, 6, 48)
Using the library directly¶
PYTHONPATH=src python -c 'from dice import *; print((3*D6+2).roll())'
CLI¶
python tools/src/dice.py '3*D6+2'
The shell ' apostrophes are required.
Interactive
python tools/src/dice.py -i
Enter a Python-syntax dice expressions, like 3*D6+2.
[dice] (4*D6).kh(3)
6
[dice] count 6
[6 rolls] (4*D6).kh(3)
7
14
13
14
11
12
[6 rolls]
10
14
12
15
16
10
[6 rolls]
Die¶
- class dice.Die(d: int, *, n: int | None = None, adj: int | None = None, keep: int | None = None)[source]¶
Creates a useful source of random numbers from a “die expression”. A die expression can also provide some expected value information for most (not all) cases. The syntax uses Python objects and operators.
Examples:
>>> from random import seed >>> seed(42)
The base objects, D4, D6, etc.
>>> D6.roll() 6
A dice expression in Python syntax.
>>> d = 3 * D6 >>> d.roll() 8 >>> d.pool() [2, 2, 3] >>> str(d) '3D6'
A set of dice rolls. Note
d = 3 * D6.>>> roll10 = [(d - 3).roll() for i in range(10)] >>> roll10 [6, 14, 7, 0, 6, 8, 11, 12, 8, 3]
Expected values.
>>> d.min 3 >>> d.max 18 >>> d.mean 10.5 >>> d.stdev 2.958...
The “roll 4d6 keep the highest 3” expression. The ()’s are required.
>>> char = (4 * D6).kh(3) >>> char.pool() [3, 4, 6]
Expected Values¶
The expected values are exact, and can do things like transform a dice value into a z-score:
(d.roll() - d.mean) / d.stdev. This will have value from about -3 to about +3 and can be used to turn a number into a descriptive summary.The predictions do not account for
kh()modifiers.See https://rpubs.com/Avijit0616/698613
The expected values are the basis for comparison among two Die instances.
>>> D8 > D6 True
Note¶
A thing that “seems” to behave inconsistently in unit tests.
>>> seed(42) >>> [r for r in range(10) if D20.roll() >= 13] [7, 9]
- property mean: float¶
The mean of this dice expression.
The mean, or expected value is
\[E(F) = \frac{1}{f}\sum\limits_{1 \leq x < f} x = \frac{f + 1}{2}\]For “keep high” rules, we could enumerate all possible rolls. It’s this:
rolls = chain(*self.n * [range(1, 1 + self.faces)]) rolls_kh = (sorted(r)[-self.keep:] for r in rolls) domain = [sum(r) + self.adj for r in rolls_kh] d_m = mean(domain) d_s = stdev(domain)
- property stdev: float¶
The standard deviation of this dice expression.
The variance, \(\sigma^2\), or \(\text{var}\), is
\[\text{Var}(F) = E(F^2) - E(F)^2\]The expected value, \(E(F)\), is the mean.
The sum of the squares, \(E(F^2)\), is
\[E(F^2) = \frac{1}{f}\sum\limits_{1 \leq x < f} x^2 = \frac{\frac{f^{3}}{3} + \frac{f^{2}}{2} + \frac{f}{6}}{f}\]We can compute the variance as follows:
\[\begin{split}\text{Var}(F) &= E(F^2) - E(F)^2 \\ &= \frac{\frac{f^{3}}{3} + \frac{f^{2}}{2} + \frac{f}{6}}{f} - \frac{f + 1}{2}^2 \\ &= \frac{\frac{f^{3}}{3} + \frac{f^{2}}{2} + \frac{f}{6}}{f} - \left(\frac{f}{2} + \frac{1}{2}\right)^{2}\end{split}\]The mean and variance scale linearly for the number of dice, \(d\). The mean of multiple dice, \(d\), is \(\mu(F, d) = E(F) \times d\). The variance, of multiple dice, \(d\), similarly is \(\text{Var}(F, d) = \text{Var}(F) \times d\).
The standard deviation doesn’t scale linearly; it is \(\sigma = \sqrt{\text{Var}(F) \times d}\).
Wild Die¶
- class dice.WildDie(d: int, *, n: int | None = None, adj: int | None = None, keep: int | None = None)[source]¶
The “Wild Die” mechanic for OpenD6 games.
A 1 is a critical failure. A 6 is a critical success, roll again and accumulate the total.
>>> seed(42) >>> dice = 5 * WildDie(6) >>> dice.roll() 30 >>> dice.wild 'success' >>> dice.roll() 32 >>> dice.wild 'success' >>> dice.roll() 18 >>> dice.wild ''
UniformValue¶
- class dice.UniformValue(low: int, high: int)[source]¶
A uniformly distributed value on the closed interval. Both ends included. Used with Aggregates that have a uniform distribution of instances.
This is compatible with
Dieto permit aggregation of distinct domains.This isn’t obviously useful, really.
- property mean: float¶
Mean of the uniform distribution.
- property stdev: float¶
Standard deviation of the uniform distribution.
Interaction¶
- class dice.Interaction(completekey='tab', stdin=None, stdout=None)[source]¶
An Interactive dice roller.
- do_EOF(arg: str) bool | None¶
Exit the the dice-roller.
- do_exit(arg: str) bool | None¶
Exit the the dice-roller.
CLI¶
- dice.main(interactive: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x1042982d0>] = False, expected_value: ~typing.Annotated[bool, <typer.models.OptionInfo object at 0x104298410>] = False, count: ~typing.Annotated[int, <typer.models.OptionInfo object at 0x104298690>] = 1, expression: ~typing.Annotated[str, <typer.models.ArgumentInfo object at 0x104278ec0>] = '', seed_value: ~typing.Annotated[str | None, <typer.models.OptionInfo object at 0x104298910>] = None)[source]¶
CLI for dice.
Either provide a Python-syntax dice expression or start an interactive command loop. The Python syntax means the * is required for ‘3*D6’.