Browse Source

pathfind better

master
Nicholas Hayashi 4 years ago
parent
commit
e247b377ad
  1. 58
      src/grid.lua
  2. 119
      src/hexyz.lua
  3. 8
      src/main.lua
  4. 96
      src/mob.lua
  5. 14
      src/table.lua
  6. 12
      src/util.lua

58
src/grid.lua

@ -3,8 +3,8 @@ require "colors"
require "gui" require "gui"
HEX_SIZE = 20 HEX_SIZE = 20
HEX_GRID_WIDTH = 65
HEX_GRID_HEIGHT = 33
HEX_GRID_WIDTH = 65 -- 65
HEX_GRID_HEIGHT = 33 -- 33
HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT) HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT)
-- this is in hex coordinates -- this is in hex coordinates
@ -50,55 +50,27 @@ function color_at(elevation)
end end
end end
function generate_flow_field(start)
local frontier = { start }
local came_from = {}
came_from[start.x] = {}
came_from[start.x][start.y] = true
while not (#frontier == 0) do
local current = table.pop(frontier)
log(current)
for _,neighbour in pairs(hex_neighbours(current)) do
if get_tile(neighbour.x, neighbour.y) then
if not (came_from[neighbour.x] and came_from[neighbour.x][neighbour.y]) then
log("hi")
if true then return came_from end
table.insert(frontier, neighbour)
came_from[neighbour.x] = {}
came_from[neighbour.x][neighbour.y] = current
end
end
end
end
return came_from
end
function random_map(seed, do_seed_rng) function random_map(seed, do_seed_rng)
local elevation_map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, seed)
local map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, 105)
--log(map.seed)
if do_seed_rng then math.randomseed(elevation_map.seed) end if do_seed_rng then math.randomseed(elevation_map.seed) end
HEX_MAP = {}
local world = am.group():tag"world" local world = am.group():tag"world"
for i,_ in pairs(elevation_map) do
HEX_MAP[i] = {}
for j,elevation in pairs(elevation_map[i]) do
for i,_ in pairs(map) do
for j,noise in pairs(map[i]) do
local off = hex_to_evenq(vec2(i, j)) local off = hex_to_evenq(vec2(i, j))
local mask = vec4(0, 0, 0, math.max(((off.x - HEX_GRID_DIMENSIONS.x/2) / HEX_GRID_DIMENSIONS.x) ^ 2 local mask = vec4(0, 0, 0, math.max(((off.x - HEX_GRID_DIMENSIONS.x/2) / HEX_GRID_DIMENSIONS.x) ^ 2
, ((-off.y - HEX_GRID_DIMENSIONS.y/2) / HEX_GRID_DIMENSIONS.y) ^ 2)) , ((-off.y - HEX_GRID_DIMENSIONS.y/2) / HEX_GRID_DIMENSIONS.y) ^ 2))
local color = color_at(elevation) - mask
local color = color_at(noise) - mask
local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6) local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6)
HEX_MAP[i][j] = {
elevation = elevation,
map.set(i, j, {
elevation = noise,
sprite = node, sprite = node,
tile = {} tile = {}
}
})
world:append(node) world:append(node)
end end
@ -109,16 +81,12 @@ function random_map(seed, do_seed_rng)
-- @NOTE no idea why the y-coord doesn't need to be transformed -- @NOTE no idea why the y-coord doesn't need to be transformed
local home = spiral_map(HEX_GRID_CENTER, 3) local home = spiral_map(HEX_GRID_CENTER, 3)
for _,hex in pairs(home) do for _,hex in pairs(home) do
HEX_MAP[hex.x][hex.y].elevation = 0
HEX_MAP[hex.x][hex.y].sprite.color = color_at(0)
map[hex.x][hex.y].elevation = 0
map[hex.x][hex.y].sprite.color = color_at(0)
world:append(am.circle(hex_to_pixel(vec2(hex.x, hex.y)), HEX_SIZE/2, COLORS.MAGENTA, 4)) world:append(am.circle(hex_to_pixel(vec2(hex.x, hex.y)), HEX_SIZE/2, COLORS.MAGENTA, 4))
end end
return am.translate(WORLDSPACE_COORDINATE_OFFSET)
return map, am.translate(WORLDSPACE_COORDINATE_OFFSET)
^ world:tag"world" ^ world:tag"world"
end end
function grid_neighbours(hex)
return table.filter(hex_neighbours(hex), function(_hex) return get_tile(_hex.x, _hex.y) end)
end

119
src/hexyz.lua

