D&D 5e HP Calculator

JavaScript (ES6), 81 78 76 74 bytes

Takes input as (class, level, constitution_ability_score). The class is case-insensitive.

(c,l,s,h=(d=parseInt(c,34)*59.9%97%77%6|4)+(s-10>>1))=>(h>0?h*l:h+l-1)+d-2

This is essentially using the same math as my initial version, but d is now computed without any lookup table.

Test cases

let f =

(c,l,s,h=(d=parseInt(c,34)*59.9%97%77%6|4)+(s-10>>1))=>(h>0?h*l:h+l-1)+d-2

console.log(f('Barbarian', 15, 13))  // 125
console.log(f('Rogue',     10, 18))  // 93
console.log(f('Wizard',    15, 18))  // 122
console.log(f('Wizard',    16,  1))  // 16
console.log(f('Barbarian', 15,  7))  // 80
console.log(f('Warlock',   15,  3))  // 18
console.log(f('Ranger',    14,  1))  // 18
console.log(f('Warlock',    3, 14))  // 24
console.log(f('Druid',      3,  4))  // 9
console.log(f('Cleric',    11,  5))  // 25
console.log(f('Bard',      20, 11))  // 103
console.log(f('Barbarian', 11, 13))  // 93
console.log(f('Bard',       8, 19))  // 75
console.log(f('Bard',      16, 17))  // 131
console.log(f('Fighter',   10,  6))  // 44
console.log(f('Monk',      10,  2))  // 13
console.log(f('Cleric',    14, 17))  // 115
console.log(f('Cleric',     6,  5))  // 15
console.log(f('Rogue',      7, 13))  // 45
console.log(f('Cleric',     4, 14))  // 31
console.log(f('Rogue',     19, 15))  // 136
console.log(f('Paladin',   13, 13))  // 95
console.log(f('Cleric',    13, 15))  // 94
console.log(f('Bard',       8,  5))  // 19
console.log(f('Monk',      20, 11))  // 103
console.log(f('Barbarian',  8, 20))  // 101
console.log(f('Monk',       1,  4))  // 5
console.log(f('Bard',       5, 17))  // 43
console.log(f('Monk',      18,  7))  // 57
console.log(f('Wizard',    17,  5))  // 19


Initial version, 87 84 bytes

(c,l,s,h=(d=+'55654607554506'[parseInt(c,35)%24%15])+(s-10>>1))=>(h>0?h*l:h+l-1)+d-2

How it works

The tricky part is to convert the class string c into the corresponding hit dice. More precisely, the value that we're going to store is d = dice / 2 + 1.

We use the formula parseInt(c, 35) % 24 % 15 which gives:

Class       | Base 35 -> decimal | MOD 24 | MOD 15 | d
------------+--------------------+--------+--------+---
"Sorcerer"  |      1847055419467 |     19 |      4 | 4
"Wizard"    |               1138 |     10 |     10 | 4
"Bard"      |             484833 |      9 |      9 | 5
"Cleric"    |          662409592 |     16 |      1 | 5
"Druid"     |           20703143 |     23 |      8 | 5
"Monk"      |             973475 |     11 |     11 | 5
"Rogue"     |           41566539 |      3 |      3 | 5
"Warlock"   |        59391165840 |      0 |      0 | 5
"Fighter"   |        28544153042 |      2 |      2 | 6
"Paladin"   |        46513817828 |     20 |      5 | 6
"Ranger"    |         1434103117 |     13 |     13 | 6
"Barbarian" |     25464249364423 |      7 |      7 | 7

By inserting the values of d at the corresponding positions into a string and padding unused slots with zeros, we get:

00000000001111
01234567890123
--------------
55654607554506

Hence the final formula:

d = +'55654607554506'[parseInt(c, 35) % 24 % 15]

Once we have d, we compute:

h = d + ((s - 10) >> 1))

which is the theoretical number of points that are gained at each level-up.

If h is positive, we simply compute:

h * l

