浏览代码

rewrite pathfinder (part 1)

olivier.massot 8 年之前
父节点
当前提交
42a465b741
共有 3 个文件被更改,包括 85 次插入97 次删除
  1. 20 22
      pypog/geometry_objects.py
  2. 64 73
      pypog/pathfinding.py
  3. 1 2
      tests/test_geometry.py

+ 20 - 22
pypog/geometry_objects.py

@@ -189,6 +189,12 @@ class BaseGeometry:
         after a rotation of 'rotations' times around the (x, y) center """
         after a rotation of 'rotations' times around the (x, y) center """
         raise NotImplementedError("this method is abstract and should be reimplemented in subclasses")
         raise NotImplementedError("this method is abstract and should be reimplemented in subclasses")
 
 
+    @staticmethod
+    def square_distance(x1, y1, x2, y2):
+        """ distance between 1 and 2 (run faster than a standard distance) """
+        return (x1 - x2) ** 2 + (y1 - y2) ** 2
+
+
 class SquareGeometry(BaseGeometry):
 class SquareGeometry(BaseGeometry):
     """ Geometry on square grids """
     """ Geometry on square grids """
     _nodiags = False
     _nodiags = False
@@ -370,25 +376,22 @@ class HexGeometry(BaseGeometry):
     This class should be overridden """
     This class should be overridden """
 
 
     @staticmethod
     @staticmethod
-    def cv_cube_off(xu, yu, zu):
+    def from_cubic(xu, yu, zu):
         """convert cubic coordinates (xu, yu, zu) in standards coordinates (x, y) [offset]"""
         """convert cubic coordinates (xu, yu, zu) in standards coordinates (x, y) [offset]"""
-        y = int(xu + (zu - (zu & 1)) / 2)
-        x = zu
-        return (x, y)
+        return (zu, int(xu + (zu - (zu & 1)) / 2))
 
 
     @staticmethod
     @staticmethod
-    def cv_off_cube(x, y):
+    def to_cubic(x, y):
         """converts standards coordinates (x, y) [offset] in cubic coordinates (xu, yu, zu)"""
         """converts standards coordinates (x, y) [offset] in cubic coordinates (xu, yu, zu)"""
         zu = x
         zu = x
         xu = int(y - (x - (x & 1)) / 2)
         xu = int(y - (x - (x & 1)) / 2)
         yu = int(-xu - zu)
         yu = int(-xu - zu)
         return (xu, yu, zu)
         return (xu, yu, zu)
 
 
-    # > unused
     @staticmethod
     @staticmethod
     def cube_round(x, y, z):
     def cube_round(x, y, z):
-        """returns the nearest cell (in cubic coords)
-        x, y, z can be floating numbers, no problem."""
+        """returns the nearest (xu, yu, zu) position in cubic coordinates
+        (x, y, z can be floating numbers)"""
         rx, ry, rz = round(x), round(y), round(z)
         rx, ry, rz = round(x), round(y), round(z)
         x_diff, y_diff, z_diff = abs(rx - x), abs(ry - y), abs(rz - z)
         x_diff, y_diff, z_diff = abs(rx - x), abs(ry - y), abs(rz - z)
         if x_diff > y_diff and x_diff > z_diff:
         if x_diff > y_diff and x_diff > z_diff:
@@ -399,21 +402,16 @@ class HexGeometry(BaseGeometry):
             rz = -rx - ry
             rz = -rx - ry
         return (rx, ry, rz)
         return (rx, ry, rz)
 
 
-    # > unused
     @staticmethod
     @staticmethod
