Anydice: two-colored polyhedral dice pool

anydicedicestatistics

I trying to calculate probabilities for this dice mechanic, but I'm not sure that anydice can calculate that at all. The dice pool mechanic works like this:

Players assemble a dice pool with green and red dice. Any dice can be d4, d6, d8, d10 or d12. Green dice represent player skill and advantages, red dice – difficulty and disadvantages. The minimum number of dice in every dice pool is 4: two green and two red. There is no maximum number of dice in a pool, but for sanity sake it will be probably capped at 6 green and 6 red dice.
A player rolls a dice pool and takes the 2 highest dice from it. After that he is looking at their color. If they are both red it's fail, if they both green it's full success, if one green and one red then it's success with complication.
If green and red dice after a roll have the same number, they are removed from the result. If after all removals there is only red dice, then it's fail. If after all removals there is only green dice, then it's full success. If after all removals there is no dice left, then it's success with complication.

I want to know probabilities of full success, success with complication and fail in that dice pool mechanic. Is that even possible with anydice? And if not, what kind of instrument or math approximation I can use?

Here the same dice pool mechanic with examples:

  1. Player has 3 green dice (2d4,1d8) and 2 red dice (1d4,1d6). He rolls the dice pool and get results: 1 (r), 1 (r), 3 (g), 5 (g), 7 (g). He takes 2 highest dice – 5 and 7. They are both green so the roll is full success.
  2. Player has 3 green dice (1d8,2d10) and 3 red dice (1d4,1d6,1d12). He rolls the dice pool and get results: 3 (r), 4 (r), 10 (r), 3 (g), 4 (g), 10 (g). All dices cancel each other out so the roll is success with complication.

Best Answer

Efficient computation

The trouble with AnyDice is that it works by enumerating all possible multisets of numbers rolled. While this is faster than enumerating all ordered (equivalently, unsorted) rolls and is convenient in that it gives you the entire rolled sequence at once, the number of possible multisets can still grow quickly as you add dice. Mixing different dice also makes things much more difficult.

Fortunately, it turns out that, as long as you can express the dice mechanic in terms of an incremental calculation where you're told how many dice rolled each number in each pool for one number at a time without carrying too much information from number to number, it's possible to compute the solution more efficiently. It's also possible to handle mixed standard dice without much loss in efficiency. I've implemented this approach in my Icepool Python package. Here's a script:

import icepool
from icepool import d4, d6, d8, d10, d12, EvalPool

class GreenRed(EvalPool):
    def next_state(self, state, outcome, green, red):
        # State is the number of top-two dice for green and red.
        top_green, top_red = state or (0, 0)
        # If there are remaining places in the top two...
        remaining_top_two = 2 - (top_green + top_red)
        if remaining_top_two > 0:
            # Compute the number of non-eliminated dice that rolled this outcome.
            net = green - red
            # Then add them to the winning team's top two.
            if net > 0:
                top_green += min(net, remaining_top_two)
            elif net < 0:
                top_red += min(-net, remaining_top_two)
        return top_green, top_red
    
    def final_outcome(self, final_state, *pools):
        top_green, top_red = final_state
        if (top_green > 0) and not (top_red > 0): return 2
        elif (top_red > 0) and not (top_green > 0): return 0
        else: return 1
    
    def direction(self, *_):
        # See outcomes in descending order.
        return -1
    
green_red = GreenRed()
# The argument lists are implicitly cast to pools.
print(green_red.eval([d10, d8], [d6, d8]))

Denominator: 3840

Outcome Weight Probability
0 265 6.901042%
1 2784 72.500000%
2 791 20.598958%

We can handle even much larger pools. Here's two entire dice sets (10 dice total) on each side:

print(green_red.eval([d12, d10, d8, d6, d4, d12, d10, d8, d6, d4], [d12, d10, d8, d6, d4, d12, d10, d8, d6, d4]))

Denominator: 281792804290560000

Outcome Weight Probability
0 67701912081930556 24.025423%
1 146388980126698888 51.949155%
2 67701912081930556 24.025423%

You can try this in your browser using this JupyterLite notebook. Fair warning, though, I'm currently doing a major revision to the package.

Related Topic