git.fiddlerwoaroof.com
Browse code

dijkstra algorithm improved, monsters only go to the center of the room the player is currently in, thief AI added

Ed L authored on 01/08/2012 03:50:31
Showing 5 changed files
... ...
@@ -1,29 +1,78 @@
1 1
 import random
2 2
 import copy
3 3
 
4
+try:
5
+	import numpypy as numpy
6
+except ImportError: pass
7
+import numpy
8
+
4 9
 class DjikstraMap(object):
5 10
 	def __init__(self, mp=None):
11
+		self.goals = []
12
+		self.iters = 0
6 13
 		#print '__init__ djm'
7 14
 		self.map = None
8 15
 		if mp is not None:
9 16
 			self.load_map(mp)
17
+		self.items = {}
18
+
19
+	def __getitem__(self, key):
20
+		return self.map[key]
21
+
22
+	def __setitem__(self, key, value):
23
+		x,y = key
24
+		self.map[x,y] = value
25
+
26
+	def __iter__(self):
27
+		for x in xrange(self.width):
28
+			for y in xrange(self.height):
29
+				yield (x,y), self[x,y]
30
+
31
+	def get_cell(self, x,y=None):
32
+		if y == self.wall:
33
+			x,y = x
34
+		return self[x,y]
10 35
 
11 36
 	def load_map(self, mp):
12
-		self.map = [
13
-			[ [255,None][cell] for cell in row ]
14
-				for row in mp
15
-		]
16
-		self.width = len(self.map)
17
-		self.height = len(self.map[0])
37
+		self.width = len(mp)
38
+		self.height = len(mp[0])
39
+		self.max = dist( (0,0), (self.width, self.height) ) ** 2
40
+		self.max = int(self.max)
41
+		self.wall = self.width*self.height + 1
42
+		self.map = numpy.array([
43
+			[ [self.max,self.wall][cell] for (y,cell) in enumerate(row) ]
44
+				for (x,row) in enumerate(mp)
45
+		], dtype=numpy.uint64)
46
+		self._set_goals()
47
+
18 48
 	def set_goals(self, *args, **k):
19
-		for x,y in args:
20
-			self.map[x][y] = k.get('weight', 0)
49
+		self.goals.extend(args)
50
+
51
+	def _set_goals(self):
52
+		for x,y in self.goals:
53
+			self[x,y] = 0
54
+
55
+	def get_cost(self, pos):
56
+		while self[pos] != min_neighbor+1 and self.cycle():
57
+			pass
58
+		return self[pos]
21 59
 
22 60
 	def iter_map(self):
23
-		for x, row in enumerate(self.map):
24
-			for y, cell in enumerate(row):
25
-				if cell is not None:
26
-					yield (x,y), cell
61
+		wall = self.wall
62
+		for idx, cell in enumerate(self.map.flat):
63
+			if cell == self.wall: continue
64
+			h = self.height
65
+			x,y = idx // h, idx % h
66
+			assert cell == self.map[x,y], "%s %s %s != %s" % (x,y, cell, self[x,y])
67
+			yield (x,y), cell
68
+
69
+	def iter_map(self):
70
+		return (
71
+			( (x,y), self[x,y] )
72
+				for x in xrange(self.width)
73
+				for y in xrange(self.height)
74
+				if self[x,y] != self.wall
75
+		)
27 76
 
28 77
 	def get_cross(self, pos, rad):
29 78
 		ox,oy = pos
... ...
@@ -40,22 +89,16 @@ class DjikstraMap(object):
40 89
 			elif y < 0 or y >= self.height:
41 90
 				result[idx] = None
42 91
 			else:
43
-				result[idx] = self.map[x][y]
92
+				result[idx] = self[x,y]
44 93
 		return result
45 94
 
46 95
 	def get_rect(self, pos, rad):
47 96
 		x,y = pos
