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