pathfinder.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. '''
  2. Created on 17 déc. 2015
  3. Implement the A* algorithm
  4. Use the path function like that:
  5. path(my_grid, (xs, ys), (xt, yt), my_moving_cost_function)
  6. >> [(xs, ys), (x1, y1), (x2, y2), ...(xt, yt)]
  7. where:
  8. - my_grid is a Grid, HexGrid, or SquareGrid object
  9. - (xs, ys) is the starting cell
  10. - (xt, yt) is the targeted cell
  11. - my_moving_cost_function is a pointer to your custom function. This function should be like:
  12. def my_moving_cost_function((x0, y0), (x1, y1)):
  13. ...
  14. return cost
  15. this function should return an INTEGER which represent the cost of a move from (x0, y0) to (x1, y1),
  16. where (x0, y0) and (x1, y1) are adjacent cells
  17. If cost is negative, move is impossible.
  18. If move is strictly positive, it represents the difficulty to move from 0 to 1:
  19. the returned path will be the easiest from (xs, ys) to (xt, yt)
  20. 3D paths:
  21. The path method takes account of the differents altitudes of the cells, but it is not designed to
  22. work for a flying mover.
  23. More clearly: the path will be on the ground: walking, climbing, but no flying for instance.
  24. @author: olivier.massot
  25. '''
  26. from pypog import geometry
  27. def distance(coord1, coord2):
  28. """distance between 1 and 2"""
  29. x1, y1 = coord1
  30. xu1, yu1, zu1 = geometry.cv_off_cube(x1, y1)
  31. x2, y2 = coord2
  32. xu2, yu2, zu2 = geometry.cv_off_cube(x2, y2)
  33. return max(abs(xu1 - xu2), abs(yu1 - yu2), abs(zu1 - zu2))
  34. def square_distance(coord1, coord2):
  35. """distance between 1 and 2 (quicker than distance)"""
  36. return (coord1[0] - coord2[0])**2 + (coord1[1] - coord2[1])**2
  37. class Path(object):
  38. def __init__(self):
  39. self._cells = []
  40. self._costs = []
  41. self._modes = []
  42. self._val = []
  43. class Node():
  44. def __init__(self, coord):
  45. self.parent = None # coords of the previous node
  46. self.coord = coord
  47. self.k_dep = 1
  48. self.g_cost = 0
  49. self.h_cost = 0
  50. self.cost = 0
  51. def create(self, parent, target, k_dep):
  52. self.parent = parent
  53. self.k_dep = k_dep
  54. self.h_cost = self.distance(self.coord, target)
  55. self.g_cost = self.parent.g_cost + self.k_dep
  56. self.cout = self.g_cost + self.h_cost
  57. def parent(self):
  58. return self.parent
  59. def distance(self, coord1, coord2):
  60. """distance (en cases) entre deux coordonnees"""
  61. x1, y1 = coord1
  62. x2, y2 = coord2
  63. return geometry.distance_off(x1, y1, x2, y2)
  64. def _default_moving_cost_function(from_coord, to_coord):
  65. return 1
  66. def path(grid, origin, target, moving_cost_function = None):
  67. """return the shorter path from origin to target on the Grid object
  68. the path is estimated following:
  69. - geometry of the grid
  70. - altitudes of the cells
  71. - cost of the move returned by the 'moving_cost_function'
  72. origin and target should be Cell objects
  73. """
  74. if moving_cost_function == None:
  75. moving_cost_function = _default_moving_cost_function
  76. nodes = {} # coord: node
  77. nO = Node(origin)
  78. nO.parent = None
  79. nO.cost = 0
  80. # kept = [nO]
  81. path = []
  82. position = nO
  83. while position.coord != target:
  84. # we maybe could avoid the re-computing by storing the neighbours coordinates?
  85. neighbours = grid.cell(position.coord).neighbours
  86. for coord in [coord for coord in neighbours if not coord in nodes.keys()]:
  87. cost = moving_cost_function(position.coord, coord)
  88. if cost < 0:
  89. continue
  90. node = Node(coord)
  91. node.create(position, target, cost)
  92. try:
  93. existing = nodes[coord]
  94. if existing.cost <= node.cost:
  95. continue
  96. except KeyError:
  97. pass
  98. nodes[coord] = node
  99. if len(nodes) == 0:
  100. print("No path found")
  101. return []
  102. best = min(nodes.values(), key=lambda x: x.cost)
  103. del nodes[best.coord]
  104. position = best
  105. else:
  106. # build the result
  107. while position.coord != origin:
  108. path.insert(0, (position.coord, position.k_dep))
  109. position = position.parent
  110. return path