Last active
August 29, 2015 14:22
-
-
Save sheagcraig/265db8b7d7508b8a3463 to your computer and use it in GitHub Desktop.
Old Skool
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 python | |
"""oldskool.py | |
Generate character stats using old school methods. | |
""" | |
# I wanted to quickly generate some old school character stats. And I | |
# wanted to express each method as tersely as possible in python. | |
# As it turns out, this is pretty simple to do as one-liners, aside | |
# from the differences in whether the player is allowed to reorder the | |
# stats or not. | |
from collections import OrderedDict | |
from random import randint | |
ATTRIBUTES = ("Strength", | |
"Intelligence", | |
"Wisdom", | |
"Dexterity", | |
"Constitution", | |
"Charisma") | |
PALADIN = {"Strength": 12, "Intelligence": 9, "Wisdom": 13, "Constitution": 9, | |
"Charisma": 17} | |
RANGER = {"Strength": 13, "Intelligence": 13, "Wisdom": 14, "Constitution": 14} | |
ILLUSIONIST = {"Intelligence": 15, "Dexterity": 16, "Constitution": 9, | |
"Charisma": 17} | |
MONK = {"Strength": 15, "Wisdom": 15, "Dexterity": 15, "Constitution": 11} | |
def d6(num=1): | |
"""Roll a d6, num times.""" | |
return sum([randint(1, 6) for x in xrange(num)]) | |
def evaluate(attrs): | |
"""Evaluate a list of scores to see if it is really bad or not.""" | |
# Handle (in this case) OrderedDict data: | |
if isinstance(attrs, dict): | |
scores = attrs.values() | |
elif len(attrs) == 6: | |
scores = attrs | |
else: | |
raise TypeError("Too many scores!") | |
# Really good. | |
if scores.count(18) + scores.count(17) > 2 or sum(scores) / 6 > 14: | |
print "%s\n%s" % (u'\U0001F63B', score_string(attrs)) | |
# Really bad. | |
elif sum(scores) < 48 or max(scores) < 14 or sum(scores) / 6 < 10: | |
print "%s\n%s" % (u'\U0001F4A9', score_string(attrs)) | |
# Okay. | |
else: | |
print "%s\n%s" % (u'\U0001F352', score_string(attrs)) | |
def score_string(attrs): | |
"""Nicely represent scores of different types.""" | |
if isinstance(attrs, dict): | |
result = ["%s: %s" % (key, attrs[key]) for key in attrs] | |
else: | |
result = [str(attr) for attr in attrs] | |
return "\n".join(result) | |
def method_zero(): | |
"""Basic D&D / AD&D 2e Method I. | |
Roll 3d6 in order. Deal with it. | |
"Iron Man" method. | |
""" | |
return OrderedDict((attr, d6(3)) for attr in ATTRIBUTES) | |
def method_one(): | |
"""AD&D 1e method I. | |
All scores are recorded and arranged in the order the player | |
desires. 4d6 are rolled, and the lowest die (or one of the lower) is | |
discarded. | |
""" | |
return [sum(sorted([d6() for x in xrange(4)])[1:]) for y in xrange(6)] | |
def method_two(): | |
"""AD&D 1e method II. | |
All scores are recorded and arranged as in Method I. 3d6 are rolled | |
12 times and the highest 6 scores are retained. | |
""" | |
return sorted([d6(3) for x in xrange(12)])[6:] | |
def method_three(): | |
"""AD&D 1e method III. | |
Scores rolled are according to each ability category, in order, | |
STRENGTH, INTELLIGENCE, WISDOM, DEXTERITY, CONSTITUTION, CHARISMA. | |
3d6 are rolled 6 times for each ability and the highest score in | |
each category is retained for that category. | |
""" | |
return OrderedDict((attr, max((d6(3) for x in xrange(6)))) for attr in | |
ATTRIBUTES) | |
def method_four(): | |
"""AD&D 1e method IV. | |
3d6 are rolled sufficient times to generate the 6 ability scores, in | |
order, for 12 characters. The player then selects the single set of | |
scores which he or she finds most desirable and these scores are | |
noted on the character record sheet. | |
""" | |
return [method_zero() for x in xrange(12)] | |
def rolls_for_a_class(c_class, method): | |
"""Return num of rolls it takes for stats elibible for paladinhood""" | |
is_eligible = False | |
ctr = 1 | |
while not is_eligible: | |
is_eligible = meets_minimums(method(), c_class) | |
ctr += 1 | |
return ctr | |
def percentage_of_class(c_class, method, trials=1000000): | |
return [meets_minimums(method(), c_class) for _ in | |
xrange(trials)].count(True) / float(trials) * 100 | |
def meets_minimums(stats, required): | |
"""Return bool whether a set of stats meet minimums.""" | |
result = True | |
# Stats rolled are in order. | |
if isinstance(stats, dict) and isinstance(required, dict): | |
for stat in required: | |
if stats[stat] < required[stat]: | |
result = False | |
break | |
# Stats are unordered. | |
elif isinstance(required, dict): | |
sorted_requirements = sorted(required.values()) | |
sorted_requirements.reverse() | |
sorted_stats = sorted(stats) | |
for requirement in sorted_requirements: | |
if sorted_stats.pop() < requirement: | |
result = False | |
break | |
else: | |
raise AttributeError("Incorrect required type.") | |
return result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment