git.fiddlerwoaroof.com
Raw Blame History
from . import dice
from .dice import MULT,ADD
from . import type_dict

import collections

class Attributes(object):
	def __init__(self, strength, intellect, dexterity, spirit):
		self.str = strength
		self.int = intellect
		self.dex = dexterity
		self.spt = spirit
		self.state = HEALTHY

def health_mod(func):
	def _inner(self):
		mod = 0
		if self.attr.state >= WOUNDED:
			mod = -3
		result = func(self)
		result += mod
		return result
	return _inner



class Skills(object):
	def __init__(self, attr):
		self.skills = {
			'agility','craft','fighting','knowledge',
			'perception','persuasion','shooting','speed',
			'stealth','toughness'
		}
		self.attr = attr
		self.training = Trainer(self)
		self.train = self.training.select

	def check(self, skill, dieroll):
		dieroll.dice = dice.DieJar().d20
		roll = dieroll.roll()
		if roll == 1:
			result = False
		if roll == 20:
			result = True
		else:
			result = roll < getattr(self, skill)
		return result, roll-result # (passes, difference)

	@property
	@health_mod
	def agility(self):
		return (self.attr.dex * 2) + self.training.agility
	@property
	@health_mod
	def craft(self):
		return self.attr.dex + self.attr.int + self.training.craft
	@property
	@health_mod
	def fighting(self):
		return self.attr.str + self.attr.int + self.training.fighting
	@property
	@health_mod
	def knowledge(self):
		return self.attr.int * 2 + self.training.knowledge
	@property
	@health_mod
	def perception(self):
		return self.attr.int + attr.spt + self.training.perception
	@property
	@health_mod
	def persuasion(self):
		return self.attr.spt * 2 + self.training.persuasion
	@property
	@health_mod
	def shooting(self):
		return self.attr.dex + self.attr.int + self.training.shooting
	@property
	@health_mod
	def speed(self):
		return self.attr.str + self.attr.dex + self.training.speed
	@property
	@health_mod
	def stealth(self):
		return self.attr.dex + self.attr.spt + self.training.stealth
	@property
	@health_mod
	def toughness(self):
		return self.attr.str + self.attr.spt + self.training.toughness

class Trainer(object):
	mods = dict(
		untrained=-1,
		familiar=0,
		trained=1,
		experienced=2,
		mastered=3
	)

	def __init__(self, skills):
		self.skills = skills
		self.training = collections.defaultdict(lambda: -1)
		self.points = 500
		self.training_cost = 100

	def select(self, skill, degree):
		if hasattr(degree, 'upper'):
			degree = self.mods.get(degree, 0)
		if self.points >= self.training_cost and degree in self.mods.values():
			self.training[skill] = degree
			self.points -= self.training_cost

	def __getattr__(self, key):
		skills = object.__getattribute__(self, 'skills')
		if key in self.skills.skills:
			return self.training[key]
		else:
			raise
	def __setattr__(self, key, value):
		if hasattr(self, 'skills') and key in self.skills.skills:
			if -1 <= value <= 3:
				self.training[key] = int(round(value))
			else:
				raise AttributeError(
					'cannot set training of %s to %d, (out of range [-1,3])' % (key, value)
				)
		else:
			object.__setattr__(self, key, value)


class Equipment(object): pass
class Weapon(Equipment): pass
class Armor(Equipment): pass


class Slot(object):
	def __init__(self, name, type):
		self.name = name
		self.attr = 'slotted_%s' % name
		self.type = type

	def __get__(self, instance, owner):
		return self.getitem(instance)
	def __set__(self, instance, value):
		self.setitem(instance, value)

	def setitem(self, instance, value):
		if isinstance(value, self.type):
			setattr(instance, 'slotted_%s' % self.name, value)
		else:
			raise ValueError(
				'Can\'t use an object of type %s in a slot of type %s' % (type(value), self.type)
			)


	def getitem(self, instance):
		return getattr(instance, self.attr, None)

	def filled(self, instance):
		return self.getitem(instance) is not None

	@classmethod
	def add_slot(cls, name, type):
		def _inner(to_cls):
			inst = cls(name, type)
			setattr(to_cls, name, inst)
			if not hasattr(to_cls, 'equipment_slots'):
				to_cls.equipment_slots = Slots((name,inst))
			else:
				to_cls.equipment_slots[name] = inst
			return to_cls
		return _inner