@ -206,6 +206,34 @@ function spiral_map(center, radius)
return setmetatable(map, {__index={center=center, radius=radius}}) return setmetatable(map, {__index={center=center, radius=radius}})
end end
function map_get(t, x, y)
return t[x] and t[x][y]
end
function map_set(t, x, y, v)
if t[x] then
t[x][y] = v
else
t[x] = {}
t[x][y] = v
end
return t
end
function map_partial_set(t, x, y, k, v)
local entry = map_get(t, x, y)
if not entry then
map_set(t, x, y, { k = v })
else
entry.k = v
end
return t
end
-- Returns Unordered Parallelogram-Shaped Map of |width| and |height| with Simplex Noise -- Returns Unordered Parallelogram-Shaped Map of |width| and |height| with Simplex Noise
function parallelogram_map(width, height, seed) function parallelogram_map(width, height, seed)
local seed = seed or math.random(width * height) local seed = seed or math.random(width * height)
@ -229,7 +257,14 @@ function parallelogram_map(width, height, seed)
map[i][j] = noise map[i][j] = noise
end end
end end
return setmetatable(map, {__index={width=width, height=height, seed=seed}})
return setmetatable(map, { __index = {
width = width,
height = height,
seed = seed,
get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
}})
end end
-- Returns Unordered Triangular (Equilateral) Map of |size| with Simplex Noise -- Returns Unordered Triangular (Equilateral) Map of |size| with Simplex Noise
@ -255,7 +290,13 @@ function triangular_map(size, seed)
map[i][j] = noise map[i][j] = noise
end end
end end
return setmetatable(map, {__index={size=size, seed=seed}})
return setmetatable(map, { __index = {
size = size,
seed = seed,
get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
}})
end end
-- Returns Unordered Hexagonal Map of |radius| with Simplex Noise -- Returns Unordered Hexagonal Map of |radius| with Simplex Noise
@ -286,7 +327,13 @@ function hexagonal_map(radius, seed)
map[i][j] = noise map[i][j] = noise
end end
end end
return setmetatable(map, {__index={radius=radius, seed=seed}})
return setmetatable(map, { __index = {
radius = radius,
seed = seed,
get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
}})
end end
-- Returns Unordered Rectangular Map of |width| and |height| with Simplex Noise -- Returns Unordered Rectangular Map of |width| and |height| with Simplex Noise
@ -314,6 +361,70 @@ function rectangular_map(width, height, seed)
map[i][j] = noise map[i][j] = noise
end end
end end
return setmetatable(map, {__index={width=width, height=height, seed=seed}})
return setmetatable(map, { __index = {
width = width,
height = height,
seed = seed,
get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
}})
end
--============================================================================
-- PATHFINDING
-- generic A* pathfinding
-- @NOTE is it better to move the functions to be members of the map?
--
-- returns a map that has map[hex.x][hex.y] = 'the vec2 which is the hex you came from when trying to get to |goal|'
function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f)
local neighbour_f = neighbour_f or hex_neighbours
local heuristic_f = heuristic_f or math.distance
local cost_f = cost_f or function() return 1 end
local came_from = {}
came_from[start.x] = {}
came_from[start.x][start.y] = false
local frontier = {}
frontier[1] = { hex = start, priority = 0 }
local cost_so_far = {}
cost_so_far[start.x] = {}
cost_so_far[start.x][start.y] = 0
local made_it = false
while not (#frontier == 0) do
local current = table.remove(frontier, 1)
if current.hex == goal then
made_it = true
break
end
for _,next_ in pairs(neighbour_f(current.hex)) do
local entry = map.get(next_.x, next_.y)
if entry then
local new_cost = map_get(cost_so_far, current.hex.x, current.hex.y)
+ cost_f(entry)
local next_cost = map_get(cost_so_far, next_.x, next_.y)
if not next_cost or new_cost < next_cost then
map_set(cost_so_far, next_.x, next_.y, new_cost)
local priority = new_cost + heuristic_f(goal, next_)
table.insert(frontier, { hex = next_, priority = priority })
map_set(came_from, next_.x, next_.y, current)
end
end
end
end
if not made_it then
log(" we didn't make it!")
end
return came_from
end end

8
src/main.lua

@ -56,7 +56,7 @@ function game_action(scene)
-- draw stuff -- draw stuff
win.scene"hex_cursor".center = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET win.scene"hex_cursor".center = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET
win.scene"score".text = string.format("SCORE: %.2f", time) win.scene"score".text = string.format("SCORE: %.2f", time)
win.scene"coords".text = string.format("%d,%d", off.x, off.y)
win.scene"coords".text = string.format("%d,%d", hex.x, hex.y)
end end
function game_scene() function game_scene()
@ -70,8 +70,12 @@ function game_scene()
win.scene:remove(curtain) win.scene:remove(curtain)
end)) end))
local world
HEX_MAP, world = random_map()
local scene = am.group{ local scene = am.group{
random_map(),
world,
curtain, curtain,
hex_cursor, hex_cursor,
score, score,

96
src/mob.lua

@ -1,59 +1,16 @@
MOBS = {} MOBS = {}
-- check if a |tile| is passable by |mob|
function can_pass_through(mob, tile)
return tile.elevation < 0.5 and tile.elevation > -0.5
-- check if a the tile at |hex| is passable by |mob|
function can_pass_through(mob, hex)
local tile = HEX_MAP.get(hex.x, hex.y)
return tile and tile.elevation < 0.5 and tile.elevation > -0.5
end end
function get_movement_cost(mob, start_hex, goal_hex) function get_movement_cost(mob, start_hex, goal_hex)
return 1 return 1
end end
function Astar(mob, start_hex, goal_hex)
local function heuristic(start_hex, goal_hex)
return math.distance(start_hex, goal_hex)
end
local came_from = {}
came_from[start_hex.x] = {}
came_from[start_hex.x][start_hex.y] = false
local frontier = {}
frontier[1] = { position = start_hex, priority = 0 }
local cost_so_far = {}
cost_so_far[start_hex.x] = {}
cost_so_far[start_hex.x][start_hex.y] = 0
while not (#frontier == 0) do
local current = table.remove(frontier, 1)
if current.position == goal_hex then log('found it!') break end
for _,_next in pairs(hex_neighbours(current.position)) do
local tile = get_tile(_next.x, _next.y)
if tile then
local new_cost = cost_so_far[current.position.x][current.position.y]
+ get_movement_cost(mob, current.position, _next)
if not twoD_get(cost_so_far, _next.x, _next.y) or new_cost < twoD_get(cost_so_far, _next.x, _next.y) then
twoD_set(cost_so_far, _next.x, _next.y, new_cost)
local priority = new_cost + heuristic(goal_hex, _next)
table.insert(frontier, { position = _next, priority = priority })
twoD_set(came_from, _next.x, _next.y, current)
end
end
end
end
return came_from
end
function get_spawn_hex(mob) function get_spawn_hex(mob)
local spawn_hex local spawn_hex
repeat repeat
@ -78,11 +35,12 @@ function get_spawn_hex(mob)
spawn_hex = evenq_to_hex(vec2(x, -y)) spawn_hex = evenq_to_hex(vec2(x, -y))
local tile = HEX_MAP[spawn_hex.x][spawn_hex.y] local tile = HEX_MAP[spawn_hex.x][spawn_hex.y]
until can_pass_through(mob, tile)
until can_pass_through(mob, spawn_hex)
return spawn_hex return spawn_hex
end end
-- @NOTE spawn hex
function make_mob() function make_mob()
local mob = {} local mob = {}
@ -90,23 +48,28 @@ function make_mob()
local spawn_position = hex_to_pixel(spawn_hex) + WORLDSPACE_COORDINATE_OFFSET local spawn_position = hex_to_pixel(spawn_hex) + WORLDSPACE_COORDINATE_OFFSET
mob.position = spawn_position mob.position = spawn_position
mob.path = Astar(mob, spawn_hex, HEX_GRID_CENTER)
mob.sprite = am.circle(spawn_position, 18, COLORS.WHITE, 4)
mob.hex = spawn_hex
mob.path = Astar(HEX_MAP, HEX_GRID_CENTER, spawn_hex,
win.scene:action(coroutine.create(function()
local goal = spawn_hex
local current = mob.path[HEX_GRID_CENTER.x][HEX_GRID_CENTER.y].position
log(current)
-- neighbour function
function(hex)
return table.filter(hex_neighbours(hex), function(_hex)
return can_pass_through(mob, _hex)
end)
end,
while current ~= goal do
if current then
win.scene:append(am.circle(hex_to_pixel(current) + WORLDSPACE_COORDINATE_OFFSET, 4, COLORS.MAGENTA))
current = mob.path[current.x][current.y].position
end
am.wait(am.delay(0.01))
-- heuristic function
function(source, target)
return math.distance(source, target)
end,
-- cost function
function(map_entry)
return math.abs(map_entry.elevation)
end end
end))
)
mob.sprite = am.circle(spawn_position, 18, COLORS.WHITE, 4)
win.scene:append(mob.sprite) win.scene:append(mob.sprite)
return mob return mob
@ -121,8 +84,19 @@ function do_mob_spawning()
end end
function do_mob_updates() function do_mob_updates()
--if win:key_pressed"a" then
for _,mob in pairs(MOBS) do for _,mob in pairs(MOBS) do
mob.hex = pixel_to_hex(mob.position - WORLDSPACE_COORDINATE_OFFSET)
local frame_target = map_get(mob.path, mob.hex.x, mob.hex.y)
if frame_target then
mob.position = lerp(mob.position, hex_to_pixel(frame_target.hex) + WORLDSPACE_COORDINATE_OFFSET, 0.9)
mob.sprite.center = mob.position
else
end
end end
--end
end end

14
src/table.lua

@ -1,14 +0,0 @@
function table.shift(t, count)
local e = t[1]
t[1] = nil
for i,e in pairs(t) do
if e then
t[i - 1] = e
end
end
return e
end

12
src/util.lua

@ -1,14 +1,6 @@
function twoD_get(t, x, y)
return t[x] and t[x][y]
function lerp(v1, v2, t)
return v1 * t + v2 * (1 - t)
end end
function twoD_set(t, x, y, v)
if t[x] then
t[x][y] = v
else
t[x] = {}
t[x][y] = v
end
end
Loading…
Cancel
Save