Pārlūkot izejas kodu

rewrite pathfinder (part 1)

olivier.massot 8 gadi atpakaļ
vecāks
revīzija
42a465b741
3 mainītis faili ar 85 papildinājumiem un 97 dzēšanām
  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 """
         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):
     """ Geometry on square grids """
     _nodiags = False
@@ -370,25 +376,22 @@ class HexGeometry(BaseGeometry):
     This class should be overridden """
 
     @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]"""
-        y = int(xu + (zu - (zu & 1)) / 2)
-        x = zu
-        return (x, y)
+        return (zu, int(xu + (zu - (zu & 1)) / 2))
 
     @staticmethod
-    def cv_off_cube(x, y):
+    def to_cubic(x, y):
         """converts standards coordinates (x, y) [offset] in cubic coordinates (xu, yu, zu)"""
         zu = x
         xu = int(y - (x - (x & 1)) / 2)
         yu = int(-xu - zu)
         return (xu, yu, zu)
 
-    # > unused
     @staticmethod
     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)
         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:
@@ -399,21 +402,16 @@ class HexGeometry(BaseGeometry):
             rz = -rx - ry
         return (rx, ry, rz)
 
-    # > unused
     @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):
     """ Flat-hexagonal grid object """

+ 64 - 73
pypog/pathfinding.py

@@ -31,55 +31,32 @@
     ** By Cro-Ki l@b, 2017 **
 '''
 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():
-    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.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
     the path is estimated following:
     - geometry of the grid
@@ -88,55 +65,69 @@ def path(grid, origin, target, moving_cost_function=None):
 
     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)
+
         del nodes[best.coord]
-        position = best
+        pos = best
 
     else:
+
         # 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

+ 1 - 2
tests/test_geometry.py

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