tdl.map

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          """
     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 quickFOV(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 = math.ceil(radius)
    163      mapSize = radius * 2 + 1
    164      fov = _getFOVType(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      try:
    172          tcodMap = _lib.TCOD_map_new(mapSize, mapSize)
    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      # points = []
    197      # issteep = abs(y2-y1) > abs(x2-x1)
    198      # if issteep:
    199          # x1, y1 = y1, x1
    200          # x2, y2 = y2, x2
    201      # rev = False
    202      # if x1 > x2:
    203          # x1, x2 = x2, x1
    204          # y1, y2 = y2, y1
    205          # rev = True
    206      # deltax = x2 - x1
    207      # deltay = abs(y2-y1)
    208      # error = int(deltax / 2)
    209      # y = y1
    210      # ystep = None
    211      # if y1 < y2:
    212          # ystep = 1
    213      # else:
    214          # ystep = -1
    215      # for x in range(x1, x2 + 1):
    216          # if issteep:
    217              # points.append((y, x))
    218          # else:
    219              # points.append((x, y))
    220          # error -= deltay
    221          # if error < 0:
    222              # y += ystep
    223              # error += deltax
    224      # # Reverse the list if the coordinates were reversed
    225      # if rev:
    226          # points.reverse()
    227      # return points
    228
    229  __all__ = ['AStar', 'quickFOV']
    230