# Complicated Homemade Game dice odds, AnyDice or Troll formula help required

I'm playing a home made game, and it uses a slightly complicated system with regards to training.

It works rolling 1d10+bonuses against a variable target number, minus "headway". Headway is gained if you fail to reach the modified target number, and how much is determined by rolling a specific dice determined by how good you are at that matter. The "headway" is cumulative, incidentally, and is lost upon success.

So, for example, training a specific ability the target number might be 10, you have a bonus of 1, and a headway dice 1d3. You get a 5 on the 1d10, for a total of 6, failing the roll. You then roll the 1d3, and get a 1. The next time you train that ability, 1d10+1 would require a roll of 9 for success (the +1 headway will push you to 10). If you fail that, you roll again, and get a 3, meaning that the next time you roll, you only need a roll of 6 (you now have headway of 1+3).

What formula would be needed so we can determine the odds of success from multiple such rolls?

• Hi, I've edited your word usage to say roll of 6/9 rather than total of 6/9. Total is generally used to indicate your roll plus any bonuses (headway in this case), but you appear to be referring to the roll itself before bonuses are applied. – doppelgreener Aug 19 '14 at 7:45
• Is this the correct programatic formula? if( 1d10+mod >= target ) { Success! } else { target = target - 1d3;} repeat; ? – GMNoob Aug 19 '14 at 7:45
• Actually, yeah: @BFldyq I've assumed, perhaps mistakenly, that headway adds to the roll and does not shift the target number. If headway lowers the target (same effect, different approach) my edit may be incorrect. – doppelgreener Aug 19 '14 at 7:47
• I believe the headway lowers the target. There's a pre-existing +1 bonus in the example and the first roll is awkwardly noted as "roll of 5 on the 1d10 for a total of 6", which would be clearer as "total of 6 on 1d10+1" for consistency with the next iteration's notation, since we don't need to have how to use bonuses explained. – SevenSidedDie Aug 19 '14 at 8:18
• Yes, the headway does indeed lower the target number. I have no idea why it's not a simple bonus, but that's how it is. I suspect it was for book-keeping purposes. – BFldyq Aug 19 '14 at 8:47

## ANYDICE CAN NOT SOLVE THIS PROBLEM

I tried the below program but it failed to function properly, the odds that it returns are not correct. I ran my own program with 1 million itterations and got the following results instead:

You can Run the program here: http://www.daganev.com/headwaycalc.swf My previous attempted sollution is here But the results are not correct.

set "maximum function depth" to 30
TARGET: 10
NUM_OF_ROLLS: 1
BONUS: 1
function: traintarget of TT:n with roll R:n rollnumber N:n {
if R>=TT {result: N}
else {
NUM_OF_ROLLS: N + 1
TARGET: TT - 1d3
result: [traintarget of TARGET with roll R rollnumber NUM_OF_ROLLS]
}
}

output [traintarget of TARGET with roll (1d10+BONUS) rollnumber NUM_OF_ROLLS] named "Number of Rolls to Reach Target"


I just couldn't get it to work on AnyDice, so here's a Python 3 script that calculates the number of tries iteration times and yields the results in a nice table. It's far slower than GMNoob's AnyDice answer, and the results do not match.

from math import sqrt
import random

bonus = 1
iterations = 10000000

def d(x): return random.randint(1,x)

results = *(10-bonus)
for i in range(iterations):
target = 10
tries = 1
success = False
while not success:
success = (d(10)+bonus>=target)
if success:
break
else:
tries+=1
target-=d(3)
results[tries-1]+=1

average = sum([results[i]*(i+1) for i in range(len(results))])/iterations
deviation = sqrt(sum([results[i]*(i-average)**2 for i in range(len(results))])/(iterations-1))

print("Average: {:.3f}+-{:.3f}".format(average,deviation))
print("Number of Tries | "+" | ".join([str(x).rjust(6) for x in range(1,1+len(results))]))
print("Chance          | "+" | ".join([str(round(x/iterations*100,3)).rjust(6) for x in results]))


