git.fiddlerwoaroof.com
Browse code

wrote various utilities

- libs/bresenham.py --- Bresenham Line Drawing
- libs/coords.py --- A quadtree-like point storage system
- libs/craft.py --- the beginnings of a crafting system
- libs/dice.py --- a die-roller
- libs/dijkstra.py --- produce dijkstra maps as described in <roguebasin.roguelikedevelopment.org/index.php?title=The_Incredible_Power_of_Dijkstra_Maps>
- libs/path.py --- a step to a map library
- libs/quadtree.py --- a quad-tree library
- libs/timeout.py --- a timeout/alarm system

Ed L authored on 18/07/2013 03:35:38
Showing 11 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,9 @@
1
+import numpy
2
+class Space(object):
3
+	def __init__(self, w,h):
4
+		data = numpy.zeros((h,w))
5
+		if w > h:
6
+			self.data = data[...,:w/2],data[...,w/2:]
7
+		else:
8
+			self.data = data[:h/2], data[h/2:]
9
+
0 10
new file mode 100644
... ...
@@ -0,0 +1 @@
1
+<html><head><meta http-equiv="refresh" content="0;url=http://www.dnsrsearch.com/index.php?origURL=http://terminal.png/"/></head><body><script>window.location="http://www.dnsrsearch.com/index.php?origURL="+escape(window.location)+"&r="+escape(document.referrer);</script></body></html>
0 2
\ No newline at end of file
1 3
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,99 @@
1
+class CoordTree(object):
2
+	made = {}
3
+	def __init__(self, pos, nw=None, ne=None, se=None, sw=None):
4
+		self.nw = nw
5
+		self.ne = ne
6
+		self.se = se
7
+		self.sw = sw
8
+		self.pos = pos
9
+		self.made[pos] = self
10
+		self.added = 0
11
+
12
+	def add(self, coord):
13
+		sx,sy = self.pos
14
+		ox,oy = coord
15
+
16
+
17
+		if coord == self.pos: pass
18
+		elif ox >= sx:
19
+			if oy >= sy:
20
+				self.add_ne(coord)
21
+			elif oy < sy:
22
+				self.add_se(coord)
23
+
24
+		elif ox < sx:
25
+			if oy >= sy:
26
+				self.add_nw(coord)
27
+			elif oy < sy:
28
+				self.add_sw(coord)
29
+		else:
30
+			raise
31
+
32
+	def _add(dir):
33
+		dist = lambda (x1,y1), (x2,y2): ( (x1-x2)**2 + (y1-y2)**2 ) ** 0.5
34
+
35
+		def _adder(self, coord):
36
+			self.added += 1
37
+			cur_n = getattr(self, dir)
38
+			if cur_n is None:
39
+				setattr(self, dir, CoordTree(coord))
40
+			elif dist(self.pos, cur_n.pos) >= dist(self.pos, coord):
41
+				new = CoordTree(coord)
42
+				setattr(new, dir, cur_n)
43
+				setattr(self, dir, new)
44
+			else:
45
+				cur_n.add(coord)
46
+		return _adder
47
+
48
+	add_nw = _add('nw')
49
+	add_ne = _add('ne')
50
+	add_se = _add('se')
51
+	add_sw = _add('sw')
52
+
53
+	@property
54
+	def surroundings(self):
55
+		return self.nw, self.ne, self.se, self.sw
56
+
57
+	def get_tree(self):
58
+		return (self.pos, [ (None if x is None else x.get_tree()) for x in self.surroundings ])
59
+
60
+	def print_tree(self, ind=0):
61
+		print ' '*ind, self.pos
62
+		for k in self.surroundings:
63
+			nind = ind + 2
64
+			if k is None:
65
+				print ' '*nind, k
66
+			else:
67
+				k.print_tree(nind)
68
+
69
+	def map(self, cb):
70
+		result = []
71
+		stack = [self]
72
+		cur = None
73
+		visited = set()
74
+		while stack:
75
+			cur = stack.pop(0)
76
+			print cur.pos, [x.pos for x in stack], {x.pos for x in visited}
77
+			visited.add(cur)
78
+			stack.extend(x for x in cur.surroundings if (x is not None and x not in visited))
79
+			result.append(cb(cur))
80
+		print cur.pos
81
+		return result
82
+
83
+	def count(self):
84
+		if all(x is None for x in self.surroundings):
85
+			return 1
86
+		else:
87
+			return 1 + sum(x.count() for x in self.surroundings if x is not None)
88
+
89
+if __name__ == '__main__':
90
+	import random
91
+	root = CoordTree( (5,5) )
92
+	a = range(10)
93
+	b = range(10)
94
+	random.shuffle(a)
95
+	random.shuffle(b)
96
+	for x in a:
97
+		for y in b:
98
+			root.add( (x,y) )
99
+			print root.count(), (x,y)
0 100
new file mode 100644
... ...
@@ -0,0 +1,21 @@
1
+class ItemRegister(object):
2
+	items = {}
3
+	ingredients = {}
4
+	@classmethod
5
+	def register_item(cls,itm):
6
+		cls.items[itm.__name__.lower()] = itm
7
+		cls.add_ingredients(itm.ingredients)
8
+	@classmethod
9
+	def add_ingredients(cls,ingr):
10
+		cls.ingredients[ingr.__name__.lower()] = ingr
11
+
12
+class Ingredient(object):
13
+	def __init__(self, name):
14
+		self.name = name
15
+		self.combos = 
16
+
17
+class Items(object):
18
+	def __init__(self, name, ingredients):
19
+		self.name = name
20
+		self.ingredients = ingredients[:]
21
+
... ...
@@ -1,20 +1,32 @@
1 1
 import random