-    def hex_distance_cube(xa, ya, za, xb, yb, zb):
-        """returns the manhattan distance between the two cells"""
-        return max(abs(xa - xb), abs(ya - yb), abs(za - zb))
-
-    # > unused
-    @staticmethod
-    def distance_off(xa, ya, xb, yb):
-        """ distance between A and B (offset coordinates)"""
-        # 10 times quicker if no conversion...
-        xua, yua, zua = HexGeometry.cv_off_cube(xa, ya)
-        xub, yub, zub = HexGeometry.cv_off_cube(xb, yb)
-        return max(abs(xua - xub), abs(yua - yub), abs(zua - zub))
-
+    def cubic_distance(*args):
+        """returns the manhattan distance between the two cells,
+        using cubic coordinates"""
+        try:
+            xa, ya, za, xb, yb, zb = args
+            return max(abs(xa - xb), abs(ya - yb), abs(za - zb))
+        except ValueError:
+            xa, ya, xb, yb = args
+            HexGeometry.cubic_distance(*HexGeometry.to_cubic(xa, ya), *HexGeometry.to_cubic(xb, yb))
 
 
 class FHexGeometry(HexGeometry):
 class FHexGeometry(HexGeometry):
     """ Flat-hexagonal grid object """
     """ Flat-hexagonal grid object """

+ 64 - 73
pypog/pathfinding.py

@@ -31,55 +31,32 @@
     ** By Cro-Ki l@b, 2017 **
     ** By Cro-Ki l@b, 2017 **
 '''
 '''
 from pypog.geometry_objects import HexGeometry
 from pypog.geometry_objects import HexGeometry
+from pypog.grid_objects import BaseGrid
 
 
-def distance(coord1, coord2):
-    """distance between 1 and 2"""
-    x1, y1 = coord1
-    xu1, yu1, zu1 = HexGeometry.cv_off_cube(x1, y1)
-    x2, y2 = coord2
-    xu2, yu2, zu2 = HexGeometry.cv_off_cube(x2, y2)
-    return max(abs(xu1 - xu2), abs(yu1 - yu2), abs(zu1 - zu2))
-
-def square_distance(coord1, coord2):
-    """distance between 1 and 2 (quicker than distance)"""
-    return (coord1[0] - coord2[0]) ** 2 + (coord1[1] - coord2[1]) ** 2
-
-class Path(object):
-    def __init__(self):
-        self._cells = []
-        self._costs = []
-        self._modes = []
-        self._val = []
+class NoPathFound(Exception):
+    pass
 
 
 class Node():
 class Node():
-    def __init__(self, coord):
-        self.parent = None  # coords of the previous node
-        self.coord = coord
-        self.k_dep = 1
-        self.g_cost = 0
-        self.h_cost = 0
-        self.cost = 0
-
-    def create(self, parent, target, k_dep):
+    target = None
+    def __init__(self, x, y, parent=None):
+        self._x = x
+        self._y = y
         self.parent = parent
         self.parent = parent
-        self.k_dep = k_dep
-        self.h_cost = self.distance(self.coord, target)
-        self.g_cost = self.parent.g_cost + self.k_dep
-        self.cout = self.g_cost + self.h_cost
+        self.gcost = 0
+        self.hcost = 0
 
 
-    def parent(self):
-        return self.parent
+    def compute(self, moving_cost):
+        # the manhattan distance to the final target
+        self.hcost = HexGeometry.cubic_distance(self._x, self._y, *self.target)
 
 
-    def distance(self, coord1, coord2):
-        """distance (en cases) entre deux coordonnees"""
-        x1, y1 = coord1
-        x2, y2 = coord2
-        return HexGeometry.distance_off(x1, y1, x2, y2)
+        # the cumulated moving cost of the path that lead here
+        self.gcost = self.parent.g_cost + self.moving_cost
 
 
-def _default_moving_cost_function(from_coord, to_coord):
-    return 1
+    @property
+    def cost(self):
+        return self.g_cost + self.h_cost
 
 
-def path(grid, origin, target, moving_cost_function=None):
+def path(grid, from_x, from_y, to_x, to_y):
     """return the shorter path from origin to target on the Grid object
     """return the shorter path from origin to target on the Grid object
     the path is estimated following:
     the path is estimated following:
     - geometry of the grid
     - geometry of the grid
