untdl.map

Package untdl :: Module map |
[frames] | no frames]

# Source Code for Module untdl.map

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