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