Added library with richer data types
Getty Ritter
8 years ago
| 1 | import random | |
| 2 | ||
| 3 | class Die: | |
| 4 | @staticmethod | |
| 5 | def d4(): | |
| 6 | return random.randint(1, 4) | |
| 7 | ||
| 8 | @staticmethod | |
| 9 | def d6(): | |
| 10 | return random.randint(1, 6) | |
| 11 | ||
| 12 | @staticmethod | |
| 13 | def d8(): | |
| 14 | return random.randint(1, 8) | |
| 15 | ||
| 16 | @staticmethod | |
| 17 | def d10(): | |
| 18 | return random.randint(1, 10) | |
| 19 | ||
| 20 | @staticmethod | |
| 21 | def d12(): | |
| 22 | return random.randint(1, 12) | |
| 23 | ||
| 24 | @staticmethod | |
| 25 | def d20(): | |
| 26 | return random.randint(1, 20) | |
| 27 | ||
| 28 | ||
| 29 | class Data(object): | |
| 30 | @classmethod | |
| 31 | def from_json(cls, obj): | |
| 32 | if hasattr(cls, 'FIELDS'): | |
| 33 | values = {} | |
| 34 | for name, typ in cls.FIELDS: | |
| 35 | values[name] = typ.from_json(obj[name]) | |
| 36 | return cls(**values) | |
| 37 | else: | |
| 38 | raise AttributeError( | |
| 39 | 'Class {0} has no FIELDS attribute'.format(cls.__name__)) | |
| 40 | ||
| 41 | @classmethod | |
| 42 | def ensure(cls, name, obj): | |
| 43 | if isinstance(obj, cls): | |
| 44 | return obj | |
| 45 | else: | |
| 46 | raise TypeError(('Unexpected type for field {0}: ' | |
| 47 | 'expected {1}, got {2}').format( | |
| 48 | name, type(cls), type(obj))) | |
| 49 | ||
| 50 | def __repr__(self): | |
| 51 | return '{0}({1})'.format( | |
| 52 | self.__class__.__name__, | |
| 53 | ', '.join('{0}={1}'.format(k, repr(self.__dict__[k])) | |
| 54 | for k, _ in self.__class__.FIELDS)) | |
| 55 | ||
| 56 | def __str__(self): | |
| 57 | return '{0}({1})'.format( | |
| 58 | self.__class__.__name__, | |
| 59 | ', '.join('{0}={1}'.format(k, str(self.__dict__[k])) | |
| 60 | for k, _ in self.__class__.FIELDS)) | |
| 61 | ||
| 62 | def __init__(self, **kwargs): | |
| 63 | for field, typ in self.__class__.FIELDS: | |
| 64 | self.__dict__[field] = typ.ensure(field, kwargs[field]) | |
| 65 | ||
| 66 | class Stat(Data): | |
| 67 | STATS = [ | |
| 68 | 'strength', | |
| 69 | 'dexterity', | |
| 70 | 'constitution', | |
| 71 | 'intelligence', | |
| 72 | 'wisdom', | |
| 73 | 'charisma', | |
| 74 | 'sanity' | |
| 75 | ] | |
| 76 | ||
| 77 | @staticmethod | |
| 78 | def from_json(val): | |
| 79 | return Stat(val) | |
| 80 | ||
| 81 | @staticmethod | |
| 82 | def random(): | |
| 83 | sum(sorted([Die.d6(), Die.d6(), Die.d6(), Die.d6()][1:])) | |
| 84 | ||
| 85 | def __init__(self, score): | |
| 86 | self.score = score | |
| 87 | ||
| 88 | def modifier(self): | |
| 89 | return (self.score // 2) - 5 | |
| 90 | ||
| 91 | def __repr__(self): | |
| 92 | return 'Stat({0})'.format(self.score) | |
| 93 | ||
| 94 | def __str__(self): | |
| 95 | return str(self.score) | |
| 96 | ||
| 97 | def mk_block(name, fields): | |
| 98 | class Block(Data): | |
| 99 | FIELDS = fields | |
| 100 | Block.__name__ = name | |
| 101 | return Block | |
| 102 | ||
| 103 | StatBlock = mk_block('StatBlock', [(s, Stat) for s in Stat.STATS]) | |
| 104 | ||
| 105 | class Value(Data): | |
| 106 | @staticmethod | |
| 107 | def from_json(obj): | |
| 108 | if (isinstance(obj, str) or | |
| 109 | isinstance(obj, int) or | |
| 110 | isinstance(obj, float)): | |
| 111 | return Value(obj) | |
| 112 | elif isinstance(obj, dict): | |
| 113 | if len(obj) == 1: | |
| 114 | k, v = obj.popitem() | |
| 115 | if isinstance(v, dict): | |
| 116 | return Value(k, **v) | |
| 117 | else: | |
| 118 | return Value(str(k) + ': ' + str(v)) | |
| 119 | else: | |
| 120 | raise Exception('???') | |
| 121 | elif isinstance(obj, list): | |
| 122 | return [ Value.from_json(elem) for elem in obj ] | |
| 123 | ||
| 124 | @staticmethod | |
| 125 | def ensure(n, obj): | |
| 126 | if isinstance(obj, Value): | |
| 127 | return obj | |
| 128 | elif isinstance(obj, list): | |
| 129 | return [ Value.ensure(n, elem) for elem in obj ] | |
| 130 | else: | |
| 131 | return Value(obj) | |
| 132 | ||
| 133 | def __init__(self, datum, **tags): | |
| 134 | self.datum = datum | |
| 135 | self.tags = tags | |
| 136 | ||
| 137 | def __repr__(self): | |
| 138 | if not self.tags: | |
| 139 | return 'Value({0})'.format(repr(self.datum)) | |
| 140 | else: | |
| 141 | return 'Value({0}, {1})'.format( | |
| 142 | repr(self.datum), | |
| 143 | ', '.join('{0}={1}'.format(k, repr(v)) | |
| 144 | for k, v in self.tags.items())) | |
| 145 | ||
| 146 | def __str__(self): | |
| 147 | if not self.tags: | |
| 148 | return str(self.datum) | |
| 149 | else: | |
| 150 | return '{0} ({1})'.format( | |
| 151 | str(self.datum), | |
| 152 | ', '.join('{0}: {1}'.format(k, str(v)) | |
| 153 | for k, v in self.tags.items())) | |
| 154 | ||
| 155 | class Character(Data): | |
| 156 | FIELDS = [ | |
| 157 | ('name', Value), | |
| 158 | ('class', Value), | |
| 159 | ('level', Value), | |
| 160 | ('background', Value), | |
| 161 | ('race', Value), | |
| 162 | ('alignment', Value), | |
| 163 | ('stats', StatBlock), | |
| 164 | ('speed', Value), | |
| 165 | ('size', Value), | |
| 166 | ('proficiency bonus', Value), | |
| 167 | ('hit die', Value), | |
| 168 | ('hit point maximum', Value), | |
| 169 | ('ac', Value), | |
| 170 | ('personality', Value), | |
| 171 | ('ideals', Value), | |
| 172 | ('bonds', Value), | |
| 173 | ('flaw', Value), | |
| 174 | ('saving throws', Value), | |
| 175 | ('languages', Value), | |
| 176 | ('proficiencies', Value), | |
| 177 | ('skills', Value), | |
| 178 | ('spell slots', Value), | |
| 179 | ('spells', Value), | |
| 180 | ('equipment', Value), | |
| 181 | ('money', Value), | |
| 182 | ('features & traits', Value), | |
| 183 | ] |