git.fiddlerwoaroof.com
Browse code

Merge branch 'release/0.1'

edwlan authored on 31/07/2013 01:40:01
Showing 34 changed files
... ...
@@ -1,3 +1,5 @@
1
+.*.swp
2
+
1 3
 *.py[cod]
2 4
 
3 5
 
... ...
@@ -31,3 +33,12 @@ nosetests.xml
31 33
 .mr.developer.cfg
32 34
 .project
33 35
 .pydevproject
36
+
37
+# ignore the symlinks here
38
+*.dylib
39
+*.so
40
+*.png
41
+
42
+
43
+# ignore experiments
44
+*_test*.py
34 45
new file mode 100644
... ...
@@ -0,0 +1,3 @@
1
+!*.dylib
2
+!*.so
3
+!*.png
0 4
new file mode 100755
1 5
Binary files /dev/null and b/blobs/darwin/libtcod.dylib differ
2 6
new file mode 100755
3 7
Binary files /dev/null and b/blobs/darwin/libtcodgui.dylib differ
4 8
new file mode 100755
5 9
Binary files /dev/null and b/blobs/darwin/libtcodxx.dylib differ
6 10
similarity index 100%
7 11
rename from libtcod.so
8 12
rename to blobs/linux_x86_64/libtcod.so
9 13
similarity index 100%
10 14
rename from libtcod_debug.so
11 15
rename to blobs/linux_x86_64/libtcod_debug.so
12 16
similarity index 100%
13 17
rename from libtcodgui.so
14 18
rename to blobs/linux_x86_64/libtcodgui.so
15 19
similarity index 100%
16 20
rename from libtcodgui_debug.so
17 21
rename to blobs/linux_x86_64/libtcodgui_debug.so
18 22
similarity index 100%
19 23
rename from libtcodxx.so
20 24
rename to blobs/linux_x86_64/libtcodxx.so
21 25
similarity index 100%
22 26
rename from libtcodxx_debug.so
23 27
rename to blobs/linux_x86_64/libtcodxx_debug.so
24 28
new file mode 100644
25 29
Binary files /dev/null and b/blobs/terminal.png differ
26 30
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
1 38
new file mode 100644
... ...
@@ -0,0 +1,36 @@
1
+from __future__ import print_function, division
2
+
3
+# from: http://roguebasin.roguelikedevelopment.org/index.php?title=Bresenham's_Line_Algorithm#Python
4
+
5
+def line(x0, y0, x1, y1, wd):
6
+	dx = abs(x1-x0); sx = 1 if x0 < x1 else -1
7
+	dy = abs(y1-y0); sy = 1 if y0 < y1 else -1
8
+	err = dx-dy
9
+	ed = 1 if dx + dy == 0 else (dx*dx + dy*dy)**0.5
10
+
11
+	e2 = x2 = y2 = 0
12
+
13
+	wd = (wd + 1) / 2.0
14
+	while True:
15
+		yield (x0,y0)
16
+		e2 = err; x2 = x0
17
+		if 2*e2 >= -dx:
18
+			e2 += dx
19
+			y2 = y0
20
+			while e2 < ed*wd and (y1 != y2 or dx > dy):
21
+				yield (x0, y2)
22
+				y2 += sy
23
+				e2 += dx
24
+			if x0 == x1: break
25
+			e2 = err
26
+			err -= dy
27
+			x0 += sx
28
+		if 2*e2 <= dy:
29
+			e2 = dx-e2
30
+			while e2 < ed*wd and (x1 != x2 or dx < dy):
31
+				yield (x2, y0)
32
+				x2 += sx
33
+				e2 += dy
34
+			if y0 == y1: break
35
+			err += dx
36
+			y0 += sy
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,106 +1,64 @@
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
 
