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 through the tile or False
    113      for light-blocking tiles and for indexes 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      Always check if the index is in bounds both in the callback and in the
    121      returned values.  These values can go into the negatives as well.
    122
    123      @type x: int
    124      @param x: x center of the field-of-view
    125      @type y: int
    126      @param y: y center of the field-of-view
    127      @type callback: function
    128      @param callback: This should be a function that takes two positional arguments x,y
    129                       and returns True if the tile at that position is transparent
    130                       or False if the tile blocks light or is out of bounds.
    131      @type fov: string
    132      @param fov: The type of field-of-view to be used.  Available types are:
    133
    134                  'BASIC', 'DIAMOND', 'SHADOW', 'RESTRICTIVE', 'PERMISSIVE',
    135                  'PERMISSIVE0', 'PERMISSIVE1', ..., 'PERMISSIVE8'
    136      @type radius: float
    137      @param radius: Raduis of the field-of-view.
    138
    139                     When sphere is True a floating point can be used to fine-tune
    140                     the range.  Otherwise the radius is just rounded up.
    141
    142                     Be careful as a large radius has an exponential affect on
    143                     how long this function takes.
    144      @type lightWalls: boolean
    145      @param lightWalls: Include or exclude wall tiles in the field-of-view.
    146      @type sphere: boolean
    147      @param sphere: True for a spherical field-of-view.  False for a square one.
    148
    149      @rtype: iterator
    150      @return: Returns an iterator of (x, y) points that are within the field-of-view
    151      """
    152      trueRadius = radius
    153      radius = math.ceil(radius)
    154      mapSize = radius * 2 + 1
    155      fov = _getFOVType(fov)
    156
    157      setProp = _lib.TCOD_map_set_properties # make local
    158      inFOV = _lib.TCOD_map_is_in_fov
    159
    160      cTrue = ctypes.c_bool(1)
    161      cFalse = ctypes.c_bool(False)
    162      try:
    163          tcodMap = _lib.TCOD_map_new(mapSize, mapSize)
    164          # pass one, write callback data to the tcodMap
    165          for (x_, cX), (y_, cY) in itertools.product(((i, ctypes.c_int(i)) for i in range(mapSize)),
    166                                                      ((i, ctypes.c_int(i)) for i in range(mapSize))):
    167
    168              pos = (x_ + x - radius,
    169                     y_ + y - radius)
    170              transparent = bool(callback(*pos))
    171              setProp(tcodMap, cX, cY, transparent, cFalse)
    172
    173          # pass two, compute fov and build a list of points
    174          _lib.TCOD_map_compute_fov(tcodMap, radius, radius, radius, lightWalls, fov)
    175          touched = [] # points touched by field of view
    176          for (x_, cX),(y_, cY) in itertools.product(((i, ctypes.c_int(i)) for i in range(mapSize)),
    177                                                     ((i, ctypes.c_int(i)) for i in range(mapSize))):
    178              if sphere and math.hypot(x_ - radius, y_ - radius) > trueRadius:
    179                  continue
    180              if inFOV(tcodMap, cX, cY):
    181                  touched.append((x_ + x - radius, y_ + y - radius))
    182      finally:
    183          _lib.TCOD_map_delete(tcodMap)
    184      return touched
    185
    186  # def bresenham(x1, y1, x2, y2):
    187      # points = []
    188      # issteep = abs(y2-y1) > abs(x2-x1)
    189      # if issteep:
    190          # x1, y1 = y1, x1
    191          # x2, y2 = y2, x2
    192      # rev = False
    193      # if x1 > x2:
    194          # x1, x2 = x2, x1
    195          # y1, y2 = y2, y1
    196          # rev = True
    197      # deltax = x2 - x1
    198      # deltay = abs(y2-y1)
    199      # error = int(deltax / 2)
    200      # y = y1
    201      # ystep = None
    202      # if y1 < y2:
    203          # ystep = 1
    204      # else:
    205          # ystep = -1
    206      # for x in range(x1, x2 + 1):
    207          # if issteep:
    208              # points.append((y, x))
    209          # else:
    210              # points.append((x, y))
    211          # error -= deltay
    212          # if error < 0:
    213              # y += ystep
    214              # error += deltax
    215      # # Reverse the list if the coordinates were reversed
    216      # if rev:
    217          # points.reverse()
    218      # return points
    219
    220  __all__ = ['AStar', 'quickFOV']
    221