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 array
      6  import ctypes
      7  import itertools
      8  import math
      9
     10  import tdl
     11  from .__tcod import _lib, _PATHCALL
     12
     13  _FOVTYPES = {'BASIC' : 0, 'DIAMOND': 1, 'SHADOW': 2, 'RESTRICTIVE': 12, 'PERMISSIVE': 11}
     14
     15 -def _getFOVType(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          found = _lib.TCOD_path_compute(self, origX, origY, destX, destY)
     98          if not found:
     99              return [] # path not found
    100          x, y = ctypes.c_int(), ctypes.c_int()
    101          xRef, yRef = ctypes.byref(x), ctypes.byref(y)
    102          recalculate = ctypes.c_bool(True)
    103          path = []
    104          while _lib.TCOD_path_walk(self, xRef, yRef, recalculate):
    105              path.append((x.value, y.value))
    106          return path
    107
    108 -def quickFOV(x, y, callback, fov='PERMISSIVE', radius=7.5, lightWalls=True, sphere=True):
    109      """All field-of-view functionality in one call.
    110
    111      Before using this call be sure to make a function, lambda, or method that takes 2
    112      positional parameters and returns True if light can pass the tile or False
    113      for light-blocking tiles and for positions that are out of bounds of the
    114      dungeon.
    115
    116      This function is 'quick' as in no hassle but can quickly become a very slow
    117      function call if a large radius is used or the callback provided itself
    118      isn't optimized.
    119
    120      @type x: int
    121      @param x: x center of the field-of-view
    122      @type y: int
    123      @param y: y center of the field-of-view
    124      @type callback: function
    125      @param callback: This should be a function that takes two positional arguments x,y
    126                       and returns True if the tile at that position is transparent
    127                       or False if the tile blocks light or is out of bounds.
    128      @type fov: string
    129      @param fov: The type of field-of-view to be used.  Available types are:
    130
    131                  'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
    132                  'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
    133      @type radius: float
    134      @param radius: Raduis of the field-of-view.
    135
    136                     When sphere is True a floating point can be used to fine-tune
    137                     the range.  Otherwise the radius is just rounded up.
    138
    139                     Be careful as a large radius has an exponential affect on
    140                     how long this function takes.
    141      @type lightWalls: boolean
    142      @param lightWalls: Include or exclude wall tiles in the field-of-view.
    143      @type sphere: boolean
    144      @param sphere: True for a spherical field-of-view.  False for a square one.
    145
    146      @rtype: iterator
    147      @return: Returns an iterator of (x, y) points that are within the field-of-view
    148      """
    149      trueRadius = radius
    150      radius = math.ceil(radius)
    151      mapSize = radius * 2 + 1
    152      fov = _getFOVType(fov)
    153
    154      setProp = _lib.TCOD_map_set_properties # make local
    155      inFOV = _lib.TCOD_map_is_in_fov
    156
    157      cTrue = ctypes.c_bool(1)
    158      cFalse = ctypes.c_bool(False)
    159      try:
    160          tcodMap = _lib.TCOD_map_new(mapSize, mapSize)
    161          # pass one, write callback data to the tcodMap
    162          for (x_, cX), (y_, cY) in itertools.product(((i, ctypes.c_int(i)) for i in range(mapSize)),
    163                                                      ((i, ctypes.c_int(i)) for i in range(mapSize))):
    164
    165              pos = (x_ + x - radius,
    166                     y_ + y - radius)
    167              transparent = bool(callback(*pos))
    168              setProp(tcodMap, cX, cY, transparent, cFalse)
    169
    170          # pass two, compute fov and build a list of points
    171          _lib.TCOD_map_compute_fov(tcodMap, radius, radius, radius, lightWalls, fov)
    172          touched = [] # points touched by field of view
    173          for (x_, cX),(y_, cY) in itertools.product(((i, ctypes.c_int(i)) for i in range(mapSize)),
    174                                                     ((i, ctypes.c_int(i)) for i in range(mapSize))):
    175              if sphere and math.hypot(x_ - radius, y_ - radius) > trueRadius:
    176                  continue
    177              if inFOV(tcodMap, cX, cY):
    178                  touched.append((x_ + x - radius, y_ + y - radius))
    179      finally:
    180          _lib.TCOD_map_delete(tcodMap)
    181      return touched
    182
    183  # def bresenham(x1, y1, x2, y2):
    184      # points = []
    185      # issteep = abs(y2-y1) > abs(x2-x1)
    186      # if issteep:
    187          # x1, y1 = y1, x1
    188          # x2, y2 = y2, x2
    189      # rev = False
    190      # if x1 > x2:
    191          # x1, x2 = x2, x1
    192          # y1, y2 = y2, y1
    193          # rev = True
    194      # deltax = x2 - x1
    195      # deltay = abs(y2-y1)
    196      # error = int(deltax / 2)
    197      # y = y1
    198      # ystep = None
    199      # if y1 < y2:
    200          # ystep = 1
    201      # else:
    202          # ystep = -1
    203      # for x in range(x1, x2 + 1):
    204          # if issteep:
    205              # points.append((y, x))
    206          # else:
    207              # points.append((x, y))
    208          # error -= deltay
    209          # if error < 0:
    210              # y += ystep
    211              # error += deltax
    212      # # Reverse the list if the coordinates were reversed
    213      # if rev:
    214          # points.reverse()
    215      # return points
    216
    217  __all__ = ['AStar', 'quickFOV']
    218