git.fiddlerwoaroof.com
Browse code

beginning implementation of combat

edwlan authored on 29/07/2013 03:45:45
Showing 11 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,328 @@
1
+from . import dice
2
+from .dice import MULT,ADD
3
+from . import type_dict
4
+
5
+import collections
6
+
7
+class Attributes(object):
8
+	def __init__(self, strength, intellect, dexterity, spirit):
9
+		self.str = strength
10
+		self.int = intellect
11
+		self.dex = dexterity
12
+		self.spt = spirit
13
+		self.state = HEALTHY
14
+
15
+def health_mod(func):
16
+	def _inner(self):
17
+		mod = 0
18
+		if self.attr.state >= WOUNDED:
19
+			mod = -3
20
+		result = func(self)
21
+		result += mod
22
+		return result
23
+	return _inner
24
+
25
+
26
+
27
+class Skills(object):
28
+	def __init__(self, attr):
29
+		self.skills = {
30
+			'agility','craft','fighting','knowledge',
31
+			'perception','persuasion','shooting','speed',
32
+			'stealth','toughness'
33
+		}
34
+		self.attr = attr
35
+		self.training = Trainer(self)
36
+		self.train = self.training.select
37
+
38
+	def check(self, skill, dieroll):
39
+		dieroll.dice = dice.DieJar().d20
40
+		roll = dieroll.roll()
41
+		if roll == 1:
42
+			result = False
43
+		if roll == 20:
44
+			result = True
45
+		else:
46
+			result = roll < getattr(self, skill)
47
+		return result, roll-result # (passes, difference)
48
+
49
+	@property
50
+	@health_mod
51
+	def agility(self):
52
+		return (self.attr.dex * 2) + self.training.agility
53
+	@property
54
+	@health_mod
55
+	def craft(self):
56
+		return self.attr.dex + self.attr.int + self.training.craft
57
+	@property
58
+	@health_mod
59
+	def fighting(self):
60
+		return self.attr.str + self.attr.int + self.training.fighting
61
+	@property
62
+	@health_mod
63
+	def knowledge(self):
64
+		return self.attr.int * 2 + self.training.knowledge
65
+	@property
66
+	@health_mod
67
+	def perception(self):
68
+		return self.attr.int + attr.spt + self.training.perception
69
+	@property
70
+	@health_mod
71
+	def persuasion(self):
72
+		return self.attr.spt * 2 + self.training.persuasion
73
+	@property
74
+	@health_mod
75
+	def shooting(self):
76
+		return self.attr.dex + self.attr.int + self.training.shooting
77
+	@property
78
+	@health_mod
79
+	def speed(self):
80
+		return self.attr.str + self.attr.dex + self.training.speed
81
+	@property
82
+	@health_mod
83
+	def stealth(self):
84
+		return self.attr.dex + self.attr.spt + self.training.stealth
85
+	@property
86
+	@health_mod
87
+	def toughness(self):
88
+		return self.attr.str + self.attr.spt + self.training.toughness
89
+
90
+class Trainer(object):
91
+	mods = dict(
92
+		untrained=-1,
93
+		familiar=0,
94
+		trained=1,
95
+		experienced=2,
96
+		mastered=3
97
+	)
98
+
99
+	def __init__(self, skills):
100
+		self.skills = skills
101
+		self.training = collections.defaultdict(lambda: -1)
102
+		self.points = 500
103
+		self.training_cost = 100
104
+
105
+	def select(self, skill, degree):
106
+		if hasattr(degree, 'upper'):
107
+			degree = self.mods.get(degree, 0)
108
+		if self.points >= self.training_cost and degree in self.mods.values():
109
+			self.training[skill] = degree
110
+			self.points -= self.training_cost
111
+
112
+	def __getattr__(self, key):
113
+		skills = object.__getattribute__(self, 'skills')
114
+		if key in self.skills.skills:
115
+			return self.training[key]
116
+		else:
117
+			raise
118
+	def __setattr__(self, key, value):
119
+		if hasattr(self, 'skills') and key in self.skills.skills:
120
+			if -1 <= value <= 3:
121
+				self.training[key] = int(round(value))
122
+			else:
123
+				raise AttributeError(
124
+					'cannot set training of %s to %d, (out of range [-1,3])' % (key, value)
125
+				)
126
+		else:
127
+			object.__setattr__(self, key, value)
128
+
129
+
130
+class Equipment(object): pass
131
+class Weapon(Equipment): pass
132
+class Armor(Equipment): pass
133
+
134
+
135
+class Slot(object):
136
+	def __init__(self, name, type):
137
+		self.name = name
138
+		self.attr = 'slotted_%s' % name
139
+		self.type = type
140
+
141
+	def __get__(self, instance, owner):
142
+		return self.getitem(instance)
143
+	def __set__(self, instance, value):
144
+		self.setitem(instance, value)
145
+
146
+	def setitem(self, instance, value):
147
+		if isinstance(value, self.type):
148
+			setattr(instance, 'slotted_%s' % self.name, value)
149
+		else:
150
+			raise ValueError(
151
+				'Can\'t use an object of type %s in a slot of type %s' % (type(value), self.type)
152
+			)
153
+
154
+
155
+	def getitem(self, instance):
156
+		return getattr(instance, self.attr, None)
157
+
158
+	def filled(self, instance):
159
+		return self.getitem(instance) is not None
160
+
161
+	@classmethod
162
+	def add_slot(cls, name, type):
163
+		def _inner(to_cls):
164
+			inst = cls(name, type)
165
+			setattr(to_cls, name, inst)
166
+			if not hasattr(to_cls, 'equipment_slots'):
167
+				to_cls.equipment_slots = Slots((name,inst))
168
+			else:
169
+				to_cls.equipment_slots[name] = inst
170
+			return to_cls
171
+		return _inner
172
+
173
+
174
+class Slots(collections.MutableMapping):
175
+	def __init__(self, *args, **kw):
176
+		self.slots = dict(args)
177
+		self.slots.update(kw)
178
+		self.slots_by_type = type_dict.TypeDict(__default=[])
179
+		for k in self.slots:
180
+			slot = self.slots[k]
181
+			self.slots_by_type[slot.type].append(slot)
182
+
183
+	def __getitem__(self, key):
184
+		return self.slots[key]
185
+	def __setitem__(self, k, v):
186
+		self.slots[k] = v
187
+	def __delitem__(self, key):
188
+		del self.slots[key]
189
+
190
+	def __iter__(self):
191
+		return iter(self.slots)
192
+	def __len__(self):
193
+		return len(self.slots)
194
+
195
+	def slots_of_type(self, slot_type):
196
+		return self.slots_by_type[slot_type]
197
+
198
+class Race(object):
199
+	registry = {}
200
+	@classmethod
201
+	def register(cls, new):
202
+		cls.registry[new.__name__.lower()] = new
203
+
204
+	@classmethod
205
+	def random_race(cls):
206
+		return random.choice(self.registry.values())
207
+	@property
208
+	def name(self):
209
+		return self.__class__.__name__.lower()
210
+
211
+	allowed_professions = set()
212
+	def __init__(self, attr):
213
+		self.mod(attr)
214
+
215
+	def allows_profession(self, prof):
216
+		return prof in self.allowed_professions
217
+
218
+@Race.register
219
+class Human(Race):
220
+	def mod(self, attr):
221
+		attr.spt += 1
222
+
223
+@Race.register
224
+class Elf(Race):
225
+	allowed_professions = {'fighter', 'wizard', 'thief'}
226
+	def mod(self, attr):
227
+		attr.int += 1
228
+
229
+@Race.register
230
+class Dwarf(Race):
231
+	allowed_professions = {'fighter', 'Priest', 'thief'}
232
+	def mod(self, atr):
233
+		attr.str += 1
234
+
235
+@Race.register
236
+class Hobbit(Race):
237
+	allowed_professions = {'thief', 'barbarian'}
238
+	def mod(self, atr):
239
+		attr.dex += 1
240
+
241
+
242
+class Sword(Weapon):
243
+	attack = 1
244
+	damage_mod = 1
245
+
246
+
247
+HEALTHY = 0
248
+WOUNDED = 1
249
+KNOCKOUT = 2
250
+
251
+@Slot.add_slot('weapon', Weapon)
252
+@Slot.add_slot('armor', Armor)
253
+class Adventurer(object):
254
+	@property
255
+	def state(self):
256
+		return self.attributes.state
257
+	@state.setter
258
+	def state(self, val):
259
+		if val not in {HEALTHY, WOUNDED, KNOCKOUT}:
260
+			raise ValueError('Value for state invalid')
261
+		self.attributes.state = val
262
+
263
+	def __init__(self, name, race, str, int, dex, spt):
264
+		self.name = name
265
+		self.attributes = Attributes(str, int, dex, spt)
266
+		self.skills = Skills(self.attributes)
267
+		self.race = Race.registry[race](self.attributes)
268
+
269
+	def wield(self, slot, equipment):
270
+		turns = 1
271
+		if self.equipment_slots[slot].filled(self):
272
+			turns += 1
273
+		self.equipment_slots[slot].setitem(self, equipment)
274
+		return turns
275
+
276
+	def attack(self, opponent, type='fighting', num_allies=0):
277
+		dieroll = dice.DieRoll()
278
+
279
+		ally_bonus = -1 if num_allies > 0 else 0
280
+		# TODO: implement professions, figure out how to mod ally_bonus for thieves cleanly
281
+		dieroll.add_adjustment(add=ally_bonus)
282
+
283
+		pass_, damage = self.skills.check(type, dieroll)
284
+		if pass_:
285
+			if self.weapon is not None:
286
+				damage += self.weapon.damage_mod
287
+			print '%s inflicts %d damage upon %s' % (self.name, damage, opponent.name)
288
+			opponent.take_damage(damage)
289
+
290
+	def take_damage(self, damage):
291
+		if self.armor is not None:
292
+			damage += self.armor.damage_mod
293
+
294
+		dieroll = dice.DieRoll()
295
+		dieroll.add_adjustment(-damage)
296
+
297
+		result, damage = self.skills.check('toughness', dieroll)
298
+
299
+		if not result:
300
+			if damage > 0:
301
+				self.state += 1
302
+				print '%s takes %d damage and is now %s' % (self.name, damage, ['healthy', 'wounded', 'ko'][self.state if self.state < 3 else 2])
303
+				if self.state >= KNOCKOUT:
304
+					self.die()
305
+
306
+	def die(self): pass
307
+
308
+def get_stats():
309
+	stats = [7,7,7,7]
310
+	import random
311
+	mod = +1
312
+	for x in range(100):
313
+		idx = random.choice([x for x in range(len(stats)) if stats[x] > 5])
314
+		stats[idx] += mod
315
+		mod *= -1
316
+	print sum(stats), stats
317
+	return stats
318
+
319
+
320
+a = Adventurer('bob', 'elf', *get_stats())
321
+b = Adventurer('bill', 'elf', *get_stats())
322
+
323
+while KNOCKOUT not in {a.state, b.state}:
324
+	a.attack(b)
325
+	b.attack(a)
326
+
327
+print a.name, 'is', ['healthy', 'wounded', 'ko'][a.state if a.state < 3 else 2]
328
+print b.name, 'is', ['healthy', 'wounded', 'ko'][b.state if b.state < 3 else 2]
0 329
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,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,3 +1,7 @@
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
 
