Apparently when spending a Benny to reroll a Wild Card roll in Savage Worlds, you repeat the entire roll (i.e. reroll both the skill die and the wild die) and then take the highest of your new and original results.
If you wanted to always spend a Benny for a reroll, you could model that in AnyDice e.g. like this:
ROLL: [highest of [explode d6] and [explode d8]]
output [highest of ROLL and ROLL]
Note that I'm saving the distribution of results for a single Wild Card roll in the variable ROLL
, and then pass it as both parameters to [highest of NUMBER and NUMBER]
to simulate a reroll. I'll explain the way that works in slightly more detail below.
However, it's more likely that you'd only want to spend the Benny if your original roll failed to meet the target. For that, you'll need a custom helper function. Here's one that should do the trick:
function: reroll ROLL:n as REROLL:d if under TARGET:n and take highest {
if ROLL >= TARGET {
result: ROLL \ don't waste a benny if it's already a success \
} else {
result: [highest of ROLL and REROLL]
}
}
Again, the easiest way to use that function is to save the distribution for a single Wild Card roll in a variable and then pass that variable as both the ROLL
and REROLL
parameters to the function, like this:
ROLL: [highest of [explode d6] and [explode d8]]
output [reroll ROLL as ROLL if under 4 and take highest]
The trick to understanding this code is to realize that AnyDice never actually rolls any dice.* Rather, what the AnyDice documentation calls "dice" are actually probability distributions over the integers.
For example, d6
in AnyDice returns a distribution that has a 1/6 probability for each of the numbers from 1 to 6. And passing this distribution to the [explode DIE]
function converts it into a more complicated distribution that (for the default explode limit of 2) still has a 1/6 probability for the numbers from 1 to 5, a 1/36 probability for 7 to 11 and a 1/216 probability for 13 to 18.
AnyDice also lets us save these distributions in variables, so writing e.g. ROLL: [explode d6]
means that the variable ROLL
now contains the distribution of results for an exploding d6. Note that it does not contain a specific number that would result from actually doing the roll; this means that, for example, ROLL + ROLL
does not give the same output as 2 * ROLL
in AnyDice, but rather gives the distribution of results from summing two independent exploding d6 rolls.
So how can we work with the actual numbers resulting from a dice roll in AnyDice, then? Well, here's where function come in. When defining a function in AnyDice, you can specify what kind of values its parameters should have by adding a "type tag" to the parameter name: :n
for numbers, :d
for dice (i.e. distributions) and :s
for sequences (i.e. lists of numbers). And if the actual value you give when calling the function is not of the right type, AnyDice will automatically convert it (e.g. turning a number into a single-element sequence or a single-sided die as needed).
But something fairly magical happens if you pass a die to a function expecting a number (or a sequence**). In that case, what AnyDice does is that it calls the function for every possible outcome of the roll and collects the results into a new distribution, weighted by the probabilities of the different outcomes. This can be used to effectively "freeze" or "collapse" the probability distribution into a definite fixed number that can be used in arbitrary calculations.
That's why the helper function I posted above has the ROLL
parameter tagged as a number: I want AnyDice to call it for each possible outcome of the original roll so that I can compare the outcome to the target value and use the result of that comparison to decide if I want to try a reroll or not. (Comparing a distribution directly to a number in AnyDice would just return another distribution, and using a distribution as the condition in an if
statement just gives an error message saying you can't do that.)
Note that we do need to pass the reroll as a separate parameter to the function, since we want it to be independent of the "frozen" value of the original roll. But this parameter can be a distribution, since we don't need to use it in an if
condition or do anything else with it that would require its value to be fixed.
And yes, the same "magic" also happens when calling built-in functionsin AnyDice, which is how you can use the [highest of NUMBER and NUMBER]
function (which just compares two numbers and returns the higher one) to calculate the distribution of the highest of two dice rolls.
*) Well, there is a Roller tab that lets you actually roll dice if you want to. Well, sort of, at least.
**) The behavior of :n
and :s
differs if the "die" passed as a parameter is actually a pool of multiple identical dice, like 3d6
. For :n
, AnyDice will call the function with all possible sums of the roll, while for :s
AnyDice will call it with all possible sorted sequences of individual dice values.