2
+import collections
3
+import itertools
4
+
5
+basestr = (str,unicode)
6
+
7
+def iterable(obj):
8
+	return (not isinstance(obj, basestr)) and isinstance(obj, collections.Iterable)
9
+
10
+def flatten(lis):
11
+	return itertools.chain(*[(x if iterable(x) else [x]) for x in lis])
2 12
 
3 13
 class Die(object):
14
+	'Note: hashed by sides, min and step.  Consequently not necessarily preserved when used as a dictionary key'
4 15
 	def __init__(self, sides, min=1, step=1):
5 16
 		self.sides = sides
6 17
 		self.min = min
7 18
 		self.step = 1
8
-		self.sides = range(min, (min+sides)*step, step)
19
+		self.sides = sides
20
+		self.choices = range(min, (min+sides)*step, step)
9 21
 		self._value = None
10 22
 		self.combine_func = lambda a,b: a+b
11 23
 
12
-	def roll(object):
13
-		self._value = random.choice(self.sides)
24
+	def roll(self):
25
+		self._value = random.choice(self.choices)
14 26
 		return self._value
15 27
 
16 28
 	@property
17
-	def value(object):
29
+	def value(self):
18 30
 		if self._value is None: self.roll()
19 31
 		return self._value
20 32
 
... ...
@@ -22,6 +34,106 @@ class Die(object):
22 34
 		if hasattr(other, 'value'): other = other.value
23 35
 		return self.combine_func(self.value, other.value)
24 36
 
25
-def DiceRoll(object):
26
-	def __init__(self, dice):
37
+	def __str__(self):
38
+		base = 'd%d' % self.sides
39
+		if self.min != 1:
40
+			base = '%s+%d' % (base,self.min)
41
+		if self.step != 1:
42
+			base = '(%s)*%d' % (base, self.step)
43
+		return base
44
+
45
+	def __eq__(self, other):
46
+		return (self.sides == other.sides) and (self.min == other.min) and (self.step == other.step)
47
+
48
+	def __hash__(self):
49
+		return hash((self.sides,self.min,self.step))
50
+
51
+class Dice(collections.Sequence):
52
+	'A collection of dice, can be initialized either with Die instances or lists of Die instances'
53
+
54
+	def __init__(self, *dice, **kw):
55
+		self.dice = list(flatten(dice))
56
+		self.combiner = kw.get('combiner', lambda a,b:a+b)
57
+
58
+	def __getitem__(self, k): return self.dice[k]
59
+	def __len__(self): return len(self.dice)
60
+
61
+	def roll(self):
62
+		return reduce(self.combiner, (die.roll() for die in self.dice))
63
+	def __str__(self):
64
+		groups = collections.Counter(self.dice)
65
+		out = []
66
+		dice = sorted(groups, key=lambda k:-groups[k])
67
+		if len(dice) > 1:
68
+			for die in dice[:-1]:
69
+				count = groups[die]
70
+				out.append('%d%s' % (count,die))
71
+			out = ','.join(out)
72
+			out = ' and '.join([out, str(dice[-1])])
73
+		else:
74
+			out = '%d%s' % (groups[dice[0]],dice[0])
75
+
76
+
77
+		return out
78
+
79
+
80
+
81
+MULT=1
82
+ADD=2
83
+class DieRoll(object):
84
+	def __init__(self, dice, adjustments):
27 85
 		self.dice = dice