... ...
@@ -19,14 +23,17 @@ class Application(object):
19 23
 		self.events = events.EventHandler()
20 24
 		player.ArrowHandler(self.player, self.events)
21 25
 
22
-
23
-
26
+		self.actors = []
27
+		for x in range(40):
28
+			self.actors.append(player.Actor(random.randrange(100), random.randrange(63), self.map))
24 29
 
25 30
 		tc.sys_set_fps(60)
26 31
 
27 32
 	def init(self):
28 33
 		self.screen.init("test")
29 34
 		self.player.draw()
35
+		for actor in self.actors:
36
+			actor.draw()
30 37
 
31 38
 	def run(self):
32 39
 		while not tc.console_is_window_closed():
... ...
@@ -53,7 +53,7 @@ class Map(object):
53 53
 			ox,oy = object.pos
54 54
 			x = squeeze(ox+dx, 0, self.width-1)
55 55
 			y = squeeze(oy+dy, 0, self.height-1)
56
-			if not self.fov.is_passable((x,y)):
56
+			if not self.is_passable((x,y)):
57 57
 				x,y = ox,oy
58 58
 
59 59
 		else:
... ...
@@ -61,7 +61,7 @@ class Map(object):
61 61
 			tx,ty = ox+dx, oy+dy
62 62
 			gx,gy = ox,oy