@@ -88,55 +65,69 @@ def path(grid, origin, target, moving_cost_function=None):
 
 
     origin and target should be Cell objects
     origin and target should be Cell objects
     """
     """
-    if moving_cost_function == None:
-        moving_cost_function = _default_moving_cost_function
+    if not isinstance(grid, BaseGrid):
+        raise TypeError("grid has to be an instance of BaseGrid (given: {})".format(type(grid).__name__))
 
 
-    nodes = {}  # coord: node
+    nodes = {}
 
 
-    nO = Node(origin)
-    nO.parent = None
-    nO.cost = 0
+    # pass target to the Node class:
+    Node.target = (to_x, to_y)
 
 
-#     kept = [nO]
-    path = []
-    position = nO
+    # origin node
+    nO = Node(from_x, from_y)
 
 
-    while position.coord != target:
+    # current position
+    pos = nO
 
 
-        # we maybe could avoid the re-computing by storing the neighbours coordinates?
-        neighbours = grid.cell(position.coord).neighbours
+    while (pos.x, pos.y) != (to_x, to_y):
 
 
-        for coord in [coord for coord in neighbours if not coord in nodes.keys()]:
+        # lists the neighbors of the current position
+        neighbours = grid.neighbors(pos.x, pos.y)
 
 
-                cost = moving_cost_function(position.coord, coord)
-                if cost < 0:
-                    continue
 
 
-                node = Node(coord)
 
 
-                node.create(position, target, cost)
+        # removes the coordinates already checked
+        neighbours = set(neighbours) - set(nodes.keys())
+
+        for x, y in neighbours:
+
+            # use the grid's movingcost() function to get the moving cost from position to (x, y)
+            cost = grid._movingcost(pos.x, pos.y, x, y)
+
+            # cost is negative, can not go there
+            if cost < 0:
+                continue
 
 
-                try:
-                    existing = nodes[coord]
-                    if existing.cost <= node.cost:
-                        continue
-                except KeyError:
-                    pass
+            # instanciate the new node with 'pos' as parent
+            node = Node(x, y, pos)
+            node.compute(cost)
 
 
-                nodes[coord] = node
+            # check if there is already a node with a lower cost
+            try:
+                if nodes[(x, y)].cost <= node.cost:
+                    continue
+            except KeyError:
+                pass
+
+            # memorize the node
+            nodes[(x, y)] = node
 
 
-        if len(nodes) == 0:
-            print("No path found")
-            return []
+        # no new nodes were found
+        if not nodes:
+            raise NoPathFound()
 
 
+        # retrieves the lowest cost
         best = min(nodes.values(), key=lambda x: x.cost)
         best = min(nodes.values(), key=lambda x: x.cost)
+
         del nodes[best.coord]
         del nodes[best.coord]
-        position = best
+        pos = best
 
 
     else:
     else:
+
         # build the result
         # build the result
-        while position.coord != origin:
-            path.insert(0, (position.coord, position.k_dep))
-            position = position.parent
+        path = []
+        while (pos.x, pos.y) != (from_x, from_y):
+            path.insert(0, (pos.x, pos.y, pos.k_dep))
+            pos = pos.parent
 
 
     return path
     return path

+ 1 - 2
tests/test_geometry.py

@@ -4,11 +4,10 @@
 
 
     ** By Cro-Ki l@b, 2017 **
     ** By Cro-Ki l@b, 2017 **
 '''
 '''
-from math import inf
 import unittest
 import unittest
 
 
 from pypog.geometry_objects import FHexGeometry, SquareGeometry, BaseGeometry, \
 from pypog.geometry_objects import FHexGeometry, SquareGeometry, BaseGeometry, \
-    BoundingRect
+    BoundingRect, inf
 
 
 
 
 class Test(unittest.TestCase):
 class Test(unittest.TestCase):