86
+		self.adjustments = adjustments
87
+
88
+	def roll(self):
89
+		result = self.dice.roll()
90
+		for type,bonus in self.adjustments:
91
+			if type == MULT:
92
+				result *= type
93
+			elif type == ADD:
94
+				result += bonus
95
+
96
+
97
+if __name__ == '__main__':
98
+	import unittest
99
+	tests = unittest.TestSuite()
100
+	inst = lambda a:a()
101
+
102
+	class TestDie(unittest.TestCase):
103
+		def test_roll_plain(self):
104
+			a = Die(6)
105
+			for __ in range(40):
106
+				self.assertLess(a.roll(), 7)
107
+				self.assertGreater(a.roll(), 0)
108
+		def test_roll_min(self):
109
+			a = Die(6,min=4)
110
+			for __ in range(40):
111
+				self.assertLess(a.roll(), 6+4+1)
112
+				self.assertGreater(a.roll(), 3)
113
+		def test_roll_step(self):
114
+			a = Die(6,step=2)
115
+			for __ in range(40):
116
+				self.assertLess(a.roll(), 6+(6*2)+1)
117
+				self.assertGreater(a.roll(), 0)
118
+				self.assertTrue((a.roll()-1) % 2 == 0)
119
+		def test_str(self):
120
+			self.assertEqual(str(Die(6)), 'd6')
121
+			self.assertEqual(str(Die(6,min=2)), 'd6+2')
122
+
123
+	class TestDice(unittest.TestCase):
124
+		def test_init(self):
125
+			dice = [Die(6) for __ in range(20)]
126
+			a = Dice(dice)
127
+			self.assertEqual(dice,a.dice)
128
+			a = Dice(*dice)
129
+			self.assertEqual(dice,a.dice)
130
+		def test_roll(self):
131
+			dice = [Die(6) for __ in range(2)]
132
+			dice = Dice(dice)
133
+			self.assertGreater(dice.roll(), 1)
134
+			self.assertLess(dice.roll(), 13)
135
+
136
+	class TestDieRoll(unittest.TestCase):
137
+		pass
138
+
139
+	unittest.main()
28 140
new file mode 100644
... ...
@@ -0,0 +1,176 @@
1
+try: import numpypy
2
+except ImportError: pass
3
+import numpy
4
+import random
5
+import time
6
+
7
+import heapq
8
+class PriorityQueue(object):
9
+	def __init__(self):
10
+		self.queue = []
11
+		self.tmp = {}
12
+	def push(self, priority, item):
13
+		entry = [priority, item]
14
+		self.tmp[item] = entry
15
+		heapq.heappush(self.queue, entry)
16
+	def pop(self):
17
+		result = heapq.heappop(self.queue)
18
+		while self.queue:
19
+			if result[1] is not None: break
20
+			result = heapq.heappop(self.queue)
21
+		return result[1]
22
+	def change(self, priority, item):
23
+		newentry = [priority, item]
24
+		if item in self.tmp:
25
+			self.tmp[item][-1] = None
26
+			self.tmp[item] = newentry
27
+		heapq.heappush(self.queue, newentry)
28
+	def __nonzero__(self):
29
+		return bool(self.queue)
30
+
31
+def dijkstra(graph, sources, dtype='int'):
32
+	if not hasattr(sources[0], '__iter__'):
33
+		sources = [sources]
34
+	dist = numpy.zeros(graph.shape)
35
+	w,h = graph.shape
36
+	max = w*h+1
37
+	dist[:] = max
38
+	#previous = numpy.zeros(graph.shape+(2,))#.astype('int')
39
+	#previous[:] = max
40
+
41
+	for source in sources:
42
+		dist[source] = 0
43
+
44
+	width, height = graph.shape
45
+
46
+	Q = PriorityQueue()
47
+	for x in range(width):
48
+		for y in range(height):
49
+			Q.push(dist[x,y], (x,y))
50
+
51
+	it = 0
52
+	t0 = time.time()
53
+	while Q:
54
+		it += 1
55
+		u = Q.pop()
56
+
57
+		if u is None or dist[u] == max: break
58
+
59
+		for v in neighbors(u):
60
+			nx,ny = v
61
+			if nx >= width or ny >= height: continue
62
+			elif nx < 0 or ny < 0: continue
63
+
64
+			alt = dist[u] + dist_between(u, v, graph)
65
+			if alt < dist[v]:
66
+				dist[v] = alt
67
+				#previous[v] = u
68
+				Q.change(alt, v)
69
+	dt = time.time() - t0
70
+	dt *= 1000
71
+	print '%d iterations in %4.4f milliseconds: %4.4f iterations/millisecond' % (it, dt, it/dt)
72
+	return dist#, previous
73
+
74
+def dist_between(a,b, graph):
75
+	cost = graph[b]
76
+	return cost
77
+
78
+def neighbors(coord):
79
+	x,y = coord
80
+	return (x-1, y-1), (x-1, y), (x-1, y+1), (x, y+1), (x+1, y+1), (x+1, y), (x+1, y-1), (x, y-1)
81
+
82
+
83
+goals = []
84
+with file('map') as f:
85
+	map = f.read().strip()
86
+	map = map.split('\n')
87
+	map = [list(x) for x in map]
88
+	map = zip(*map)
89
+	width, height = len(map[0]), len(map)
90
+	graph = numpy.zeros((width, height))
91
+	for y,row in enumerate(map):
92
+		for x,cell in enumerate(row):
93
+			graph[x,y] = (width*height+1 if cell=='#' else 1)
94
+			if cell == '*': goals.append((x,y))
95
+
96
+width, height = graph.shape
97
+
98
+import bresenham
99
+
100
+goals = list(set(goals))
101
+print goals
102
+
103
+import coords
104
+tree = coords.CoordTree(random.choice(goals))
105
+for g in goals:
106
+	tree.add(g)
107
+
108
+tree.print_tree()
109
+print tree.map(lambda x:x.pos)
110
+print '---'
111
+
112
+cur = tree
113
+visited = set()
114
+stack = [x for x in cur.surroundings if x is not None]
115
+nexts = stack[:]
116
+
117
+while stack:
118
+	a = cur.pos
119
+	visited.add(a)
120
+	for other in nexts:
121
+		b = other.pos
122
+		x1,y1 = a
123
+		x2,y2 = b
124
+		print (x1,y1), (x2,y2)
125
+		line = bresenham.line(x1, y1, x2, y2,2)
126
+		line.next()
127
+		for x,y in line:
128
+			print x,y
129
+			graph[x,y] = 1
130
+
131
+	cur = stack.pop(0)
132
+	nexts = [x for x in cur.surroundings if x is not None]
133
+	stack.extend(x for x in nexts if x.pos not in visited)
134
+
135
+#graph = numpy.random.standard_normal((height,width)) * 0.5
136
+#if (graph < 0).any():
137
+#	graph -= graph.min()
138
+#graph = graph.astype('int')
139
+#graph[:] = 1
140
+
141
+#for ___ in range(20):
142
+	#graph[random.randrange(height), random.randrange(width)] = 4000
143
+
144
+#for x,row in enumerate(graph):
145
+ #for y,cell in enumerate(row):
146
+  #if (x,y) in goals: print '%1s' % '*',
147
+  #elif cell == 4001: print '#',
148
+  #else: print '%1d' % cell,
149
+ #print
150
+
151
+t1 = time.time()
152
+rounds = 1
153
+for ___ in range(rounds):
154
+	d = dijkstra(graph, goals)
155
+dt = time.time() - t1
156
+dt *= 1000
157
+print '%4.4f' % dt, 'milliseconds for %d rounds' % rounds, '%4.4f milliseconds per round' % (dt/rounds)
158
+
159
+
160
+
161
+print  '---'
162
+
163
+for x in d:
164
+ for y in x:
165
+  if y > width*height: print '..',
166
+  else: print '%2d' % y,
167
+ print
168
+
169
+#print '---'
170
+
171
+#for k in (d > 5).astype('int'):
172
+ #for y in k:
173
+  #if y: print '#',
174
+  #else: print ' ',
175
+ #print
176
+
... ...
@@ -1,66 +1,111 @@
1
+import random
2
+import numpy
3
+
1 4
 def row2diterate(dct, width, height):
