This is a small notebook to show calculations for various aspects of die rolls and hit point generation in 5th edition D&D.

In [1]:

```
import pandas as pd
import numpy as np
```

How we arrive at the numbers for die averages.

In [2]:

```
# 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()
```

Out[2]:

In [3]:

```
# 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()
```

Out[3]:

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.

In [4]:

```
player_hp = d8.max() + 14.0 * np.ceil(d8.mean()) + 5.0
player_hp
```

Out[4]:

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.

In [5]:

```
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()
```

Out[5]:

In [6]:

```
# Description of HP rolls using infinite reroll of 1s
d8_house_rule.describe()
```

Out[6]:

In [7]:

```
# Description of normal rolling rules for comparison.
d8.describe()
```

Out[7]:

In [9]:

```
# HP Results for a 20th level character using avg hp
d8.max() + 19.0 * np.ceil(d8.mean())
```

Out[9]:

In [10]:

```
# 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())
```

Out[10]:

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.

In [11]:

```
# Example of a 5HD Bugbear
bugbear_hp = 5.0 * d8.mean() + 5.0
np.floor(bugbear_hp)
```

Out[11]:

In [14]:

```
# Example of a 22HD Dragon Turtle
d20_values = range(1, 21)
d20 = pd.Series(d20_values)
d20.describe()
22.0 * d20.mean() + 110
```

Out[14]:

This is a small set of scripts to simulate and compare the results of various HP generation methods being discussed on various forums.

- avg_hp = Average hp value for comparison
- normal = Normal rolling of HPs
- roll_all = Reroll any 1 infinatly
- roll_once = Reroll a 1 once

In [32]:

```
# 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()
```

Out[32]: