Savage Worlds Dice Probabilities

The basic game mechanic of Savage Worlds involves trying to roll a target number or better with a 4, 6, 8, 10, or 12-sided die.

Savage Worlds Dice

The die can ace if the player rolls the maximum number on it, allowing the player to roll again, adding the result to their original roll. In other games, this mechanic is often referred to as exploding dice or open-ended dice, as the player may continue to roll until they do not roll the max value. For example, if you rolled an 8 on an 8-sided die, you could keep rolling until you did not roll an 8.

Open-Ended d8 Roll

Additionally, player characters and powerful non-player characters are considered wild cards. Wild cards roll an additional wild die, typically a 6-sided die, and the player uses the better of the two rolls. This wild die can also ace. For example, if you rolled another 8-sided die along with a wild die, you might get a higher result on the d6 than the d8.

d8 Roll with Wild Die

As mentioned on other sites, this makes for some interesting probabilities. However, many of these articles either don’t provide a way to calculate the odds of these mechanics, or they provide a more mathematical explanation that might be hard to comprehend if you’ve been out of college for a while like myself.

This article attempts to explain these probabilities in a more accessible way using a close analog to dice, rational numbers in the form of fractions, along with some Python code to aid in calculating probabilities easily.

Exploding Die Probability

The probability of hitting a given number on a die expressed as a rational number is the number of values that will succeed over the number of sides the die has. For example, the probability of hitting an 8 on a d8 is 1/8. Only rolling an 8 is a success. Dividing 1 by 8 and multiplying by 100% gives us a decimal probability of 12.5%. The probability of hitting a 6 or better on a d8 is 3/8, or 37.5%, as rolling a 6, 7, or 8 are all considered successes.

The probability of hitting a 10 on an exploding d8 involves two independent events: first rolling an 8, then rolling a 2 or better. The probability of independent events involves multiplying the probability of the events together.1

\(P(E ∩ F) = P(E) × P(F)\\\)

The chance of rolling the first 8 is 1/8, while the chance of rolling a 2 or better is 7/8 (any value but 1 succeeds). Thus, the chance of rolling a 10 with an exploding d8 is:

\(P = \frac{1}{8}\times\frac{7}{8} = \frac{7}{64} = \textbf{10.9375%}\\\)

Python contains a fractions module that makes coding this trivial.

from fractions import Fraction

def exploding(target, sides):
    exact, remainder = divmod(target, sides)
    exact_roll_prob = Fraction(1, pow(sides, exact)) # 1/n + 1/n + ...
    last_roll_prob = Fraction(sides - (max(remainder, 1) - 1), sides)
    return exact_roll_prob * last_roll_prob

You can compare the results of your calculations using tools such as Troll. Values may not match exactly, however. The code above performs its calculations with rational numbers before converting to floating point values for the final answer. If we were to use floating point numbers for the calculations as well, rounding errors due to floating point math imprecision would introduce small variances in the answer. This is most likely what is happening with Troll.

Wild Die Probability

The probability of hitting a 10 with an exploding d8 and an exploding d6 wild die involves a union of events that are not mutually exclusive. The probability of mutually exclusive events involves adding the probability of the events together. However, we’re not done yet. There is a chance that you’ll hit with both die, and we don’t want to count the probability of hitting the target with both dice when just one of them counts as a success. So to get an accurate probability, we need to add the probability of hitting with either die and subtract the chance of hitting both.

\(P(E ∪ F) = P(E) + P(F) − P(E ∩ F)\\\)

The probability of hitting a 10 with a d8 and a d6 wild die as described earlier is

 \(P(E) = \frac{1}{8}\times\frac{7}{8} = \frac{7}{64} = 10.9375\%\\\)

 \(P(F) = \frac{1}{6}\times\frac{3}{6} = \frac{3}{36} = 8.3333\%\\\)

 \(P(E ∩ F) = \frac{7}{64}\times\frac{3}{36} = \frac{21}{2304} = 0.9114\%\\\)

\(P(E ∪ F) = \frac{7}{64}+\frac{3}{36}-\frac{21}{2304} = \frac{47}{256} = \textbf{18.3593%}\\\)

The Python code to calculate the probability of hitting a target number or better with a given dice and a wild die builds upon the code for exploding die.

def wild(target, sides, wild=6):
    a = exploding(target, sides)
    b = exploding(target, wild)
    return a + b - (a * b)

With the two Python functions, you can accurately calculate the probabilities to hit a range of target numbers with assorted dice.


Resources and References

Statistics in a Nutshell, 2nd Edition

Chapter 2 of O’Reilly’s Statistics in a Nutshell covers probability and was a useful resource in understanding the math from a more practical angle. Specifically useful was the section on calculating the probability of mutually exclusive events starting on page 32.

Python Code and Chart Data

The Python code for this article can be found in a single script in this gist. The data for the charts above was created using the script as is available in this CSV file. The script has been tested with Python 2.7.3 and 3.3.0.

Random Kung Fu Move Name Generator in 5 Lines of Python

I love Python because of things like random.choice, a useful little method that allows you to create mission-critical functionality with very little code. For example, who doesn’t need a random Kung Fu move name at least once a week? I know I do. With Python, its a snap.


>>> from random import choice
>>> a = ['flying', 'thunderous', 'crushing']
>>> b = ['hammer', 'eagle', 'snake', 'dragon', 'lotus']
>>> c = ['fist', 'kick', 'palm', 'strike']
>>> ' '.join([choice(a), choice(b), choice(c)])
'thunderous snake fist'