Skip to content

Instantly share code, notes, and snippets.

@Taywee
Created August 18, 2018 06:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Taywee/ab4f5d48f384b6af6b4be1515f540ffe to your computer and use it in GitHub Desktop.
Save Taywee/ab4f5d48f384b6af6b4be1515f540ffe to your computer and use it in GitHub Desktop.
Simple MIT-licensed Dungeons and Dragons encounter calculator written in python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright © 2018 Taylor C. Richberger <taywee@gmx.com>
# This code is released under the MIT license
import locale
import argparse
from collections import namedtuple
Threshold = namedtuple('Threshold', ['easy', 'medium', 'hard', 'deadly'])
THRESHOLDS = {
1: Threshold(easy=25, medium=50, hard=75, deadly=100),
2: Threshold(easy=50, medium=100, hard=150, deadly=200),
3: Threshold(easy=75, medium=150, hard=225, deadly=400),
4: Threshold(easy=125, medium=250, hard=375, deadly=500),
5: Threshold(easy=250, medium=500, hard=750, deadly=1100),
6: Threshold(easy=300, medium=600, hard=900, deadly=1400),
7: Threshold(easy=350, medium=750, hard=1100, deadly=1700),
8: Threshold(easy=450, medium=900, hard=1400, deadly=2100),
9: Threshold(easy=550, medium=1100, hard=1600, deadly=2400),
10: Threshold(easy=600, medium=1200, hard=1900, deadly=2800),
11: Threshold(easy=800, medium=1600, hard=2400, deadly=3600),
12: Threshold(easy=1000, medium=2000, hard=3000, deadly=4500),
13: Threshold(easy=1100, medium=2200, hard=3400, deadly=5100),
14: Threshold(easy=1250, medium=2500, hard=3800, deadly=5700),
15: Threshold(easy=1400, medium=2800, hard=4300, deadly=6400),
16: Threshold(easy=1600, medium=3200, hard=4800, deadly=7200),
17: Threshold(easy=2000, medium=3900, hard=5900, deadly=8800),
18: Threshold(easy=2100, medium=4200, hard=6300, deadly=9500),
19: Threshold(easy=2400, medium=4900, hard=7300, deadly=10900),
20: Threshold(easy=2800, medium=5700, hard=8500, deadly=12700),
}
MULTIPLIERS = [0.5, 1, 1.5, 2, 2.5, 3, 4, 5]
MULTIPLIER_SELECTOR = [0, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5]
def multiplier(playercount, monstercount):
'''Find the multiplier based on the player count and monster count'''
if monstercount > 15:
monstercount = 15
monstercount -= 1
index = MULTIPLIER_SELECTOR[monstercount]
if playercount < 3:
index += 2
elif playercount < 6:
index += 1
return MULTIPLIERS[index]
def party_threshold(levels):
'''Calculate the total difficulty threshold based on player levels'''
easy = 0
medium = 0
hard = 0
deadly = 0
for level in levels:
threshold = THRESHOLDS[level]
easy += threshold.easy
medium += threshold.medium
hard += threshold.hard
deadly += threshold.deadly
return Threshold(easy, medium, hard, deadly)
def main():
parser = argparse.ArgumentParser(description='Help calculate encounter difficulty')
parser.add_argument('-V', '--version', action='version', version='0.1')
parser.add_argument('-m', '--monster', dest='monsters', help='The monster experiences, for calculating difficulty', type=int, nargs='*', default=None)
parser.add_argument('levels', help='The player levels, required', type=int, nargs='+')
args = parser.parse_args()
threshold = party_threshold(args.levels)
print('Difficulty level experience thresholds for party:')
print('{:6} {:6} {:6} {:6}'.format('easy', 'medium', 'hard', 'deadly'))
print('{:6} {:6} {:6} {:6}'.format(threshold.easy, threshold.medium, threshold.hard, threshold.deadly))
print()
if args.monsters:
total_monster_exp = sum(args.monsters)
mult = multiplier(playercount=len(args.levels), monstercount=len(args.monsters))
modified_monster_exp = total_monster_exp * mult
if modified_monster_exp > threshold.deadly:
difficulty = 'deadly'
elif modified_monster_exp > threshold.hard:
difficulty = 'hard'
elif modified_monster_exp > threshold.medium:
difficulty = 'medium'
elif modified_monster_exp > threshold.easy:
difficulty = 'easy'
else:
difficulty = 'trivial'
print('Combined monster experience is {}'.format(total_monster_exp))
print('Is multiplied by {} to become {}'.format(mult, modified_monster_exp))
print('This encounter is {}'.format(difficulty))
else:
print('Total experience for difficulty based on monster count:')
print('{:>2}: {:6} {:6} {:6} {:6}'.format('#', 'easy', 'medium', 'hard', 'deadly'))
for count in range(1, 16):
mult = multiplier(playercount=len(args.levels), monstercount=count)
print('{:>2}: {:6} {:6} {:6} {:6}'.format(
count,
round(threshold.easy / mult),
round(threshold.medium / mult),
round(threshold.hard / mult),
round(threshold.deadly / mult),
))
print()
print('Average per-monster experience for same table:')
print('{:>2}: {:6} {:6} {:6} {:6}'.format('#', 'easy', 'medium', 'hard', 'deadly'))
for count in range(1, 16):
mult = multiplier(playercount=len(args.levels), monstercount=count)
print('{:>2}: {:6} {:6} {:6} {:6}'.format(
count,
round(threshold.easy / mult / count),
round(threshold.medium / mult / count),
round(threshold.hard / mult / count),
round(threshold.deadly / mult / count),
))
if __name__ == '__main__':
locale.setlocale(locale.LC_ALL, '')
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment