git.fiddlerwoaroof.com
Browse code

Merge branch 'feature/enemies' into develop

edwlan authored on 29/07/2013 14:35:14
Showing 17 changed files
4 6
new file mode 100644
... ...
@@ -0,0 +1,36 @@
1
+grass:
2
+   char: '.'
3
+   fg: [0,255,0]
4
+   bg: [0,100,0]
5
+   passable: true
6
+   transparent: true
7
+   prob: 10
8
+sand:
9
+   char: ','
10
+   fg: [255,255,0]
11
+   bg: [100,100,0]
12
+   passable: true
13
+   transparent: true
14
+scrub:
15
+   char: ','
16
+   fg: [0,127,0]
17
+   bg: [0,64,0]
18
+   passable: true
19
+   transparent: true
20
+water:
21
+   char: '~'
22
+   fg: [128,128,255]
23
+   bg: [64,64,128]
24
+   transparent: true
25
+   prob: 5
26
+brush:
27
+   char: '#'
28
+   fg: [0,122,0]
29
+   bg: [0,64,0]
30
+   passable: true
31
+   prob: 0.5
32
+tree:
33
+   char: '#'
34
+   fg: [0,244,0]
35
+   bg: [0,122,0]
36
+   prob: 0.1
0 37
new file mode 100644
... ...
@@ -0,0 +1,9 @@
1
+class Cache(object):
2
+	def __init__(self):
3
+		self.cache = {}
4
+	def add(self, key, value):
5
+		self.cache[key] = value
6
+	def get(self, key, cb):
7
+		if key not in self.cache:
8
+			self.add(key, cb())
9
+		return self.cache[key]
0 10
new file mode 100644
... ...
@@ -0,0 +1,122 @@
1
+from . import dice
2
+
3
+from .combat_parts import races
4
+from .combat_parts import equipment
5
+from .combat_parts import skills
6
+from .combat_parts import constants as const
7
+
8
+import collections
9
+import random
10
+
11
+def get_stats(sum_):
12
+	stats = [sum_/4]*4
13
+	if sum(stats) != sum_:
14
+		stats[0] += sum_ - sum(stats)
15
+
16
+	mod = +1
17
+	for x in range(100):
18
+		idx = random.choice([x for x in range(len(stats)) if stats[x] > 3])
19
+		stats[idx] += mod
20
+		mod *= -1
21
+	return stats
22
+
23
+class Attributes(object):
24
+	@classmethod
25
+	def randomize(cls, sum_):
26
+		chosen_stats = get_stats(sum_)
27
+		return cls(*chosen_stats)
28
+
29
+	def __init__(self, strength, intellect, dexterity, spirit):
30
+		self.str = strength
31
+		self.int = intellect
32
+		self.dex = dexterity
33
+		self.spt = spirit
34
+		self.state = const.HEALTHY
35
+	def __str__(self):
36
+		return '%s %s %s %s %s' % (self.str, self.int, self.dex, self.spt, self.state)
37
+
38
+
39
+@equipment.Slot.add_slot('weapon', equipment.Weapon)
40
+@equipment.Slot.add_slot('armor', equipment.Armor)
41
+class Adventurer(object):
42
+	@property
43
+	def state(self):
44
+		return self.attributes.state
45
+	@state.setter
46
+	def state(self, val):
47
+		if val < 0: val == 0
48
+		if val > const.KNOCKOUT: val = const.KNOCKOUT
49
+		self.attributes.state = val
50
+
51
+	@classmethod
52
+	def randomize(cls, stat_sum=28):
53
+		name = '%s %s%s' % (
54
+			random.choice(['Bill', 'Bob', 'Jerry']),
55
+			random.choice(['Longnose', 'Short-Tail', 'Squint']),
56
+			random.choice(['', ' the Great', ' The Cross-eyed', ' The Magnificent'])
57
+		)
58
+		race = races.Race.random_race()
59
+		attr = Attributes.randomize(stat_sum)
60
+		print attr
61
+		return cls(name, race, attr)
62
+
63
+	@classmethod
64
+	def with_stats(cls, name, race, str, int, dex, spt):
65
+		attr = Attributes(str, int, dex, spt)
66
+		return cls(name, race, attr)
67
+
68
+	def __init__(self, name, race, attr):
69
+		self.name = name
70
+		self.attributes = attr
71
+		self.skills = skills.Skills(self.attributes)
72
+		self.race = races.Race.registry[race](self.attributes)
73
+
74
+	def wield(self, slot, equipment):
75
+		turns = 1
76
+		if self.equipment_slots[slot].filled(self):
77
+			turns += 1
78
+		self.equipment_slots[slot].setitem(self, equipment)
79
+		return turns
80
+
81
+	def attack(self, opponent, type='fighting', num_allies=0):
82
+		dieroll = dice.DieRoll()
83
+
84
+		ally_bonus = -1 if num_allies > 0 else 0
85
+		# TODO: implement professions, figure out how to mod ally_bonus for thieves cleanly
86
+		dieroll.add_adjustment(add=ally_bonus)
87
+
88
+		pass_, damage = self.skills.check(type, dieroll)
89
+		if pass_:
90
+			if self.weapon is not None:
91
+				damage += self.weapon.damage_mod
92
+			print '%s inflicts %d damage upon %s' % (self.name, damage, opponent.name)
93
+			opponent.take_damage(damage)
94
+
95
+	def take_damage(self, damage):
96
+		if self.armor is not None:
97
+			damage += self.armor.damage_mod
98
+
99
+		dieroll = dice.DieRoll()
100
+		dieroll.add_adjustment(-damage)
101
+
102
+		result, __ = self.skills.check('toughness', dieroll)
103
+
104
+		if not result:
105
+			if damage > 0:
106
+				self.state += 1
107
+				print '%s takes %d damage and is now %s' % (self.name, damage, ['healthy', 'wounded', 'ko'][self.state if self.state < 3 else 2])
108
+				if self.state >= const.KNOCKOUT:
109
+					self.die()
110
+
111
+	def die(self): pass
112
+
113
+if __name__ == '__main__':
114
+	a = Adventurer.randomize(28)
115
+	b = Adventurer.randomize(28)
116
+
117
+	while const.KNOCKOUT not in {a.state, b.state}:
118
+		a.attack(b)
119
+		b.attack(a)
120
+
121
+	print a.name, 'is', ['healthy', 'wounded', 'ko'][a.state if a.state < 3 else 2]
122
+	print b.name, 'is', ['healthy', 'wounded', 'ko'][b.state if b.state < 3 else 2]
1 124
new file mode 100644
... ...
@@ -0,0 +1,5 @@
1
+HEALTHY = 0
2
+WOUNDED = 1
3
+KNOCKOUT = 2
4
+
5
+
0 6
new file mode 100644
... ...
@@ -0,0 +1,77 @@
1
+from .. import type_dict
2
+
3
+import collections
4
+
5
+class Equipment(object): pass
6
+class Weapon(Equipment): pass
7
+class Armor(Equipment): pass
8
+
9
+
10
+class Slot(object):
11
+	def __init__(self, name, type):
12
+		self.name = name
13
+		self.attr = 'slotted_%s' % name
14
+		self.type = type
15
+
16
+	def __get__(self, instance, owner):
17
+		return self.getitem(instance)
18
+	def __set__(self, instance, value):
19
+		self.setitem(instance, value)
20
+
21
+	def setitem(self, instance, value):
22
+		if isinstance(value, self.type):
23
+			setattr(instance, 'slotted_%s' % self.name, value)
24
+		else:
25
+			raise ValueError(
26
+				'Can\'t use an object of type %s in a slot of type %s' % (type(value), self.type)
27
+			)
28
+
29
+
30
+	def getitem(self, instance):
31
+		return getattr(instance, self.attr, None)
32
+
33
+	def filled(self, instance):
34
+		return self.getitem(instance) is not None
35
+
36
+	@classmethod
37
+	def add_slot(cls, name, type):
38
+		def _inner(to_cls):
39
+			inst = cls(name, type)
40
+			setattr(to_cls, name, inst)
41
+			if not hasattr(to_cls, 'equipment_slots'):
42
+				to_cls.equipment_slots = Slots((name,inst))
43
+			else:
44
+				to_cls.equipment_slots[name] = inst
45
+			return to_cls
46
+		return _inner
47
+
48
+
49
+class Slots(collections.MutableMapping):
50
+	def __init__(self, *args, **kw):
51
+		self.slots = dict(args)
52
+		self.slots.update(kw)
53
+		self.slots_by_type = type_dict.TypeDict(__default=[])
54
+		for k in self.slots:
55
+			slot = self.slots[k]
56
+			self.slots_by_type[slot.type].append(slot)
57
+
58
+	def __getitem__(self, key):
59
+		return self.slots[key]
60
+	def __setitem__(self, k, v):
61
+		self.slots[k] = v
62
+	def __delitem__(self, key):
63
+		del self.slots[key]
64
+
65
+	def __iter__(self):
66
+		return iter(self.slots)
67
+	def __len__(self):
68
+		return len(self.slots)
69
+
70
+	def slots_of_type(self, slot_type):
71
+		return self.slots_by_type[slot_type]
72
+
73
+class Sword(Weapon):
74
+	attack = 1
75
+	damage_mod = 1
76
+
77
+
0 78
new file mode 100644
... ...
@@ -0,0 +1,48 @@
1
+import random
2
+
3
+class Race(object):
4
+	registry = {}
5
+	@classmethod
6
+	def register(cls, new):
7
+		cls.registry[new.__name__.lower()] = new
8
+
9
+	@classmethod
10
+	def random_race(cls):
11
+		return random.choice(cls.registry.keys())
12
+
13
+	@property
14
+	def name(self):
15
+		return self.__class__.__name__.lower()
16
+
17
+	allowed_professions = set()
18
+	def __init__(self, attr):
19
+		self.mod(attr)
20
+
21
+	def allows_profession(self, prof):
22
+		return prof in self.allowed_professions
23
+
24
+@Race.register
25
+class Human(Race):
26
+	def mod(self, attr):
27
+		attr.spt += 1
28
+
29
+@Race.register
30
+class Elf(Race):
31
+	allowed_professions = {'fighter', 'wizard', 'thief'}
32
+	def mod(self, attr):
33
+		attr.int += 1
34
+
35
+@Race.register
36
+class Dwarf(Race):
37
+	allowed_professions = {'fighter', 'Priest', 'thief'}
38
+	def mod(self, attr):
39
+		attr.str += 1
40
+
41
+@Race.register
42
+class Hobbit(Race):
43
+	allowed_professions = {'thief', 'barbarian'}
44
+	def mod(self, attr):
45
+		attr.dex += 1
46
+
47
+
48
+
0 49
new file mode 100644
... ...
@@ -0,0 +1,124 @@
1
+from ..dice import MULT, ADD
2
+from .. import dice
3
+from .constants import *
4
+
5
+import collections
6
+
7
+def health_mod(func):
8
+	def _inner(self):
9
+		mod = 0
10
+		if self.attr.state >= WOUNDED:
11
+			mod = -3
12
+		result = func(self)
13
+		result += mod
14
+		return result
15
+	return _inner
16
+
17
+class Skills(object):
18
+	def __init__(self, attr):
19
+		self.skills = {
20
+			'agility','craft','fighting','knowledge',
21
+			'perception','persuasion','shooting','speed',
22
+			'stealth','toughness'
23
+		}
24
+		self.attr = attr
25
+		self.training = Trainer(self)
26
+		self.train = self.training.select
27
+
28
+	def check(self, skill, dieroll=None):
29
+		die = dice.DieJar().d20
30
+		if dieroll is None:
31
+			dieroll = die
32
+		else:
33
+			dieroll.dice = dice.DieJar().d20
34
+
35
+		roll = dieroll.roll()
36
+		if roll == 1:
37
+			result = False
38
+		if roll == 20:
39
+			result = True
40
+		else:
41
+			result = roll < getattr(self, skill)
42
+		return result, roll-result # (passes, difference)
43
+
44
+	@property
45
+	@health_mod
46
+	def agility(self):
47
+		return (self.attr.dex * 2) + self.training.agility
48
+	@property
49
+	@health_mod
50
+	def craft(self):
51
+		return self.attr.dex + self.attr.int + self.training.craft
52
+	@property
53
+	@health_mod
54
+	def fighting(self):
55
+		return self.attr.str + self.attr.int + self.training.fighting
56
+	@property
57
+	@health_mod
58
+	def knowledge(self):
59
+		return self.attr.int * 2 + self.training.knowledge
60
+	@property
61
+	@health_mod
62
+	def perception(self):
63
+		return self.attr.int + attr.spt + self.training.perception
64
+	@property
65
+	@health_mod
66
+	def persuasion(self):
67
+		return self.attr.spt * 2 + self.training.persuasion
68
+	@property
69
+	@health_mod
70
+	def shooting(self):
71
+		return self.attr.dex + self.attr.int + self.training.shooting
72
+	@property
73
+	@health_mod
74
+	def speed(self):
75
+		return self.attr.str + self.attr.dex + self.training.speed
76
+	@property
77
+	@health_mod
78
+	def stealth(self):
79
+		return self.attr.dex + self.attr.spt + self.training.stealth
80
+	@property
81
+	@health_mod
82
+	def toughness(self):
83
+		return self.attr.str + self.attr.spt + self.training.toughness
84
+
85
+class Trainer(object):
86
+	mods = dict(
87
+		untrained=-1,
88
+		familiar=0,
89
+		trained=1,
90
+		experienced=2,
91
+		mastered=3
92
+	)
93
+
94
+	def __init__(self, skills):
95
+		self.skills = skills
96
+		self.training = collections.defaultdict(lambda: -1)
97
+		self.points = 500
98
+		self.training_cost = 100
99
+
100
+	def select(self, skill, degree):
101
+		if hasattr(degree, 'upper'):
102
+			degree = self.mods.get(degree, 0)
103
+		if self.points >= self.training_cost and degree in self.mods.values():
104
+			self.training[skill] = degree
105
+			self.points -= self.training_cost
106
+
107
+	def __getattr__(self, key):
108
+		skills = object.__getattribute__(self, 'skills')
109
+		if key in self.skills.skills:
110
+			return self.training[key]
111
+		else:
112
+			raise
113
+	def __setattr__(self, key, value):
114
+		if hasattr(self, 'skills') and key in self.skills.skills:
115
+			if -1 <= value <= 3:
116
+				self.training[key] = int(round(value))
117
+			else:
118
+				raise AttributeError(
119
+					'cannot set training of %s to %d, (out of range [-1,3])' % (key, value)
120
+				)
121
+		else:
122
+			object.__setattr__(self, key, value)
123
+
124
+
0 125
new file mode 100644
... ...
@@ -0,0 +1,173 @@
1
+import random
2
+import collections
3
+import itertools
4
+import re
5
+
6
+basestr = (str,unicode)
7
+
8
+class DieJar(object):
9
+	parser = re.compile(r'^(?:_?(?P<num>[1-9][0-9]*))?d(?P<sides>[1-9][0-9]*)$')
10
+	def __getattr__(self, key):
11
+		out = []
12
+		for sect in key.split('__'):
13
+			out.extend(self.parse_section(sect))
14
+		return Dice(out)
15
+
16
+	def parse_section(self, key):
17
+		out = self.parser.match(key)
18
+		if out is None:
19
+			raise AttributeError('invalid die specification')
20
+		else:
21
+			dct = out.groupdict()
22
+			dct['sides'] = int(dct['sides'])
23
+			dct['num'] = int(dct['num'] or 1)
24
+			dice = [Die(dct['sides']) for __ in range(dct['num'])]
25
+			return dice
26
+
27
+
28
+
29
+
30
+
31
+def iterable(obj):
32
+	return (not isinstance(obj, basestr)) and isinstance(obj, collections.Iterable)
33
+
34
+def flatten(lis):
35
+	return itertools.chain(*[(x if iterable(x) else [x]) for x in lis])
36
+
37
+class Die(object):
38
+	'Note: hashed by sides, min and step.  Consequently not necessarily preserved when used as a dictionary key'
39
+	def __init__(self, sides, min=1, step=1):
40
+		self.sides = sides
41
+		self.min = min
42
+		self.step = 1
43
+		self.sides = sides
44
+		self.choices = range(min, (min+sides)*step, step)
45
+		self._value = None
46
+		self.combine_func = lambda a,b: a+b
47
+
48
+	def roll(self):
49
+		self._value = random.choice(self.choices)
50
+		return self._value
51
+
52
+	@property
53
+	def value(self):
54
+		if self._value is None: self.roll()
55
+		return self._value
56
+
57
+	def combine(self, other):
58
+		if hasattr(other, 'value'): other = other.value
59
+		return self.combine_func(self.value, other.value)
60
+
61
+	def __str__(self):
62
+		base = 'd%d' % self.sides
63
+		if self.min != 1:
64
+			base = '%s+%d' % (base,self.min)
65
+		if self.step != 1:
66
+			base = '(%s)*%d' % (base, self.step)
67
+		return base
68
+
69
+	def __eq__(self, other):
70
+		return (self.sides == other.sides) and (self.min == other.min) and (self.step == other.step)
71
+
72
+	def __hash__(self):
73
+		return hash((self.sides,self.min,self.step))
74
+
75
+class Dice(collections.Sequence):
76
+	'A collection of dice, can be initialized either with Die instances or lists of Die instances'
77
+
78
+	def __init__(self, *dice, **kw):
79
+		self.dice = list(flatten(dice))
80
+		self.combiner = kw.get('combiner', lambda a,b:a+b)
81
+
82
+	def __getitem__(self, k): return self.dice[k]
83
+	def __len__(self): return len(self.dice)
84
+
85
+	def roll(self):
86
+		return reduce(self.combiner, (die.roll() for die in self.dice))
87
+	def __str__(self):
88
+		groups = collections.Counter(self.dice)
89
+		out = []
90
+		dice = sorted(groups, key=lambda k:-groups[k])
91
+		if len(dice) > 1:
92
+			for die in dice[:-1]:
93
+				count = groups[die]
94
+				out.append('%d%s' % (count,die))
95
+			out = ','.join(out)
96
+			out = ' and '.join([out, str(dice[-1])])
97
+		else:
98
+			out = '%d%s' % (groups[dice[0]],dice[0])
99
+
100
+
101
+		return out
102
+
103
+
104
+
105
+MULT=1
106
+ADD=2
107
+class DieRoll(object):
108
+	def __init__(self, dice=None, adjustments=None):
109
+		self.dice = dice
110
+		self.adjustments = [] if adjustments is None else adjustments
111
+
112
+	def add_adjustment(self, add=None, mult=None):
113
+		if None not in {add,mult}: raise ValueError('can\'t add two adjustments at once')
114
+		elif {add,mult} - {None} == set(): raise ValueError('No adjustment given')
115
+
116
+		if add is not None:
117
+			adjustment = (ADD,add)
118
+		elif mult is not None:
119
+			adjustment = (MULT,mult)
120
+		self.adjustments.append(adjustment)
121
+
122
+	def roll(self):
123
+		result = self.dice.roll()
124
+		for type,bonus in self.adjustments:
125
+			if type == MULT:
126
+				result *= type
127
+			elif type == ADD:
128
+				result += bonus
129
+		return result
130
+
131
+if __name__ == '__main__':
132
+	import unittest
133
+	tests = unittest.TestSuite()
134
+	inst = lambda a:a()
135
+
136
+	class TestDie(unittest.TestCase):
137
+		def test_roll_plain(self):
138
+			a = Die(6)
139
+			for __ in range(40):
140
+				self.assertLess(a.roll(), 7)
141
+				self.assertGreater(a.roll(), 0)
142
+		def test_roll_min(self):
143
+			a = Die(6,min=4)
144
+			for __ in range(40):
145
+				self.assertLess(a.roll(), 6+4+1)
146
+				self.assertGreater(a.roll(), 3)
147
+		def test_roll_step(self):
148
+			a = Die(6,step=2)
149
+			for __ in range(40):
150
+				self.assertLess(a.roll(), 6+(6*2)+1)
151
+				self.assertGreater(a.roll(), 0)
152
+				self.assertTrue((a.roll()-1) % 2 == 0)
153
+		def test_str(self):
154
+			self.assertEqual(str(Die(6)), 'd6')
155
+			self.assertEqual(str(Die(6,min=2)), 'd6+2')
156
+
157
+	class TestDice(unittest.TestCase):
158
+		def test_init(self):
159
+			dice = [Die(6) for __ in range(20)]
160
+			a = Dice(dice)
161
+			self.assertEqual(dice,a.dice)
162
+			a = Dice(*dice)
163
+			self.assertEqual(dice,a.dice)
164
+		def test_roll(self):
165
+			dice = [Die(6) for __ in range(2)]
166
+			dice = Dice(dice)
167
+			self.assertGreater(dice.roll(), 1)
168
+			self.assertLess(dice.roll(), 13)
169
+
170
+	class TestDieRoll(unittest.TestCase):
171
+		pass
172
+
173
+	unittest.main()
0 174
new file mode 100644
... ...
@@ -0,0 +1,119 @@
1
+import abc
2
+import collections
3
+from . import combat
4
+
5
+class Overlay(object):
6
+	__metaclass__ = abc.ABCMeta
7
+
8
+	char = abc.abstractproperty()
9
+	color = (0,0,0)
10
+	blocks = False
11
+
12
+	@property
13
+	def pos(self):
14
+		return self.x, self.y
15
+	def __init__(self, x,y, map):
16
+		self.events = collections.OrderedDict()
17
+		self.x = x
18
+		self.y = y
19
+		self.map = map
20
+		for key, value in self.handled_events.viewitems():
21
+			self.events.setdefault(key, []).extend(value)
22
+
23
+	def draw(self):
24
+		self.map.add(self)
25
+
26
+	def register_event(self, event, cb):
27
+		'''register a callback for an event
28
+
29
+		if the callback returns a false value besides None, execution will end after that event'''
30
+		self.events.setdefault(event, []).append(cb)
31
+
32
+	def trigger_event(self, event, *args, **kw):
33
+		if event not in self.events:
34
+			raise ValueError('%r has no event %r' % (self, event))
35
+
36
+		for cb in self.events[event]:
37
+			result = cb(self, *args, **kw)
38
+			result = result is None or result # if the event returns a false value besides None, break
39
+			if result == False: break
40
+
41
+	handled_events = collections.OrderedDict()
42
+	@classmethod
43
+	def handle_event(cls, event):
44
+		def _inner(cb):
45
+			cls.handled_events.setdefault(event, []).append(cb)
46
+			return cb
47
+		return _inner
48
+
49
+
50
+
51
+class Actor(Overlay):
52
+	char = ord('@')
53
+	color = (255,0,0)
54
+	blocks = True
55
+
56
+	@property
57
+	def pos(self):
58
+		return self.x, self.y
59
+
60
+	def __init__(self, x,y, map, adventurer=None):
61
+		super(Actor, self).__init__(x,y, map)
62
+		self.inventory = []
63
+		if adventurer is None:
64
+			adventurer = combat.Adventurer.randomize()
65
+		self.adventurer = adventurer
66
+
67
+	def move(self, dx, dy):
68
+		dx, dy = self.map.move(self, dx,dy)
69
+		self.x += dx
70
+		self.y += dy
71
+
72
+	def tick(self):
73
+		result = True
74
+		if self.adventurer.state >= 2:
75
+			result = False
76
+			self.char = ord('%')
77
+			self.blocks = False
78
+		return result
79
+
80
+	def ishostile(self, other):
81
+		return self.adventurer.state < 2 #TODO: implement factions
82
+
83
+	def bump(self, other):
84
+		print '%s bumped %s' % (type(self).__name__, type(other).__name__)
85
+		if isinstance(other, Actor) and other.ishostile(self):
86
+			self.adventurer.attack(other.adventurer)
87
+			other.trigger_event('attacked', self)
88
+		elif isinstance(other, Object):
89
+			self.pickup(other)
90
+			other.trigger_event('picked_up', self)
91
+
92
+		other.trigger_event('bumped', self)
93
+
94
+	@Overlay.handle_event('attacked')
95
+	def attacked_by(self, other):
96
+		if self.adventurer.skills.check('agility'):
97
+			self.adventurer.attack(other.adventurer)
98
+
99
+	@Overlay.handle_event('bumped')
100
+	def bumped_by(self, other):
101
+		print '%s was bumped by %s' % (type(self).__name__, type(other).__name__)
102
+
103
+class Object(Overlay):
104
+	item = None
105
+	@Overlay.handle_event('picked_up')
106
+	def picked_up_by(self, other):
107
+		self.map.remove(self)
108
+
109
+class Weapon(Object):
110
+	item = None
111
+
112
+class Potion(Object):
113
+	char = ord('!')
114
+
115
+class Scroll(Object):
116
+	char = ord('!')
117
+
118
+class Equipment(Object):
119
+	pass
0 120
new file mode 100644
... ...
@@ -0,0 +1,74 @@
1
+# Copyright (c) 2011 Edward Langley
2
+# All rights reserved.
3
+#
4
+# Redistribution and use in source and binary forms, with or without
5
+# modification, are permitted provided that the following conditions
6
+# are met:
7
+#
8
+# Redistributions of source code must retain the above copyright notice,
9
+# this list of conditions and the following disclaimer.
10
+#
11
+# Redistributions in binary form must reproduce the above copyright
12
+# notice, this list of conditions and the following disclaimer in the
13
+# documentation and/or other materials provided with the distribution.
14
+#
15
+# Neither the name of the project's author nor the names of its
16
+# contributors may be used to endorse or promote products derived from
17
+# this software without specific prior written permission.
18
+#
19
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
30
+
31
+import random
32
+import numpy.random
33
+
34
+class MyRand(random.Random):
35
+  def random(self):
36
+    return numpy.random.random()
37
+  def seed(self, a=None):
38
+    if a is None:
39
+      import time
40
+      a = time.time() * 10000
41
+    print a
42
+    numpy.random.seed(int(a))
43
+    self.gauss_next = None
44
+  def setstate(self, state):
45
+    numpy.random.set_state(state)
46
+  def getstate(self):
47
+    return numpy.random.get_state()
48
+  def jumpahead(self, n):
49
+    self.seed()
50
+
51
+_inst = random._inst = MyRand()
52
+random.seed = _inst.seed
53
+random.random = _inst.random
54
+random.uniform = _inst.uniform
55
+random.triangular = _inst.triangular
56
+random.randint = _inst.randint
57
+random.choice = _inst.choice
58
+random.randrange = _inst.randrange
59
+random.sample = _inst.sample
60
+random.shuffle = _inst.shuffle
61
+random.normalvariate = _inst.normalvariate
62
+random.lognormvariate = _inst.lognormvariate
63
+random.expovariate = _inst.expovariate
64
+random.vonmisesvariate = _inst.vonmisesvariate
65
+random.gammavariate = _inst.gammavariate
66
+random.gauss = _inst.gauss
67
+random.betavariate = _inst.betavariate
68
+random.paretovariate = _inst.paretovariate
69
+random.weibullvariate = _inst.weibullvariate
70
+random.getstate = _inst.getstate
71
+random.setstate = _inst.setstate
72
+random.jumpahead = _inst.jumpahead
73
+random.getrandbits = _inst.getrandbits
74
+
0 75
new file mode 100644
... ...
@@ -0,0 +1,72 @@
1
+import collections
2
+import copy
3
+
4
+class TypeDict(collections.MutableMapping):
5
+	def __init__(self, *args, **kw):
6
+		if '__default' in kw:
7
+			self.default = kw.pop('__default')
8
+
9
+		self.store = dict(args)
10
+		self.store.update(kw)
11
+
12
+	def get_store(self, cls):
13
+		cur = self.store
14
+		bases = cls.__bases__
15
+		if bases:
16
+			head, tail = bases[0], bases[1:]
17
+			while tail:
18
+				cur = self.store.setdefault(head, {})
19
+				head, tail = tail[0], tail[1:]
20
+		return cur
21
+
22
+	def __getitem__(self, cls):
23
+		store = self.get_store(cls)
24
+		try:
25
+			print store
26
+			return store[cls]
27
+		except KeyError:
28
+			if hasattr(self, 'default'):
29
+				return copy.copy(self.default)
30
+			else:
31
+				raise
32
+
33
+	def __setitem__(self, cls, value):
34
+		store = self.get_store(cls)
35
+		store[cls] = value
36
+	def __delitem__(self, cls):
37
+		store = self.get_store(cls)
38
+		del store[cls]
39
+
40
+	def __iter__(self):
41
+		return TypeDictIterator(self)
42
+	def __len__(self):
43
+		return len(self.slots)
44
+
45
+class _Null: pass
46
+
47
+class TypeDictIterator(collections.Iterator):
48
+	def __init__(self, dct):
49
+		self.dct = dct
50
+		self.dcts = [dct]
51
+		self.iters = [iter(dct)]
52
+		self.cur = self.iters[-1]
53
+
54
+	def next(self):
55
+		result = _Null
56
+		try: result = self.cur.next()
57
+		except StopIteration:
58
+			self.iters.pop()
59
+			self.dcts.pop()
60
+			if self.iters:
61
+				self.cur = self.iters[-1]
62
+				result = self.cur.next()
63
+			else:
64
+				raise StopIteration
65
+
66
+		if result in self.dcts[-1] and self.dcts[-1][result]:
67
+			self.iters.append(iter(self.dcts[-1][result]))
68
+			self.cur = self.iters[-1]
69
+
70
+		return result
71
+
72
+
... ...
@@ -1,12 +1,19 @@
1
+import libs.patch_random
2
+import random
3
+#random.seed(2)
4
+
1 5
 import libtcodpy as tc
2 6
 import numpy as np
3 7
 
8
+from libs import overlay
9
+from libs import combat
4 10
 from src import events
5 11
 from src import player
6 12
 from src import console
7 13
 from src import map
8 14
 
9 15
 import random
16
+import bisect
10 17
 
11 18
 
12 19
 class Application(object):
... ...
@@ -15,22 +22,39 @@ class Application(object):
15 22
 		self.terrain_registry = map.TerrainRegistry()
16 23
 		self.terrain_registry.load_from_file('data/terrain.yml')
17 24
 		self.map = map.Map(100,63, self.terrain_registry)
18
-		self.player = player.Player(4,5, self.map)
25
+		self.player = player.Player(4,5, self.map, combat.Adventurer.randomize())
19 26
 		self.events = events.EventHandler()
20 27
 		player.ArrowHandler(self.player, self.events)
21 28
 
29
+		self.actors = []
30
+		for x in range(40):
31
+			self.actors.append(overlay.Actor(random.randrange(100), random.randrange(63), self.map))
22 32
 
23
-
33
+		self.objects = []
34
+		for x in range(50):
35
+			self.objects.append(overlay.Potion(random.randrange(100), random.randrange(63), self.map))
24 36
 
25 37
 		tc.sys_set_fps(60)
26 38
 
39
+	def update_actors(self):
40
+		to_pop = []
41
+		for idx,actor in enumerate(self.actors):
42
+			if not actor.tick():
43
+				bisect.insort(to_pop, idx)
44
+		for pop in reversed(to_pop):
45
+			self.actors.pop(pop)
46
+
27 47
 	def init(self):
28 48
 		self.screen.init("test")
29 49
 		self.player.draw()
50
+		self.update_actors()
51
+		for overlay in self.actors + self.objects:
52
+			overlay.draw()
30 53
 
31 54
 	def run(self):
32 55
 		while not tc.console_is_window_closed():
33 56
 			self.events.tick()
57
+			self.update_actors()
34 58
 			self.map.draw(self.screen)
35 59
 			tc.console_print(0, 0,1, '%d' % tc.sys_get_fps())
36 60
 			tc.console_flush()
... ...
@@ -46,14 +46,22 @@ class Map(object):
46 46
 	def add(self, object):
47 47
 		self.overlays.setdefault(object.pos,[]).append(object)
48 48
 
49
+	def check_and_execute_bump(self, object, x,y):
50
+		if (x,y) in self.overlays:
51
+			for other in self.overlays[x,y]:
52
+				object.bump(other)
53
+
49 54
 	def move(self, object, dx,dy):
