It is possible to calculate these results efficiently using AnyDice. It's just not very easy or intuitive.
First of all, this optimized version of Carcer's code works fine for up to 5d6 vs. 5d6, but times out for larger pools:
function: neon city overdrive ACTION:s DANGER:s {
BOON: (ACTION = 6) - (DANGER = 6)
if BOON > 0 { result: 5 + BOON }
loop N over {5,4,3,2,1} {
if (ACTION = N) > (DANGER = N) { result: N }
}
result: 0
}
output [neon city overdrive 5d6 5d6]
All I really did to speed Carcer's code up was replace calls to the built-in [count N in SEQ]
function with (SEQ = N)
, which does the same thing but slightly faster. It's certainly possible to micro-optimize the code even further e.g. by unrolling the loop and returning early if the highest action roll isn't cancelled, but none of those optimizations actually helps break the 5d6 vs. 5d6 barrier. With more than 10 total dice, there are just too many combinations to enumerate.
Now, usually the best way to make AnyDice code handle large dice pools faster is to relabel the dice to merge equivalent sides, so that AnyDice will know not to waste time testing too many equivalent possible rolls. For example, if you want to calculate the number of rolls meeting or exceeding a certain threshold 1 ≤ T ≤ 6 in Nd6, then Nd(d6 >= T)
is a lot faster than [count {T..6} in Nd6]
. In general, these kinds of optimizations work great in any situation where, if rolling the dice physically, you would divide the rolled dice into a few coarse groups (like "at least T" and "below T") and count how many dice are in each group, or set aside some of the rolled dice as "these don't matter". But there's no obvious way to apply this kind of optimization here, since all the rolls potentially matter if all higher ones are cancelled.
…or is there?
Remember, low rolls in this mechanic only matter if all higher action die rolls have been cancelled. So let's imagine rolling the dice and counting them step by step, like this:
Divide the rolled dice into two groups: "sixes" and "everything below six". If the "sixes" group contains more action dice than danger dice, stop: the result is 6 (plus boons equal to the number of uncancelled sixes minus one).
Otherwise set aside all the sixes, and divide the rest of the dice into two groups: "fives" and "everything below five". If the "fives" group contains more action dice than danger dice, stop: the result is 5.
Otherwise set aside all the fives, and divide the rest of the dice into two groups: "fours" and "everything below four". If the "fours" group contains more action dice than danger dice, stop: the result is 4.
…and so on until finally, if all dice that rolled 2 or higher have been cancelled, we end up simply comparing the number of remaining action and danger dice to determine if the result is 1 or 0.
Now, the important feature of this process is that, at each step, the actual numbers rolled on the dice in the "everything below N" group don't matter — they're all equivalent. In fact, we could reroll all the dice in that "everything below N" group after each step (making sure that all the rerolls are still below N, of course!) and the distribution of results wouldn't change.
Or, if we really wanted to be weird, we could start by rolling a bunch of custom six-sided dice with all the sides except 6 blank.
Then, after the first step above, we could replace all the blank rolls with custom five-sided dice, again with all sides except 5 blank, roll those dice and proceed to the second step.
And if the second step didn't yield a result either, we could replace the blank rolls with custom four-sided dice, with all sides except 4 blank, roll those dice, and so on…
Now, obviously, this would be a really weird and inefficient way to roll dice at the table. But it's a very efficient way to do it in AnyDice, since it means that e.g. for rolls that contain one or more uncancelled sixes we don't have to consider all the possible combinations of lower rolls.
All we need to do is write a helper function that calls itself recursively:
function: nco helper N:n AMAX:n DMAX:n AROLL:n DROLL:n {
if AROLL > DROLL {
if N = 6 { result: 5 + AROLL - DROLL }
result: N
}
A: AMAX - AROLL
D: DMAX - DROLL
if N = 2 { result: A > D }
X: d(N-1) = N-1
result: [nco helper N-1 A D AdX DdX]
}
function: nco A:n D:n {
X: d6 = 6
result: [nco helper 6 A D AdX DdX]
}
loop A over {6} {
loop D over {0..6} {
output [nco A D] named "action [A]d6, danger [D]d6"
}
}
In the code above, N
is the highest possible roll at the current step (initially 6), and thus also the number we'll return as the result if we find an uncancelled action roll on this step (with the number of boons added to the result if N = 6
). AMAX
and DMAX
are the total numbers of action and danger dice left at this step, respectively, while AROLL
and DROLL
are the numbers of those dice that rolled N
.
The helper function first compares AROLL
and DROLL
: if the former is larger, then we've found an uncancelled action roll, and stop. Otherwise it calculates the numbers of remaining action and danger rolls, A
and B
, and constructs a custom N-1
sided die X
with one side out of N
numbered 1 and the rest numbered 0. This makes AdX
match the distribution of the number of N-1
rolls in a pool of A
dice with N-1
sides each, and similarly for DdX
; these rolls are then passed recursively to another instance of the helper function as AROLL
and DROLL
, making AnyDice evaluate the recursive call for each possible combination of those rolls.
The loops at the bottom of the code call the [nco A D]
function for varying numbers of action and danger dice and output the results. By default, I had the code use 6 action dice and a range of danger dice from 0 to 6, but you can of course edit the loop ranges as you like. The default output, graphed, looks like this:
How much faster is this recursive solution, by the way? Well, based on some testing, it runs fine up to 7d6 vs. 7d6, but will time out for 8d6 vs. 8d6. Which isn't too shabby, given that the number of possible combinations goes up exponentially with the number of dice. (It could be made a lot faster if AnyDice supported memoization, but alas, it doesn't.) In any case it's at least more than enough to meet the OP's requirement of "up to 5 or 6 dice in each pool".