63 63
 			for x,y in libs.bresenham.line(ox,oy, tx,ty, 1):
64
-				if not self.fov.is_passable((x,y)): break
64
+				if not self.is_passable((x,y)): break
65 65
 				else: gx,gy = x,y
66 66
 			x,y = gx,gy
67 67
 
... ...
@@ -75,6 +75,19 @@ class Map(object):
75 75
 			if (x,y) in self.overlays and self.overlays[x,y] == []:
76 76
 				self.overlays.pop((x,y))
77 77
 
78
+	def set_pov(self, pov):
79
+		self.pov = pov
80
+
81
+	def get_visible_objects(self):
82
+		o,r = self.pov
83
+		results = set()
84
+		for x,y in self.overlays:
85
+			if self.fov.is_visible(o,r, (x,y)):
86
+				results.update(self.overlays[x,y])
87
+		return results
88
+
89
+
90
+
78 91
 	def get_rgb(self, colors, fg=True,slices=(slice(0),slice(0))):
79 92
 		result = np.rollaxis(colors[slices], 2)
80 93
 		return [x.transpose() for x in result]
... ...
@@ -123,6 +136,12 @@ class Map(object):
123 136
 	def dim(self):
124 137
 		return self.width, self.height
125 138
 
139
+	def is_passable(self, coord):
140
+		if coord in self.overlays and any(x.blocks for x in self.overlays[coord]):
141
+			return False
142
+		else:
143
+			return self.fov.is_passable(coord)
144
+
126 145
 