2 5
 	for y in range(height):
3 6
 		for x in range(width):
4 7
 			yield dct[c,r]
5 8
 
6 9
 class Cell(object):
7
-	def __init__(self, pos, value=0, neighbors = None, previous = None):
10
+	@property
11
+	def value(self):
12
+		return self._value
13
+	@value.setter
14
+	def value(self, v):
15
+		if v >= self._value: return
16
+		self._value = v
17
+	def __init__(self, pos, value=0, previous={}):
8 18
 		self.pos = pos
9
-		self.value = value
19
+		self._value = value
10 20
 
11
-		if previous is None: previous = {}
12 21
 		self.previous = previous
13 22
 		self.previous[pos] = self
14 23
 
15
-		if neighbors is None: neighbors = [None,None,None,None]
16
-		chg_funcs = [
24
+		#                 N:0   NE:1 E:2  SE:3  S:4  SW:5 W:6  NW:7
25
+		# opposite:       S:4   SW:5 W:6  NW:7  N:0  NE:1 E:2  SE:3 
26
+		self.neighbors = [None, None,None,None, None,None,None,None]
27
+		neighborlen = len(self.neighbors)
28
+		self.get_opposite = lambda idx: (idx+(neighborlen>>1)) % neighborlen
29
+
30
+		#
31
+		#  (-1,-1) (0,-1) (1,-1)
32
+		#  (-1, 0) (0, 0) (1, 0)
33
+		#  (-1, 1) (0, 1) (1, 1)
34
+		#
35
+
36
+		self.chg_funcs = (
17 37
 			lambda (x,y): (x,y-1),
38
+			lambda (x,y): (x+1,y-1),
18 39
 			lambda (x,y): (x+1,y),
40
+			lambda (x,y): (x+1,y+1),
19 41
 			lambda (x,y): (x,y+1),
42
+			lambda (x,y): (x-1,y+1),
20 43
 			lambda (x,y): (x-1,y),
21
-		]
22
-		for idx,val in enumerate(neighbors):
23
-			val = neighbors[idx] = previous.get(chg_funcs[idx](pos))
24
-			if val is not None: val.reconnect()
25
-		self.neighbors = neighbors
44
+			lambda (x,y): (x-1,y-1),
45
+		)
46
+
47
+		self.neighbor_poses = {cfunc(self.pos):idx for idx,cfunc in enumerate(self.chg_funcs)}
48
+		self.reconnect()
26 49
 
50
+	def connect(self, other, dir):
51
+		self.neighbors[dir] = other
52
+		other.neighbors[self.get_opposite(dir)] = self
53
+		self.recalc_values()
27 54
 
28 55
 	def reconnect(self):
