简单的走格子玩法可以试试嗷~。如果格子数量过于庞大,~~可以考虑下是不是设计不合理呢?~~可以考虑下其他方法~
首先构造一个堆类(heap, AKA 优先队列/Priority Queue),这个数据结构会在计算时存储过程数据,文件命名为 heap.lua。
local floor = math.floor
local function f_min(a, b) return a < b end
local function new(template, comp_func)
return setmetatable({
_heap = {},
_sort = comp_func or f_min,
_size = 0 },
template)
end
---构造方法
---@type heap
local heap = setmetatable({}, {
__call = function(self, ...)
return new(self, ...)
end })
heap.__index = heap
---添加一些基础的方法
function heap:empty()
return (self._size == 0)
end
function heap:clear()
self._heap = {}
self._size = 0
self._sort = self._sort or f_min
return self
end
--- heap 中常用的 percolate up, percolae down, heapify 的 lua version
--- percolate up: 添加数据后的比较和排序
local function _percolate_up(heap, index)
if index == 1 then return end
local p_index
if index <= 1 then return end
if index % 2 == 0 then
p_index = index / 2
else p_index = (index - 1) / 2
end
if not heap._sort(heap._heap[p_index], heap._heap[index]) then
heap._heap[p_index], heap._heap[index] = heap._heap[index], heap._heap[p_index]
_percolate_up(heap, p_index)
end
end
--- percolate down: 取出数据后的比较和排序
local function _percolate_down(heap, index)
local l_index, r_index, min_index
l_index = 2 * index
r_index = l_index + 1
if r_index > heap._size then
if l_index > heap._size then return
else min_index = l_index end
else
if heap._sort(heap._heap[l_index],heap._heap[r_index]) then
min_index = l_index
else
min_index = r_index
end
end
if not heap._sort(heap._heap[index], heap._heap[min_index]) then
heap._heap[index], heap._heap[min_index] = heap._heap[min_index], heap._heap[index]
_percolate_down(heap, min_index)
end
end
--- 对 heap 进行整理
function heap:heapify(item)
if self._size == 0 then return end
if item then
local idx = nil
for i = 1, #self._heap do
if self._heap[i] == item then idx = i end
end
if idx then
percolate_down(self, idx)
percolate_up(self, idx)
end
return
end
for i = floor(self._size / 2), 1, -1 do
percolate_down(self, i)
end
return self
end
function heap:push(item)
if item then
self._size = self._size + 1
self._heap[self._size] = item
_percolate_up(self, self._size)
end
return self
end
function heap:pop()
local root
if self._size > 0 then
root = self._heap[1]
self._heap[1] = self._heap[self._size]
self._heap[self._size] = nil
self._size = self._size - 1
if self._size > 1 then
percolate_down(self, 1)
end
end
return root
end
return heap
A* 分四方向和八方向,所以两种都有定义:
local heap = require 'heap'
local min = math.min
local max = math.max
local abs = math.abs
local floor = math.floor
local function f_min(a,b) return a.f < b.f end
---四方向
local four_dir = {
{1, 0},
{0, 1},
{0, -1},
{-1, 0},
}
---八方向
local eight_dir = {
{1, 1},
{1, 0},
{1, -1},
{0, 1},
{0, -1},
{-1, 1},
{-1, 0},
{-1, -1}
}
local function astar_path_finding(map, is_four_dir)
---@type astar
local self = {}
-- init map
---@param map @二维数组,第一层是行,第二层是列
---@param is_four_dir boolean @true: 四方向; false: 八方向
function self.init(map, is_four_dir)
self.startPoint = { row = 0, col = 0 }
self.endPoint = { row = 0, col = 0 }
self.map = map
self.cost = 10 -- 四方向的开销定为10
self.cost2 = 14 -- 对脚节点的开销取近似值14
self.open_list = heap(f_min)
self.mapRows = #map
self.mapCols = #map[1]
self.four_dir = is_four_dir
self.nodes = {}
for i = 1, self.mapRows do
for j = 1, self.mapCols do
table.insert(self.nodes,
{
father = nil,
row = i,
col = j,
g = 0,
h = 0,
f = 0,
state = 0, -- 0: 1:open 2:closed
})
end
end
end
-- begin with 1
function self.get_index(row, col)
return (row - 1) * self.mapCols + col
end
-- begin with 1
function self.get_row_col_by_index(index)
local row = floor((index - 1) / self.mapCols)
return row + 1, index - row * self.mapCols
end
function self.get_path(startRow, startCol, endRow, endCol)
self.startPoint.row = endRow
self.startPoint.col = endCol
self.endPoint.row = startRow
self.endPoint.col = startCol
return self.search_path()
end
function self.search_path()
-- if the end point is invalid then get empty
if self.map[self.endPoint.row][self.endPoint.col] ~= 0 then
print('error: can not reach end point!')
return nil
end
-- inclue the first node
local idx = self.get_index(self.startPoint.row, self.startPoint.col)
self.open_list:push(self.nodes[idx])
-- check boundary and validity
local check = function(row, col)
if 1 <= row and row <= self.mapRows
and 1 <= col and col <= self.mapCols then
if self.map[row][col] == 0
or (row == self.endPoint.row and col == self.endPoint.col) then
return true
end
end
return false
end
local dir = self.four_dir and four_dir or eight_dir
while not self.open_list:empty() do
-- check
local node = self.open_list:pop()
node.state = 2
if node.row == self.endPoint.row and node.col == self.endPoint.col then
-- find the path
return self.build_path(node)
end
for i = 1, #dir do
local row = node.row + dir[i][1]
local col = node.col + dir[i][2]
local idx = self.get_index(row, col)
if check(row, col) then
local g, h, f = self.get_cost(node, row, col,
row ~= node.row and col ~= node.col)
local newNode = self.nodes[idx]
if newNode.state == 0 then
-- add new node
newNode.father = node
newNode.g = g
newNode.h = h
newNode.f = f
newNode.state = 1
self.open_list:push(newNode)
elseif newNode.state == 1 then
-- alreay in openlist
if newNode.f > f then
-- a better way then update
newNode.father = node
newNode.g = g
newNode.h = h
newNode.f = f
end
else
-- in closelist
end
end
end
end
-- no path
print('no available path')
return nil
end
function self.get_cost(father, row, col, isdiag)
local g, h
if isdiag then
g = father.g + self.cost2
else
g = father.g + self.cost
end
-- calc H value
if self.four_dir then
h = self.manhattan(row, col)
else
h = self.diagonal(row, col)
end
return g, h, (g + h)
end
-- calc path
function self.build_path(node)
local path = {}
local sumCost = node.f -- total cost
while node do
path[#path + 1] = { x = node.row, y = node.col }
node = node.father
end
return path, sumCost
end
---四方向计算曼哈顿距离
function self.manhattan(row, col)
local h = abs(row - self.endPoint.row) + abs(col - self.endPoint.col)
return h * self.cost
end
-- 八方向计算欧式距离直到与终点垂直或平行
function self.diagonal(row, col)
local dx = abs(row - self.endPoint.row)
local dy = abs(col - self.endPoint.col)
local minD = min(dx, dy)
return minD * self.cost2 + (dx + dy - 2 * minD) * self.cost
end
self.init(map, four_dir)
return self
end
return astar_path_finding
非常简单,require 之后直接调用 init 方法构造数据,然后调用 get_path 方法就可以获得路线了~