4
-class Player(object):
5
-	@property
6
-	def pos(self):
7
-		return self.x, self.y
8
-
9
-	def __init__(self, x,y, map):
10
-		self.x = x
11
-		self.y = y
12
-		self.char = ord('@')
13
-		self.color = (255,255,255)
14
-		self.map = map
15
-	def move(self, dx, dy):
16
-		self.map.move(self, dx,dy)
17
-		self.x += dx
18
-		self.y += dy
19
-
20
-class Console(object):
21
-	# TBI: must have a self.con member with the console to be drawn on
22
-	pass
23
-
24
-class Screen(object):
25
-	def __init__(self, w, h):
26
-		self.width = w
27
-		self.height = h
28
-		self.con = 0
29
-	def init(self, title, fullscreen=False):
30
-		tc.console_init_root(self.width, self.height, title, fullscreen, 2)
31
-		return self
32
-
33
-class Map(object):
34
-	def __init__(self, w, h):
35
-		self.width = w
36
-		self.height = h
37
-		self.map = np.random.random_integers(ord('a'), ord('z'), (w,h) )
38
-		self.fgcolors = np.random.random_integers(100,255, (w,h,3) )
39
-		self.bgcolors = np.random.random_integers(0,100, (w,h,3) )
40
-		overlays = {} # (x,y): { set( (char,fg), ... ) }
41
-
42
-	def move(self, object, dx,dy):
43
-		overlays[object.pos].remove(object)
44
-		ox,oy = object.pos
45
-		x = ox+dx
46
-		y = oy+dy
47
-		if 0 < x < self.width and 0 < y < self.height:
48
-			self.overlays[x,y] = object
49
-			return x,y
50
-		#elif 
51
-
52
-	def get_rgb(self, fg=True,slices=(slice(0),slice(0))):
53
-		if fg:
54
-			return np.rollaxis(self.fgcolors[slices], 2)
55
-		else:
56
-			return np.rollaxis(self.bgcolors[slices], 2)
57
-
58
-	def draw(self, con, tl=(0,0)):
59
-		br = tl[0]+con.width, tl[1]+con.height
60
-		slices = tuple(map(slice, tl,br))
61
-		tc.console_fill_foreground(con.con, *self.get_rgb(slices=slices))
62
-		tc.console_fill_background(con.con, *self.get_rgb(False,slices=slices))
63
-		tc.console_fill_char(con.con, self.map[slices])
64
-
65
-class EventHandler(object):
66
-	def __init__(self):
67
-		self.key = tc.Key()
68
-		self.mouse = tc.Mouse()
69
-		self.cbs = {}
70
-
71
-	def tick(self):
72
-		tc.sys_check_for_event(tc.EVENT_KEY_PRESS|tc.EVENT_MOUSE|tc.KEY_PRESSED,
73
-			self.key, self.mouse
74
-		)
75
-
76
-		char = chr(self.key.c)
77
-		if char != '\x00' and char in self.cbs:
78
-			for cb in self.cbs[char]:
79
-				cb()
80
-
81
-		elif self.key.vk in self.cbs:
82
-			for cb in self.cbs[self.key.vk]:
83
-				cb()
8
+from libs import overlay
9
+from libs import combat
10
+from src import events
11
+from src import player
12
+from src import console
13
+from src import map
84 14
 
15
+import random
16
+import bisect
85 17
 
86 18
 
19
+WIDTH = 79
20
+HEIGHT = 46
87 21
 class Application(object):
88 22
 	def __init__(self):
89
-		self.screen = Screen(120,75)
90
-		self.map = Map(120,78)
91
-		self.player = Player(40,25, self.map)
92
-		self.events = EventHandler()
23
+		self.screen = console.Screen(WIDTH, HEIGHT)
24
+		self.terrain_registry = map.TerrainRegistry()
25
+		self.terrain_registry.load_from_file('data/terrain.yml')
26
+		self.map = map.Map(WIDTH,HEIGHT, self.terrain_registry)
27
+		self.player = player.Player(4,5, self.map, combat.Adventurer.randomize())
28
+		self.events = events.EventHandler()
29
+		player.ArrowHandler(self.player, self.events)
30
+
31
+		self.actors = []
32
+		for x in range(40):
33
+			self.actors.append(overlay.Actor(random.randrange(WIDTH), random.randrange(HEIGHT), self.map))
34
+
35
+		self.objects = []
36
+		for x in range(50):
37
+			self.objects.append(overlay.Potion(random.randrange(WIDTH), random.randrange(HEIGHT), self.map))
93 38
 
94 39
 		tc.sys_set_fps(60)
95 40
 
41
+	def update_actors(self):
42
+		to_pop = []
43
+		for idx,actor in enumerate(self.actors):
44
+			if not actor.tick():
45
+				bisect.insort(to_pop, idx)
46
+		for pop in reversed(to_pop):
47
+			self.actors.pop(pop)
48
+
96 49
 	def init(self):
97 50
 		self.screen.init("test")
51
+		self.player.draw()
52
+		self.update_actors()
53
+		for overlay in self.actors + self.objects:
54
+			overlay.draw()
98 55
 
99 56
 	def run(self):
100 57
 		while not tc.console_is_window_closed():
101 58
 			self.events.tick()
59
+			self.update_actors()
102 60
 			self.map.draw(self.screen)
103
-			tc.console_print(0, 0,0, '%d' % tc.sys_get_fps())
61
+			tc.console_print(0, 0,1, '%d' % tc.sys_get_fps())
104 62
 			tc.console_flush()
