Rolling dice with {droll}

Rolling dice with {droll}

2021-08-12 · This page is made of stone

droll \ˈdrōl\ adjective. 1. having a humorous, whimsical, or odd quality.

droll is an R package for parsing dice notation, analyzing rolls, calculating success probabilities, and plotting outcome distributions. It can help detail-oriented DMs prepare (un)fair encounters in advance or decide on skill check DCs on the fly. Players might also find it useful for determining the best course of action when in a tough situation.

It is designed to be a very lightweight (only one required dependency), very fast (less than 0.4s to get the full distribution of 40d6), and very precise (symbolic internal representation courtesy of Ryacas) anydice for R.

Installation #

Install the released version of droll from CRAN with:

1install.packages("droll")

Or install the development version from GitHub with:

1# install.packages("remotes")
2remotes::install_github("curso-r/droll")

Usage #

What are you looking for in droll? Are you a level 1 user, a seasoned level 10 programmer, or a god-like level 20 statistician? Choose your class:

🖍️ User #

The most basic usage of droll involves simply rolling dice. You can create any die you want with the d() function and then write an expression that involves that die. Note that, if you want to roll NdX, you should write N * dX.

 1# Create some dice
 2d20 <- d(20)
 3d6  <- d(6)
 4d4  <- d(4)
 5
 6# Roll a skill check while blessed
 7(d20 + 8) + d4
 8#> [1] 26
 9
10# Roll for damage!
118 * d6
12#> [1] 17
13
14# Dexterity saving throw with disadvantage
15if (min(d20, d20) + 4 < 18) {
16  print("Full damage!")
17} else {
18  print("Half damage.")
19}
20#> [1] "Full damage!"

Nice and easy, right? If you are a DM, you might also want to use two functions: check_prob() and check_dc(). They allow you to, respectively, calculate the probability of passing (or failing) a skill check and find the necessary DC so a skill check has a given probability of success (or failure). You don’t even need to create the dice you’re going to use inside these two!

 1# What's the probability of this player succeeding in a DC 15 skill check?
 2check_prob(d20 + 8, 15)
 3#> [1] 0.7
 4
 5# What should the DC be for this player to have a 50% chance of success?
 6check_dc(d20 + 8, 0.5)
 7#> [1] 19
 8
 9# What's the probability of this player failing in a DC 10 skill check?
10check_prob(d20 + 8, 10, success = FALSE)
11#> [1] 0.05
12
13# What should the DC be for this player to have a 90% chance of failure?
14check_dc(d20 + 8, 0.9, success = FALSE)
15#> [1] 27

There are no attack_*() functions because the mechanics of checks and attacks is the same, i.e., success means rolling a value higher than or equal to a certain threshold. These functions can, therefore, be used for attacks too!

🗡️ Programmer #

If you are already used to R’s d/p/q/r notation, you might want to get deeper insights into the roll distribution. This is why the droll(), proll(), qroll(), and rroll() functions exist! They are, respectively, the density, the distribution function, the quantile function, and the random generation for the distribution described by a roll expression.

 1# P[d20 + 8 = 12]
 2droll(12, d20 + 8)
 3#> [1] 0.05
 4
 5# P[d20 + 8 <= 12]
 6proll(12, d20 + 8)
 7#> [1] 0.2
 8
 9# inf{x: P[d20 + 8 <= x] >= 0.5}
10qroll(0.5, d20 + 8)
11#> [1] 18
12
13# Draw 3 simulations from d20 + 8
14rroll(3, d20 + 8)
15#> [1] 14 24 18

Once you know how to use these four functions, you can look for their plot_*() variations. They generate plots (using ggplot2 if it’s available) corresponding to the full distributions of d/p/q and a simple histogram in the case of plot_rroll().

1# Density of 8d6
2droll_plot(8 * d6)

1# Distribution function of 8d6
2proll_plot(8 * d6)

1# Quantile function of 8d6
2qroll_plot(8 * d6)

1# Histogram of 1000 rolls of 8d6
2rroll_plot(1000, 8 * d6)

Every p/q function also has a convenient lower.tail argument that can be set to FALSE in order to run calculations from the upper tail of the distribution.

🪄 Statistician #

Since you are an R veteran, you should be able to bend droll to your will. If you’d like to peek into the fabric of droll’s reality, you can use the r() function to get a full roll distribution. If you want maximum precision, you can also stop droll from casting its internal representation (powered by Ryacas) to doubles with precise = TRUE.

 1# Get full distribution of 8d6
 2r(8 * d6)
 3#> # A tibble: 41 × 4
 4#>    outcome     n           d           p
 5#>      <dbl> <dbl>       <dbl>       <dbl>
 6#>  1       8     1 0.000000595 0.000000595
 7#>  2       9     8 0.00000476  0.00000536
 8#>  3      10    36 0.0000214   0.0000268
 9#>  4      11   120 0.0000714   0.0000982
10#>  5      12   330 0.000196    0.000295
11#>  6      13   792 0.000472    0.000766
12#>  7      14  1708 0.00102     0.00178
13#>  8      15  3368 0.00201     0.00379
14#>  9      16  6147 0.00366     0.00745
15#> 10      17 10480 0.00624     0.0137
16#> # … with 31 more rows
17
18# Unlimited power
19r(8 * d6, precise = TRUE)
20#> # A tibble: 41 × 4
21#>    outcome n     d          p
22#>      <dbl> <chr> <chr>      <chr>
23#>  1       8 1     1/1679616  1/1679616
24#>  2       9 8     1/209952   1/186624
25#>  3      10 36    1/46656    5/186624
26#>  4      11 120   5/69984    55/559872
27#>  5      12 330   55/279936  55/186624
28#>  6      13 792   11/23328   143/186624
29#>  7      14 1708  427/419904 2995/1679616
30#>  8      15 3368  421/209952 707/186624
31#>  9      16 6147  683/186624 695/93312
32#> 10      17 10480 655/104976 11495/839808
33#> # … with 31 more rows

The data frame returned by r() can be used as the roll argument of every function discussed above. This skips all internal calculations, so it’s a useful shortcut if you want to run multiple diagnostics on the same roll expression.

As a level 20 statistician, you are not constrained by droll’s built-in dice either. You can create custom dice using the same d() function described before.

 1# Create a fudge die
 2dF <- d(-1:1)
 3rroll(5, dF)
 4#> [1]  1 -1  1 -1 -1
 5
 6# Create a 2d20kh, the "advantage die"
 7df <- r(max(d20, d20))
 8kh <- d(rep(df$outcome, df$n))
 9rroll(5, kh)
10#> [1] 20 11 16 20  8