Browse code
quadtree
Ed L authored on 17/07/2013 21:27:47
Showing 8 changed files
Showing 8 changed files
- data/settings.yaml
- libs/dice.py
- libs/path.py
- libs/quadtree.py
- main.py
- map.py
- settings.yaml
- terminal.png
0 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,27 @@ |
1 |
+import random |
|
2 |
+ |
|
3 |
+class Die(object): |
|
4 |
+ def __init__(self, sides, min=1, step=1): |
|
5 |
+ self.sides = sides |
|
6 |
+ self.min = min |
|
7 |
+ self.step = 1 |
|
8 |
+ self.sides = range(min, (min+sides)*step, step) |
|
9 |
+ self._value = None |
|
10 |
+ self.combine_func = lambda a,b: a+b |
|
11 |
+ |
|
12 |
+ def roll(object): |
|
13 |
+ self._value = random.choice(self.sides) |
|
14 |
+ return self._value |
|
15 |
+ |
|
16 |
+ @property |
|
17 |
+ def value(object): |
|
18 |
+ if self._value is None: self.roll() |
|
19 |
+ return self._value |
|
20 |
+ |
|
21 |
+ def combine(self, other): |
|
22 |
+ if hasattr(other, 'value'): other = other.value |
|
23 |
+ return self.combine_func(self.value, other.value) |
|
24 |
+ |
|
25 |
+def DiceRoll(object): |
|
26 |
+ def __init__(self, dice): |
|
27 |
+ self.dice = dice |
0 | 28 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,71 @@ |
1 |
+def row2diterate(dct, width, height): |
|
2 |
+ for y in range(height): |
|
3 |
+ for x in range(width): |
|
4 |
+ yield dct[c,r] |
|
5 |
+ |
|
6 |
+class Cell(object): |
|
7 |
+ def __init__(self, pos, value=0, neighbors = None, previous = None): |
|
8 |
+ self.pos = pos |
|
9 |
+ self.value = value |
|
10 |
+ |
|
11 |
+ if previous is None: previous = {} |
|
12 |
+ self.previous = previous |
|
13 |
+ self.previous[pos] = self |
|
14 |
+ |
|
15 |
+ if neighbors is None: neighbors = [None,None,None,None] |
|
16 |
+ chg_funcs = [ |
|
17 |
+ lambda (x,y): (x,y-1), |
|
18 |
+ lambda (x,y): (x+1,y), |
|
19 |
+ lambda (x,y): (x,y+1), |
|
20 |
+ 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 |
|
26 |
+ |
|
27 |
+ |
|
28 |
+ 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)) |
|
38 |
+ |
|
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 |
+ |
|
47 |
+ |
|
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 |
|
53 |
+ |
|
54 |
+ if (nx,ny) in self.previous: |
|
55 |
+ val = self.previous[nx,ny] |
|
56 |
+ if val.value + 1 < self.value: |
|
57 |
+ self.value = val.value + 1 |
|
58 |
+ else: |
|
59 |
+ val = Cell((nx,ny), self.value+1, None, self.previous) |
|
60 |
+ |
|
61 |
+ self.neighbors[idx] = val |
|
62 |
+ if self not in val.neighbors or val.value > self.value + 1: |
|
63 |
+ val.expand(tl,br) |
|
64 |
+ |
|
65 |
+ |
|
66 |
+ |
|
67 |
+ |
|
68 |
+ @staticmethod |
|
69 |
+ def get_topleftmost(cell): |
|
70 |
+ return cell.neighbors[min(cell.neighbors, key=lambda (x,y): x+y)] |
|
71 |
+ |
0 | 72 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,140 @@ |
1 |
+from __future__ import print_function |
|
2 |
+import collections |
|
3 |
+ |
|
4 |
+# |
|
5 |
+# (0,0) (1,0) | (2,0) (3,0) |
|
6 |
+# (0,1) (1,1) | (2,1) (3,1) |
|
7 |
+# -------------+------------ |
|
8 |
+# (0,2) (1,2) | (2,2) (3,2) |
|
9 |
+# (0,3) (1,3) | (2,3) (3,3) |
|
10 |
+# |
|
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')): |
|
27 |
+ @property |
|
28 |
+ def tl(self): |
|
29 |
+ return self.center - self.halfDimension |
|
30 |
+ @property |
|
31 |
+ def br(self): |
|
32 |
+ return self.center + self.halfDimension |
|
33 |
+ @property |
|
34 |
+ def tr(self): |
|
35 |
+ return XY(self.br[0], self.tl[1]) |
|
36 |
+ @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 |
|
64 |
+ |
|
65 |
+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))) |
|
132 |
+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() |
... | ... |
@@ -111,12 +111,13 @@ class LevelMap(object): |
111 | 111 |
self.fovmaps = [None] |
112 | 112 |
|
113 | 113 |
def get_tile_type(self, level,x,y): |
114 |
- tile_val = mapping.get(self.map.data[level][y][x],(None,' '))[1] |
|
114 |
+ lvl = self.map.get_level(level) |
|
115 |
+ tile_val = mapping.get(lvl[y][x],(None,' '))[1] |
|
115 | 116 |
return names.get(tile_val,('',True,True)) |
116 | 117 |
|
117 | 118 |
def get_fovmap(self,level): |
118 | 119 |
if level >= len(self.fovmaps): |
119 |
- levelmap = self.map.data[level] |
|
120 |
+ levelmap = mp.get_level(level) |
|
120 | 121 |
self.fovmaps.append(libtcod.map_new(len(levelmap), len(levelmap[0]))) |
121 | 122 |
libtcod.map_clear(self.fovmaps[-1]) |
122 | 123 |
return self.fovmaps[level] |
... | ... |
@@ -151,7 +152,8 @@ class LevelMap(object): |
151 | 152 |
def calculate_level(self, level): |
152 | 153 |
if not self.has_level(level): |
153 | 154 |
print 'calculate_level' |
154 |
- for y,row in enumerate(mp.data[level]): |
|
155 |
+ lvl = mp.get_level(level) |
|
156 |
+ for y,row in enumerate(lvl): |
|
155 | 157 |
for x,__ in enumerate(row): |
156 | 158 |
lm.adj_map(level, x,y) |
157 | 159 |
|
... | ... |
@@ -164,7 +166,8 @@ for c in [con,message_con]: |
164 | 166 |
libtcod.console_clear(c) |
165 | 167 |
|
166 | 168 |
offset_x, offset_y = 0,0 |
167 |
-MAP_Y,MAP_X = len(mp.data[level]), len(mp.data[level][0]) |
|
169 |
+levelmap = mp.get_level(level) |
|
170 |
+MAP_Y,MAP_X = len(levelmap), len(levelmap[0]) |
|
168 | 171 |
player_x, player_y = random.randrange(0,MAP_X),random.randrange(0,MAP_Y) |
169 | 172 |
if not lm.get_walkable(level,player_x,player_y): |
170 | 173 |
player_x,player_y = [(x,y) for x in range(player_x-2,player_x+3) for y in range(player_y-2,player_y+3) if lm.get_walkable(level,x,y)][-1] |
... | ... |
@@ -188,7 +191,7 @@ while not libtcod.console_is_window_closed(): |
188 | 191 |
t1 = time.time() |
189 | 192 |
lm.comp_fov(level, player_x, player_y,10) |
190 | 193 |
player_screen_pos = player_x-offset_x,player_y-offset_y |
191 |
- for y,row in enumerate(mp.data[level][offset_y:offset_y+Settings.SCREEN_HEIGHT]): |
|
194 |
+ for y,row in enumerate(levelmap[offset_y:offset_y+Settings.SCREEN_HEIGHT]): |
|
192 | 195 |
for x,cell in enumerate(row[offset_x:offset_x+Settings.SCREEN_WIDTH]): |
193 | 196 |
color,char,bgcolor = mapping.get(cell, (libtcod.Color(0,0,0),' ',libtcod.Color(0,0,0))) |
194 | 197 |
|
... | ... |
@@ -279,7 +282,8 @@ while not libtcod.console_is_window_closed(): |
279 | 282 |
else: |
280 | 283 |
player_prop = player_x/MAP_X, player_y/MAP_Y |
281 | 284 |
offset_prop = offset_x/MAP_X, offset_y/MAP_Y |
282 |
- MAP_Y,MAP_X = len(mp.data[level]), len(mp.data[level][0]) |
|
285 |
+ levelmap = mp.get_level(level) |
|
286 |
+ MAP_Y,MAP_X = len(levelmap), len(levelmap[0]) |
|
283 | 287 |
libtcod.console_clear(0) |
284 | 288 |
libtcod.console_clear(con) |
285 | 289 |
|
... | ... |
@@ -1,21 +1,22 @@ |
1 |
+ |
|
1 | 2 |
# Copyright (c) 2013 Edward Langley |
2 | 3 |
# All rights reserved. |
3 |
-# |
|
4 |
+# |
|
4 | 5 |
# Redistribution and use in source and binary forms, with or without |
5 | 6 |
# modification, are permitted provided that the following conditions |
6 | 7 |
# are met: |
7 |
-# |
|
8 |
+# |
|
8 | 9 |
# Redistributions of source code must retain the above copyright notice, |
9 | 10 |
# this list of conditions and the following disclaimer. |
10 |
-# |
|
11 |
+# |
|
11 | 12 |
# Redistributions in binary form must reproduce the above copyright |
12 | 13 |
# notice, this list of conditions and the following disclaimer in the |
13 | 14 |
# documentation and/or other materials provided with the distribution. |
14 |
-# |
|
15 |
+# |
|
15 | 16 |
# Neither the name of the project's author nor the names of its |
16 | 17 |
# contributors may be used to endorse or promote products derived from |
17 | 18 |
# this software without specific prior written permission. |
18 |
-# |
|
19 |
+# |
|
19 | 20 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | 21 |
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | 22 |
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
... | ... |
@@ -29,6 +30,7 @@ |
29 | 30 |
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | 31 |
|
31 | 32 |
from __future__ import division |
33 |
+import collections |
|
32 | 34 |
import random |
33 | 35 |
import numpy |
34 | 36 |
|
... | ... |
@@ -62,24 +64,84 @@ P2 |
62 | 64 |
%(data)s |
63 | 65 |
""" |
64 | 66 |
|
67 |
+class MapData(collections.MutableMapping): |
|
68 |
+ def __init__(self, data, base=9): |
|
69 |
+ self.data = data |
|
70 |
+ self.base = base |
|
71 |
+ self.below = [[None for __ in range(base)] for ___ in range(base)] |
|
72 |
+ |
|
73 |
+ @classmethod |
|
74 |
+ def rand_new(cls, mean, base=9): |
|
75 |
+ data = numpy.random.normal(mean, max(mean/4,1), (base,base)).astype(int) |
|
76 |
+ return cls(data,base) |
|
77 |
+ |
|
78 |
+ def get_cell(self, x,y, depth=1): |
|
79 |
+ cur = self |
|
80 |
+ if depth > 1: |
|
81 |
+ below = self.get_below(x,y) |
|
82 |
+ depth -= 1 |
|
83 |
+ while depth > 1: |
|
84 |
+ below = below.get_below(0,0) |
|
85 |
+ depth -= 1 |
|
86 |
+ cur = below |
|
87 |
+ return cur.data[y,x] |
|
88 |
+ |
|
89 |
+ def get_below(self, x,y): |
|
90 |
+ below = self.below[y][x] |
|
91 |
+ if below is None: |
|
92 |
+ mean = self.get_cell(x,y) |
|
93 |
+ below = self.below[y][x] = self.rand_new(mean,self.base) |
|
94 |
+ return below |
|
95 |
+ |
|
96 |
+ def get_all_below(self): |
|
97 |
+ for y, lis in enumerate(self.below): |
|
98 |
+ for x, val in enumerate(lis): |
|
99 |
+ if val is not None: continue |
|
100 |
+ self.get_below(x,y) |
|
101 |
+ return numpy.concatenate([numpy.concatenate([x.data for x in l],1) for l in self.below]) |
|
102 |
+ |
|
103 |
+ def get_rect_below(self, x,y, w,h): |
|
104 |
+ tw = w/self.base |
|
105 |
+ tw = int(numpy.ceil(tw)) |
|
106 |
+ th = h/self.base |
|
107 |
+ th = int(numpy.ceil(th)) |
|
108 |
+ out = [] |
|
109 |
+ for ny in range(y,min(len(self.data),y+th)): |
|
110 |
+ out.append([]) |
|
111 |
+ for nx in range(x,min(len(self.data[0]),x+tw)): |
|
112 |
+ out[-1].append(self.get_below(nx,ny)) |
|
113 |
+ |
|
114 |
+ return numpy.concatenate([numpy.concatenate([x.data for x in l],1) for l in out])[:h,:w] |
|
115 |
+ |
|
116 |
+ def __getitem__(self, key): |
|
117 |
+ return self.data.__getitem__(key) |
|
118 |
+ def __delitem__(self, key): |
|
119 |
+ self.data.__setitem__(key, 0.0) |
|
120 |
+ def __len__(self): |
|
121 |
+ return len(self.data) |
|
122 |
+ def __setitem__(self, key, value): |
|
123 |
+ self.data.__setitem__(key, value) |
|
124 |
+ def __iter__(self): |
|
125 |
+ return iter(self.data) |
|
126 |
+ |
|
127 |
+ |
|
65 | 128 |
class Map(object): |
66 | 129 |
def __init__(self, data, depth, base=9): |
67 | 130 |
self.data = data |
68 | 131 |
self.depth = depth |
69 | 132 |
self.base = base |
133 |
+ |
|
70 | 134 |
@classmethod |
71 | 135 |
def rand_new(cls, depth,base=9): |
72 |
- data = [numpy.random.random_integers(0,10,(1,1))] |
|
73 |
- for x in range(1,depth+1): |
|
74 |
- global out |
|
75 |
- out = numpy.zeros((base**x, base**x),'int') |
|
76 |
- for r, row in enumerate(data[-1]): |
|
77 |
- for c, col in enumerate(row): |
|
78 |
- new = numpy.random.normal(col,max(col/4,1),(base,base)).astype(int) |
|
79 |
- out[r*base:r*base+base,c*base:c*base+base] = new |
|
80 |
- data.append(out) |
|
136 |
+ data = MapData.rand_new(numpy.random.randint(0,10),base) |
|
81 | 137 |
return cls(data,depth) |
82 | 138 |
|
139 |
+ def get_level(self, level): |
|
140 |
+ cur = self.data |
|
141 |
+ for idx in range(1, level): |
|
142 |
+ cur = cur.get_below(0,0) |
|
143 |
+ return cur.get_rect_below(0,0, self.base**level, self.base**level) |
|
144 |
+ |
|
83 | 145 |
def to_pgm(self,level): |
84 | 146 |
level = self.data[level].copy() |
85 | 147 |
level -= min(l.min() for l in self.data) |