48
-		result = []
49
-		for cx in range(x-rad, x+rad+1):
50
-			result.append([])
51
-			for cy in range(y-rad, y+rad+1):
52
-				if cx < 0 or cx >= len(self.map):
53
-					result[-1].append(None)
54
-				elif cy < 0 or cy >= len(self.map[0]):
55
-					result[-1].append(None)
56
-				else:
57
-					result[-1].append(self.map[cx][cy])
58
-
97
+		lx,ty = x-rad, y-rad
98
+		if x-rad < 0: lx = 0
99
+		if y-rad < 0: ty = 0
100
+		end = rad+1
101
+		result = self.map[lx:x+end,ty:y+end]
59 102
 		return result
60 103
 
61 104
 	def get_line(self, pos1, pos2):
... ...
@@ -66,37 +109,6 @@ class DjikstraMap(object):
66 109
 		if x1 == x2:
67 110
 			return [ (x1,y) for y in range(y1,y2+1) ]
68 111
 
69
-	def get_borders(self, pos, rad):
70
-		x,y = pos
71
-
72
-		results = []
73
-		results.extend(
74
-			self.get_line(
75
-				(min(x-rad, 0), min(y-rad, 0)),
76
-				(min(x-rad, 0), min(y+rad, self.height))
77
-			)
78
-		)
79
-		results.extend(
80
-			self.get_line(
81
-				(min(x-rad, 0),          min(y-rad, 0)),
82
-				(min(x+rad, self.width), min(y-rad, 0))
83
-			)
84
-		)
85
-
86
-		results.extend(
87
-			self.get_line(
88
-				(min(x+rad, self.width), min(y+rad, self.height)),
89
-				(min(x-rad, 0),          min(y+rad, self.height))
90
-			)
91
-		)
92
-
93
-		results.extend(
94
-			self.get_line(
95
-				(min(x+rad, self.width), min(y+rad, self.height)),
96
-				(min(x+rad, self.width), min(y-rad, 0))
97
-			)
98
-		)
99
-
100 112
 	def iter(self, num):
101 113
 		result = True
102 114
 		for _ in range(num):
... ...
@@ -105,38 +117,39 @@ class DjikstraMap(object):
105 117
 				break
106 118
 		return result
107 119
 
120
+	def closest_goal(self, pos):
121
+		return min( (g for g in self.goals), key=lambda g: dist(g,pos) )
122
+
108 123
 	def cycle(self):
109 124
 		changed = False
110 125
 		out = self.map
111 126
 		for pos, cell in self.iter_map():
112 127
 			x,y = pos
113
-
114
-			#rect = self.get_rect(pos, 2)
115
-			#neighbors = [n for n in borders(rect)]
116
-			neighbors = (r for r in sum(self.get_rect(pos, 1), []) if r is not None)
117
-			#neighbors = (r for r in self.get_cross(pos, 1) if r is not None)
118
-
119
-			try:
120
-				min_neighbor = min(neighbors)
121
-			except ValueError: continue
122
-
123
-			if cell > min_neighbor + 1:
128
+			neighbors = numpy.min(self.get_rect(pos, 1))
129
+			if cell > neighbors + 1:
124 130
 				changed = True
125
-				out[x][y] = min_neighbor + 1
131
+				self[x,y] = neighbors + 1
132
+
133
+		if changed:
134
+			self.iters += 1
126 135
 		return changed
127 136
 
128 137
 	def visualize(self):
129 138
 		print
130
-		for row in zip(*self.map):
131
-			for cell in row:
132
-				if cell is None: print ' ',
133
-				elif cell > 9: print '*',
134
-				else: print cell,
135
-			print
139
+		out = []
140
+		for x in range(self.width):
141
+			out.append([])
142
+			for y in range(self.height):
143
+				cell = self[x,y]
144
+				if cell == self.wall: out[-1].append(' ')
145
+				elif cell == self.max: out[-1].append('x')
146
+				elif cell > 9: out[-1].append('*')
147
+				else: out[-1].append(str(cell))
148
+		print '\n'.join(''.join(x) for x in zip(*out))
136 149
 