127 146
 class FovCache(object):
128 147
 	# TODO: get allow updates to base_map
... ...
@@ -1,8 +1,10 @@
1 1
 import libtcodpy as tc
2
+import libs.combat
2 3
 
3 4
 class Actor(object):
4 5
 	char = ord('@')
5 6
 	color = (255,0,0)
7
+	blocks = True
6 8
 
7 9
 	@property
8 10
 	def pos(self):
... ...
@@ -20,11 +22,20 @@ class Actor(object):
20 22
 		dx, dy = self.map.move(self, dx,dy)
21 23
 		self.x += dx
22 24
 		self.y += dy
23
-		self.map.pov = (self.pos, 10)
25
+
26
+	def tick(self):
27
+		pass
24 28
 
25 29
 class Player(Actor):
26 30
 	char = ord('@')
27 31
 	color = (255,255,255)
32
+	light_radius = 10
33
+	def __init__(self, x,y, map):
34
+		Actor.__init__(self, x,y, map)
35
+		self.map.set_pov((self.pos, self.light_radius))
36
+	def move(self, dx, dy):
37
+		Actor.move(self, dx,dy)
38
+		self.map.set_pov((self.pos, self.light_radius))
28 39
 
29 40
 class ArrowHandler(object):
30 41
 	def __init__(self, player, eh):
31 42
Binary files a/terminal.png and b/terminal.png differ