Source code for steering.path

# -*- coding: utf-8 -*-
""" Iterator that describes a Path

This module implements an iterator :py:class:`~.path.Path` to be used the 
descriptions of the points that form a particular path. There are also 
the following classes with specialised paths:

    * :py:class:`~.path.CyclicPath`
    * :py:class:`~.path.MirroredPath`
    
Aswell as the following pre-implemented useful paths:

    * :py:class:`~.path.PathCircumference`
    * :py:class:`~.path.PathParabola`

"""
import math

[docs]class Path(object): """ Iterator that describes a **Path** Provides a flexible interface to describe dynamic paths as an iterator, it uses a **Path Function** in the form of f(x) = y to describe the path. Parameters ---------- path_func: function number -> number The function that describes the path domain_start: int Start point for the function domain, defaults to 0 domain_end: int End point for the function domain increment: int Step to generate every point on the path Example ------- The way this class is meant to be used is by sub-classing it and creating your own class with your own properties, this is a slightly useluess implementation of :py:class:`~.path.CircumferencePath` that only traverses the path once. .. code-block:: python class OnceCircumferencePath(Path): def __init__(self, center, radius): self.center = center self.radius = radius def circumference_path(self, x): angle = math.radians(x) center = self.center x = center[0] + math.cos(angle)*self.radius y = center[1] + math.sin(angle)*self.radius return x, y super(OnceCircumferencePath, self).__init__(parabola_path, domain_start = 0, domain_end = 360, increment = 15) >>> mypath = OnceCircumferencePath(center = (50, 50), radius = 50) >>> next(mypath) (100.0, 50.0) >>> next(mypath) (98.29629131445341, 62.940952255126035) >>> next(mypath) (93.30127018922194, 75.0) A good tip is to use lambda functions in order to have dynamically updated paths, this allows to have attributes like 'center' update with the position of something in the game, which will alter the points the path will produce .. code-block:: python class OnceCircumferencePath(Path): def __init__(self, center, radius): self.center = center self.radius = radius def circumference_path(self, x): angle = math.radians(x) # Notice that we are now calling the attribute 'center' as a function center = self.center() x = center[0] + math.cos(angle)*self.radius y = center[1] + math.sin(angle)*self.radius return x, y super(OnceCircumferencePath, self).__init__(parabola_path, domain_start = 0, domain_end = 360, increment = 15) >>> character = SomeGameObjectWithARect() # The 'center' parameter is now defined as a lambda functions that gets the position of a character >>> mypath = OnceCircumferencePath(center = (lambda: character.rect.center), radius = 50) """ def __init__(self, path_func, domain_end, domain_start = 0, increment = 1): self.path_func = path_func self.increment = increment self.x = -increment + domain_start self.domain_start = domain_start self.domain_end = domain_end self.current = None def __iter__(self): return self def __next__(self): if self.x < self.domain_end: self.x += self.increment self.current = lambda: self.path_func(self, self.x) return self.current() else: raise StopIteration def __repr__(self): return 'Function Path'
[docs] def reset(self): """ Returns the iterator to it's initial point """ self.x = self.domain_start
[docs] def as_list(self): """ Returns the path as a list of points This ignores the infinity of :py:class:`~.path.CyclicPath` and :py:class:`~.path.MirroredPath` and returns a finite list. Nevertheless, you should keep in mind that if for your own sub-classes this methods does not return the expected results, it's probabbly the method's fault (my faul) and you should implement your own since this is used for drawing indicators. Returns ------- list(tuple(float, float)) """ path_list = [] for x in range(self.domain_start, self.domain_end+1, self.increment): path_list.append(self.path_func(self, x)) return path_list
[docs]class CyclicPath(Path): """Iterator that implements Cyclic Paths This is a sub-class of :py:class:`~.Path` that returns to the path's starting point once it reaches the end, this produces an infinite iterator. Uses the same parameters as :py:class:`~.Path`. """ def __init__(self, path_func, domain_end, domain_start = 0, increment = 1): super(CyclicPath, self).__init__(path_func, domain_end, domain_start, increment) def __next__(self): if self.x >= self.domain_end: self.reset() return super(CyclicPath, self).__next__()
[docs]class MirroredPath(Path): """ Iterator that implements Mirrored Paths This is a sub-class of :py:class:`~.Path` that **Mirrors** the path produced by the given function, this produces an infinite iterator that backtracks on the traversed path once it reaches it's domain_end, and does the same after it reaches domain_start. Uses the same parameters as :py:class:`~.Path`. """ def __init__(self, path_func, domain_end, domain_start = 0, increment = 1): super(MirroredPath, self).__init__(path_func, domain_end, domain_start, increment) def __repr__(self): return 'Mirrored ' + super(MirroredPath, self).__repr__() def __next__(self): self.x += self.increment if self.x > self.domain_end or self.x < self.domain_start: self.increment = -self.increment self.x += self.increment self.current = lambda: self.path_func(self, self.x) return self.current()
[docs] def as_list(self): path_list = [] if self.increment > 0: f_range = range(self.domain_start, self.domain_end+1, self.increment) else: f_range = range(self.domain_end, self.domain_start-1, self.increment) for x in f_range: path_list.append(self.path_func(self, x)) return path_list
[docs]class PathCircumference(CyclicPath): """ Circumference-like :py:class:`~.CyclicPath` Parameters ---------- center: tuple(int, int) or function -> tuple(int, int) radius: int """ def __init__(self, center, radius, start = 0): if not callable(center): callable_center = lambda : center else: callable_center = center self.center = callable_center self.radius = radius self.start = start def circumference_path(self, t): angle = math.radians(t) center = self.center() x = center[0] + math.cos(angle)*self.radius y = center[1] + math.sin(angle)*self.radius return x, y super(PathCircumference, self).__init__(circumference_path, domain_start = self.start, domain_end = self.start + 360, increment = 15) def __repr__(self): return 'PathCircumference'
[docs]class PathParabola(MirroredPath): """ Parabola-like :py:class:`~.MirroredPath` Parameters ---------- origin: tuple(int, int) or function -> tuple(int, int) Lowest point of the parabola width: int height: int """ def __init__(self, origin, width = 400, height = 100): if not callable(origin): callable_origin = lambda: origin else: callable_origin = origin self.origin = callable_origin self.domain_range = int(math.sqrt(height)) self.amplitude = width/(2*self.domain_range) def parabola_path(self, t): origin = self.origin() x = origin[0] + t*self.amplitude y = origin[1] - t**2 return x, y super(PathParabola, self).__init__(parabola_path, domain_start = -self.domain_range, domain_end = self.domain_range, increment = 2) def __repr__(self): return 'PathParbola'