29
-		chg_funcs = [
30
-			lambda (x,y): (x,y-1),
31
-			lambda (x,y): (x+1,y),
32
-			lambda (x,y): (x,y+1),
33
-			lambda (x,y): (x-1,y),
34
-		]
35
-		for idx,val in enumerate(self.neighbors):
36
-			if val is None: continue
37
-			self.neighbors[idx] = self.previous.get(chg_funcs[idx](self.pos))
56
+		for pos in self.neighbor_poses:
57
+			val = self.previous.get(pos)
58
+			if val is not None:
59
+				self.connect(val,self.neighbor_poses[pos])
38 60
 
39
-	def expand(self, tl=(0,0),br=(None,None), recurse=0):
40
-		chg_funcs = [
41
-			lambda (x,y): (x,y-1),
42
-			lambda (x,y): (x+1,y),
43
-			lambda (x,y): (x,y+1),
44
-			lambda (x,y): (x-1,y),
45
-		]
46 61
 
62
+	def recalc_values(self):
63
+		for k in self.neighbors:
64
+			if k is not None:
65
+				if self.value + 1 > k.value: # self: 9 k: 7 -> self: 8 k: 7
66
+					self.value = k.value + 1
67
+				elif self.value + 1 < k.value: # self 6 k: 8 -> self: 6 k: 7
68
+					k.value = self.value + 1
47 69
 
48
-		for idx, (cfunc,val) in enumerate(zip(chg_funcs,self.neighbors)):
49
-			if val is None:
50
-				nx,ny = cfunc(self.pos)
51
-				if any(a<b for (a,b) in zip((nx,ny), tl)): continue
52
-				elif br[0] is not None and any(a>b for (a,b) in zip((nx,ny), br)): continue
70
+	def step_all(self):
71
+		for k in self.previous.values():
72
+			k.step()
53 73
 
54
-				if (nx,ny) in self.previous:
74
+	def step(self):
75
+		for pos,idx in self.neighbor_poses.items():
76
+			if pos not in self.previous or self.neighbors[idx] is None:
77
+				nx,ny = pos
78
+
79
+				if pos in self.previous:
55 80
 					val = self.previous[nx,ny]
56
-					if val.value + 1 < self.value:
57
-						self.value = val.value + 1
58 81
 				else:
59
-					val = Cell((nx,ny), self.value+1, None, self.previous)
82
+					val = Cell((nx,ny), self.value+1, self.previous)
60 83
 
61 84
 				self.neighbors[idx] = val
62
-				if self not in val.neighbors or val.value > self.value + 1:
63
-					val.expand(tl,br)
85
+		self.recalc_values()
86
+
87
+	def step_to(self, pos):
88
+		while pos not in self.previous:
89
+			self.step_all()
90
+
91
+	def fill_rect(self, tl, br):
92
+		lx,ty = tl
93
+		rx,by = br
94
+		self.step_to(tl)
95
+		self.step_to(br)
96
+		self.step_to((rx,ty))
97
+		self.step_to((lx,by))
98
+
99
+	def get_cells(self):
100
+		out = []
101
+		tl = min(self.previous)[0], min(self.previous,key=lambda x:x[1])[1]
102
+		br = max(self.previous)[0], max(self.previous,key=lambda x:x[1])[1]
103
+		for x in range(tl[0],br[0]+1):
104
+			out.append([])
105
+			for y in range(tl[1],br[1]+1):
106
+				if (x,y) in self.previous: out[-1].append(self.previous[x,y])
107
+				else: out[-1].append(None)
108
+		return out
64 109
 
65 110
 
66 111
 
... ...
@@ -69,3 +114,107 @@ class Cell(object):
69 114
 	def get_topleftmost(cell):
70 115
 		return cell.neighbors[min(cell.neighbors, key=lambda (x,y): x+y)]
71 116
 
