also, here’s the latest version of the code I built to play around with this. i rebuilt to be able to accommodate changing the base dice. enjoy
def calc_at_least_dice(bonus, adv, disadv, base_dice=2):
# maps the resulting dice roll to the number of times that value is rolled
val_map = {}
total_to_roll = base_dice+abs(adv-disadv)
bonus_roll = 0 # 1 if adv >= disadv else -1
adv_func = max if adv >= disadv else min
exploded_values = [
[i for i in range(1, 7)]
for j in range(total_to_roll)
]
roll_values = [1 for i in range(total_to_roll)]
total_rolls = 0
expected_roll_values = 6 ** total_to_roll
while total_rolls < expected_roll_values:
dice = [0 for i in range(base_dice)]
accum = 0
seen = set()
for roll in roll_values:
if roll in seen:
accum += bonus_roll
for i in range(len(dice)):
if adv_func(roll, dice[i]) == roll:
dice = dice[0:i] + [roll] + dice[i:-1]
break
seen.add(roll)
roll_total = accum + bonus + sum(dice[0:base_dice])
val_map[roll_total] = val_map.get(roll_total, 0) + 1
total_rolls += 1
next_die_plus_index = 0
while next_die_plus_index < len(roll_values) and roll_values[next_die_plus_index] == 6:
roll_values[next_die_plus_index] = 1
next_die_plus_index += 1
if next_die_plus_index >= len(roll_values):
break
roll_values[next_die_plus_index] += 1
# print(val_map)
k2 = '' if adv == disadv else f'k{base_dice}'
# print(f'{total_to_roll}d6{k3}+{bonus}')
# print("total\t% at least = roll")
percentage = 0.0
probabilities = {}
for roll in reversed(sorted(val_map.keys())):
num_rolled = val_map[roll]
percentage += num_rolled / total_rolls
probabilities[roll] = percentage
return {
roll: round(100.0*probabilities[roll], 2) for roll in sorted(probabilities.keys())
}
probs = {}
# headers = ['roll total']
roll_types = []
base_dice = 3
for adv in range(0, 3):
for bonus in range(10, 11):
roll_dist = calc_at_least_dice(bonus, adv, 0, base_dice)
k2 = '' if adv == 0 else f'k{base_dice}'
dice_amt = f'{base_dice+adv}d6{k2}+{bonus}'
roll_types.append(dice_amt)
# headers.append(dice_amt)
for roll in sorted(roll_dist.keys()):
pct = roll_dist[roll]
probs[roll] = probs.get(roll, {})
probs[roll][dice_amt] = str(pct)
print(f'roll total\t' + '\t'.join(roll_types))
for roll_total in sorted(probs.keys()):
# print(f'{roll_total}: {str(probs[roll_total])}')
# print(str([probs[roll_total].get(dice_amt, str(0.0)) for dice_amt in roll_types]))
values = [probs[roll_total].get(dice_amt, str(0.0)) for dice_amt in roll_types]
print(f'{roll_total}\t' + '\t'.join(values))
prints out the likelihood that your roll will be at least the roll total listed:
roll total | 3d6+10 | 4d6k3+10 | 5d6k3+10 |
---|---|---|---|
13 | 100.0 | 100.0 | 100.0 |
14 | 99.54 | 99.92 | 99.99 |
15 | 98.15 | 99.61 | 99.92 |
16 | 95.37 | 98.84 | 99.73 |
17 | 90.74 | 97.22 | 99.2 |
18 | 83.8 | 94.29 | 98.05 |
19 | 74.07 | 89.51 | 95.86 |
20 | 62.5 | 82.48 | 92.05 |
21 | 50.0 | 73.07 | 86.01 |
22 | 37.5 | 61.65 | 77.46 |
23 | 25.93 | 48.77 | 66.13 |
24 | 16.2 | 35.49 | 52.56 |
25 | 9.26 | 23.15 | 37.71 |
26 | 4.63 | 13.04 | 23.42 |
27 | 1.85 | 5.79 | 11.39 |
28 | 0.46 | 1.62 | 3.55 |
as you can see, at 20, your likelihood of rolling at least a 20 increases by 20% with 1 advantage, and 10% again with 2, which imo helps make it much more intuitive to think about “average effectiveness of advantage”