50 55
 		self.overlays[object.pos].remove(object)
51 56
 
57
+		collide_x, collide_y = None, None
58
+
52 59
 		if abs(dx) < 2 and abs(dy) < 2:
53 60
 			ox,oy = object.pos
54 61
 			x = squeeze(ox+dx, 0, self.width-1)
55 62
 			y = squeeze(oy+dy, 0, self.height-1)
56
-			if not self.fov.is_passable((x,y)):
63
+			if not self.is_passable((x,y)):
64
+				collide_x, collide_y = x,y
57 65
 				x,y = ox,oy
58 66
 
59 67
 		else:
... ...
@@ -61,10 +69,15 @@ class Map(object):
61 69
 			tx,ty = ox+dx, oy+dy
62 70
 			gx,gy = ox,oy
63 71
 			for x,y in libs.bresenham.line(ox,oy, tx,ty, 1):
64
-				if not self.fov.is_passable((x,y)): break
72
+				if not self.is_passable((x,y)):
73
+					collide_x, collide_y = x,y
74
+					break
65 75
 				else: gx,gy = x,y
66 76
 			x,y = gx,gy
67 77
 
78
+		if collide_x is not None:
79
+			self.check_and_execute_bump(object, collide_x, collide_y)
80
+
68 81
 		self.overlays.setdefault((x,y), []).append(object)
