Created
August 18, 2018 06:27
-
-
Save Taywee/ab4f5d48f384b6af6b4be1515f540ffe to your computer and use it in GitHub Desktop.
Simple MIT-licensed Dungeons and Dragons encounter calculator written in python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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