1 | """ |
||
2 | |||
3 | Example:: |
||
4 | |||
5 | >>> import numpy as np |
||
6 | >>> import tcod.path |
||
7 | >>> dungeon = np.array( |
||
8 | ... [ |
||
9 | ... [1, 0, 1, 1, 1], |
||
10 | ... [1, 0, 1, 0, 1], |
||
11 | ... [1, 1, 1, 0, 1], |
||
12 | ... ], |
||
13 | ... dtype=np.int8, |
||
14 | ... ) |
||
15 | ... |
||
16 | >>> height, width = dungeon.shape |
||
17 | |||
18 | # Create a pathfinder from a numpy array. |
||
19 | # This is the recommended way to use the tcod.path module. |
||
20 | >>> astar = tcod.path.AStar(dungeon) |
||
21 | >>> print(astar.get_path(0, 0, 4, 2)) |
||
22 | [(0, 1), (1, 2), (2, 1), (3, 0), (4, 1), (4, 2)] |
||
23 | >>> astar.cost[0, 1] = 1 # You can access the map array via this attribute. |
||
24 | >>> print(astar.get_path(0, 0, 4, 2)) |
||
25 | [(1, 0), (2, 0), (3, 0), (4, 1), (4, 2)] |
||
26 | |||
27 | # Create a pathfinder from an edge_cost function. |
||
28 | # Calling Python functions from C is known to be very slow. |
||
29 | >>> def edge_cost(my_x, my_y, dest_x, dest_y): |
||
30 | ... return dungeon[dest_y, dest_x] |
||
31 | ... |
||
32 | >>> dijkstra = tcod.path.Dijkstra( |
||
33 | ... tcod.path.EdgeCostCallback(edge_cost, width, height), |
||
34 | ... ) |
||
35 | ... |
||
36 | >>> dijkstra.set_goal(0, 0) |
||
37 | >>> print(dijkstra.get_path(4, 2)) |
||
38 | [(1, 0), (2, 0), (3, 0), (4, 1), (4, 2)] |
||
39 | """ |
||
40 | |||
41 | from __future__ import absolute_import |
||
42 | |||
43 | import numpy as np |
||
0 ignored issues
–
show
|
|||
44 | |||
45 | from tcod.libtcod import lib, ffi |
||
46 | |||
47 | |||
48 | @ffi.def_extern() |
||
49 | def _pycall_path_old(x1, y1, x2, y2, handle): |
||
50 | """libtcodpy style callback, needs to preserve the old userData issue.""" |
||
51 | func, userData = ffi.from_handle(handle) |
||
52 | return func(x1, y1, x2, y2, userData) |
||
53 | |||
54 | |||
55 | @ffi.def_extern() |
||
56 | def _pycall_path_simple(x1, y1, x2, y2, handle): |
||
57 | """Does less and should run faster, just calls the handle function.""" |
||
58 | return ffi.from_handle(handle)(x1, y1, x2, y2) |
||
59 | |||
60 | |||
61 | @ffi.def_extern() |
||
62 | def _pycall_path_swap_src_dest(x1, y1, x2, y2, handle): |
||
63 | """A TDL function dest comes first to match up with a dest only call.""" |
||
64 | return ffi.from_handle(handle)(x2, y2, x1, y1) |
||
65 | |||
66 | |||
67 | @ffi.def_extern() |
||
68 | def _pycall_path_dest_only(x1, y1, x2, y2, handle): |
||
69 | """A TDL function which samples the dest coordinate only.""" |
||
70 | return ffi.from_handle(handle)(x2, y2) |
||
71 | |||
72 | def _get_pathcost_func(name): |
||
73 | """Return a properly cast PathCostArray callback.""" |
||
74 | return ffi.cast('TCOD_path_func_t', ffi.addressof(lib, name)) |
||
75 | |||
76 | |||
77 | class _EdgeCostFunc(object): |
||
78 | """ |
||
79 | Args: |
||
80 | userdata (Any): Custom userdata to send to the C call. |
||
81 | width (int): The maximum width of this callback. |
||
82 | height (int): The maximum height of this callback. |
||
83 | """ |
||
84 | _CALLBACK_P = lib._pycall_path_old |
||
85 | |||
86 | def __init__(self, userdata, width, height): |
||
87 | self._userdata = userdata |
||
88 | self.width = width |
||
89 | self.height = height |
||
90 | |||
91 | def get_tcod_path_ffi(self): |
||
92 | """ |
||
93 | |||
94 | Returns: |
||
95 | Tuple[CData, CData]: A cffi (callback, userdata) pair. |
||
96 | |||
97 | |||
98 | """ |
||
99 | return (self._CALLBACK_P, ffi.new_handle(self._userdata), |
||
100 | self.width, self.height) |
||
101 | |||
102 | def __repr__(self): |
||
103 | return '%s(%r, width=%r, height=%r)' % ( |
||
104 | self.__class__.__name__, |
||
105 | self._userdata, self.width, self.height, |
||
106 | ) |
||
107 | |||
108 | |||
109 | class EdgeCostCallback(_EdgeCostFunc): |
||
110 | """Calculate cost from an edge-cost callback. |
||
111 | |||
112 | Args: |
||
113 | callback (Callable[[int, int, int, int], float]): |
||
114 | A callback which can handle the following parameters: |
||
115 | `(source_x:int, source_y:int, dest_x:int, dest_y:int) -> float` |
||
116 | width (int): The maximum width of this callback. |
||
117 | height (int): The maximum height of this callback. |
||
118 | """ |
||
119 | _CALLBACK_P = lib._pycall_path_simple |
||
120 | |||
121 | def __init__(self, callback, width, height): |
||
122 | self.callback = callback |
||
123 | super(EdgeCostCallback, self).__init__(callback, width, height) |
||
124 | |||
125 | class NodeCostArray(np.ndarray): |
||
126 | """Calculate cost from a numpy array of nodes. |
||
127 | |||
128 | Args: |
||
129 | array (numpy.ndarray): A numpy array with the cost of each node. |
||
130 | """ |
||
131 | |||
132 | _C_ARRAY_CALLBACKS = { |
||
133 | np.float32: ('float*', _get_pathcost_func('PathCostArrayFloat32')), |
||
134 | np.bool_: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')), |
||
135 | np.int8: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')), |
||
136 | np.uint8: ('uint8_t*', _get_pathcost_func('PathCostArrayUInt8')), |
||
137 | np.int16: ('int16_t*', _get_pathcost_func('PathCostArrayInt16')), |
||
138 | np.uint16: ('uint16_t*', _get_pathcost_func('PathCostArrayUInt16')), |
||
139 | np.int32: ('int32_t*', _get_pathcost_func('PathCostArrayInt32')), |
||
140 | np.uint32: ('uint32_t*', _get_pathcost_func('PathCostArrayUInt32')), |
||
141 | } |
||
142 | |||
143 | def __new__(cls, array): |
||
144 | """Validate a numpy array and setup a C callback.""" |
||
145 | self = np.ascontiguousarray(array).view(cls) |
||
146 | return self |
||
147 | |||
148 | @property |
||
149 | def width(self): |
||
150 | return self.shape[1] |
||
151 | |||
152 | @property |
||
153 | def height(self): |
||
154 | return self.shape[0] |
||
155 | |||
156 | def __repr__(self): |
||
157 | return '%s(%r)' % (self.__class__.__name__, |
||
158 | repr(self.view(np.ndarray))) |
||
159 | |||
160 | def get_tcod_path_ffi(self): |
||
161 | if len(self.shape) != 2: |
||
162 | raise ValueError('Array must have a 2d shape, shape is %r' % |
||
163 | (self.shape,)) |
||
164 | if self.dtype.type not in self._C_ARRAY_CALLBACKS: |
||
165 | raise ValueError('dtype must be one of %r, dtype is %r' % |
||
166 | (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type)) |
||
167 | |||
168 | array_type, callback = \ |
||
169 | self._C_ARRAY_CALLBACKS[self.dtype.type] |
||
170 | userdata = ffi.new( |
||
171 | 'PathCostArray*', |
||
172 | (self.width, ffi.cast(array_type, self.ctypes.data)), |
||
173 | ) |
||
174 | return callback, userdata, self.width, self.height |
||
175 | |||
176 | |||
177 | class _PathFinder(object): |
||
178 | """A class sharing methods used by AStar and Dijkstra.""" |
||
179 | |||
180 | def __init__(self, cost, diagonal=1.41): |
||
181 | self.cost = cost |
||
182 | self.diagonal = diagonal |
||
183 | self._path_c = None |
||
184 | self._callback = self._userdata = None |
||
185 | |||
186 | if hasattr(self.cost, 'map_c'): |
||
187 | self.width, self.height = self.cost.width, self.cost.height |
||
188 | self._path_c = ffi.gc( |
||
189 | self._path_new_using_map(self.cost.map_c, diagonal), |
||
190 | self._path_delete, |
||
191 | ) |
||
192 | return |
||
193 | |||
194 | if not hasattr(self.cost, 'get_tcod_path_ffi'): |
||
195 | assert not callable(self.cost), \ |
||
196 | "Any callback alone is missing width & height information. " \ |
||
197 | "Wrap your callback in tcod.path.EdgeCostCallback" |
||
198 | self.cost = NodeCostArray(self.cost) |
||
199 | |||
200 | self._callback, self._userdata, self.width, self.height = \ |
||
201 | self.cost.get_tcod_path_ffi() |
||
202 | self._path_c = ffi.gc( |
||
203 | self._path_new_using_function( |
||
204 | self.width, |
||
205 | self.height, |
||
206 | self._callback, |
||
207 | self._userdata, |
||
208 | diagonal |
||
209 | ), |
||
210 | self._path_delete, |
||
211 | ) |
||
212 | |||
213 | def __repr__(self): |
||
214 | return '%s(cost=%r, diagonal=%r)' % (self.__class__.__name__, |
||
215 | self.cost, self.diagonal) |
||
216 | |||
217 | def __getstate__(self): |
||
218 | state = self.__dict__.copy() |
||
219 | del state['_path_c'] |
||
220 | del state['width'] |
||
221 | del state['height'] |
||
222 | del state['_callback'] |
||
223 | del state['_userdata'] |
||
224 | return state |
||
225 | |||
226 | def __setstate__(self, state): |
||
227 | self.__dict__.update(state) |
||
228 | self.__init__(self.cost, self.diagonal) |
||
229 | |||
230 | _path_new_using_map = lib.TCOD_path_new_using_map |
||
231 | _path_new_using_function = lib.TCOD_path_new_using_function |
||
232 | _path_delete = lib.TCOD_path_delete |
||
233 | |||
234 | |||
235 | class AStar(_PathFinder): |
||
236 | """ |
||
237 | Args: |
||
238 | cost (Union[tcod.map.Map, numpy.ndarray, Any]): |
||
239 | diagonal (float): Multiplier for diagonal movement. |
||
240 | A value of 0 will disable diagonal movement entirely. |
||
241 | """ |
||
242 | |||
243 | def get_path(self, start_x, start_y, goal_x, goal_y): |
||
244 | """Return a list of (x, y) steps to reach the goal point, if possible. |
||
245 | |||
246 | Args: |
||
247 | start_x (int): Starting X position. |
||
248 | start_y (int): Starting Y position. |
||
249 | goal_x (int): Destination X position. |
||
250 | goal_y (int): Destination Y position. |
||
251 | Returns: |
||
252 | List[Tuple[int, int]]: |
||
253 | A list of points, or an empty list if there is no valid path. |
||
254 | """ |
||
255 | lib.TCOD_path_compute(self._path_c, start_x, start_y, goal_x, goal_y) |
||
256 | path = [] |
||
257 | x = ffi.new('int[2]') |
||
258 | y = x + 1 |
||
259 | while lib.TCOD_path_walk(self._path_c, x, y, False): |
||
260 | path.append((x[0], y[0])) |
||
261 | return path |
||
262 | |||
263 | |||
264 | class Dijkstra(_PathFinder): |
||
265 | """ |
||
266 | Args: |
||
267 | cost (Union[tcod.map.Map, numpy.ndarray, Any]): |
||
268 | diagonal (float): Multiplier for diagonal movement. |
||
269 | A value of 0 will disable diagonal movement entirely. |
||
270 | """ |
||
271 | |||
272 | _path_new_using_map = lib.TCOD_dijkstra_new |
||
273 | _path_new_using_function = lib.TCOD_dijkstra_new_using_function |
||
274 | _path_delete = lib.TCOD_dijkstra_delete |
||
275 | |||
276 | def set_goal(self, x, y): |
||
277 | """Set the goal point and recompute the Dijkstra path-finder. |
||
278 | """ |
||
279 | lib.TCOD_dijkstra_compute(self._path_c, x, y) |
||
280 | |||
281 | def get_path(self, x, y): |
||
282 | """Return a list of (x, y) steps to reach the goal point, if possible. |
||
283 | """ |
||
284 | lib.TCOD_dijkstra_path_set(self._path_c, x, y) |
||
285 | path = [] |
||
286 | pointer_x = ffi.new('int[2]') |
||
287 | pointer_y = pointer_x + 1 |
||
288 | while lib.TCOD_dijkstra_path_walk(self._path_c, pointer_x, pointer_y): |
||
289 | path.append((pointer_x[0], pointer_y[0])) |
||
290 | return path |
||
291 |
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.