import random
class Die:
@staticmethod
def d4():
return random.randint(1, 4)
@staticmethod
def d6():
return random.randint(1, 6)
@staticmethod
def d8():
return random.randint(1, 8)
@staticmethod
def d10():
return random.randint(1, 10)
@staticmethod
def d12():
return random.randint(1, 12)
@staticmethod
def d20():
return random.randint(1, 20)
class Data(object):
@classmethod
def from_json(cls, obj):
if hasattr(cls, 'FIELDS'):
values = {}
for name, typ in cls.FIELDS:
values[name] = typ.from_json(obj[name])
return cls(**values)
else:
raise AttributeError(
'Class {0} has no FIELDS attribute'.format(cls.__name__))
@classmethod
def ensure(cls, name, obj):
if isinstance(obj, cls):
return obj
else:
raise TypeError(('Unexpected type for field {0}: '
'expected {1}, got {2}').format(
name, type(cls), type(obj)))
def __repr__(self):
return '{0}({1})'.format(
self.__class__.__name__,
', '.join('{0}={1}'.format(k, repr(self.__dict__[k]))
for k, _ in self.__class__.FIELDS))
def __str__(self):
return '{0}({1})'.format(
self.__class__.__name__,
', '.join('{0}={1}'.format(k, str(self.__dict__[k]))
for k, _ in self.__class__.FIELDS))
def __init__(self, **kwargs):
for field, typ in self.__class__.FIELDS:
self.__dict__[field] = typ.ensure(field, kwargs[field])
class Stat(Data):
STATS = [
'strength',
'dexterity',
'constitution',
'intelligence',
'wisdom',
'charisma',
'sanity'
]
@staticmethod
def from_json(val):
return Stat(val)
@staticmethod
def random():
sum(sorted([Die.d6(), Die.d6(), Die.d6(), Die.d6()][1:]))
def __init__(self, score):
self.score = score
def modifier(self):
return (self.score // 2) - 5
def __repr__(self):
return 'Stat({0})'.format(self.score)
def __str__(self):
return str(self.score)
def mk_block(name, fields):
class Block(Data):
FIELDS = fields
Block.__name__ = name
return Block
StatBlock = mk_block('StatBlock', [(s, Stat) for s in Stat.STATS])
class Value(Data):
@staticmethod
def from_json(obj):
if (isinstance(obj, str) or
isinstance(obj, int) or
isinstance(obj, float)):
return Value(obj)
elif isinstance(obj, dict):
if len(obj) == 1:
k, v = obj.popitem()
if isinstance(v, dict):
return Value(k, **v)
else:
return Value(str(k) + ': ' + str(v))
else:
raise Exception('???')
elif isinstance(obj, list):
return [ Value.from_json(elem) for elem in obj ]
@staticmethod
def ensure(n, obj):
if isinstance(obj, Value):
return obj
elif isinstance(obj, list):
return [ Value.ensure(n, elem) for elem in obj ]
else:
return Value(obj)
def __init__(self, datum, **tags):
self.datum = datum
self.tags = tags
def __repr__(self):
if not self.tags:
return 'Value({0})'.format(repr(self.datum))
else:
return 'Value({0}, {1})'.format(
repr(self.datum),
', '.join('{0}={1}'.format(k, repr(v))
for k, v in self.tags.items()))
def __str__(self):
if not self.tags:
return str(self.datum)
else:
return '{0} ({1})'.format(
str(self.datum),
', '.join('{0}: {1}'.format(k, str(v))
for k, v in self.tags.items()))
class Character(Data):
FIELDS = [
('name', Value),
('class', Value),
('level', Value),
('background', Value),
('race', Value),
('alignment', Value),
('stats', StatBlock),
('speed', Value),
('size', Value),
('proficiency bonus', Value),
('hit die', Value),
('hit point maximum', Value),
('ac', Value),
('personality', Value),
('ideals', Value),
('bonds', Value),
('flaw', Value),
('saving throws', Value),
('languages', Value),
('proficiencies', Value),
('skills', Value),
('spell slots', Value),
('spells', Value),
('equipment', Value),
('money', Value),
('features & traits', Value),
]