a_star.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. # -*- coding: utf-8 -*-
  2. import heapq # used for the so colled "open list" that stores known nodes
  3. from pathfinding.core.heuristic import manhatten, octile
  4. from pathfinding.core.util import backtrace, bi_backtrace
  5. from pathfinding.core.diagonal_movement import DiagonalMovement
  6. from .finder import Finder, TIME_LIMIT, MAX_RUNS, BY_END
  7. class AStarFinder(Finder):
  8. def __init__(self, heuristic=None, weight=1,
  9. diagonal_movement=DiagonalMovement.never,
  10. time_limit=TIME_LIMIT,
  11. max_runs=MAX_RUNS):
  12. """
  13. find shortest path using A* algorithm
  14. :param heuristic: heuristic used to calculate distance of 2 points
  15. (defaults to manhatten)
  16. :param weight: weight for the edges
  17. :param diagonal_movement: if diagonal movement is allowed
  18. (see enum in diagonal_movement)
  19. :param time_limit: max. runtime in seconds
  20. :param max_runs: max. amount of tries until we abort the search
  21. (optional, only if we enter huge grids and have time constrains)
  22. <=0 means there are no constrains and the code might run on any
  23. large map.
  24. """
  25. super(AStarFinder, self).__init__(
  26. heuristic=heuristic,
  27. weight=weight,
  28. diagonal_movement=diagonal_movement,
  29. time_limit=time_limit,
  30. max_runs=max_runs)
  31. if not heuristic:
  32. if diagonal_movement == DiagonalMovement.never:
  33. self.heuristic = manhatten
  34. else:
  35. # When diagonal movement is allowed the manhattan heuristic is
  36. # not admissible it should be octile instead
  37. self.heuristic = octile
  38. def check_neighbors(self, start, end, grid, open_list,
  39. open_value=True, backtrace_by=None):
  40. """
  41. find next path segment based on given node
  42. (or return path if we found the end)
  43. """
  44. # pop node with minimum 'f' value
  45. node = heapq.nsmallest(1, open_list)[0]
  46. open_list.remove(node)
  47. node.closed = True
  48. # if reached the end position, construct the path and return it
  49. # (ignored for bi-directional a*, there we look for a neighbor that is
  50. # part of the oncoming path)
  51. if not backtrace_by and node == end:
  52. return backtrace(end)
  53. # get neighbors of the current node
  54. neighbors = self.find_neighbors(grid, node)
  55. for neighbor in neighbors:
  56. if neighbor.closed:
  57. # already visited last minimum f value
  58. continue
  59. if backtrace_by and neighbor.opened == backtrace_by:
  60. # found the oncoming path
  61. if backtrace_by == BY_END:
  62. return bi_backtrace(node, neighbor)
  63. else:
  64. return bi_backtrace(neighbor, node)
  65. # check if the neighbor has not been inspected yet, or
  66. # can be reached with smaller cost from the current node
  67. self.process_node(neighbor, node, end, open_list, open_value)
  68. # the end has not been reached (yet) keep the find_path loop running
  69. return None
  70. def find_path(self, start, end, grid):
  71. """
  72. find a path from start to end node on grid using the A* algorithm
  73. :param start: start node
  74. :param end: end node
  75. :param grid: grid that stores all possible steps/tiles as 2D-list
  76. :return:
  77. """
  78. start.g = 0
  79. start.f = 0
  80. return super(AStarFinder, self).find_path(start, end, grid)