- proper A* implementation
- not really as I'm a freak - tiles are now walkable by default
This commit is contained in:
parent
7000f0fb4d
commit
82d393a064
@ -19,7 +19,10 @@ end
|
||||
--- @class Tile
|
||||
--- @field atlasData TileAtlasData
|
||||
--- @field position Vec3
|
||||
local tile = {}
|
||||
--- @field walkable boolean
|
||||
local tile = {
|
||||
walkable = true
|
||||
}
|
||||
tile.__index = tile
|
||||
|
||||
--- TODO: сделать как love.graphics.draw несколько сигнатур у функции
|
||||
|
||||
@ -1,34 +1,162 @@
|
||||
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
|
||||
--- @param to Vec3
|
||||
--- @param acc Deque
|
||||
local function greedy_trace_step(cur, to, acc)
|
||||
local lengthTable = {}
|
||||
for x = -1, 1 do
|
||||
for y = -1, 1 do
|
||||
local point = Vec3 { cur.x + x, cur.y + y }
|
||||
table.insert(lengthTable, { point, (point - to):length() })
|
||||
--- @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
|
||||
local min = lengthTable[1]
|
||||
for i = 2, #lengthTable do
|
||||
if lengthTable[i][2] < min[2] then min = lengthTable[i] end
|
||||
end
|
||||
local next = min[1]
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
acc = acc:push_back(cur)
|
||||
if cur == to then
|
||||
return acc
|
||||
--- Восстановление пути (включая начало и цель)
|
||||
--- @param cameFrom table<string, Vec3|nil>
|
||||
--- @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
|
||||
return greedy_trace_step(next, to, acc)
|
||||
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)
|
||||
return greedy_trace_step(from, to, deque.new())
|
||||
-- не считаем путь до непроходимой точки (ибо нефиг)
|
||||
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
|
||||
|
||||
11
main.lua
11
main.lua
@ -9,7 +9,16 @@ function love.conf(t)
|
||||
end
|
||||
|
||||
function love.load()
|
||||
character.spawn("Hero", "warrior", Tree.assets.files.sprites.character)
|
||||
for x = 0, 29, 1 do
|
||||
for y = 0, 29, 1 do
|
||||
if math.random() > 0.8 then
|
||||
local c = character.spawn("Hero", "warrior", Tree.assets.files.sprites.character)
|
||||
c.logic.mapLogic.position = Vec3 { x, y }
|
||||
c.logic.mapLogic.displayedPosition = Vec3 { x, y }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- PlayerFaction.characters = { Hero1, Hero2 }
|
||||
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user