- 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
|
--- @class Tile
|
||||||
--- @field atlasData TileAtlasData
|
--- @field atlasData TileAtlasData
|
||||||
--- @field position Vec3
|
--- @field position Vec3
|
||||||
local tile = {}
|
--- @field walkable boolean
|
||||||
|
local tile = {
|
||||||
|
walkable = true
|
||||||
|
}
|
||||||
tile.__index = tile
|
tile.__index = tile
|
||||||
|
|
||||||
--- TODO: сделать как love.graphics.draw несколько сигнатур у функции
|
--- TODO: сделать как love.graphics.draw несколько сигнатур у функции
|
||||||
|
|||||||
@ -1,34 +1,162 @@
|
|||||||
local deque = require "lib.utils.deque"
|
local deque = require "lib.utils.deque"
|
||||||
|
local SQRT2 = math.sqrt(2)
|
||||||
|
|
||||||
--- @param cur Vec3
|
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
|
--- @param to Vec3
|
||||||
--- @param acc Deque
|
--- @return boolean
|
||||||
local function greedy_trace_step(cur, to, acc)
|
local function can_step(from, to)
|
||||||
local lengthTable = {}
|
if not isWalkable(to) then return false end
|
||||||
for x = -1, 1 do
|
|
||||||
for y = -1, 1 do
|
local dx = to.x - from.x
|
||||||
local point = Vec3 { cur.x + x, cur.y + y }
|
local dy = to.y - from.y
|
||||||
table.insert(lengthTable, { point, (point - to):length() })
|
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
|
||||||
end
|
end
|
||||||
local min = lengthTable[1]
|
return true
|
||||||
for i = 2, #lengthTable do
|
end
|
||||||
if lengthTable[i][2] < min[2] then min = lengthTable[i] end
|
|
||||||
end
|
|
||||||
local next = min[1]
|
|
||||||
|
|
||||||
acc = acc:push_back(cur)
|
--- @param cur Vec3
|
||||||
if cur == to then
|
--- @return Vec3[]
|
||||||
return acc
|
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
|
end
|
||||||
return greedy_trace_step(next, to, acc)
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Восстановление пути (включая начало и цель)
|
||||||
|
--- @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
|
||||||
|
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
|
end
|
||||||
|
|
||||||
--- @param from Vec3
|
--- @param from Vec3
|
||||||
--- @param to Vec3
|
--- @param to Vec3
|
||||||
--- @return Deque
|
--- @return Deque
|
||||||
local function trace(from, to)
|
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
|
end
|
||||||
|
|
||||||
return trace
|
return trace
|
||||||
|
|||||||
11
main.lua
11
main.lua
@ -9,7 +9,16 @@ function love.conf(t)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function love.load()
|
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 }
|
-- PlayerFaction.characters = { Hero1, Hero2 }
|
||||||
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
|
love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true })
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user