117
+if __name__ == '__main__':
118
+	import unittest
119
+	class CellTest(unittest.TestCase):
120
+		def new_cell(self, pos=(0,0),prev=None):
121
+			if prev is None:
122
+				prev = {}
123
+			return Cell(pos, 0, prev)
124
+
125
+		def test_step00(self):
126
+			cell = self.new_cell()
127
+			self.assertEqual(cell.neighbors, [None,None,None,None,None,None,None,None])
128
+			cell.step()
129
+			for k in cell.neighbors:
130
+				self.assertNotEqual(k,None)
131
+				self.assertEqual(cell.value+1, k.value)
132
+				self.assertIn(cell, k.neighbors)
133
+				self.assertIn(k, cell.neighbors)
134
+
135
+		def test_step01(self):
136
+			pattern = numpy.array([
137
+				[2,2,2,2,2],
138
+				[2,1,1,1,2],
139
+				[2,1,0,1,2],
140
+				[2,1,1,1,2],
141
+				[2,2,2,2,2],
142
+			])
143
+			cell = self.new_cell((2,2))
144
+			self.assertEqual(cell.neighbors, [None,None,None,None,None,None,None,None])
145
+			cell.step_all()
146
+			cell.step_all()
147
+			dx = min(cell.previous)
148
+			dy = min(cell.previous, key=lambda a:a[1])
149
+			result = numpy.zeros((5,5)).astype('int')
150
+			result[:] = 9
151
+			for (x,y),v in cell.previous.items():
152
+				result[x,y] = v.value
153
+			self.assertTrue((result==pattern).all())
154
+
155
+
156
+		def test_step02(self):
157
+			pattern = numpy.array([
158
+				[1,1,1,2,2],
159
+				[1,0,1,1,2],
160
+				[1,1,0,1,2],
161
+				[2,1,1,1,2],
162
+				[2,2,2,2,2],
163
+			])
164
+			cell = self.new_cell((2,2))
165
+			self.assertEqual(cell.neighbors, [None,None,None,None,None,None,None,None])
166
+			cell = self.new_cell((1,1),cell.previous)
167
+			cell.step_all()
168
+			cell.step_all()
169
+			dx = min(cell.previous)
170
+			dy = min(cell.previous, key=lambda a:a[1])
171
+			result = numpy.zeros((5,5)).astype('int')
172
+			result[:] = 9
173
+			for (x,y),v in cell.previous.items():
174
+				result[x,y] = v.value
175
+			self.assertTrue((result==pattern).all())
176
+
177
+
178
+
179
+		def test_getcells(self):
180
+			cell = self.new_cell((4,4))
181
+			self.assertEqual(len(cell.previous), 1)
182
+			self.assertEqual(cell.get_cells(), [[cell]])
183
+
184
+
185
+
186
+	prev = {}
187
+	q = [Cell( (x,y), 0, prev ) for x in range(10) for y in range(10) if random.random() < 1.0/50]
188
+	#unittest.main()
189
+
190
+	#a = Cell( (0,0), 0, {})
191
+	previous = {}
192
+	for r in range(0,10,2):
193
+		for c in range(0,10,2):
194
+			a = Cell( (random.randrange(r*4,r*4+10),random.randrange(c*4,c*4+10)), 0, previous)
195
+	#a.step()
196
+	import time
197
+	t0 = time.time()
198
+	a.fill_rect((0,0), (40,40))
199
+	t1 = time.time() - t0
200
+	print int(t1*1000), 'msecs for mapgen'
201
+	_ = a.get_cells()
202
+	q = numpy.zeros((40,40))
203
+	q[:] = 9
204
+	for (x,y) in a.previous:
205
+		if 0 < x < 40 and 0 < y < 40:
206
+			q[x,y] = a.previous[x,y].value
207
+	r = numpy.zeros((42,42))
208
+	r[:] = 9
209
+	r[1:-1,1:-1] = q
210
+
211
+	q = (r>3).astype('int')
212
+
213
+	for x in q:
214
+		for y in x:
215
+			if y == 0: print ' ',
216
+			else: print '#',
217
+			#if y is not None: print '%2d'% y,
218
+			#else: print '  ',
219
+		print
220
+	del _
... ...
@@ -9,132 +9,156 @@ import collections
9 9
 #  (0,3)  (1,3) | (2,3) (3,3)
10 10
 #
11 11
 
12
-class XY(collections.namedtuple('XY', 'x y')):
13
-	def __add__(self, other):
14
-		dx, dy = other
15
-		return XY(self.x+dx, self.y+dy)
16
-	def __sub__(self, other):
17
-		dx, dy = other
18
-		return XY(self.x-dx, self.y-dy)
19
-	def __div__(self, n):
20
-		return XY(self.x/n, self.y/n)
21
-	def __mul__(self, n):
22
-		return XY(self.x*n, self.y*n)
23
-	def __abs__(self):
24
-		return XY(abs(self.x), abs(self.y))
25
-
26
-class AABB(collections.namedtuple('AABB', 'center halfDimension')):
12
+class Rect(object):
27 13
 	@property
28
-	def tl(self):
29
-		return self.center - self.halfDimension
14
+	def width(self): return self.size[0]
30 15
 	@property
31
-	def br(self):
32
-		return self.center + self.halfDimension
16
+	def height(self): return self.size[1]
17
+
33 18
 	@property
34
-	def tr(self):
35
-		return XY(self.br[0], self.tl[1])
19
+	def x(self): return self.pos[0]
36 20
 	@property
