Interactive calculator
It's not AnyDice, but I have a Year Zero Engine calculator here, powered by my Icepool Python package. This is based on the Year Zero Engine Standard Reference Document v1.0; in particular, 1s are "banes", which cannot be rerolled, and if you choose to push (i.e. reroll at all), any banes showing at the end cause some sort of damage to the roller.
For you, I've added an option to optimize pushing for probability of winning.
The entire source code is embedded in the page itself, or you can view it here. Here are a few key parts.
Vector-valued die
Fundamentally, we track eight pieces of information:
- The number of successes and banes.
- The number of each die type that rolled zero successes (2-5 raw) and therefore are always positive expected value in terms of successes to push.
- The number of d10s and d12s that rolled a single success, which we could risk for doubles if we really wanted.
@cache
def year_zero_die(sides):
"""success, bane, zero on d6, d8, d10, d12, ones on d10, d12."""
def func(x):
if x == 1:
result = (0, 1, 0, 0, 0, 0, 0, 0)
elif x < 6:
result = (0, 0, sides == 6, sides == 8, sides == 10, sides == 12, 0, 0)
elif x < 10:
result = (1, 0, 0, 0, 0, 0, sides == 10, sides == 12)
else:
result = (2, 0, 0, 0, 0, 0, 0, 0)
return icepool.Vector(result)
return icepool.d(sides).map(func)
Finding the optimal push strategy
After computing the result of the initial roll, if we didn't win, we will certainly want to push any dice that rolled zero successes (2-5 raw). We may or may not want to push d10s and d12s that rolled single successes; we consider all options, keeping in mind that we would never push a single-success d10 unless we pushed all of the single-success d12s, since the d10 is less likely to double and more likely to zero or bane.
@cache
def calc_maximize_win(success, bane, z6, z8, z10, z12, s10, s12):
"""Try to maximize the chance of winning."""
if success > 0:
return icepool.vectorize(success, 0)
if not can_win(success, bane, z6, z8, z10, z12, s10, s12):
return icepool.vectorize(success, 0)
best = calc_push_everything(success, bane, z6, z8, z10, z12, s10, s12)
best_win = icepool.Die([best]).marginals[0].probability_gt(0)
while True:
if s10 > 0:
s10 -= 1
elif s12 > 0:
s12 -= 1
else:
break
candidate = calc_push_everything(success, bane, z6, z8, z10, z12, s10, s12)
candidate_win = icepool.Die([candidate]).marginals[0].probability_gt(0)
if candidate_win >= best_win:
# candidate wins ties, since it has fewer rerolls = fewer banes
best = candidate
best_win = candidate_win
return best