137 150
 	def get_neighbor_values(self, x,y):
138 151
 		b = enumerate((enumerate(r,-1) for r in self.get_rect( (x,y), 1 )),-1)
139
-		result = [(i1,i2, v) for i1, r in b for i2,v in r if v is not None]
152
+		result = [(i1,i2, v) for i1, r in b for i2,v in r if v != self.wall]
140 153
 		#print result
141 154
 		return result
142 155
 
... ...
@@ -157,15 +170,37 @@ class DjikstraMap(object):
157 170
 
158 171
 		return dx,dy
159 172
 
160
-def borders(rect):
161
-	mx, my = len(rect)-1, len(rect[0])-1
162
-	for x, row in enumerate(rect):
163
-		for y, cell in enumerate(row):
164
-			if x in {0,mx} or y in {0,my}:
165
-				if cell is not None:
166
-					yield cell
173
+	def borders(self, rect):
174
+		mx, my = len(rect)-1, len(rect[0])-1
175
+		for x, row in enumerate(rect):
176
+			for y, cell in enumerate(row):
177
+				if x in {0,mx} or y in {0,my}:
178
+					if cell != self.wall:
179
+						yield cell
167 180
 
168 181
 def dist( p1, p2 ):
169 182
 	x1,y1 = p1
170 183
 	x2,y2 = p2
171 184
 	return int( ( (x2-x1)**2+(y2-y1)**2 ) ** .5 )
185
+
186
+if __name__ == '__main__':
187
+	import random
188
+	width, height = 199,50
189
+	map = [ [ random.choice([0,0,0,0,0]) for _ in range(height) ] for __ in range(width) ]
190
+
191
+	import time
192
+	goals = [ (random.randrange(width), random.randrange(height)) for _ in range(1) ]
193
+	ot = time.time()
194
+	for _ in range(5):
195
+		print '\tinit'
196
+		t0 = time.time()
197
+		dj = DjikstraMap()
198
+		dj.set_goals(*goals)
199
+		dj.load_map(map)
200
+		dj.iter(10)
201
+		#while dj.cycle(): pass
202
+		t = time.time() - t0
203
+		print '\tdone', t, 'iters', dj.iters
204
+	print time.time() - ot
205
+	dj.visualize()
206
+
... ...
@@ -19,10 +19,12 @@ class Level(object):
19 19
 
20 20
 	def get_djikstra(self, x,y):
21 21
 		if (x,y) not in self.djikstra_cache:
22
-			dj = self.djikstra_cache[x,y] = djikstra.DjikstraMap(self.map.map.data)
22
+			print 'new (%s, %s)' % (x,y)
23
+			dj = self.djikstra_cache[x,y] = djikstra.DjikstraMap()
23 24
 			dj.set_goals( (x,y), weight=0)
25
+			dj.load_map(self.map.map.data)
24 26
 		dj = self.djikstra_cache[x,y]
25
-		dj.iter(3)
27
+		dj.iter(5)
26 28
 		return dj
27 29
 
28 30
 	def __init__(self, width, height, con, item_types=None, monster_types=None):
... ...
@@ -102,19 +104,11 @@ class Level(object):
102 104
 				wall = cell.block_sight
103 105
 				walkable = not cell.blocked
104 106
 
105
-				if (x,y) in self.clear_cells and not self.is_blocked(x,y):
106
-					libtcod.console_set_char(self.con, x,y, ord(' '))
107 107
 				if wall or walkable:
108 108
 					color = {
109 109
 						True: {True: self.color_light_wall, False: self.color_light_ground},
110 110
 						False: {True: self.color_dark_wall, False: self.color_dark_ground}
111 111
 					}[visible][wall]
112
-					if not self.is_blocked(x,y):
113
-						dj=self.get_djikstra(*self.player.pos)
114
-						dist = dj.get_cell( (x,y) )
115
-						if 0 < dist < 10:
116
-							libtcod.console_put_char(self.con, x,y, ord(str(dist)))
117
-							self.clear_cells.add((x,y))
118 112
 				elif not walkable:
