This is a small notebook to show calculations for various aspects of die rolls and hit point generation in 5th edition D&D.
import pandas as pd
import numpy as np
How we arrive at the numbers for die averages.
# Logical flaw is some folks think the avg of a die is half it's max value.
d8_wrong_values = [0, 1, 2, 3, 4, 5, 6, 7, 8]
d8_wrong = pd.Series(d8_wrong_values)
d8_wrong.mean()
4.0
# The average of a die is based on it's possible outcomes, not zero.
d8_right = [1, 2, 3, 4, 5, 6, 7, 8]
d8 = pd.Series(d8_right)
d8.describe()
count 8.00000 mean 4.50000 std 2.44949 min 1.00000 25% 2.75000 50% 4.50000 75% 6.25000 max 8.00000 dtype: float64
This same pattern is repeated for every die type in the game. (i.e. 1d8, 1d10 ...)
Beyond mistaking how die averages are derrived, average player HP and average Monster HPs are derrived differently. This causes some confusion too.
Players take the average roll of a die rounded up each level. It's one of the few cases of rounding up in 5e and is done each level. Not so with monsters.
player_hp = d8.max() + 14.0 * np.ceil(d8.mean()) + 5.0
player_hp
83.0
Some folks opt for a house rule to reroll ones. This makes only the slightest difference and is not worth it to my mind. Model below is for rerollling all 1s infinatly, there are variatoins that have players roll once. The only model that approaches just taking avg hp is rerolling 1s infinatly. See simulation below.
d8_house_rule_values = [4.5, 2, 3, 4, 5, 6, 7, 8]
d8_house_rule = pd.Series(d8_house_rule_values)
d8_house_rule.mean()
4.9375
# Description of HP rolls using infinite reroll of 1s
d8_house_rule.describe()
count 8.000000 mean 4.937500 std 2.007797 min 2.000000 25% 3.750000 50% 4.750000 75% 6.250000 max 8.000000 dtype: float64
# Description of normal rolling rules for comparison.
d8.describe()
count 8.00000 mean 4.50000 std 2.44949 min 1.00000 25% 2.75000 50% 4.50000 75% 6.25000 max 8.00000 dtype: float64
# HP Results for a 20th level character using avg hp
d8.max() + 19.0 * np.ceil(d8.mean())
103.0
# HP Results for a 20th level character rolling using the reroll 1s infinitely house rules
d8_house_rule.max() + 19.0 * np.ceil(d8_house_rule.mean())
103.0
Monster hit points are not tallied every level (monsters don't have levels). Instead, their average HD value is multiplied by the number of HD. If these were players the HP value would be significantly higher because of the different mechanisms.
# Example of a 5HD Bugbear
bugbear_hp = 5.0 * d8.mean() + 5.0
np.floor(bugbear_hp)
27.0
# Example of a 22HD Dragon Turtle
d20_values = range(1, 21)
d20 = pd.Series(d20_values)
d20.describe()
22.0 * d20.mean() + 110
341.0
This is a small set of scripts to simulate and compare the results of various HP generation methods being discussed on various forums.
# Hacky code. I'm trying to make this explicit for clarity.
import random
def get_hp_values(pc_level=20):
normal= 8 # all players start with max hp
avg_hp = normal
roll_all= normal
roll_once = normal
for _ in range (pc_level - 1): # Roll for each of the 19 levels past first
avg_hp = avg_hp + 5
normal = normal + random.randint(1, 8)
roll_all = roll_all + random.randint(2, 8)
for _ in range(pc_level - 1):
roll = random.randint(1, 8)
if roll == 1:
roll = random.randint(1, 8)
roll_once = roll_once + roll
return [normal, roll_all, roll_once, avg_hp]
avg_hp, normal, roll_all, roll_once = [], [], [], []
for _ in range(10000):
result = get_hp_values(20) # Change this value for the level of PC you want to simulate.
normal.append(result[0])
roll_all.append(result[1])
roll_once.append(result[2])
avg_hp.append(result[3])
hp_rolls = pd.DataFrame({'normal': normal, 'once': roll_once, 'all': roll_all, 'avg': avg_hp})
hp_rolls.describe()
normal | once | all | avg | |
---|---|---|---|---|
count | 10000.000000 | 10000.000000 | 10000.00000 | 10000.0 |
mean | 93.266200 | 101.902300 | 103.10310 | 103.0 |
std | 9.986136 | 8.954003 | 8.75359 | 0.0 |
min | 58.000000 | 70.000000 | 64.00000 | 103.0 |
25% | 87.000000 | 96.000000 | 97.00000 | 103.0 |
50% | 93.000000 | 102.000000 | 103.00000 | 103.0 |
75% | 100.000000 | 108.000000 | 109.00000 | 103.0 |
max | 129.000000 | 133.000000 | 133.00000 | 103.0 |