Skip to content

Commit f8df193

Browse files
author
lsampras
committed
Merge branch 'master' into pheromones
2 parents cb0d60e + 922598f commit f8df193

9 files changed

+391
-31
lines changed

game_map.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
class Map:
2+
3+
def __init__(self, game_map):
4+
self.static = game_map
5+
self.active = []
6+
self.mock = []
7+
self.special = {}
8+
9+
def is_valid_point(self, pos):
10+
return self.static[pos.y][pos.x]
11+
12+
def valid_next_pos(self, cur_unit):
13+
return [pos for pos in cur_unit.next_pos() if self.is_valid_point(pos)]
14+
15+
def visible_units(self, cur_unit):
16+
return [unit for unit in self.active if cur_unit.can_see(unit)]
17+
18+
def _poten(self, next_pos, actors):
19+
poten = [0] * len(next_pos)
20+
21+
for i, pos in enumerate(next_pos):
22+
for actor in actors:
23+
poten[i] += actor.poten_at(pos)
24+
25+
return poten
26+
27+
def mock_poten(self, next_pos):
28+
return self._poten(next_pos, self.mock)
29+
30+
def active_poten(self, cur_unit, next_pos):
31+
active_units = self.visible_units(cur_unit)
32+
return self._poten(next_pos, active_units)
33+
34+
def next_pos_potential(self, cur_unit, next_pos):
35+
mock_poten = self.mock_poten(next_pos)
36+
active_poten = self.active_poten(cur_unit, next_pos)
37+
# total_poten = [max(a, b) for a, b in zip(mock_poten, total_poten)]
38+
total_poten = [a + b for a, b in zip(mock_poten, active_poten)]
39+
return [(score, pos) for score, pos in zip(total_poten, next_pos)]

main.py

+29-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from timeit import default_timer as timer
2-
31
import map_generator
2+
from game_map import Map
3+
from mock_object import PathObject, WallObject
44
from movement_cost import linear_cost
55
from moves import adjacent_linear
66
from path_finding import PathFinder
77
from point import Point
8+
from unit import SCOUT
89

910
map_data = None
1011

@@ -16,22 +17,32 @@ def is_valid_pos(cur_pos):
1617

1718
if __name__ == "__main__":
1819
map_data = map_generator.generate_map(100, 100, seed=25, obstacle_density=0.35)
19-
start = Point(59, 45)
20-
mid = Point(0, 0)
21-
end = Point(86, 40)
22-
waypoints = [start, mid, end]
20+
start = Point(35, 42)
21+
block = Point(39, 42)
22+
end = Point(41, 42)
2323
moves = adjacent_linear()
2424
cost_func = linear_cost()
25-
start = timer()
2625
path_finder = PathFinder(linear_cost(), linear_cost(), is_valid_pos)
27-
path, store = path_finder.find_path_waypoints(moves, waypoints, return_store=True)
28-
print(timer() - start)
29-
map_generator.view_path(map_data, path, store)
30-
# start = timer()
31-
# potn = potential.manhattan(map_data)
32-
# print(timer() - start)
33-
# potential.view_potential(potn)
34-
# start = timer()
35-
# terrainer = TerrainAnalyzer(map_data)
36-
# print(timer() - start)
37-
# terrainer.view_terrain(True)
26+
path, store = path_finder.find_path(moves, start, end, return_store=True)
27+
game_map = Map(map_data)
28+
wall = WallObject([block])
29+
taken_path = []
30+
cur_unit = SCOUT.copy(start)
31+
while True:
32+
next_step = path[0]
33+
if next_step == block:
34+
break
35+
else:
36+
cur_unit.cur_pos = next_step
37+
del path[0]
38+
taken_path.append(next_step)
39+
40+
while path:
41+
vis_path = [pos for pos in path if cur_unit.can_see_point(pos)]
42+
game_map.mock = [PathObject(vis_path), wall]
43+
best_step = path_finder.best_potential_step(game_map, cur_unit)
44+
taken_path.append(best_step)
45+
cur_unit.cur_pos = best_step
46+
del path[0]
47+
48+
map_generator.view_map(map_data, None, [taken_path], [block])

