從某個國外的網站上看到了一篇介紹路徑搜索算法的文章,使用python3編寫,非常優秀。我使用的是python2,對代碼稍作修改即可實現。作者還用C++和C#編寫。
該文章源於一個遊戲塔防的策略。點擊打開鏈接
動畫演示了不同牆壁下,圖上隨機生成的粒子點如何經過最短的路徑到達目標位置。實際上每個粒子點並不是意義計算如何到達目標,而是當用戶改變地圖或者改變目標點位置後,重新計算了圖上每個位置的最短運動方向。相當於生成了一張表格,粒子落在表格中的哪個區域,就對應項哪個方向運動。
在文章末尾有個鏈接,詳細的介紹了路徑搜索的各個思想和步驟。點擊打開鏈接
下面給出python2.7的代碼,作者給出的是python3。
首先是implementation.py文件的修改。修改如下
# Sample code from http://www.redblobgames.com/pathfinding/
# Copyright 2014 Red Blob Games <[email protected]>
#
# Feel free to use this code in your own projects, including commercial projects
# License: Apache v2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>
class SimpleGraph:
def __init__(self):
self.edges = {}
def neighbors(self, id):
return self.edges[id]
example_graph = SimpleGraph()
example_graph.edges = {
'A': ['B'],
'B': ['A', 'C', 'D'],
'C': ['A'],
'D': ['E', 'A'],
'E': ['B']
}
import collections
class Queue:
def __init__(self):
self.elements = collections.deque()
def empty(self):
return len(self.elements) == 0
def put(self, x):
self.elements.append(x)
def get(self):
return self.elements.popleft()
# utility functions for dealing with square grids
def from_id_width(id, width):
return (id % width, id // width)
def draw_tile(graph, id, style, width):
r = "."
if 'number' in style and id in style['number']: r = "%d" % style['number'][id]
if 'point_to' in style and style['point_to'].get(id, None) is not None:
(x1, y1) = id
(x2, y2) = style['point_to'][id]
if x2 == x1 + 1: r = ("\u2192").decode('unicode_escape')
if x2 == x1 - 1: r = ("\u2190").decode('unicode_escape')
if y2 == y1 + 1: r = ("\u2193").decode('unicode_escape')
if y2 == y1 - 1: r = ("\u2191").decode('unicode_escape')
if 'start' in style and id == style['start']: r = "A"
if 'goal' in style and id == style['goal']: r = "Z"
if 'path' in style and id in style['path']: r = "@"
if id in graph.walls: r = "#" * width
return r
def draw_grid(graph, width=1, **style):
for y in range(graph.height):
for x in range(graph.width):
#print("%%-%ds" % width % draw_tile(graph, (x, y), style, width), end="")
print draw_tile(graph, (x, y), style, width),
print
# data from main article
DIAGRAM1_WALLS = [from_id_width(id, width=30) for id in [21,22,51,52,81,82,93,94,111,112,123,124,133,134,141,142,153,154,163,164,171,172,173,174,175,183,184,193,194,201,202,203,204,205,213,214,223,224,243,244,253,254,273,274,283,284,303,304,313,314,333,334,343,344,373,374,403,404,433,434]]
class SquareGrid:
def __init__(self, width, height):
self.width = width
self.height = height
self.walls = []
def in_bounds(self, id):
(x, y) = id
return 0 <= x < self.width and 0 <= y < self.height
def passable(self, id):
return id not in self.walls
def neighbors(self, id):
(x, y) = id
results = [(x+1, y), (x, y-1), (x-1, y), (x, y+1)]
if (x + y) % 2 == 0: results.reverse() # aesthetics
results = filter(self.in_bounds, results)
results = filter(self.passable, results)
return results
class GridWithWeights(SquareGrid):
def __init__(self, width, height):
#super().__init__(width, height)
SquareGrid.__init__(self, width, height)
self.weights = {}
def cost(self, from_node, to_node):
return self.weights.get(to_node, 1)
diagram4 = GridWithWeights(10, 10)
diagram4.walls = [(1, 7), (1, 8), (2, 7), (2, 8), (3, 7), (3, 8)]
diagram4.weights = {loc: 5 for loc in [(3, 4), (3, 5), (4, 1), (4, 2),
(4, 3), (4, 4), (4, 5), (4, 6),
(4, 7), (4, 8), (5, 1), (5, 2),
(5, 3), (5, 4), (5, 5), (5, 6),
(5, 7), (5, 8), (6, 2), (6, 3),
(6, 4), (6, 5), (6, 6), (6, 7),
(7, 3), (7, 4), (7, 5)]}
import heapq
class PriorityQueue:
def __init__(self):
self.elements = []
def empty(self):
return len(self.elements) == 0
def put(self, item, priority):
heapq.heappush(self.elements, (priority, item))
def get(self):
return heapq.heappop(self.elements)[1]
def dijkstra_search(graph, start, goal):
frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while not frontier.empty():
current = frontier.get()
if current == goal:
break
for next in graph.neighbors(current):
new_cost = cost_so_far[current] + graph.cost(current, next)
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost
frontier.put(next, priority)
came_from[next] = current
return came_from, cost_so_far
def reconstruct_path(came_from, start, goal):
current = goal
path = [current]
while current != start:
current = came_from[current]
path.append(current)
path.append(start) # optional
path.reverse() # optional
return path
def heuristic(a, b):
(x1, y1) = a
(x2, y2) = b
return abs(x1 - x2) + abs(y1 - y2)
def a_star_search(graph, start, goal):
frontier = PriorityQueue()
frontier.put(start, 0)
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while not frontier.empty():
current = frontier.get()
if current == goal:
break
for next in graph.neighbors(current):
new_cost = cost_so_far[current] + graph.cost(current, next)
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + heuristic(goal, next)
frontier.put(next, priority)
came_from[next] = current
return came_from, cost_so_far
作者在代碼中自己實現了一個簡單地Queue,,不清楚爲何不import python自帶的Queue。
作者對於圖的理解認爲越簡單表示越好,於是不定義任何圖的class,用元組代替圖中的每個節點,或者是邊。
尤其要注意的是在作者提供的算法中,python裏的字典變量起到了非常大的作用。
class SquareGrid中有一個filter的用法非常棒,作爲python入門人員,這個函數值得學習吸收!
作者在畫圖時把牆壁的width設置爲2,我們這兒設置爲1即可。
文章最後給出了許多優化建議,以及鏈接,也都值得細讀。