AnyDice handles exploding dice in a peculiar fashion that's poorly-suited for roll-and-count dice pools. I have cobbled together a somewhat inelegant solution, but it appears to work correctly.
Here's the link to the program.
Instead of using dice in the usual manner, I've created several functions that, given parameters of a die, return its output, i.e. the number of successes it has produced.
function: roll ROLL:n threshold T {
if ROLL >=T {result: 1}
result: 0
}
function: rollexploding ROLL:n threshold T{
if ROLL =6 {result: 1+[rollexploding 1d6 threshold T]}
result: [roll ROLL threshold T]
}
function: rerollfailures ROLL:n threshold T{
if ROLL < T {result: [roll 1d6 threshold T]}
result: [roll ROLL threshold T]
}
function: rerollthenexplode ROLL: n threshold T {
if ROLL < T {result: [rollexploding 1d6 threshold T]}
result: [rollexploding ROLL threshold T]
}
Then I made a wrapper function that would figure out which of these functions to call, and handle requests for multiple dice being rolled:
function: wrapper DICE:n threshold T explode E reroll R{
RES:0
loop N over {1..DICE} {
if E & R {RES: RES+[rerollthenexplode 1d6 threshold T]}
else {if E {RES:RES+[rollexploding 1d6 threshold T]}
else {if R {RES:RES+[rerollfailures 1d6 threshold T]}
else {RES:RES+[roll 1d6 threshold T]}
}
}
}
result:RES
}
Finally, I made a bunch of functions that simplify input by providing pre-determined combinations of parameters. I'll provide only 3 of them here:
function: b DICE:n{
result:[wrapper DICE threshold 4 explode 0 reroll 0]
}
function: be DICE:n{
result:[wrapper DICE threshold 4 explode 1 reroll 0]
}
function: gr DICE:n{
result:[wrapper DICE threshold 3 explode 0 reroll 1]
}
To use it, type things like output [b 4]
To input the result of a function into another function, simply call the second function with the first function as a parameter.
For example, your complete AnyDice program could look like this:
function: hitme NN:n {
result: [count {5, 6} in NN d6]
}
function: woundme HH:n {
result: [count {1, 2, 3, 4, 5} in HH d6]
}
output [woundme [hitme 5]]
Best Answer
The following anydice program will show you what the statistical distribution of ability score results for the Colville method looks like.
The trick here is that we don't actually want to have to reroll anything, because recursive functions are expensive and take forever (plus there's a limit to how far Anydice will recurse). Fortunately we actually have a really neat shortcut we can use in the specific case of rerolling until we get a result that's in the range we actually want; we can use a function as a filter to check the value is in the desired range, which returns the input value if it is, or the so-called empty die,
d{}
, if it is not.The result of the empty die is basically discarded when anydice calculates probabilities, so we are shown results based only on rolls which met our parameters; since we were just going to reroll anyway until we got a result that was in our range, this is statistically identical to actually rerolling (potentially forever).
So we have two functions, one of which discards results for individual ability scores unless they are 8 or higher, one of which discards arrays of ability scores if there aren't two scores of 15 or more.
The other trick is that the latter function also takes an index to return one of those ability scores since unfortunately we can't get anydice to return a sequence from a function, only a flat number, and we so have to use the index to inspect the individual rolls; fortunately the generated sequence is automatically sorted in descending order by default, so we can just iterate through each position to build a complete distribution.
That gives us a result that looks like this when graphed:
This seems to agree perfectly with Ryan Thompson's R-based answer so I feel pretty confident I haven't messed up how this works anywhere.