Added library with richer data types
Getty Ritter
7 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 | ] |