If not, we need to take into account the fact that at least 1 point is gained at each level-up. So we compute instead:

h + l - 1

In both cases, we adjust the result by adding d - 2, so that the initial number of points is correctly integrated.

Test cases

let f =

(c,l,s,h=(d=+'55654607554506'[parseInt(c,35)%24%15])+(s-10>>1))=>(h>0?h*l:h+l-1)+d-2

console.log(f('Barbarian', 15, 13))  // 125
console.log(f('Rogue',     10, 18))  // 93
console.log(f('Wizard',    15, 18))  // 122
console.log(f('Wizard',    16,  1))  // 16
console.log(f('Barbarian', 15,  7))  // 80
console.log(f('Warlock',   15,  3))  // 18
console.log(f('Ranger',    14,  1))  // 18
console.log(f('Warlock',    3, 14))  // 24
console.log(f('Druid',      3,  4))  // 9
console.log(f('Cleric',    11,  5))  // 25
console.log(f('Bard',      20, 11))  // 103
console.log(f('Barbarian', 11, 13))  // 93
console.log(f('Bard',       8, 19))  // 75
console.log(f('Bard',      16, 17))  // 131
console.log(f('Fighter',   10,  6))  // 44
console.log(f('Monk',      10,  2))  // 13
console.log(f('Cleric',    14, 17))  // 115
console.log(f('Cleric',     6,  5))  // 15
console.log(f('Rogue',      7, 13))  // 45
console.log(f('Cleric',     4, 14))  // 31
console.log(f('Rogue',     19, 15))  // 136
console.log(f('Paladin',   13, 13))  // 95
console.log(f('Cleric',    13, 15))  // 94
console.log(f('Bard',       8,  5))  // 19
console.log(f('Monk',      20, 11))  // 103
console.log(f('Barbarian',  8, 20))  // 101
console.log(f('Monk',       1,  4))  // 5
console.log(f('Bard',       5, 17))  // 43
console.log(f('Monk',      18,  7))  // 57
console.log(f('Wizard',    17,  5))  // 19


Batch, 172 bytes

@set/aa=1-%3/2,h=4-a
@for %%c in (-1.Sorcerer -1.Wizard 1.Fighter 1.Paladin 1.Ranger 2.Barbarian)do @if %%~xc==.%1 set/aa-=c=%%~nc,h+=c*2
@cmd/cset/a"a*=(a>>9),-~a*~-%2+h

Takes class, level, and constitution as command-line arguments. Explanation: The HP can be calculated as (HP at level 1) + (level - 1) + min(further HP per level, 0) * (level - 1). The further HP per level is half the hit die plus the constitution modifier. Most classes use d8 so this becomes one less than half the constitution (%3/2-1), while the HP at level 1 is 3 more than that. The further HP per level and HP at level 1 are then adjusted for the six classes that don't use d8. The further HP per level is then limited to 0 (this actually uses the negative value as it's slightly golfier this way.)


R, 181 163 bytes

function(s,n,t){a=Hmisc::Cs(rc,za,rd,er,ui,mk,gu,rl,gh,la,ng,rb)
b=c(6,6,rep(8,6),rep(10,3),12)
d=b[a == substr(s,3,4)]
m=floor((t-10)/2)
d+m+(n-1)*max(d/2+1+m,1)}

Anonymous function. Runs as f(class, level, CON).

Explanation: Creates vectors for class s to dice max d, using the 3rd and 4th letters in the class name (smallest unique mapping I found).

CON mod m is straight from the spec, and HP = first level (d + m) + the rest of the levels ((n-1) * max(average_die + m, 1).

> f("Barbarian", 15, 13)
[1] 125
> f("Rogue", 10, 18)
[1] 93
> f("Wizard", 15, 18)
[1] 122
> f("Wizard", 16, 1)
[1] 16
> f("Barbarian", 15, 7)
[1] 80
> f("Warlock", 15, 3)
[1] 18
> f("Ranger", 14, 1)
[1] 18

Tags:

Code Golf