Completed
Push — master ( ec6dcf...05562e )
by Kyle
49s
created

tdl/map.py (1 issue)

Severity
1
"""
2
    Rogue-like map utilitys such as line-of-sight, field-of-view, and path-finding.
3
4
    .. deprecated:: 3.2
5
        The features provided here are better realized in the
6
        :any:`tcod.map` and :any:`tcod.path` modules.
7
"""
8
9
from __future__ import absolute_import
10
11
import itertools as _itertools
12
import math as _math
13
14
import numpy as np
15
16
from tcod import ffi as _ffi
0 ignored issues
show
Unused ffi imported from tcod as _ffi
Loading history...
17
from tcod import lib as _lib
18
from tcod import ffi, lib
19
20
import tcod.map
21
import tcod.path
22
import tdl as _tdl
23
from . import style as _style
24
25
_FOVTYPES = {'BASIC' : 0, 'DIAMOND': 1, 'SHADOW': 2, 'RESTRICTIVE': 12,
26
             'PERMISSIVE': 11}
27
28
def _get_fov_type(fov):
29
    "Return a FOV from a string"
30
    oldFOV = fov
31
    fov = str(fov).upper()
32
    if fov in _FOVTYPES:
33
        return _FOVTYPES[fov]
34
    if fov[:10] == 'PERMISSIVE' and fov[10].isdigit() and fov[10] != '9':
35
        return 4 + int(fov[10])
36
    raise _tdl.TDLError('No such fov option as %s' % oldFOV)
37
38
class Map(tcod.map.Map):
39
    """Field-of-view and path-finding on stored data.
40
41
    .. versionchanged:: 4.1
42
        `transparent`, `walkable`, and `fov` are now numpy boolean arrays.
43
44
    .. versionchanged:: 4.3
45
        Added `order` parameter.
46
47
    .. deprecated:: 3.2
48
        :any:`tcod.map.Map` should be used instead.
49
50
    Set map conditions with the walkable and transparency attributes, this
51
    object can be iterated and checked for containment similar to consoles.
52
53
    For example, you can set all tiles and transparent and walkable with the
54
    following code:
55
56
    Example:
57
        >>> import tdl.map
58
        >>> map_ = tdl.map.Map(80, 60)
59
        >>> map_.transparent[:] = True
60
        >>> map_.walkable[:] = True
61
62
    Attributes:
63
        transparent: Map transparency
64
65
            Access this attribute with ``map.transparent[x,y]``
66
67
            Set to True to allow field-of-view rays, False will
68
            block field-of-view.
69
70
            Transparent tiles only affect field-of-view.
71
        walkable: Map accessibility
72
73
            Access this attribute with ``map.walkable[x,y]``
74
75
            Set to True to allow path-finding through that tile,
76
            False will block passage to that tile.
77
78
            Walkable tiles only affect path-finding.
79
80
        fov: Map tiles touched by a field-of-view computation.
81
82
            Access this attribute with ``map.fov[x,y]``
83
84
            Is True if a the tile is if view, otherwise False.
85
86
            You can set to this attribute if you want, but you'll typically
87
            be using it to read the field-of-view of a :any:`compute_fov` call.
88
    """
89
90
    def __init__(self, width, height, order='F'):
91
        super(Map, self).__init__(width, height, order)
92
93
    def compute_fov(self, x, y, fov='PERMISSIVE', radius=None,
94
                    light_walls=True, sphere=True, cumulative=False):
95
        """Compute the field-of-view of this Map and return an iterator of the
96
        points touched.
97
98
        Args:
99
            x (int): Point of view, x-coordinate.
100
            y (int): Point of view, y-coordinate.
101
            fov (Text): The type of field-of-view to be used.
102
103
                Available types are:
104
                'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
105
                'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
106
            radius (Optional[int]): Maximum view distance from the point of
107
                view.
108
109
                A value of 0 will give an infinite distance.
110
            light_walls (bool): Light up walls, or only the floor.
111
            sphere (bool): If True the lit area will be round instead of
112
                square.
113
            cumulative (bool): If True the lit cells will accumulate instead
114
                of being cleared before the computation.
115
116
        Returns:
117
            Iterator[Tuple[int, int]]: An iterator of (x, y) points of tiles
118
                touched by the field-of-view.
119
        """