105 63
 
106 64
 
107 65
new file mode 100755
... ...
@@ -0,0 +1,12 @@
1
+#!/bin/zsh
2
+
3
+arch=linux_x86_64
4
+
5
+if [[ $1 != "" ]]; then
6
+   arch=$1
7
+fi
8
+
9
+echo blobs/$arch/libtcod*
10
+ln -s blobs/$arch/libtcod* .
11
+rm *_debug*.{so,dylib}
12
+ln -s blobs/terminal.png .
1 14
new file mode 100644
... ...
@@ -0,0 +1,14 @@
1
+import libtcodpy as tc
2
+
3
+class Console(object):
4
+	# TBI: must have a self.con member with the console to be drawn on
5
+	pass
6
+
7
+class Screen(object):
8
+	def __init__(self, w, h):
9
+		self.width = w
10
+		self.height = h
11
+		self.con = 0
12
+	def init(self, title, fullscreen=False):
13
+		tc.console_init_root(self.width, self.height, title, fullscreen, 2)
14
+		return self
0 15
new file mode 100644
... ...
@@ -0,0 +1,28 @@
1
+import libtcodpy as tc
2
+
3
+class EventHandler(object):
4
+	def __init__(self):
5
+		self.key = tc.Key()
6
+		self.mouse = tc.Mouse()
7
+		self.cbs = {}
8
+
9
+	def register(self, event, cb):
10
+		self.cbs.setdefault(event,[]).append(cb)
11
+
12
+	def tick(self):
13
+		tc.sys_check_for_event(tc.EVENT_KEY_PRESS|tc.EVENT_MOUSE|tc.KEY_PRESSED,
14
+			self.key, self.mouse
15
+		)
16
+
17
+		alt, shift, ctrl = self.key.lalt|self.key.ralt, self.key.shift, self.key.lctrl|self.key.rctrl
18
+		char = chr(self.key.c)
19
+		if char != '\x00' and char in self.cbs:
20
+			for cb in self.cbs[char]:
21
+				cb(alt,shift,ctrl)
22
+
23
+		elif self.key.vk in self.cbs:
24
+			for cb in self.cbs[self.key.vk]:
25
+				cb(alt,shift,ctrl)
26
+
27
+
28
+
0 29
new file mode 100644
... ...
@@ -0,0 +1,257 @@
1
+import numpy as np
2
+import libtcodpy as tc
3
+import yaml
4
+import libs.bresenham
5
+import libs.cache
6
+
7
+def squeeze(val, low, high):
8
+	return min(max(val, low), high)
9
+
10
+class TerrainGen(object):
11
+	def __init__(self, map):
12
+		self.width, self.height = map.dim
13
+		self.terrain_registry = map.terrain_registry
14
+
15
+	def generate(self):
16
+		w,h = self.width, self.height
17
+		terrains, probs = self.terrain_registry.get_terrain_types()
18
+		map = np.random.choice(terrains, (w,h), p=probs)
19
+		chars = np.zeros((w,h)).astype('int')
20
+		fgcolors = np.zeros((w,h,3))
21
+		bgcolors = np.zeros((w,h,3))
22
+
23
+		for x, row in enumerate(map):
24
+			for y, cell in enumerate(row):
25
+				char, fgcolor, bgcolor = self.terrain_registry.get_display(cell)
26
+				chars[x,y] = char
27
+				fgcolors[x,y] = fgcolor
28
+				bgcolors[x,y] = bgcolor
29
+		return map, chars, fgcolors, bgcolors
30
+
31
+
32
+class Map(object):
33
+	def __init__(self, w, h, terrain_registry):
34
+		self.pov = None
35
+		self.povcache = libs.cache.Cache()
36
+
37
+		self.width = w
38
+		self.height = h
39
+		self.terrain_registry = terrain_registry
40
+		self.overlays = {} # (x,y): { set( object, ... ) }
41
+
42
+		self.ids, self.map, self.fgcolors, self.bgcolors = TerrainGen(self).generate()
43
+
44
+		self.fov = FovCache(self, self.terrain_registry)
45
+
46
+	def add(self, object):
47
+		self.overlays.setdefault(object.pos,[]).append(object)
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
+
54
+	def move(self, object, dx,dy):
55
+		self.overlays[object.pos].remove(object)
56
+
57
+		collide_x, collide_y = None, None
58
+
59
+		if abs(dx) < 2 and abs(dy) < 2:
60
+			ox,oy = object.pos
61
+			x = squeeze(ox+dx, 0, self.width-1)
62
+			y = squeeze(oy+dy, 0, self.height-1)
63
+			if not self.is_passable((x,y)):
64
+				collide_x, collide_y = x,y
65
+				x,y = ox,oy
66
+
67
+		else:
68
+			ox,oy = object.pos
69
+			tx,ty = ox+dx, oy+dy
70
+			gx,gy = ox,oy
71
+			for x,y in libs.bresenham.line(ox,oy, tx,ty, 1):
72
+				if not self.is_passable((x,y)):
73
+					collide_x, collide_y = x,y
74
+					break
75
+				else: gx,gy = x,y
76
+			x,y = gx,gy
77
+
78
+		if collide_x is not None:
79
+			self.check_and_execute_bump(object, collide_x, collide_y)
80
+
81
+		self.overlays.setdefault((x,y), []).append(object)
82
+		self.update_overlay(ox,oy)
83
+		return x-ox, y-oy
84
+
85
+	def update_overlay(self, x=None, y=None):
86
+		if x is None or y is None: pass
87
+		else:
88
+			if (x,y) in self.overlays and self.overlays[x,y] == []:
89
+				self.overlays.pop((x,y))
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
+
104
+	def get_rgb(self, colors, fg=True,slices=(slice(0),slice(0))):
105
+		result = np.rollaxis(colors[slices], 2)
106
+		return [x.transpose() for x in result]
107
+
108
+	def draw(self, con, tl=(0,0)):
109
+		br = tl[0]+con.width, tl[1]+con.height
110
+
111
+		fgcolors = self.fgcolors.astype('int')
112
+		bgcolors = self.bgcolors.astype('int')
113
+		color_mask = np.ones( (con.width, con.height, 3) )
114
+		char_mask = np.ones( (con.width, con.height) ).astype('bool')
115
+		if self.pov is not None:
116
+			origin, radius = self.pov
117
+			def calc_mask():
118
+				for x in range(tl[0], br[0]):
119
+					for y in range(tl[1], br[1]):
120
+						if not self.fov.is_visible(origin, radius, (x,y)):
121
+							color_mask[x,y] = (0.25, 0.5, 0.5)
122
+							char_mask[x,y] = False
123
+				return color_mask, char_mask
124
+			color_mask, char_mask = self.povcache.get( (origin,radius), calc_mask )
125
+			fgcolors = (fgcolors * color_mask).astype('int')
126
+			bgcolors = (bgcolors * color_mask).astype('int')
127
+
128
+		slices = slice(tl[0], tl[0]+con.width), slice(tl[1], tl[1]+con.height)
129
+		tc.console_fill_foreground(con.con, *self.get_rgb(fgcolors, slices=slices))
130
+		tc.console_fill_background(con.con, *self.get_rgb(bgcolors, False,slices=slices))
131
+
132
+		chars = np.copy(self.map[slices])
133
+		for x,y in self.overlays:
134
+			screen_x = x-tl[0]
135
+			screen_y = y-tl[1]
136
+			if not char_mask[screen_x,screen_y]: continue
137
+			if 0 <= screen_x < con.width and 0 <= screen_y < con.height:
138
+				obj = self.overlays[x,y][-1]
139
+				chars[screen_x,screen_y] = obj.char
140
+				tc.console_set_char_background(con.con, screen_x,screen_y, tc.Color(*bgcolors[screen_x,screen_y]))
141
+				tc.console_set_char_foreground(con.con, screen_x,screen_y, tc.Color(*obj.color))
142
+		chars[np.logical_not(char_mask)] = ord(' ')
143
+		tc.console_fill_char(con.con, chars.transpose())
144
+
145
+	def coord_iter(self):
146
+		return ( (x,y) for x in range(self.width) for y in range(self.height) )
147
+
148
+	@property
149
+	def dim(self):
150
+		return self.width, self.height
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
+
158
+
159
+class FovCache(object):
160
+	# TODO: get allow updates to base_map
161
+	def __init__(self, map, terrain_registry):
162
+		self.width, self.height = map.dim
163
+
164
+		self.base_map = tc.map_new(*map.dim)
165
+
166
+		for x,y in map.coord_iter():
167
+			if (x,y) in map.overlays:
168
+				object = map.overlays[x,y]
169
+				pssble,trnsprnt = object.passable, object.transparent
170
+			else:
171
+				pssble,trnsprnt = terrain_registry.get_props(map.ids[x,y])
172
+			tc.map_set_properties(self.base_map, x,y, trnsprnt,pssble)
173
+
174
+		self.fovmaps = {}
175
+
176
+	def get_fovmap(self, origin, radius):
177
+		key = origin,radius
178
+
179
+		if key in self.fovmaps: fovmap = self.fovmaps[key]
180
+		else:
181
+			fovmap = tc.map_new(self.width, self.height)
182
+			tc.map_copy(self.base_map, fovmap)
183
+			self.fovmaps[key] = fovmap
184
+
185
+			x,y = origin
186
+			tc.map_compute_fov(fovmap, x,y, radius)
187
+
188
+		return fovmap
189
+
190
+	def is_visible(self, origin, radius, coord):
191
+		fovmap = self.get_fovmap(origin, radius)
192
+		return tc.map_is_in_fov(fovmap, *coord)
193
+
194
+	def is_transparent(self, coord):
195
+		return tc.map_is_transparent(self.base_map, *coord)
196
+	def is_passable(self, coord):
197
+		return tc.map_is_walkable(self.base_map, *coord)
198
+
199
+
200
+class TerrainInfo(object):
201
+	passable = False
202
+	transparent = False
203
+	char = ord(' ')
204
+	fg = (255,255,255)
205
+	bg = (0,0,0)
206
+	prob = 1
207
+
208
+	@classmethod
209
+	def make_terrain(cls, name, char, passable, transparent,fg,bg, prob=1):
210
+		if hasattr(char, 'upper'): char = ord(char)
211
+		passable = bool(passable)
212
+		transparent = bool(transparent)
213
+		return type(name, (cls,), dict(char=char, passable=passable, transparent=transparent,fg=fg,bg=bg,prob=prob))
214
+
215
+class TerrainRegistry(object):
216
+	def __init__(self):
217
+		self.id = 0
218
+		self.registry = {}
219
+		self.names = {}
220
+
221
+	def get_terrain(self, id):
222
+		ter = self.registry[id]
223
+		return ter
224
+
225
+	def get_props(self, id):
226
+		ter = self.get_terrain(id)
227
+		return ter.passable, ter.transparent
228
+
229
+	def get_display(self, char):
230
+		ter = self.get_terrain(char)
231
+		return ter.char, ter.fg, ter.bg
232
+
233
+	def get_terrain_types(self):
234
+		types, probabilities = zip(*[(x, self.registry[x].prob) for x in self.registry])
235
+		probs = [float(x) / sum(probabilities) for x in probabilities]
236
+		return types, probs
237
+
238
+
239
+	def register(self, ter):
240
+		self.id += 1
241
+		self.registry[self.id] = ter
242
+		self.names[ter.__name__] = ter
243
+		return ter
244
+
245
+	def new_terrain(self, name, char,
246
+		passable=False, transparent=False, fg=(255,255,255), bg=(0,0,0), prob=1
247
+	):
248
+		ter = TerrainInfo.make_terrain(name, char, passable, transparent, fg,bg, prob=prob)
249
+		return self.register(ter)
250
+
251
+	def load_from_file(self, fn, loader=yaml.safe_load):
252
+		with open(fn) as f:
253
+			values = loader(f)
254
+		for name, terrain in values.viewitems():
255
+			self.new_terrain(name, **terrain)
256
+
257
+
0 258
new file mode 100644
... ...
@@ -0,0 +1,48 @@
1
+import libtcodpy as tc
2
+import libs.combat
3
+import libs.overlay
4
+
5
+class Player(libs.overlay.Actor):
6
+	char = ord('@')
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))
16
+
17
+class ArrowHandler(object):
18
+	def __init__(self, player, eh):
19
+		self.player = player
20
+		eh.register(tc.KEY_LEFT,self.left)
21
+		eh.register(tc.KEY_RIGHT,self.right)
22
+		eh.register(tc.KEY_UP,self.up)
23
+		eh.register(tc.KEY_DOWN,self.down)
24
+	def left(self, alt, shift, ctrl):
25
+		val = 10 if shift else 1
26
+		if alt:
27
+			self.player.move(-val, -val)
28
+		else:
29
+			self.player.move(-val, 0)
30
+	def right(self, alt, shift, ctrl):
31
+		val = 10 if shift else 1
32
+		if alt:
33
+			self.player.move(val, val)
34
+		else:
35
+			self.player.move(val, 0)
36
+	def up(self, alt, shift, ctrl):
37
+		val = 10 if shift else 1
38
+		if alt:
39
+			self.player.move(val, -val)
40
+		else:
41
+			self.player.move(0, -val)
42
+	def down(self, alt, shift, ctrl):
43
+		val = 10 if shift else 1
44
+		if alt:
45
+			self.player.move(-val, val)
46
+		else:
47
+			self.player.move(0, val)
48
+
0 49
deleted file mode 100644
1 50
Binary files a/terminal.png and /dev/null differ