15
\$\begingroup\$

I was trying to create an AnyDice function to model the Great Weapon Fighting fighting style (which lets you reroll 1s and 2s), but I couldn't get it to work on any arbitrary dice.

I've found this one:

function: reroll R:n under N:n {
   if R < N { result: d12 } else {result: R}
}
output [reroll 1d12 under 3] named "greataxe weapon fighting"

And it works fine. But I don't know how to make the function generic so i don't need to change the d12 every time i want a different dice to reroll.

I've tried

function: reroll R:n under N:n {
   if R < N { result: d{1..R} } else {result: R}
}
output [reroll 1d12 under 3] named "greataxe weapon fighting"

but it is not giving the right probabilities. Maybe if I could fetch the die size inside the function...

\$\endgroup\$
14
\$\begingroup\$

It's a common mistake with anydice to look for a procedural solution- anydice models dice. Therefore cast your problem as "What does the dice look like that gives the answer?"

Think about how Great Weapon Fighting works with a d8. We are potentially rolling 2 8 sided dice so our result die has 8x8=64 faces. There are 2 ways we can get a 1 (or 2) - rolling a 1 or 2 followed by a 1 (or 2) so our dice has 2 1s and 2 2s. That leaves 60 faces which are equally divided between the numbers 3 to 8, so 10 each. So in anydice our dice looks like:

EIGHT: {{1..2}:2, {3..8}:10}

More generally, for an N sided die:

DGW: {{1..2}:2, {3..N}:N+2}

For a dice with an even number of sides (i.e. all of them) this can be simplified by dividing the number of faces by 2:

DGW: {{1..2}, {3..N}:N/2+1}

You can use this sequence like any other dice so a great sword can be modelled as:

2d{{1..2}, {3..6}:4}
\$\endgroup\$
  • 1
    \$\begingroup\$ This has the advantage of working just as well with weapons that roll multiple damage dice, i.e. a greatsword - with N: 6, 2dDGW returns the results you'd expect. \$\endgroup\$ – Carcer Aug 5 '17 at 9:08
6
\$\begingroup\$

You seem to be asking for something like the built-in [explode DIE] function in AnyDice, except for rerolling the die (once only) if the original roll is below a certain limit.

If you take a look at the AnyDice function library (in the left-hand-side menu) and click the explode entry, it actually has a convenient "Do it yourself" section that shows how to reimplement the built-in explode function yourself. The trick for making the syntax nice and clean is to use two functions: a wrapper function that takes in the unrolled die as a parameter and calls a helper function for every possible outcome of the roll (i.e. passing the same die to the helper function, which expects a number).

We can use the same trick here:

function: reroll DIE:d if under LIMIT:n {
  result: [reroll DIE as DIE if under LIMIT]
}
function: reroll ROLL:n as DIE:d if under LIMIT:n {
  if ROLL < LIMIT { result: DIE }
  else { result: ROLL }
}

loop SIDES over {4,6,8,12,20} {
  output [reroll dSIDES if under 3] named "d[SIDES] with GWF"
}

Here, [reroll DIE if under LIMIT] is the wrapper function, which simply calls the inner function [reroll ROLL as DIE if under LIMIT] for every possible roll of the die. The inner function then just checks if the roll is below the limit, and if so, returns the "re-rolled" die instead of the original roll.

Of course, you could also just call the inner function directly, as in:

loop SIDES over {4,6,8,12,20} {
  output [reroll dSIDES as dSIDES if under 3] named "d[SIDES] with GWF"
}

and get the same results. But sometimes it's nice to avoid repeating a parameter like that. In fact, if we're only interested in modeling rerolls due to Great Weapon Fighting, we might as well leave the constant LIMIT parameter out, too, and simplify our wrapper function into just:

function: gwf DIE:d {
  result: [reroll DIE as DIE if under 3]
}

Bonus: The output of the function(s) given above is itself a die (i.e. a probability distribution over the integers), and thus can be assigned into a custom die that "automatically rerolls itself". You can then roll as many of these custom dice as you want, or even mix them with other dice.

For example, to get the results of rolling 2dX with Great Weapon Fighting, you could do:

loop SIDES over {4,6,8,12,20} {
  GWF: [gwf dSIDES]
  output 2dGWF named "2d[SIDES] with GWF"
}

or, alternatively, just:

loop SIDES over {4,6,8,12,20} {
  output 2d[gwf dSIDES] named "2d[SIDES] with GWF"
}
\$\endgroup\$
2
\$\begingroup\$

Having reviewed all of the answers, I've found that several of them have subtle errors. If you're rolling 2 dice, it looks fine but as soon as you put 3 dice into the equation, you'll find that the calculations are wrong.

This makes a difference when you score a critical hit because you will rolling each of the damage dice twice, and each is qualified to be rerolled if you have Great Weapon Fighting and roll a 1 or 2 on any given die.

Here's what I came up with, which I believe is correct, based on comparisons I've made against other percentile distributions:

    function: roll gwf damage with SIDES_ON_DMG_DIE:n sided die {
        \ create a die that simulates rerolling the first 1 or 2 as a sequence \
        GWF_DIE: {{1..2}:2, {3..SIDES_ON_DMG_DIE}:SIDES_ON_DMG_DIE+2}
        result: dGWF_DIE
    }

    output 2d[roll gwf damage with 6 sided die] 

This allows you to specify the number of sides as well as rolling them. It dynamically creates the sequence of numbers from which to select a random number. (thanks to @Dale-M for that part)

\$\endgroup\$
1
\$\begingroup\$

I was able to make a very clunky function that uses the maximum value of the dice as a third input:

function: reroll R:n max XX:n under N:n {
   if R < N { result: d{1..XX} } else {result: R}
}
output [reroll d12 max 12 under 3] named "reroll 1s and 2s once"
\$\endgroup\$
1
\$\begingroup\$

I ran into a similar issue and was able to solve it reasonable cleanly with a pair of functions.

function: reroll D:d under N:n{
   result: [rerollcore D die D under N]
}

function: rerollcore R:n die D:d under N:n{
   if R < N { result: D } else {result: R}
}

output [reroll d12 under 3]

The reroll function exists only to capture the die being rolled, while the rerollcore function exists to perform the actual roll / reroll.

\$\endgroup\$
  • \$\begingroup\$ We seem to have posted almost exactly the same answer at almost exactly the same time. :) Mine's a bit longer and more detailed, yours is perhaps neater and more to the point. +1. \$\endgroup\$ – Ilmari Karonen Aug 5 '17 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.