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

Source Code for Module tdl.map

  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  #_PATHCALL = CFUNCTYPE(c_float, c_int, c_int, c_int, c_int, py_object) 
 19   
20 -def _get_fov_type(fov):
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
30 -class AStar(object):
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: # set None or False to zero 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) # expecting a float or 0 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
101 - def __del__(self):
102 if self._as_parameter_: 103 _lib.TCOD_path_delete(self._as_parameter_) 104 self._as_parameter_ = None
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 [] # path not found 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 # make local 176 inFOV = _lib.TCOD_map_is_in_fov 177 178 #cTrue = _ffi.new('bool *', True) 179 #cFalse = _ffi.new('bool *', False) 180 #cFalse = _ctypes.c_bool(False) 181 tcodMap = _lib.TCOD_map_new(mapSize, mapSize) 182 try: 183 # pass one, write callback data to the tcodMap 184 #for (x_, cX), (y_, cY) in _itertools.product(((i, _ctypes.c_int(i)) for i in range(mapSize)), 185 # ((i, _ctypes.c_int(i)) for i in range(mapSize))): 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 # pass two, compute fov and build a list of points 193 _lib.TCOD_map_compute_fov(tcodMap, radius, radius, radius, lightWalls, fov) 194 touched = set() # points touched by field of view 195 #for (x_, cX),(y_, cY) in _itertools.product(((i, _ctypes.c_int(i)) for i in range(mapSize)), 196 # ((i, _ctypes.c_int(i)) for i in range(mapSize))): 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
206 -def bresenham(x1, y1, x2, y2):
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 # Reverse the list if the coordinates were reversed 243 if rev: 244 points.reverse() 245 return iter(points) # force as iter so I can sleep at night
246 247 __all__ = [_var for _var in locals().keys() if _var[0] != '_'] 248 249 quickFOV = _style.backport(quick_fov) 250