Skip to content

Tests Version License Supported Python versions Bear-ified™

Now you’re playing with …

dyce logo

dyce

dyce is a Python library for dice mechanics and other problems with a finite set of possible outcomes. It counts how many ways each outcome can occur, then calculates exact probabilities.

Suggested audiences and applications:

  • Game designers for crafting and compare game mechanics
  • Tool developers for using calculations in higher level tooling

Overview

  • H represents possible outcomes and their weights as integer counts. A fair, six-sided die can be represented as H({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1}), or the shorthand H((1, 2, 3, 4, 5, 6)), or simply H(6). The sum of three six-sided dice can be represented as 3 @ H(6).
  • P represents an ordered sequence of histograms. A pool of three separate six-sided dice can be represented as 3 @ P(H(6)), or the shorthand 3 @ P(6). Ps keep each die separate, so you can select any die or group of dice and calculate the distribution of their summed results.
  • P.survey and expand are experimental interfaces useful for modeling mechanics where an outcome of one die affects how others are included. Examples include: exploding dice, conditional re-rolls, or damage that depends on whether an attack hits.
    • explode_n is provided as a convenient shorthand.
  • Optional Matplotlib and Plotly helpers support visualization.

Example: d20 versus a “target number”

Consider a mechanic popularized by one of the world’s roll playing games1 where a roll on a (typically dodecahedral) twenty-sided die “succeeds” when it is greater than or equal to a specific value (a “target number”). This example compares the outcome distributions for one d20, the higher of two d20s (“advantage”), and the lower of two d20s (“disadvantage”).

1
2
3
4
>>> from dyce import H, P
>>> d20 = H(20)  # shorthand for an evenly-weighted 20-sided die
>>> d20
H({1: 1, 2: 1, 3: 1, ..., 18: 1, 19: 1, 20: 1})
1
2
3
4
5
6
7
>>> p2d20 = 2 @ P(d20)  # a pool of two such dice
>>> d20_advantage = p2d20.at(-1)  # right-most index (-1) selects highest
>>> d20_disadvantage = p2d20.at(0)  # left-most index (0) selects lowest
>>> d20_advantage
H({1: 1, 2: 3, 3: 5, ..., 18: 35, 19: 37, 20: 39})
>>> d20_disadvantage
H({1: 39, 2: 37, 3: 35, ..., 18: 5, 19: 3, 20: 1})

With a single d20, the chance of rolling a 15 or higher is 30%. With advantage, it climbs to 51%. With disadvantage, it drops to 9%.

1
2
3
4
5
6
7
8
>>> target_number = 15
>>> # How often a d20 is greater than or equal to target_number
>>> d20_vs_target = d20.ge(target_number)
>>> print(d20_vs_target.format())  # built-in text formatting
  avg |    0.30
  std |    0.46
False |  70.00% |#################################
 True |  30.00% |##############
1
2
3
>>> d20_advantage_vs_target = d20_advantage.ge(target_number)
>>> print(d20_advantage_vs_target.format_short())
{avg: 0.51, False: 49.00%, True: 51.00%}
1
2
3
>>> d20_disadvantage_vs_target = d20_disadvantage.ge(target_number)
>>> print(d20_disadvantage_vs_target.format_short())
{avg: 0.09, False: 91.00%, True:  9.00%}

Plot: Comparison of the higher of 2d20 vs. a single d20 and the lower of 2d20 vs. a single d20

Run this example in JupyterLite: Try <code>dyce</code>!

As one might expect, with a single d20, each outcome is equally likely, with 10.5 being the average. With advantage, one is likely to roll at least a 15 over half the time, and one is nearly twice as likely to roll a 20. With disadvantage, one is likely to roll at most a 6 over half the time, and one is nearly twice as likely to roll a 1.

Installation

1
python -m pip install dyce

dyce supports CPython 3.11–3.15 and PyPy 3.11-3.12. It depends on optype for static and runtime type-checking. dyce is available under the MIT License.

dyce will opportunistically use the following, if available:

Explore further

Other efforts

dyce does not stand alone. Other works include:

Please consider contributing an issue if you observe discrepancies or think something should be added to the list.

Donors

When one worries that the flickering light of humanity may be snuffed out at any moment, when one’s heart breaks at the perverse celebration of judgment, vengeance, and death and the demonizing of empathy, compassion, and love, sometimes all that is needed is the kindness of a single stranger to reinvigorate one’s faith that—while all may not be right in the world—there is hope for us human beings.


  1. Hat tip (or profuse apologies) to the Angry GM who is not only one of the world’s greatest role playing game masters, but a remarkably reflective human being for whom I have a tremendous amount of respect and gratitude. ↩