120
        # refresh cdata
121
        if radius is None: # infinite radius
122
            radius = 0
123
        if cumulative:
124
            fov_copy = self.fov.copy()
125
        lib.TCOD_map_compute_fov(
126
            self.map_c, x, y, radius, light_walls, _get_fov_type(fov))
127
        if cumulative:
128
            self.fov[:] |= fov_copy
129
        return zip(*np.where(self.fov))
130
131
132
    def compute_path(self, start_x, start_y, dest_x, dest_y,
133
                     diagonal_cost=_math.sqrt(2)):
134
        """Get the shortest path between two points.
135
136
        Args:
137
            start_x (int): Starting x-position.
138
            start_y (int): Starting y-position.
139
            dest_x (int): Destination x-position.
140
            dest_y (int): Destination y-position.
141
            diagonal_cost (float): Multiplier for diagonal movement.
142
143
                Can be set to zero to disable diagonal movement entirely.
144
145
        Returns:
146
            List[Tuple[int, int]]: The shortest list of points to the
147
                destination position from the starting position.
148
149
            The start point is not included in this list.
150
        """
151
        return tcod.path.AStar(self, diagonal_cost).get_path(start_x, start_y,
152
                                                             dest_x, dest_y)
153
154
    def __iter__(self):
155
        return _itertools.product(range(self.width), range(self.height))
156
157
    def __contains__(self, position):
158
        x, y = position
159
        return (0 <= x < self.width) and (0 <= y < self.height)
160
161
162
163
class AStar(tcod.path.AStar):
164
    """An A* pathfinder using a callback.
165
166
    .. deprecated:: 3.2
167
        See :any:`tcod.path`.
168
169
    Before crating this instance you should make one of two types of
170
    callbacks:
171
172
    - A function that returns the cost to move to (x, y)
173
    - A function that returns the cost to move between
174
      (destX, destY, sourceX, sourceY)
175
176
    If path is blocked the function should return zero or None.
177
    When using the second type of callback be sure to set advanced=True
178
179
    Args:
180
        width (int): Width of the pathfinding area (in tiles.)
181
        height (int): Height of the pathfinding area (in tiles.)
182
        callback (Union[Callable[[int, int], float],
183
                        Callable[[int, int, int, int], float]]): A callback
184
            returning the cost of a tile or edge.
185
186
            A callback taking parameters depending on the setting
187
            of 'advanced' and returning the cost of
188
            movement for an open tile or zero for a
189
            blocked tile.
190
        diagnalCost (float): Multiplier for diagonal movement.
191
192
            Can be set to zero to disable diagonal movement entirely.
193
        advanced (bool): Give 2 additional parameters to the callback.
194
195
            A simple callback with 2 positional parameters may not
196
            provide enough information.  Setting this to True will
197
            call the callback with 2 additional parameters giving
198
            you both the destination and the source of movement.
199
200
            When True the callback will need to accept
201
            (destX, destY, sourceX, sourceY) as parameters.
202
            Instead of just (destX, destY).
203
    """
204
205
    class __DeprecatedEdgeCost(tcod.path.EdgeCostCallback):
206
        _CALLBACK_P = lib._pycall_path_swap_src_dest
207
208
    class __DeprecatedNodeCost(tcod.path.EdgeCostCallback):
209
        _CALLBACK_P = lib._pycall_path_dest_only
210
211
    def __init__(self, width, height, callback,
212
                 diagnalCost=_math.sqrt(2), advanced=False):
213
        if advanced:
214
            cost = self.__DeprecatedEdgeCost(callback, width, height)
215
        else:
216
            cost = self.__DeprecatedNodeCost(callback, width, height)
217
        super(AStar, self).__init__(cost, diagnalCost or 0.0)
218
219
    def get_path(self, origX, origY, destX, destY):
220
        """
221
        Get the shortest path from origXY to destXY.
222
223
        Returns:
224
            List[Tuple[int, int]]: Returns a list walking the path from orig
225
                to dest.
226
227
                This excludes the starting point and includes the destination.
228
229
                If no path is found then an empty list is returned.
230
        """
231
        return super(AStar, self).get_path(origX, origY, destX, destY)
232
233
def quick_fov(x, y, callback, fov='PERMISSIVE', radius=7.5, lightWalls=True,
234
              sphere=True):