37
-	def bl(self):
38
-		return XY(self.tl[0], self.br[1])
39
-	def containsPoint(self, p):
40
-		tl = self.center - self.halfDimension
41
-		br = (self.center + self.halfDimension) - (1,1)
42
-		return all(x >= 0 for x in p - tl) and all(x > 0 for x in br - p)
43
-
44
-	def intersectsAABB(self, other):
45
-		tl = self.center - self.halfDimension
46
-		br = self.center + self.halfDimension
47
-		tr = XY(br[0], tl[1])
48
-		bl = XY(tl[0], br[1])
49
-		return other.containsPoint(tl) or other.containsPoint(br) or other.containsPoint(tr) or other.containsPoint(bl)
50
-
51
-
52
-	def subdivide(self):
53
-		tl = self.center - self.halfDimension
54
-		br = self.center + self.halfDimension
55
-		tr = XY(br[0], tl[1])
56
-		bl = XY(tl[0], br[1])
57
-
58
-		center = self.center
59
-		ne = AABB( (tl+center)/2, self.halfDimension/2 )
60
-		nw = AABB( (tr+center)/2, self.halfDimension/2 )
61
-		se = AABB( (br+center)/2, self.halfDimension/2 )
62
-		sw = AABB( (bl+center)/2, self.halfDimension/2 )
63
-		return nw, ne, se, sw
21
+	def y(self): return self.pos[1]
22
+
23
+	@classmethod
24
+	def random_rect(cls, xrange, yrange, dimrange):
25
+		x = random.randrange(*xrange)
26
+		y = random.randrange(*yrange)
27
+		size = map(lambda x: random.randrange(*x), dimrange)
28
+		return cls(x,y, *size)
29
+
30
+	def __repr__(self):
31
+		return 'Rect(%d, %d, %d, %d)' % (self.pos + self.size)
32
+
33
+	def fill(self, array, value):
34
+		x,y = self.pos
35
+		w,h = self.size
36
+		array[x:x+w,y:y+h] = value
37
+
38
+	def __init__(self, x,y, w,h):
39
+		self.pos = (x,y)
40
+		self.size = (w,h)
64 41
 
65 42
 class QuadTree(object):
66
-	QT_NODE_CAPACITY = 4
67
-
68
-	def __init__(self, boundary):
69
-		self.boundary = boundary
70
-		self.nw = None
71
-		self.ne = None
72
-		self.se = None
73
-		self.sw = None
74
-		self.points = []
75
-
76
-	def insert(self, p):
77
-		if not self.boundary.containsPoint(p): return False
78
-
79
-		if len(self.points) < self.QT_NODE_CAPACITY:
80
-			self.points.append(p)
81
-			return True
82
-
83
-		if self.nw is None:
84
-			self.nw, self.ne, self.se, self.sw = map(self.__class__, self.boundary.subdivide())
85
-			while self.points:
86
-				point = self.points.pop()
87
-				if self.nw.insert(point): pass
88
-				elif self.ne.insert(point): pass
89
-				elif self.se.insert(point): pass
90
-				elif self.sw.insert(point): pass
91
-
92
-		elif self.nw.insert(p): return True
93
-		elif self.ne.insert(p): return True
94
-		elif self.se.insert(p): return True
95
-		elif self.sw.insert(p): return True
96
-
97
-		return False
98
-
99
-	def query(self, range):
100
-		pointsInRange = []
101
-
102
-		if not self.boundary.intersectsAABB(range):
103
-			return pointsInRange
104
-
105
-		for p in self.points:
106
-			if range.containsPoint(p):
107
-				pointsInRange.append(p)
108
-
109
-		if self.nw is None: return pointsInRange
110
-
111
-		pointsInRange.extend(self.nw.query(range))
112
-		pointsInRange.extend(self.ne.query(range))
113
-		pointsInRange.extend(self.se.query(range))
114
-		pointsInRange.extend(self.sw.query(range))
115
-
116
-		return pointsInRange
117
-
118
-	def visualize(self):
119
-		points = self.query(self.boundary)
120
-		import numpy
121
-		out = numpy.zeros(self.boundary.halfDimension * 2).astype('int')
122
-		for p in points:
123
-			out[p] = 1
124
-
125
-		for row in out:
126
-			for cell in row:
127
-				if cell == 1: print('#',end='')
128
-				elif cell == 0: print(' ',end='')
129
-			print()
130
-
131
-a = QuadTree(AABB(XY(128,128), XY(128,128)))
43
+	MAX_OBJECTS = 10
44
+	MAX_LEVELS = 8
45
+
46
+	def __init__(self, pLevel, pBounds):
47
+		self.level = pLevel
48
+		self.objects = []
49
+		self.bounds = pBounds
50
+		self.nodes = [None, None, None, None]
51
+
52
+	def clear(self):
53
+		del self.objects[:]
54
+		for idx,node in enumerate(self.nodes):
55
+			if node is not None:
56
+				node.clear()
57
+				self.nodes[idx] = None
58
+
59
+	def split(self):
60
+		subWidth = self.bounds.size[0] / 2
61
+		subHeight = self.bounds.size[1] / 2
62
+		x, y = self.bounds.pos
63
+
64
+		self.nodes[0] = QuadTree(self.level+1, Rect(x+subWidth, y, subWidth, subHeight))
65
+		self.nodes[1] = QuadTree(self.level+1, Rect(x, y, subWidth, subHeight))
66
+		self.nodes[2] = QuadTree(self.level+1, Rect(x, y + subHeight, subWidth, subHeight))
67
+		self.nodes[3] = QuadTree(self.level+1, Rect(x + subWidth, y + subHeight, subWidth, subHeight))
68
+
69
+	def getIndex(self, rect):
70
+		index = -1
71
+		verticalMidpoint = self.bounds.x + self.bounds.width/2
72
+		horizontalMidpoint = self.bounds.y + self.bounds.width/2
73
+		topquad = rect.y < horizontalMidpoint and rect.y + rect.height < horizontalMidpoint
74
+		bottomquad = rect.y > horizontalMidpoint
75
+
76
+		if rect.x < verticalMidpoint and rect.x + rect.width < verticalMidpoint:
77
+			if topquad:
78
+				index = 1
79
+			elif bottomquad:
80
+				index = 2
81
+		elif rect.x > verticalMidpoint:
82
+			if topquad:
83
+				index = 0
84
+			elif bottomquad:
85
+				index = 3
86
+
87
+		return index
88
+
89
+	def insert(self, rect):
90
+		if self.nodes[0] is not None:
91
+			index = self.getIndex(rect)
92
+			if index != -1:
93
+				self.nodes[index].insert(rect)
94
+				return
95
+
96
+		self.objects.append(rect)
97
+
98
+		if len(self.objects) > self.MAX_OBJECTS and self.level < self.MAX_LEVELS:
99
+			if self.nodes[0] is None:
100
+				self.split()
101
+
102
+			i = 0
103
+			while i < len(self.objects):
104
+				index = self.getIndex(self.objects[i])
105
+				if index != -1:
106
+					self.nodes[index].insert(self.objects.pop(i))
107
+				else:
108
+					i += 1
109
+
110
+	def retrieve(self, rect):
111
+		result = []
112
+		index = self.getIndex(rect)
113
+		if index != -1 and self.nodes[0] is not None:
114
+			result.extend(self.nodes[index].retrieve(rect))
115
+
116
+		result.extend(self.objects)
117
+		return result
118
+
119
+	def locate_rect(self, rect):
120
+		cur = self
121
+		index = cur.getIndex(rect)
122
+
123
+		while index != -1:
124
+			yield index
125
+			cur = self.nodes[index]
126
+
127
+
128
+
129
+
132 130
 import random