69 82
 		self.update_overlay(ox,oy)
70 83
 		return x-ox, y-oy
... ...
@@ -75,6 +88,19 @@ class Map(object):
75 88
 			if (x,y) in self.overlays and self.overlays[x,y] == []:
76 89
 				self.overlays.pop((x,y))
77 90
 
91
+	def set_pov(self, pov):
92
+		self.pov = pov
93
+
94
+	def get_visible_objects(self):
95
+		o,r = self.pov
96
+		results = set()
97
+		for x,y in self.overlays:
98
+			if self.fov.is_visible(o,r, (x,y)):
99
+				results.update(self.overlays[x,y])
100
+		return results
101
+
102
+
103
+
78 104
 	def get_rgb(self, colors, fg=True,slices=(slice(0),slice(0))):
79 105
 		result = np.rollaxis(colors[slices], 2)
80 106
 		return [x.transpose() for x in result]
... ...
@@ -123,6 +149,12 @@ class Map(object):
123 149
 	def dim(self):
124 150
 		return self.width, self.height
125 151
 
152
+	def is_passable(self, coord):
153
+		if coord in self.overlays and any(x.blocks for x in self.overlays[coord]):
154
+			return False
155
+		else:
156
+			return self.fov.is_passable(coord)
157
+
126 158
 