235
    """All field-of-view functionality in one call.
236
237
    Before using this call be sure to make a function, lambda, or method that takes 2
238
    positional parameters and returns True if light can pass through the tile or False
239
    for light-blocking tiles and for indexes that are out of bounds of the
240
    dungeon.
241
242
    This function is 'quick' as in no hassle but can quickly become a very slow
243
    function call if a large radius is used or the callback provided itself
244
    isn't optimized.
245
246
    Always check if the index is in bounds both in the callback and in the
247
    returned values.  These values can go into the negatives as well.
248
249
    Args:
250
        x (int): x center of the field-of-view
251
        y (int): y center of the field-of-view
252
        callback (Callable[[int, int], bool]):
253
254
            This should be a function that takes two positional arguments x,y
255
            and returns True if the tile at that position is transparent
256
            or False if the tile blocks light or is out of bounds.
257
        fov (Text): The type of field-of-view to be used.
258
259
            Available types are:
260
            'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
261
            'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
262
        radius (float) Radius of the field-of-view.
263
264
            When sphere is True a floating point can be used to fine-tune
265
            the range.  Otherwise the radius is just rounded up.
266
267
            Be careful as a large radius has an exponential affect on
268
            how long this function takes.
269
        lightWalls (bool): Include or exclude wall tiles in the field-of-view.
270
        sphere (bool): True for a spherical field-of-view.
271
            False for a square one.
272
273
    Returns:
274
        Set[Tuple[int, int]]: A set of (x, y) points that are within the
275
            field-of-view.
276
    """
277
    trueRadius = radius
278
    radius = int(_math.ceil(radius))
279
    mapSize = radius * 2 + 1
280
    fov = _get_fov_type(fov)
281
282
    setProp = _lib.TCOD_map_set_properties # make local
283
    inFOV = _lib.TCOD_map_is_in_fov
284
285
    tcodMap = _lib.TCOD_map_new(mapSize, mapSize)
286
    try:
287
        # pass no.1, write callback data to the tcodMap
288
        for x_, y_ in _itertools.product(range(mapSize), range(mapSize)):
289
            pos = (x_ + x - radius,
290
                   y_ + y - radius)
291
            transparent = bool(callback(*pos))
292
            setProp(tcodMap, x_, y_, transparent, False)
293
294
        # pass no.2, compute fov and build a list of points
295
        _lib.TCOD_map_compute_fov(tcodMap, radius, radius, radius, lightWalls, fov)
296
        touched = set() # points touched by field of view
297
        for x_, y_ in _itertools.product(range(mapSize), range(mapSize)):
298
            if sphere and _math.hypot(x_ - radius, y_ - radius) > trueRadius:
299
                continue
300
            if inFOV(tcodMap, x_, y_):
301
                touched.add((x_ + x - radius, y_ + y - radius))
302
    finally:
303
        _lib.TCOD_map_delete(tcodMap)
304
    return touched
305
306
def bresenham(x1, y1, x2, y2):
307
    """
308
    Return a list of points in a bresenham line.
309
310
    Implementation hastily copied from RogueBasin.
311
312
    Returns:
313
        List[Tuple[int, int]]: A list of (x, y) points,
314
            including both the start and end-points.
315
    """
316
    points = []
317
    issteep = abs(y2-y1) > abs(x2-x1)
318
    if issteep:
319
        x1, y1 = y1, x1
320
        x2, y2 = y2, x2
321
    rev = False
322
    if x1 > x2:
323
        x1, x2 = x2, x1
324
        y1, y2 = y2, y1
325
        rev = True
326
    deltax = x2 - x1
327
    deltay = abs(y2-y1)
328
    error = int(deltax / 2)
329
    y = y1
330
    ystep = None
331
    if y1 < y2:
332
        ystep = 1
333
    else:
334
        ystep = -1
335
    for x in range(x1, x2 + 1):
336
        if issteep:
337
            points.append((y, x))
338
        else:
339
            points.append((x, y))
340
        error -= deltay
341
        if error < 0:
342
            y += ystep
343
            error += deltax
344
    # Reverse the list if the coordinates were reversed
345
    if rev:
346
        points.reverse()
347
    return points
348
349
350
quickFOV = _style.backport(quick_fov)
351
AStar.getPath = _style.backport(AStar.get_path)
352