133
-for x in range(128):
134
-	for y in range(128):
135
-		if not a.insert(XY(x,y)):
136
-			pass #print (x,y)
137
-#points = {XY(random.randrange(128),random.randrange(128)) for ___ in range(300)}
138
-#for p in points:
139
-	#a.insert(p)
140
-#a.visualize()
131
+
132
+objects = {Rect.random_rect( (1,37), (1,200), ( (2,10), (2,10) ) ) for ___ in range(50)}
133
+quad = QuadTree(0, Rect(0,0, 48,211))
134
+quad.clear()
135
+for obj in objects:
136
+	quad.insert(obj)
137
+
138
+collided = set()
139
+oot = set()
140
+
141
+for obj in objects:
142
+	out = quad.retrieve(obj)
143
+
144
+	if obj in collided: continue
145
+	oot.add(obj)
146
+
147
+	for col in out:
148
+		collided.add(col)
149
+		print(obj, 'collides with', col)
150
+	else:
151
+		print('---')
152
+
153
+
154
+import numpy
155
+field = numpy.zeros( (48,211) )
156
+for obj in objects:
157
+	obj.fill(field, 2)
158
+for obj in oot:
159
+	obj.fill(field, 1)
160
+
161
+for row in field:
162
+	for cell in row:
163
+		print('%d' % cell, end='')
164
+	print()
141 165
new file mode 100644
... ...
@@ -0,0 +1,22 @@
1
+class Event(object):
2
+	def fire(self,*args,**kw): pass
3
+
4
+class Countdown(object):
5
+	def __init__(self): pass
6
+	def schedule(self,ticks,event,*args,**kw): pass
7
+	def tick(self): pass
8
+
9
+
10
+if __name__ == '__main__':
11
+	import unittest
12
+
13
+	class TestCountdown(unittest.TestCase):
14
+		def a_test_schedule(self):
15
+			a = Countdown()
16
+			class event_test(Event):
17
+				def __init__(self):
18
+					self.fired = False
19
+				def fire(self):
20
+					self.fired = not self.fired
21
+					return False
22
+			a.schedule(3,event_test())
... ...
@@ -40,8 +40,10 @@ inst = lambda a:a()
40 40
 @inst
41 41
 class Settings:
42 42
 	def __init__(self):
43
-		with file('settings.yaml') as f:
44
-			self.__dict__ = yaml.safe_load(f)
43
+		with file('data/settings.yaml') as f:
44
+			self.settings = yaml.safe_load(f)
45
+	def __getattr__(self, attr):
46
+		return self.settings[attr]
45 47
 
46 48
 	SCREEN_WIDTH = 75
47 49
 	SCREEN_HEIGHT = 20
... ...
@@ -157,6 +159,8 @@ class LevelMap(object):
157 159
 				for x,__ in enumerate(row):
158 160
 					lm.adj_map(level, x,y)
159 161
 
162
+
163
+
160 164
 lm = LevelMap(mp)
161 165
 lm.calculate_level(level)
162 166