Package untdl :: Module map
[frames] | no frames]

Source Code for Module untdl.map

  1  """ 
  2      Rogue-like map utility's such as line-of-sight, field-of-view, and path-finding. 
  3       
  4  """ 
  5  #import array 
  6  import ctypes 
  7  import itertools 
  8  import math 
  9   
 10  import untdl 
 11  # noinspection PyProtectedMember 
 12  from .__tcod import _lib, _PATHCALL 
 13   
 14  _FOVTYPES = {'BASIC': 0, 'DIAMOND': 1, 'SHADOW': 2, 'RESTRICTIVE': 12, 'PERMISSIVE': 11} 
 15   
 16   
17 -def _get_fov_type(fov):
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 # noinspection PyUnusedLocal
29 -class AStar(object):
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: # set None or False to zero 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) # expecting a float or 0 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
97 - def __del__(self):
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 [] # path not found 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 # make local 172 in_fov = _lib.TCOD_map_is_in_fov 173 174 # ctrue = ctypes.c_bool(1) 175 cfalse = ctypes.c_bool(False) 176 tcod_map = _lib.TCOD_map_new(map_size, map_size) 177 try: 178 # pass one, write callback data to the tcod_map 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 # pass two, compute fov and build a list of points 187 _lib.TCOD_map_compute_fov(tcod_map, radius, radius, radius, light_walls, fov) 188 touched = set() # points touched by field of view 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
200 -def bresenham(x1, y1, x2, y2):
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 # Reverse the list if the coordinates were reversed 236 if rev: 237 points.reverse() 238 return iter(points) # force as iter so I can sleep at night
239 240 241 __all__ = ['AStar', 'quick_fov'] 242