I’m going to discuss some basic probabilities about D&D dice rolling. Yes, I’m that big of a geek. At the same time, I am not a statistician, so it’s entirely possible I’ve screwed something up.
Rolling a single six-sided die (d6), we know that the probability of rolling any given value is 1 in 6. This should not a be a surprise. If it is, you might want to brush up on your dice knowledge. In the older days of D&D we would typically roll 3 six-sided dice (3d6), and take their sum to use as one of our ability scores (eg strength, intelligence, charisma, etc). In more recent editions of the game, a new method has emerged, generally known as 4d6 drop lowest
. It’s just what it sounds like, you roll 4d6, remove the lowest of the rolls, and sum the remaining three to use for your ability score. This can make it a little trickier to figure out what the average and standard deviation are. Fortunately, it doesn’t make it so different that a little scripting can’t find the answers for us.
So how do we calculate these means and standard deviations? Simple, we create a two dimensional array, and in that array, we place all the possible ways we could roll 4d6. It basically looks like this:
[ [ 1, 1, 1, 1 ], [ 1, 1, 1, 2 ], [ 1, 1, 1, 3 ], … [ 6, 6, 6, 4 ], [ 6, 6, 6, 5 ], [ 6, 6, 6, 6 ], ]
Now, we sum each row, and find the mean of those sums, we’ll have found the average roll when rolling 4d6. That’s close to what we want. To simulate dropping the lowest, I simply find the lowest die roll in each row, and set its value to 0. Then I repeat the process above, summing each row, and the average of those sums is now the average for 4d6 drop lowest. If I take the standard deviation of those rolls, I’ll have the standard deviation of this method. All this leads to the following:
| Max | 18 |
|---|---|
| Min | 3 |
| Mean | 12.2446 |
| Std Dev | 2.8468 |
But what does it mean? Simply put, if you roll 4d6 drop lowest, you are most likely to roll a 12. Additionally, there is a ? 68% chance that you’ll roll within one standard deviation of the mean, or [9,15]. The chances that you’ll get any given roll are in the table below:
| Roll | Probability | Frequency | ? Odds |
|---|---|---|---|
| 3 | 0.000772 | 1 | 1 in 1,295 |
| 4 | 0.003086 | 4 | 1 in 324 |
| 5 | 0.007716 | 10 | 1 in 130 |
| 6 | 0.016204 | 21 | 1 in 62 |
| 7 | 0.029321 | 38 | 1 in 34 |
| 8 | 0.047840 | 62 | 1 in 21 |
| 9 | 0.070216 | 91 | 1 in 14 |
| 10 | 0.094136 | 122 | 1 in 11 |
| 11 | 0.114198 | 148 | 1 in 9 |
| 12 | 0.128858 | 167 | 1 in 8 |
| 13 | 0.132716 | 172 | 1 in 8 |
| 14 | 0.123457 | 160 | 1 in 8 |
| 15 | 0.101080 | 131 | 1 in 10 |
| 16 | 0.072531 | 94 | 1 in 14 |
| 17 | 0.041667 | 54 | 1 in 24 |
| 18 | 0.016204 | 21 | 1 in 62 |
Here’s a pretty picture for you:
You might notice that the mean is marked below the peak of the distribution. I’m not entirely sure why that is, but I suspect it has to do with the facts that we’re dropping the lowest roll, combined with how the curve gets cut off at 18. Dropping the lowest roll shifts the mean to a higher number, but because we can’t possibly go higher than 18, the right-side tail of the distribution gets cut off, causing the mean to be less than the peak of the distribution.
Lastly, here’s a little python script that can calculate these things for you. It will also generate two gnuplot data files, one of which was used to generate the pretty picture above. Yes, this code is quick and dirty, and certainly not production quality, but it gets the job done.
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
from numpy import *
import sys
import os
import random
import pprint
outcomes = []
# roll 4d6
for d1 in range(1, 7):
for d2 in range(1, 7):
for d3 in range(1, 7):
for d4 in range(1, 7):
outcome = array([d1, d2, d3, d4])
outcomes.append(outcome)
outcomes = array(outcomes)
hist = {}
# drop the lowest, count the number of times a given sum is found
for x in range(len(outcomes)):
outcomes[x][outcomes[x].argmin()] = 0
if outcomes[x].sum() in hist:
hist[outcomes[x].sum()] += 1
else:
hist[outcomes[x].sum()] = 1
rsum = outcomes.sum()
sums = outcomes.sum(axis=1)
mean = sums.mean()
sigma = sums.std()
counts = array(hist.values())
hist2 = hist.copy()
# determine the probability of each sum occurring
for key,value in hist2.items():
hist2[key] = counts[key-3] / float(counts.sum())
f = open("dice.data", "w")
for key in hist2.keys():
print >>f, "%d\t%0.12f" % (key, hist2[key])
f.close()
f = open("dice2.data", "w")
for key,value in hist2.items():
print >>f, "%d\t%f" % (key, value)
f.close()
pprint.pprint(hist.values())
args = {
"mean": mean,
"sigma": sigma,
"-sigma": mean - sigma,
"+sigma": mean + sigma,
"++sigma": mean + (2 * sigma),
"--sigma": mean - (2 * sigma),
"+++sigma": mean + (3 * sigma),
"---sigma": mean - (3 * sigma),
}
labels = {
"+sigma_label": mean + sigma - 0.1,
"-sigma_label": mean - sigma + 0.1,
"++sigma_label": mean + (2*sigma) - 0.1,
"--sigma_label": mean - (2*sigma) + 0.1,
"+++sigma_label": mean + (3*sigma) - 0.1,
"---sigma_label": mean - (3*sigma) + 0.1,
"mean_label": mean - 0.1,
}
args.update(labels)
gnuplot = """
set terminal png enhanced size 800,600 font '/Library/Fonts/Arial.ttf' 8
set output "dice.png"
set arrow from %(mean)f,0 to %(mean)f,0.14 nohead lc 1
set label "? (%(mean)0.2f)" at %(mean_label)f,0.05 right tc ls 1
set arrow from %(-sigma)f,0 to %(-sigma)f,0.14 nohead lc 2
set label "?? (%(-sigma)0.2f)" at %(-sigma_label)f,0.05 tc ls 2
set arrow from %(+sigma)f,0 to %(+sigma)f,0.14 nohead lc 2
set label "+? (%(+sigma)0.2f)" at %(+sigma_label)f,0.05 right tc ls 2
set arrow from %(--sigma)f,0 to %(--sigma)f,0.14 nohead lc 2
set label "?2? (%(--sigma)0.2f)" at %(--sigma_label)f,0.05 tc ls 2
set arrow from %(++sigma)f,0 to %(++sigma)f,0.14 nohead lc 2
set label "+2? (%(++sigma)0.2f)" at %(++sigma_label)f,0.05 right tc ls 2
set arrow from %(---sigma)f,0 to %(---sigma)f,0.14 nohead lc 3
set label "?3? (%(---sigma)0.2f)" at %(---sigma_label)f,0.05 tc ls 3
set arrow from %(+++sigma)f,0 to %(+++sigma)f,0.14 nohead lc 3
set label "+3? (%(+++sigma)0.2f)" at %(+++sigma_label)f,0.05 right tc ls 3
plot 'dice.data' with lines smooth csplines title '4d6 drop lowest'
""" % args
f = open("dice.gnuplot", "w")
f.write(gnuplot)
f.close()
gnuplot = """
set terminal png enhanced size 800,600 font '/Library/Fonts/Arial.ttf' 8
set output "dice2.png"
set boxwidth 0.9 relative
set style data histograms
set style fill solid 1.0 border -1
set style data histogram
plot 'dice2.data' using 2:xticlabels(1) title '4d6 drop lowest'
"""
f = open("dice2.gnuplot", "w")
f.write(gnuplot)
f.close()
print "mean: %f" % mean
print "sigma: %f" % sigma
print "1 sigma: %0.4f - %0.4f" % (mean - sigma, mean + sigma)
print "2 sigma: %0.4f - %0.4f" % (mean - 2*sigma, mean + 2*sigma)
print "3 sigma: %0.4f - %0.4f" % (mean - 3*sigma, mean + 3*sigma)
os.system("gnuplot 'dice.gnuplot'")
os.system("gnuplot 'dice2.gnuplot'")
Download the 4d6 Drop Lowest Python Script.