Completed
Push — master ( 64314c...9b9cff )
by Kyle
58s
created

_EdgeCostFunc.get_cffi_callback()   A

Complexity

Conditions 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 11
rs 9.4285
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
Configuration introduced by
The import numpy could not be resolved.

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.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

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.

Loading history...
44
45
import tcod.map
0 ignored issues
show
Unused Code introduced by
The import tcod.map seems to be unused.
Loading history...
46
47
from tcod.libtcod import lib, ffi
48
49
50
@ffi.def_extern()
51
def _pycall_path_old(x1, y1, x2, y2, handle):
52
    """libtcodpy style callback, needs to preserve the old userData issue."""
53
    func, userData = ffi.from_handle(handle)
54
    return func(x1, y1, x2, y2, userData)
55
56
57
@ffi.def_extern()
58
def _pycall_path_simple(x1, y1, x2, y2, handle):
59
    """Does less and should run faster, just calls the handle function."""
60
    return ffi.from_handle(handle)(x1, y1, x2, y2)
61
62
63
@ffi.def_extern()
64
def _pycall_path_swap_src_dest(x1, y1, x2, y2, handle):
65
    """A TDL function dest comes first to match up with a dest only call."""
66
    return ffi.from_handle(handle)(x2, y2, x1, y1)
67
68
69
@ffi.def_extern()
70
def _pycall_path_dest_only(x1, y1, x2, y2, handle):
0 ignored issues
show
Unused Code introduced by
The argument y1 seems to be unused.
Loading history...
Unused Code introduced by
The argument x1 seems to be unused.
Loading history...
71
    """A TDL function which samples the dest coordinate only."""
72
    return ffi.from_handle(handle)(x2, y2)
73
74
def _get_pathcost_func(name):
75
    """Return a properly cast PathCostArray callback."""
76
    return ffi.cast('TCOD_path_func_t', ffi.addressof(lib, name))
77
78
79
class _EdgeCostFunc(object):
0 ignored issues
show
Coding Style introduced by
This class should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
80
    _CALLBACK_P = lib._pycall_path_old
81
82
    def __init__(self, userdata, width, height):
83
        """
84
        Args:
85
            userdata (Any): Custom userdata to send to the C call.
86
            width (int): The maximum width of this callback.
87
            height (int): The maximum height of this callback.
88
        """
89
        self._userdata = userdata
90
        self.width = width
91
        self.height = height
92
        self._userdata_p = None
93
94
    def get_cffi_callback(self):
95
        """
96
97
        Returns:
98
            Tuple[CData, CData]: A cffi (callback, userdata) pair.
99
100
101
        """
102
        if self._userdata_p is None:
103
            self._userdata_p = ffi.new_handle(self._userdata)
104
        return self._CALLBACK_P, self._userdata_p
105
106
    def __repr__(self):
107
        return '%s(%r, width=%r, height=%r)' % (
108
            self.__class__.__name__,
109
            self._userdata, self.width, self.height,
110
            )
111
112
    def __getstate__(self):
113
        state = self.__dict__.copy()
114
        # _userdata_p can't be pickled.
115
        state['_userdata_p'] = None
116
        return state
117
118
119
class EdgeCostCallback(_EdgeCostFunc):
0 ignored issues
show
Coding Style introduced by
This class should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
120
    _CALLBACK_P = lib._pycall_path_simple
121
122
    def __init__(self, callback, width, height):
123
        """
124
        Args:
125
            callback (Callable[[int, int, int, int], float]):
126
                A callback which can handle the following parameters:
127
                `(source_x:int, source_y:int, dest_x:int, dest_y:int) -> float`
128
            width (int): The maximum width of this callback.
129
            height (int): The maximum height of this callback.
130
        """
131
        self.callback = callback
132
        super(EdgeCostCallback, self).__init__(callback, width, height)
133
134
class NodeCostArray(np.ndarray):
0 ignored issues
show
Coding Style introduced by
This class should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
Coding Style introduced by
This class has no __init__ method.
Loading history...
135
136
    _C_ARRAY_CALLBACKS = {
137
        np.float32: ('float*', _get_pathcost_func('PathCostArrayFloat32')),
138
        np.bool_: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')),
139
        np.int8: ('int8_t*', _get_pathcost_func('PathCostArrayInt8')),
140
        np.uint8: ('uint8_t*', _get_pathcost_func('PathCostArrayUInt8')),
141
        np.int16: ('int16_t*', _get_pathcost_func('PathCostArrayInt16')),
142
        np.uint16: ('uint16_t*', _get_pathcost_func('PathCostArrayUInt16')),
143
        np.int32: ('int32_t*', _get_pathcost_func('PathCostArrayInt32')),
144
        np.uint32: ('uint32_t*', _get_pathcost_func('PathCostArrayUInt32')),
145
        }
146
147
    def __new__(cls, array):
148
        """Validate a numpy array and setup a C callback."""
149
        self = np.ascontiguousarray(array).view(cls)
150
        return self
151
152
    @property