class Slots(collections.MutableMapping):
	def __init__(self, *args, **kw):
		self.slots = dict(args)
		self.slots.update(kw)
		self.slots_by_type = type_dict.TypeDict(__default=[])
		for k in self.slots:
			slot = self.slots[k]
			self.slots_by_type[slot.type].append(slot)

	def __getitem__(self, key):
		return self.slots[key]
	def __setitem__(self, k, v):
		self.slots[k] = v
	def __delitem__(self, key):
		del self.slots[key]

	def __iter__(self):
		return iter(self.slots)
	def __len__(self):
		return len(self.slots)

	def slots_of_type(self, slot_type):
		return self.slots_by_type[slot_type]

class Race(object):
	registry = {}
	@classmethod
	def register(cls, new):
		cls.registry[new.__name__.lower()] = new

	@classmethod
	def random_race(cls):
		return random.choice(self.registry.values())
	@property
	def name(self):
		return self.__class__.__name__.lower()

	allowed_professions = set()
	def __init__(self, attr):
		self.mod(attr)

	def allows_profession(self, prof):
		return prof in self.allowed_professions

@Race.register
class Human(Race):
	def mod(self, attr):
		attr.spt += 1

@Race.register
class Elf(Race):
	allowed_professions = {'fighter', 'wizard', 'thief'}
	def mod(self, attr):
		attr.int += 1

@Race.register
class Dwarf(Race):
	allowed_professions = {'fighter', 'Priest', 'thief'}
	def mod(self, atr):
		attr.str += 1

@Race.register
class Hobbit(Race):
	allowed_professions = {'thief', 'barbarian'}
	def mod(self, atr):
		attr.dex += 1


class Sword(Weapon):
	attack = 1
	damage_mod = 1


HEALTHY = 0
WOUNDED = 1
KNOCKOUT = 2

@Slot.add_slot('weapon', Weapon)
@Slot.add_slot('armor', Armor)
class Adventurer(object):
	@property
	def state(self):
		return self.attributes.state
	@state.setter
	def state(self, val):
		if val not in {HEALTHY, WOUNDED, KNOCKOUT}:
			raise ValueError('Value for state invalid')
		self.attributes.state = val

	def __init__(self, name, race, str, int, dex, spt):
		self.name = name
		self.attributes = Attributes(str, int, dex, spt)
		self.skills = Skills(self.attributes)
		self.race = Race.registry[race](self.attributes)

	def wield(self, slot, equipment):
		turns = 1
		if self.equipment_slots[slot].filled(self):
			turns += 1
		self.equipment_slots[slot].setitem(self, equipment)
		return turns

	def attack(self, opponent, type='fighting', num_allies=0):
		dieroll = dice.DieRoll()

		ally_bonus = -1 if num_allies > 0 else 0
		# TODO: implement professions, figure out how to mod ally_bonus for thieves cleanly
		dieroll.add_adjustment(add=ally_bonus)

		pass_, damage = self.skills.check(type, dieroll)
		if pass_:
			if self.weapon is not None:
				damage += self.weapon.damage_mod
			print '%s inflicts %d damage upon %s' % (self.name, damage, opponent.name)
			opponent.take_damage(damage)

	def take_damage(self, damage):
		if self.armor is not None:
			damage += self.armor.damage_mod

		dieroll = dice.DieRoll()
		dieroll.add_adjustment(-damage)

		result, damage = self.skills.check('toughness', dieroll)

		if not result:
			if damage > 0:
				self.state += 1
				print '%s takes %d damage and is now %s' % (self.name, damage, ['healthy', 'wounded', 'ko'][self.state if self.state < 3 else 2])
				if self.state >= KNOCKOUT:
					self.die()

	def die(self): pass

def get_stats():
	stats = [7,7,7,7]
	import random
	mod = +1
	for x in range(100):
		idx = random.choice([x for x in range(len(stats)) if stats[x] > 5])
		stats[idx] += mod
		mod *= -1
	print sum(stats), stats
	return stats


a = Adventurer('bob', 'elf', *get_stats())
b = Adventurer('bill', 'elf', *get_stats())

while KNOCKOUT not in {a.state, b.state}:
	a.attack(b)
	b.attack(a)

print a.name, 'is', ['healthy', 'wounded', 'ko'][a.state if a.state < 3 else 2]
print b.name, 'is', ['healthy', 'wounded', 'ko'][b.state if b.state < 3 else 2]