A星lua实现

A星的大致流程:

每走下一步前,评估所有可走下一步的移动量,然后从中选择最佳步骤;不断重复这个过程,直到到达目的地。

 

local Astar = {}
Astar.__index = Astar

function Astar.new()
    local inst = {}
    setmetatable(inst, Astar)
    inst:ctor()
    return inst
end

function Astar:ctor()
    self.m_Tiles = nil ---格子信息, 使用[y][x]获取, 存放格子是否可以走
    self.m_TileCanWalkFunc = nil
    self.m_CachedStepInfos = nil ---用于记录走到某个格子时的一些信息

    self.m_OpenList = nil ---下一步可以走的格子都会加入该列表
    self.m_ClosedList = nil ---已走过的格子

    self.m_ToLoc = { x = 0, y = 0 } ---目标位置
end

---@param func "用于返回格子是否可走, 默认0可走, 1不可走"
function Astar:SetTiles(tiles, func)
    self.m_Tiles = tiles
    self.m_TileCanWalkFunc = func
    self.m_CachedStepInfos = nil
end

function Astar:GetTiles()
    return self.m_Tiles
end

function Astar:_CheckOutOfRange(x, y)
    local row = self.m_Tiles[y]
    if nil == row then return true end
    if nil == row[x] then return true end
    return false
end

function Astar:_ResetStepInfos()
    if nil == self.m_CachedStepInfos then return end

    for k_rowIndex, v_rowStepInfos in pairs(self.m_CachedStepInfos) do
        for k_colIndex, v_stepInfo in pairs(v_rowStepInfos) do
            v_stepInfo.g = 0 ---起点到该点的移动量
            v_stepInfo.h = 0 ---该点到终点的预估移动量
            v_stepInfo.f = 0 ---g+h
            v_stepInfo.used = false
            v_stepInfo.prevStep = nil
        end
    end
end

---@return "{ loc={ x=xxx, y=xxx }, g=xxx, h=xxx, f=xxx, used=xxx }"
function Astar:GetStepInfo(x, y)
    if nil == self.m_CachedStepInfos then
        self.m_CachedStepInfos = {}
    end
    local row = self.m_CachedStepInfos[y]
    if nil == row then
        row = {}
        self.m_CachedStepInfos[y] = row
    end
    local stepInfo = row[x]
    if nil == stepInfo then
        stepInfo = {
            loc = { x=x, y=y },
            g = 0,
            h = 0,
            f = 0,
        }
        row[x] = stepInfo
    else
        stepInfo.x = x
        stepInfo.y = y
    end

    return stepInfo
end

---2个点之间是否有最佳路径
---@return "没有最佳路径则返回nil"
function Astar:FindPath(startX, startY, toX, toY)
    if startX == toX and startY == toY then return nil end

    if self:_CheckOutOfRange(startX, startY) then return nil end
    if self:_CheckOutOfRange(toX, toY) then return nil end

    self:_ResetStepInfos()

    self.m_ToLoc.x = toX
    self.m_ToLoc.y = toY

    self.m_OpenList = {}
    self.m_ClosedList = {}
    local curStepInfo = self:GetStepInfo(startX, startY)
    curStepInfo.used = true

    if self:_AddWalkables(curStepInfo) then --到目标点了
        return self:_GetWalkPath(startX, startY)
    end

    repeat
        local best = self:_FindBest()
        if nil == best then
            break
        end
        table.insert(self.m_ClosedList, best)

        if self:_AddWalkables(best) then --到目标点了
            return self:_GetWalkPath(startX, startY)
        end
    until #self.m_OpenList <= 0

    return nil
end

---close列表转换为移动路径
function Astar:_GetWalkPath(startX, startY)
    local result = {}
    local tempStepInfo = self.m_ClosedList[#self.m_ClosedList]
    while nil ~= tempStepInfo do
        local loc = tempStepInfo.loc
        if loc.x == startX and loc.y == startY then
            break
        end
        table.insert(result, 1, loc)
        tempStepInfo = tempStepInfo.prevStep
    end

    self.m_OpenList = nil
    self.m_ClosedList = nil
    return result
end

---检查上下左右格子, 如果可以走, 则放入open列表
---@return "是否到达目标点"
function Astar:_AddWalkables(curStepInfo)
    local loc = curStepInfo.loc
    local result = self:_AddWalkable(curStepInfo, loc.x+1, loc.y)
        or self:_AddWalkable(curStepInfo, loc.x, loc.y-1)
        or self:_AddWalkable(curStepInfo, loc.x-1, loc.y)
        or self:_AddWalkable(curStepInfo, loc.x, loc.y+1)
    return result
end

---检查相邻的格子, 如果可以走则放入open列表
---@return "是否已到达目标点"
function Astar:_AddWalkable(prevStepInfo, x, y)
    local toLoc = self.m_ToLoc
    if x == toLoc.x and y == toLoc.y then --到达终点
        local stepInfo = self:GetStepInfo(x, y)
        stepInfo.prevStep = prevStepInfo
        table.insert(self.m_ClosedList, stepInfo)
        return true
    end

    if nil ~= self.m_Tiles[y] then
        local tile = self.m_Tiles[y][x]
        if (self.m_TileCanWalkFunc and self.m_TileCanWalkFunc(tile)) or 0 == tile then
            local stepInfo = self:GetStepInfo(x, y)
            if not stepInfo.used then
                stepInfo.g = #self.m_ClosedList + 1
                stepInfo.h = math.abs(x - toLoc.x) + math.abs(y - toLoc.y)
                stepInfo.f = stepInfo.g + stepInfo.h
                stepInfo.used = true
                stepInfo.prevStep = prevStepInfo
                table.insert(self.m_OpenList, stepInfo)
            else
                if prevStepInfo.prevStep and stepInfo.g < prevStepInfo.prevStep.g then
                    prevStepInfo.prevStep = stepInfo
                end
            end
        end
    end

    return false
end

---从open列表中找一个最佳的格子
function Astar:_FindBest()
    local best = nil
    local index = 0
    for i=1,#self.m_OpenList do
        local stepInfo = self.m_OpenList[i]
        if nil == best then
            best = stepInfo
            index = i
        elseif stepInfo.f <= best.f then
            best = stepInfo
            index = i
        end
    end
    if index > 0 then
        table.remove(self.m_OpenList, index)
    end
    return best
end

return Astar

 

测试代码

local a = Astar.new()
local tiles = {}
tiles[1] = { 0, 0, 0, 0, 0, 0, 0 }
tiles[2] = { 0, 0, 0, 1, 0, 0, 0 }
tiles[3] = { 0, 0, 0, 1, 0, 0, 0 }
tiles[4] = { 0, 0, 0, 1, 0, 0, 0 }
tiles[5] = { 0, 1, 0, 0, 0, 0, 0 }
tiles[6] = { 0, 0, 0, 0, 0, 0, 0 }
a:SetTiles(tiles)

local path = a:FindPath(1, 6, 5, 5)
if path and #path > 0 then
    for i=1,#path do
        local loc = path[i]
        print(string.format("row=%s, col=%s", loc.y, loc.x))
    end
end

 

【A星原理参考】

A星寻路算法介绍 - 莫水千流 - 博客园 (cnblogs.com)

 A星寻路算法 - 简书 (jianshu.com)

A星寻路算法 - szmtjs10 - 博客园 (cnblogs.com)

 

posted @ 2022-02-20 17:38  yanghui01  阅读(230)  评论(0编辑  收藏  举报