153
    def width(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
154
        return self.shape[1]
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named shape.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
155
156
    @property
157
    def height(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
158
        return self.shape[0]
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named shape.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
159
160
    def __repr__(self):
161
        return '%s(%r)' % (self.__class__.__name__,
162
                           repr(self.view(np.ndarray)))
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named view.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
163
164
    def get_cffi_callback(self):
0 ignored issues
show
Coding Style introduced by
This method should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
165
        if hasattr(self, '_callback_p'):
166
            return self._callback_p, self._userdata_p
0 ignored issues
show
Bug introduced by
The member _callback_p does not seem to be defined here; its definition is on line 175.
Loading history...
Bug introduced by
The member _userdata_p does not seem to be defined here; its definition is on line 177.
Loading history...
167
168
        if len(self.shape) != 2:
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named shape.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
169
            raise ValueError('Array must have a 2d shape, shape is %r' %
170
                             (self.shape,))
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named shape.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
171
        if self.dtype.type not in self._C_ARRAY_CALLBACKS:
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named dtype.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
172
            raise ValueError('dtype must be one of %r, dtype is %r' %
173
                             (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type))
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named dtype.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
174
175
        array_type, self._callback_p = \
0 ignored issues
show
Coding Style introduced by
The attribute _callback_p was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
176
            self._C_ARRAY_CALLBACKS[self.dtype.type]
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named dtype.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
177
        self._userdata_p = ffi.new(
0 ignored issues
show
Coding Style introduced by
The attribute _userdata_p was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
178
            'PathCostArray*',
179
            (self.width, ffi.cast(array_type, self.ctypes.data)),
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named ctypes.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
180
        )
181
        return self._callback_p, self._userdata_p
182
183
184
class _PathFinder(object):
185
    """A class sharing methods used by AStar and Dijkstra."""
186
187
    def __init__(self, cost, diagonal=1.41):
188
        self.cost = cost
189
        self.diagonal = diagonal
190
        self._path_c = None
191
192
        if hasattr(self.cost, 'map_c'):
193
            self._path_c = ffi.gc(
194
                self._path_new_using_map(self.cost.map_c, diagonal),
0 ignored issues
show
Bug introduced by
The Instance of NodeCostArray does not seem to have a member named map_c.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
195
                self._path_delete,
196
                )
197
            return
198
199
        if not hasattr(self.cost, 'get_cffi_callback'):
200
            assert not callable(self.cost), \
201
                "Any callback alone is missing width&height information."
202
            self.cost = NodeCostArray(self.cost)
203
204
        callback, userdata = self.cost.get_cffi_callback()
205
        self._path_c = ffi.gc(
206
            self._path_new_using_function(
207
                self.cost.width,
208
                self.cost.height,
209
                callback,
210
                userdata,
211
                diagonal
212
                ),
213
            self._path_delete,
214
            )
215
216
    def __repr__(self):
217
        return '%s(cost=%r, diagonal=%r)' % (self.__class__.__name__,
218
                                             self.cost, self.diagonal)
219
220
    def __getstate__(self):
221
        state = self.__dict__.copy()
222
        del state['_path_c']
223
        return state
224
225
    def __setstate__(self, state):
226
        self.__dict__.update(state)
227
        self.__init__(self.cost, self.diagonal)
228
229
    _path_new_using_map = lib.TCOD_path_new_using_map
230
    _path_new_using_function = lib.TCOD_path_new_using_function
231
    _path_delete = lib.TCOD_path_delete
232
233
234
class AStar(_PathFinder):
235
    """
236
    .. versionadded:: 2.0
237
238
    .. versionchanged:: 3.0
239
            Removed width and height parameters.
240
241
            Callbacks must be wrapped in an :any:`tcod.path.EdgeCostCallback`
242
            instance or similar.
243
244
    Args:
245
        cost (Union[tcod.map.Map, numpy.ndarray, Any]):
246
        diagonal (float): Multiplier for diagonal movement.
247
            A value of 0 will disable diagonal movement entirely.
248
    """
249
250
    def get_path(self, start_x, start_y, goal_x, goal_y):
251
        """Return a list of (x, y) steps to reach the goal point, if possible.
252
253
        Args:
254
            start_x (int): Starting X position.
255
            start_y (int): Starting Y position.
256
            goal_x (int): Destination X position.
257
            goal_y (int): Destination Y position.
258
        Returns:
259
            List[Tuple[int, int]]:
260
                A list of points, or an empty list if there is no valid path.
261
        """
262
        lib.TCOD_path_compute(self._path_c, start_x, start_y, goal_x, goal_y)
263
        path = []
264
        x = ffi.new('int[2]')
265
        y = x + 1
266
        while lib.TCOD_path_walk(self._path_c, x, y, False):
267
            path.append((x[0], y[0]))
268
        return path
269
270
271
class Dijkstra(_PathFinder):
272
    """
273
    .. versionadded:: 2.0
274
275
    .. versionchanged:: 3.0
276
            Removed width and height parameters.
277
278
            Callbacks must be wrapped in an :any:`tcod.path.EdgeCostCallback`
279
            instance or similar.
280
281
    Args:
282
        cost (Union[tcod.map.Map, numpy.ndarray, Any]):
283
        diagonal (float): Multiplier for diagonal movement.
284
            A value of 0 will disable diagonal movement entirely.
285
    """
286
287
    _path_new_using_map = lib.TCOD_dijkstra_new
288
    _path_new_using_function = lib.TCOD_dijkstra_new_using_function
289
    _path_delete = lib.TCOD_dijkstra_delete
290
291
    def set_goal(self, x, y):
292
        """Set the goal point and recompute the Dijkstra path-finder.
293
        """
294
        lib.TCOD_dijkstra_compute(self._path_c, x, y)
295
296
    def get_path(self, x, y):
297
        """Return a list of (x, y) steps to reach the goal point, if possible.
298
        """
299
        lib.TCOD_dijkstra_path_set(self._path_c, x, y)
300
        path = []
301
        pointer_x = ffi.new('int[2]')
302
        pointer_y = pointer_x + 1
303
        while lib.TCOD_dijkstra_path_walk(self._path_c, pointer_x, pointer_y):
304
            path.append((pointer_x[0], pointer_y[0]))
305
        return path
306