map_generator.py

+57-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,54 @@
11
from helper import get_x_and_y_from_store
22
from helper import get_x_and_y_from_path
3+
from point import Point
34

45
import matplotlib.pyplot as plt
6+
import matplotlib.cm as cm
57
import numpy as np
68
import perlin
79

10+
def view_map(map_data, stores=None, paths=None, points=None, grid=True):
11+
"""
12+
View map with entities like paths, stores, and points. Each
13+
entity argument is a list and the z-order (visibility) increases
14+
with index in the list. Among entities z-order varies as
15+
stores < paths < points.
816
9-
def view_map(map_data, grid=True):
10-
"""View map
11-
a) map_data: Boolean numpy array. True for passable, False for impassable.
12-
b) grid: Display grid. Defaults to True.
13-
c) color code: yellow for passable(1), purple for impassable(0).
14-
d) coordinates: click anywhere on the map to view coordinates of that point.
17+
The map shows passable as yellow and impassable as purple. Each
18+
entity list cycles through an indepedent color map, i.e. each
19+
path in paths will have a different color. All points are
20+
displayed in black color.
21+
22+
Args:
23+
map_data: Boolean numpy array. True for passable, False
24+
for impassable.
25+
coordinates: click anywhere on the map to view coordinates
26+
of that point.
27+
paths: List of paths to be shown.
28+
stores: Point stores to be shown.
29+
points: Special points to be displayed.
30+
grid: Display grid. Defaults to True.
1531
"""
32+
33+
if stores:
34+
colors = cm.autumn(np.linspace(0, 1, len(stores)))
35+
for c, store in zip(colors, stores):
36+
x, y = get_x_and_y_from_store(store)
37+
plt.scatter(x, y, color=c)
38+
39+
if paths:
40+
colors = cm.winter(np.linspace(0, 1, len(paths)))
41+
for c, path in zip(colors, paths):
42+
start, end = path[0], path[-1]
43+
x, y = get_x_and_y_from_path(path)
44+
plt.scatter(x, y, color=c)
45+
plt.plot(start.x, start.y, marker='x')
46+
plt.plot(end.x, end.y, marker='x')
47+
48+
if points:
49+
for point in points:
50+
plt.plot(point.x, point.y, color='black', marker='o')
51+
1652
m, n = map_data.shape
1753
plt.imshow(map_data)
1854
plt.xticks(np.arange(0.5, n, 1.0), [])
@@ -76,3 +112,18 @@ def mouse_move(event):
76112
x, y = int(round(event.xdata)), int(round(event.ydata))
77113
print(x, y)
78114
return
115+
116+
117+
if __name__ == "__main__":
118+
map_data = generate_map(100, 100, 0.35, 0.1, 25)
119+
offsets = []
120+
for i in range(3):
121+
for j in range(3):
122+
offsets.append(Point(i, j))
123+
124+
store_1 = {Point(41, 25) + offset: True for offset in offsets}
125+
store_2 = {Point(42, 26) + offset: True for offset in offsets}
126+
path = [Point(41, 25), Point(41, 26), Point(41, 27), Point(42, 27), Point(43, 27)]
127+
point = Point(42, 37)
128+
view_map(map_data, [store_1, store_2], [path], [point])
129+

mock_object.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import math
2+
3+
4+
class PathObject:
5+
"""
6+
Path points attract a point with degrading field upto
7+
a certain cutoff. The field strength increases with
8+
increasing index along the path.
9+
"""
10+
11+
def __init__(self, points):
12+
self.points = points
13+
14+
def poten_at(self, pos, slope=-2, cutoff=4):
15+
"""
16+
Calculate potential at point pos
17+
18+
Args:
19+
pos: point to calculate potential at
20+
slope: slope of degrading field
21+
cutoff: maximum range of affect for field
22+
23+
Returns:
24+
value: minimum potential value possible
25+
"""
26+
27+
def score(path_point, i, pos):
28+
dist = pos.dist(path_point)
29+
if dist == 0:
30+
return -math.inf
31+
else:
32+
return (i + 1) * ((1 / dist - 1 / cutoff) * (slope - i))
33+
34+
scores = [score(path_point, i, pos) for i, path_point in enumerate(self.points)]
35+
return min(scores)
36+
37+
38+
class WallObject:
39+
"""
40+
Wall objects behave as inert tiles, not allowing
41+
any unit to step on them while not affecting any other tile
42+
"""
43+
44+
def __init__(self, points):
45+
self.points = points
46+
47+
def poten_at(self, pos):
48+
"""
49+
Inert function stepping on wall in not possible. No other
50+
point is affected.
51+
52+
Args:
53+
pos: point to calculate potential at
54+
55+
Returns:
56+
value: inf or 0
57+
"""
58+
if pos in self.points:
59+
return math.inf
60+
else:
61+
return 0

movement_cost.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import random
2+
23
import math
34

45

@@ -107,4 +108,3 @@ def cost(start, end):
107108
return h_func(start, end) * random.normalvariate(mu, sigma)
108109

109110
return cost
110-

path_finding.py

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import functools
2+
import itertools
3+
14
import helper
25
import priority_queue
36

4-
import itertools
5-
import functools
6-
77

88
class PathFinder:
99
def __init__(self, movement_cost, heuristic_cost, is_valid_move):
@@ -194,19 +194,43 @@ def total_cost(cur_pos, next_pos, end):
194194

195195
return {}
196196

197-
def best_potential_step(self, cur_pos, path=None, range=None):
197+
def best_potential_step(self, game_map, cur_unit):
198198
"""
199199
Finds the point with the best potential score for the next step.
200200
If path and range is given, finds the best potential score point
201201
within the range of the next step prescribed by path.
202202
203203
Args:
204-
cur_pos Point: current position
204+
cur_pos Point: current unit
205205
path List(Point): prescribed path from source to destination
206206
range int: cutoff range from points along the path
207207
208208
Return:
209209
Point: best step
210210
"""
211-
return None
211+
next_pos = game_map.valid_next_pos(cur_unit)
212+
potential_values = game_map.next_pos_potential(cur_unit, next_pos)
213+
best_state = min(potential_values)
214+
return best_state[1]
212215

216+
def reconstruct_path(self, cur_pos, path):
217+
"""
218+
TODO
219+
Reconstructs a new path from current position,
220+
that joins the given path at some point
221+
222+
Args:
223+
cur_pos: current position of unit
224+
path: previous path from which unit has deviated
225+
226+
Return:
227+
New path
228+
"""
229+
# check if path is None and return None
230+
# Find position of intersection with path,
231+
# simple case is head of path
232+
# complex case can be to analyze sensitivity of path points
233+
# and set intersection at most sensitive point
234+
# calculate path from cur_pos to intersection
235+
# return concatenated path
236+
return None

point.py

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import math
2+
13
class Point:
24
__slots__ = 'x', 'y'
35

@@ -32,6 +34,19 @@ def __lt__(self, other):
3234
else:
3335
return False
3436

37+
def __str__(self):
38+
return "({}, {})".format(self.x, self.y)
39+
40+
def __repr__(self):
41+
return "Point({}, {})".format(self.x, self.y)
42+
3543
def __hash__(self):
3644
return hash((self.x, self.y))
3745

46+
def dist(self, other, squared=True):
47+
dist_squared = (self.x - other.x)**2 + (self.y - other.y)**2
48+
if squared:
49+
return dist_squared
50+
else:
51+
return math.sqrt(dist_squared)
52+

0 commit comments

Comments
 (0)