127 159
 class FovCache(object):
128 160
 	# TODO: get allow updates to base_map
... ...
@@ -1,30 +1,18 @@
1 1
 import libtcodpy as tc
2
+import libs.combat
3
+import libs.overlay
2 4
 
3
-class Actor(object):
4
-	char = ord('@')
5
-	color = (255,0,0)
6
-
7
-	@property
8
-	def pos(self):
9
-		return self.x, self.y
10
-
11
-	def __init__(self, x,y, map):
12
-		self.x = x
13
-		self.y = y
14
-		self.map = map
15
-
16
-	def draw(self):
17
-		self.map.add(self)
18
-
19
-	def move(self, dx, dy):
20
-		dx, dy = self.map.move(self, dx,dy)
21
-		self.x += dx
22
-		self.y += dy
23
-		self.map.pov = (self.pos, 10)
24
-
25
-class Player(Actor):
5
+class Player(libs.overlay.Actor):
26 6
 	char = ord('@')
27 7
 	color = (255,255,255)
8
+	light_radius = 10
9
+	def __init__(self, x,y, map, adventurer):
10
+		libs.overlay.Actor.__init__(self, x,y, map, adventurer)
11
+		print 'Player\'s name is %s' % self.adventurer.name
12
+		self.map.set_pov((self.pos, self.light_radius))
13
+	def move(self, dx, dy):
14
+		libs.overlay.Actor.move(self, dx,dy)
15
+		self.map.set_pov((self.pos, self.light_radius))
28 16
 
29 17
 class ArrowHandler(object):
30 18
 	def __init__(self, player, eh):
31 19
Binary files a/terminal.png and b/terminal.png differ