1 """
2 Rogue-like map utility's such as line-of-sight, field-of-view, and path-finding.
3
4 """
5
6 import ctypes
7 import itertools
8 import math
9
10 import untdl
11
12 from .__tcod import _lib, _PATHCALL
13
14 _FOVTYPES = {'BASIC': 0, 'DIAMOND': 1, 'SHADOW': 2, 'RESTRICTIVE': 12, 'PERMISSIVE': 11}
15
16
18 """Return a FOV from a string"""
19 old_fov = fov
20 fov = str(fov).upper()
21 if fov in _FOVTYPES:
22 return _FOVTYPES[fov]
23 if fov[:10] == 'PERMISSIVE' and fov[10].isdigit() and fov[10] != '9':
24 return 4 + int(fov[10])
25 raise untdl.TDLError('No such fov option as %s' % old_fov)
26
27
28
30 """A* pathfinder
31
32 Using this class requires a callback detailed in L{AStar.__init__}
33 """
34
35 __slots__ = ('_as_parameter_', '_callback', '__weakref__')
36
37 - def __init__(self, width, height, callback,
38 digital_cost=math.sqrt(2), advanced=False):
39 """Create an A* pathfinder using a callback.
40
41 Before crating this instance you should make one of two types of
42 callbacks:
43 - A function that returns the cost to move to (x, y)
44 or
45 - A function that returns the cost to move between
46 (destX, destY, sourceX, sourceY)
47 If path is blocked the function should return zero or None.
48 When using the second type of callback be sure to set advanced=True
49
50 @type width: int
51 @param width: width of the pathfinding area in tiles
52 @type height: int
53 @param height: height of the pathfinding area in tiles
54
55 @type callback: function
56 @param callback: A callback taking parameters depending on the setting
57 of 'advanced' and returning the cost of
58 movement for an open tile or zero for a
59 blocked tile.
60
61 @type digital_cost: float
62 @param digital_cost: Multiplier for diagonal movement.
63
64 Can be set to zero to disable diagonal movement
65 entirely.
66
67 @type advanced: boolean
68 @param advanced: A simple callback with 2 positional parameters may not
69 provide enough information. Setting this to True will
70 call the callback with 2 additional parameters giving
71 you both the destination and the source of movement.
72
73 When True the callback will need to accept
74 (destX, destY, sourceX, sourceY) as parameters.
75 Instead of just (destX, destY).
76
77 """
78 if not digital_cost:
79 digital_cost = 0.0
80 if advanced:
81 def new_callback(source_x, source_y, dest_x, dest_y, null):
82 path_cost = callback(dest_x, dest_y, source_x, source_y)
83 if path_cost:
84 return path_cost
85 return 0.0
86 else:
87 def new_callback(source_x, source_y, dest_x, dest_y, null):
88 path_cost = callback(dest_x, dest_y)
89 if path_cost:
90 return path_cost
91 return 0.0
92 self._callback = _PATHCALL(new_callback)
93 """A CFUNCTYPE callback to be kept in memory."""
94 self._as_parameter_ = _lib.TCOD_path_new_using_function(width, height,
95 self._callback, None, digital_cost)
96
98 _lib.TCOD_path_delete(self)
99
100 - def get_path(self, orig_x, orig_y, dest_x, dest_y):
101 """
102 Get the shortest path from origXY to destXY.
103
104 @rtype: [(x, y), ...]
105 @return: Returns a list walking the path from origXY to destXY.
106 This excludes the starting point and includes the destination.
107
108 If no path is found then an empty list is returned.
109 """
110 found = _lib.TCOD_path_compute(self, orig_x, orig_y, dest_x, dest_y)
111 if not found:
112 return []
113 x, y = ctypes.c_int(), ctypes.c_int()
114 xref, yref = ctypes.byref(x), ctypes.byref(y)
115 recalculate = ctypes.c_bool(True)
116 path = []
117 while _lib.TCOD_path_walk(self, xref, yref, recalculate):
118 path.append((x.value, y.value))
119 return path
120
121
122 -def quick_fov(x, y, callback, fov='PERMISSIVE', radius=7.5, light_walls=True, sphere=True):
123 """All field-of-view functionality in one call.
124
125 Before using this call be sure to make a function, lambda, or method that takes 2
126 positional parameters and returns True if light can pass through the tile or False
127 for light-blocking tiles and for indexes that are out of bounds of the
128 dungeon.
129
130 This function is 'quick' as in no hassle but can quickly become a very slow
131 function call if a large radius is used or the callback provided itself
132 isn't optimized.
133
134 Always check if the index is in bounds both in the callback and in the
135 returned values. These values can go into the negatives as well.
136
137 @type x: int
138 @param x: x center of the field-of-view
139 @type y: int
140 @param y: y center of the field-of-view
141 @type callback: function
142 @param callback: This should be a function that takes two positional arguments x,y
143 and returns True if the tile at that position is transparent
144 or False if the tile blocks light or is out of bounds.
145 @type fov: string
146 @param fov: The type of field-of-view to be used. Available types are:
147
148 'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
149 'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
150 @type radius: float
151 @param radius: Radius of the field-of-view.
152
153 When sphere is True a floating point can be used to fine-tune
154 the range. Otherwise the radius is just rounded up.
155
156 Be careful as a large radius has an exponential affect on
157 how long this function takes.
158 @type light_walls: boolean
159 @param light_walls: Include or exclude wall tiles in the field-of-view.
160 @type sphere: boolean
161 @param sphere: True for a spherical field-of-view. False for a square one.
162
163 @rtype: set((x, y), ...)
164 @return: Returns a set of (x, y) points that are within the field-of-view.
165 """
166 true_radius = radius
167 radius = int(math.ceil(radius))
168 map_size = radius * 2 + 1
169 fov = _get_fov_type(fov)
170
171 set_prop = _lib.TCOD_map_set_properties
172 in_fov = _lib.TCOD_map_is_in_fov
173
174
175 cfalse = ctypes.c_bool(False)
176 tcod_map = _lib.TCOD_map_new(map_size, map_size)
177 try:
178
179 for (x_, cX), (y_, cY) in itertools.product(((i, ctypes.c_int(i)) for i in range(map_size)),
180 ((i, ctypes.c_int(i)) for i in range(map_size))):
181 pos = (x_ + x - radius,
182 y_ + y - radius)
183 transparent = bool(callback(*pos))
184 set_prop(tcod_map, cX, cY, transparent, cfalse)
185
186
187 _lib.TCOD_map_compute_fov(tcod_map, radius, radius, radius, light_walls, fov)
188 touched = set()
189 for (x_, cX), (y_, cY) in itertools.product(((i, ctypes.c_int(i)) for i in range(map_size)),
190 ((i, ctypes.c_int(i)) for i in range(map_size))):
191 if sphere and math.hypot(x_ - radius, y_ - radius) > true_radius:
192 continue
193 if in_fov(tcod_map, cX, cY):
194 touched.add((x_ + x - radius, y_ + y - radius))
195 finally:
196 _lib.TCOD_map_delete(tcod_map)
197 return touched
198
199
201 """
202 Iterate over points in a bresenham line.
203
204 Implementation hastily copied from RogueBasin.
205
206 @return: Returns an iterator of (x, y) points.
207 """
208 points = []
209 is_steep = abs(y2 - y1) > abs(x2 - x1)
210 if is_steep:
211 x1, y1 = y1, x1
212 x2, y2 = y2, x2
213 rev = False
214 if x1 > x2:
215 x1, x2 = x2, x1
216 y1, y2 = y2, y1
217 rev = True
218 dx = x2 - x1
219 dy = abs(y2 - y1)
220 error = int(dx / 2)
221 y = y1
222 if y1 < y2:
223 y_step = 1
224 else:
225 y_step = -1
226 for x in range(x1, x2 + 1):
227 if is_steep:
228 points.append((y, x))
229 else:
230 points.append((x, y))
231 error -= dy
232 if error < 0:
233 y += y_step
234 error += dx
235
236 if rev:
237 points.reverse()
238 return iter(points)
239
240
241 __all__ = ['AStar', 'quick_fov']
242