119 113
 					color = libtcod.Color(100,100,200)
120 114
 
... ...
@@ -61,6 +61,7 @@ class Thief(BasicMonster):
61 61
 			self.drop(item)
62 62
 
63 63
 	def drop(self, item):
64
+		print 'drop'
64 65
 		item.x, item.y = self.owner.pos
65 66
 		self.level.add_object(item)
66 67
 		self.inventory.remove(item)
... ...
@@ -90,17 +91,19 @@ class DjikstraMonster(Monster):
90 91
 		pos = self.owner.x, self.owner.y
91 92
 
92 93
 		dx,dy = 0,0
94
+		player_room = self.level.player.get_room()
95
+
93 96
 		if self.level.is_visible(*pos):
94 97
 			if self.level.player.distance(*pos) < 2:
95 98
 				self.owner.fighter.attack(game_instance.player)
96 99
 			else:
97
-				dx, dy = self.owner.move_towards(*self.level.player.pos)
100
+				dx, dy = self.owner.get_step_towards(*self.level.player.pos)
98 101
 
99 102
 		#elif random.random() < .4:
100 103
 		#	dx,dy = self.dj.nav(*pos)
101 104
 
102
-		else:
103
-			dj = self.level.get_djikstra(*self.level.player.pos)
105
+		elif player_room is not None:
106
+			dj = self.level.get_djikstra(*player_room.center)
104 107
 			#print pos, '<---', self.level.player.distance(*pos)
105 108
 			x,y = pos
106 109
 			dx,dy = dj.nav(x,y)
... ...
@@ -248,7 +251,7 @@ class MonsterLoader(object):
248 251
 						hp=doc['hp'],
249 252
 						defense=doc['defense'],
250 253
 						power=doc['power'],
251
-						death_function=monster_death
254
+						death_function=death_func
252 255
 					),
253 256
 					ai=ai_class().load_data(cls_data),
254 257
 					level=level
... ...
@@ -17,7 +17,7 @@ class Object(object):
17 17
 
18 18
 		if level is not None:
19 19
 			level.add_object(self)
20
-			level.get_djikstra(x,y)
20
+			#level.get_djikstra(x,y)
21 21
 
22 22
 		self.level = level
23 23
 
... ...
@@ -47,7 +47,6 @@ class Object(object):
47 47
 		if not self.level.is_blocked(self.x+dx,self.y+dy):
48 48
 			self.x += dx
49 49
 			self.y += dy
50
-			self.level.get_djikstra(self.x, self.y)
51 50
 		else:
52 51
 			dx,dy = 0,0
53 52
 		return dx,dy
... ...
@@ -165,7 +164,10 @@ class Fighter(object):
165 164
 		if self.hp <= 0:
166 165
 			function = self.death_function
167 166
 			if function is not None:
168
-				function(self.owner)
167
+				try:
168
+					function(self.owner)
169
+				except TypeError:
170
+					function(self.owner.ai)
169 171
 
170 172
 	def attack(self, target):
171 173
 		damage = self.power - target.fighter.defense
... ...
@@ -209,7 +209,7 @@ class Player(Object):
209 209
 	@triggers_recompute
210 210
 	def move(self, dx, dy):
211 211
 		result = Object.move(self, dx,dy)
212
-		self.level.get_djikstra(*self.pos)
212
+		#self.level.get_djikstra(*self.pos)
213 213
 		return result
214 214
 
215 215
 	def move_or_attack(self, dx, dy):
... ...
@@ -231,5 +231,10 @@ class Player(Object):
231 231
 		self.x, self.y = self.level.map.map_entrance
232 232
 		return self
233 233
 
234
+	def get_room(self):
235
+		for room in self.level.map.gen.rooms:
236
+			if self.pos in room:
237
+				return room
238
+
234 239
 
235 240
 import game