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