Anydice: a pool where stress dice cancel out action dice of equal or lower value and return the number and value of action dice remaining in the pool

anydicedicestatistics

I've seen a couple of other posts with similar but distinctly different dice canceling mechanics so I figured I would ask for a hand.

The mechanic is this:

Players roll a pool of Xd6 Action dice and Yd6 Stress dice

Stress dice of equal or greater value than action dice will cancel each other out, removing both dice from the pool and should do so beginning with the highest values and continue in descending order. Each stress die may only cancel out a single action die and any remaining stress dice that have a lower value than the lowest action die are discarded. Action dice that remain are used for the player's turn.

ex.

player 1 has a pool of 5 action dice and 3 stress dice.

player 1 rolls 6, 5, 4, 3, 3 action and 5, 4, 2 stress

player 1 uses 5 stress to cancel 5 action and 4 stress to cancel 4 action, all 4 dice are discarded; there are no action dice with value 2 or lower, so the 2 stress is also discarded

player 1 is left with 3 action dice: 6, 3, 3

I’d love for it to end up with a result that told me how many action dice are left and what the values of those dice are as I change the number of action and stress dice in the pool, but really anything that shows this mechanic would be a big help.

Is this something that can be done?

Best Answer

Here's a script using my Icepool Python library. Rather than considering entire sets of rolls at once, Icepool uses the strategy of updating a running total based on how many dice in each pool rolled each number.

import icepool
from icepool import d6, EvalPool

class AllActionStress(EvalPool):
    def next_state(self, state, outcome, action, stress):
        # This is called for each possible outcome 6, 5, 4, 3, 2, 1
        # with the number of action and stress dice that rolled that number.
        # This method uses this information to update a "running total".
        
        # action_set: The set of surviving action dice.
        # leftover_stress: The number of surviving stress dice that rolled 
        # this number or higher.
        action_set, leftover_stress = state or ((), 0)
        leftover_stress += stress
        if leftover_stress >= action:
            leftover_stress -= action
        else:
            action -= leftover_stress
            action_set += (outcome,) * action
            leftover_stress = 0
        
        return action_set, leftover_stress
    
    def final_outcome(self, final_state, *pools):
        # Just return the action set; any remaining stress is ignored.
        return final_state[0]
    
    def direction(self, *_):
        # See outcomes in descending order.
        return -1

all_action_stress = AllActionStress()
result = all_action_stress.eval(d6.pool(5), d6.pool(3))

# The number of surviving action dice.
# If this is all you are interested in, it would be more efficient to only 
# count the number of surviving action dice in AllActionStress, rather than 
# computing the entire surviving action sets and counting the length at the
# end as we do here.
print(result.sub(len))

Denominator: 1679616

Outcome Weight Probability
2 1097586 65.347437%
3 432960 25.777321%
4 132915 7.913416%
5 16155 0.961827%

which matches posita's results.

result contains the distribution of full surviving action sets (a la "player 1 is left with 3 action dice: 6, 3, 3") but the number of possible such sets grows rapidly with number of action dice (e.g. this example results in 252 rows), so I won't reproduce the table here.

You can try the script in your browser here.

Related Topic