Try it online at Ideone.

I ran it with 10000000 iterations on my machine, with the following results:

Average: 2.532+-1.509
Number of Tries |      1 |      2 |      3 |      4 |      5 |      6 |      7 |      8 |      9
Chance          | 19.997 | 32.006 | 28.262 | 14.904 |  4.198 |  0.591 |  0.042 |  0.001 |    0.0

• I wrote my own program and can confirm these numbers are correct. – GMNoob Aug 19 '14 at 11:51

Here's a faster Python routine that gives exact results (up to the limits of numerical precision, anyway):

# Target number and initial bonus + headway:
target = 10
bonus = 1
headway = 0

# Die sizes:
attempt_die = 10  # 1d10
headway_die = 3   # 1d3

# print header:
print "target: {0}, bonus {1:+d}, initial headway {2}".format(
target, bonus, headway)
print "rolling 1d{0} for success, 1d{1} for headway".format(
attempt_die, headway_die)

# initial headway distribution:
distribution = [0.0] * (max(target - bonus, headway) + 1)
distribution[headway] = 1.0

def clamp(probability):
return min(1, max(0, probability))

cumulative = 0.0  # total success probability so far
expected = 0.0  # expected number of attempts needed

for attempt in xrange(1, 1 + max(1, target - bonus - headway)):
success = 0.0  # total success probability for this attempt

# loop through possible current headway values (in reversed order,
# to avoid having to copy the distribution list):
for curr_hw in reversed(xrange(len(distribution))):
headway_prob = distribution[curr_hw]
if headway_prob == 0: continue

# probability of rolling below the target:
failure_prob = clamp(float(target - bonus - curr_hw - 1) / attempt_die)
success += headway_prob * (1 - failure_prob)

# if the initial roll fails, increment headway (if headway exceeds
# target - bonus, set it to that value so that next roll will
# automatically succeed):
distribution[curr_hw] = 0
for roll in xrange(1, 1 + headway_die):
new_hw = min(curr_hw + roll, len(distribution) - 1)
distribution[new_hw] += failure_prob * headway_prob / headway_die

cumulative += success
expected += success * attempt
print "success on attempt {0:2d}:".format(attempt), \
"{0:9.5f}%".format(success * 100), \
"(cumulative {0:9.5f}%)".format(cumulative * 100)

# print "headway:", ", ".join(
#     "{0}: {1:g}%".format(hw, 100 * distribution[hw]) \
#     for hw in range(len(distribution))
# )

print "expected number of attempts needed =", expected


Here's the output for target = 10, bonus = 1 and headway = 0:

target: 10, bonus +1, initial headway 0
rolling 1d10 for success, 1d3 for headway
success on attempt  1:  20.00000% (cumulative  20.00000%)
success on attempt  2:  32.00000% (cumulative  52.00000%)
success on attempt  3:  28.26667% (cumulative  80.26667%)
success on attempt  4:  14.90370% (cumulative  95.17037%)
success on attempt  5:   4.19664% (cumulative  99.36701%)
success on attempt  6:   0.58975% (cumulative  99.95677%)
success on attempt  7:   0.04180% (cumulative  99.99857%)
success on attempt  8:   0.00142% (cumulative  99.99998%)
success on attempt  9:   0.00002% (cumulative 100.00000%)
expected number of attempts needed = 2.53240636049


This code works by maintaining the probabilities of all possible current headway values in the distribution array, and updating them based on the probabilities of the various possible outcomes of the die rolls.

If desired, the code above can be made to output the distribution of different headway values after each attempt by un-commenting the extra print statement at the end of the outer loop. (Note that this is not, strictly speaking, a proper probability distribution, since it does not include the probability that the training has already succeeded. Thus, for later attempts, the headway probabilities will sum to a value strictly less than 100%, although adding in the cumulative success probability will fix this.)