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]
I came up with two solutions, which why inherently different, give the same result. This instills confidence in me that they are correct.
Solution 1
This solution is the one I started working on before I read Sandwich's answer. You can try it here. The idea is to use Buckles algorithm for mapping an lexicographical index (from a die roll) to a selection from my sequence. Let's look at the code:
set "maximum function depth" to 100
function: factorial A:n {
RESULT: 1
loop N over {1..A} { RESULT: RESULT*N }
result: RESULT
}
function: N:n choose K:n {
result: [factorial N]/[factorial K]/[factorial N-K]
}
function: buckles inner CURRENT:n N:n K:n LIMIT:n INDEX:n {
NEWLIMIT: [N-CURRENT choose K] + LIMIT
if NEWLIMIT >= INDEX { result: {CURRENT,LIMIT}}
else { result: [buckles inner CURRENT+1 N K NEWLIMIT INDEX]}
}
function: N:n buckles K:n index INDEX:n {
RESULT: {}
CURRENT: 0
LIMIT: 0
loop I over {1..K-1}{
TUPLE: [buckles inner CURRENT+1 N K-I LIMIT INDEX]
CURRENT: 1@TUPLE
LIMIT: 2@TUPLE
RESULT:{RESULT,CURRENT}
}
result: {RESULT,CURRENT+INDEX-LIMIT}
}
function: select SELECTOR:s from SEQUENCE:s {
RESULT: {}
loop I over SELECTOR { RESULT: {RESULT,I@SEQUENCE} }
result: RESULT
}
function: draw K:n items from SEQUENCE:s at lexicographical index INDEX:n {
result: [select [#SEQUENCE buckles K index INDEX] from SEQUENCE]
}
function: draw from SEQUENCE:s K:n times {
INDEX: d[#SEQUENCE choose K]
result: [draw K items from SEQUENCE at lexicographical index INDEX]
}
SEQUENCE: {1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7}
output [draw from SEQUENCE 4 times] named "mydie"
The factorial
and choose
functions are self explanatory. The first calculates 1*2*3....*n, and the second one gives the total number of different ways to get k item out of n, in my case 4 out of 11. buckles
and buckles inner
implement the Buckles algorithm itself. If we pass N=11, P=4 and X=1 to the buckles
function we will receive {1,2,3,4}. If we pass N=11, P=4 and X=2, we will receive {1,2,3,5} and so on an so forth until we go all the way to N=11, P=4 and X=330 to get {8,9,10,11}. Note that 330 here is [11 choose 4]
.
select
is a helper function, that takes a selector, a sequence and returns the resulting sequence. For example for selector {1,6,7,11} and sequence {1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7} the result will be {1,3,3,7}.
draw items
function selects K items from the given sequence at a given lexicographical index. For example with K = 4 at index 1 and sequence {1, 3, 5 , 9, 11} the result will be {1, 3 , 5 , 9}.
The second draw
function allow you specifying a sequence and the number of items to draw from it. That will be the die.
Solution 2
This one is completely inspired by sandwich's answer. You try it here. And here is the code:
function: remove N:n from SEQUENCE:s {
RESULT:{}
REMOVED: 0
loop C over SEQUENCE {
if (REMOVED = 0) & (C=N) { REMOVED: 1 } else { RESULT: {RESULT,C} }
}
result: RESULT
}
function: draw from SEQUENCE:s with X:n and RESULT:s C:n more times{
if C = 0 {
result: RESULT
} else {
SEQUENCE: [remove X from SEQUENCE]
result: [draw from SEQUENCE with dSEQUENCE and {RESULT,X} C-1 more times]
}
}
function: draw from SEQUENCE:s N:n times {
result: [draw from SEQUENCE with dSEQUENCE and {} N more times]
}
SEQUENCE:{1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7}
output [draw from SEQUENCE 4 times] named "mydie"
I was not sure how to remove a value from an existing sequence, so I had to write my own function for that. It's remove
. The two draw
functions implement sandwich's idea of rolling a die and then removing the result from the die sequence. There are two of them, because one of them is recursive and the other bootstraps the first one.
I can tell you that the first solution took me about 3 hours, and the second one only a few minutes. Well done sandwich!
Best Answer
Here, use this code:
This code creates a function where the initial fudge dice are turned into a sequence. This means every possible roll is evaluated. For any one roll we check the N lower dice and if it's a -1 we "reroll" it. The hack to emulate the reroll is to add 1 cancling the old value, then adding
d{-1, 0, 1}
. This hack value is added toA
which is added at the end.