local deque = require "lib.utils.deque" local SQRT2 = math.sqrt(2) local function isWalkable(point) -- 1) В пределах уровня if point.x < 0 or point.y < 0 then return false end if point.x >= Tree.level.size.x or point.y >= Tree.level.size.y then return false end -- 2) Клетка не занята персонажем if Tree.level.characterGrid:get(point) then return false end -- 3) Клетка проходима по тайлу local tile = Tree.level.tileGrid:get(point) if not tile or not tile.walkable then return false end return true end --- @param a Vec3 --- @param b Vec3 --- @return number local function heuristic(a, b) local dx = math.abs(a.x - b.x) local dy = math.abs(a.y - b.y) return (dx + dy) + (SQRT2 - 2) * math.min(dx, dy) end --- @param from Vec3 --- @param to Vec3 --- @return boolean local function can_step(from, to) if not isWalkable(to) then return false end local dx = to.x - from.x local dy = to.y - from.y if dx ~= 0 and dy ~= 0 then -- нельзя просачиваться через диагональ local viaX = Vec3 { from.x + dx, from.y } local viaY = Vec3 { from.x, from.y + dy } if not (isWalkable(viaX) or isWalkable(viaY)) then return false end end return true end --- @param cur Vec3 --- @return Vec3[] local function neighbors(cur) local res = {} for dx = -1, 1 do for dy = -1, 1 do if not (dx == 0 and dy == 0) then local nxt = Vec3 { cur.x + dx, cur.y + dy } if can_step(cur, nxt) then res[#res + 1] = nxt end end end end return res end --- Восстановление пути (включая начало и цель) --- @param cameFrom table --- @param goal Vec3 --- @return Deque local function reconstruct_path(cameFrom, goal) local sequence = {} local cur = goal while cur do sequence[#sequence + 1] = cur cur = cameFrom[tostring(cur)] end local acc = deque.new() for i = 1, #sequence, 1 do acc = acc:push_front(sequence[i]) end return acc end --- @param openSet table множество вершин на границе --- @param closed table уже обработанные --- @param cameFrom table путь --- @param gScore table --- @param fScore table --- @param goal Vec3 --- @return Deque local function a_star_step(openSet, closed, cameFrom, gScore, fScore, goal) -- пусто: пути нет local anyKey = next(openSet) if not anyKey then return deque.new() end -- выбрать узел с минимальным fScore local currentKey, currentNode = anyKey, openSet[anyKey] local bestF = fScore[anyKey] or math.huge for k, node in pairs(openSet) do local f = fScore[k] or math.huge if f < bestF then bestF = f currentKey, currentNode = k, node end end -- достигли цели if currentNode == goal then return reconstruct_path(cameFrom, currentNode) end openSet[currentKey] = nil closed[currentKey] = true local gCurrent = gScore[currentKey] or math.huge for _, nb in ipairs(neighbors(currentNode)) do local nk = tostring(nb) if not closed[nk] then local dx = nb.x - currentNode.x local dy = nb.y - currentNode.y local step = (dx ~= 0 and dy ~= 0) and SQRT2 or 1 -- по диагонали ходить "дороже" local tentativeG = gCurrent + step if tentativeG < (gScore[nk] or math.huge) then cameFrom[nk] = currentNode gScore[nk] = tentativeG fScore[nk] = tentativeG + heuristic(nb, goal) if not openSet[nk] then openSet[nk] = nb end end end end return a_star_step(openSet, closed, cameFrom, gScore, fScore, goal) end --- @param from Vec3 --- @param to Vec3 --- @return Deque local function trace(from, to) -- не считаем путь до непроходимой точки (ибо нефиг) if not isWalkable(to) then return deque.new():push_back(from) end -- тривиальный случай if from == to then return deque.new():push_back(from) end local openSet = {} local closed = {} local cameFrom = {} local gScore = {} local fScore = {} local sk = tostring(from) openSet[sk] = from gScore[sk] = 0 fScore[sk] = heuristic(from, to) return a_star_step(openSet, closed, cameFrom, gScore, fScore, to) end return trace