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