diff --git a/README.md b/README.md index 8766519..d15ab5d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ## INTRODUCTION +AN: Basically everything in here it out of date until I remove this line. + This is a small and simple library for using hexagonal grids in amulet + lua. I wrote it for a tower defense game I'm making. It's not really well documented. If you want an actual good resource, go to [amit's guide to hexagonal grids](https://redblobgames.com/grids/hexagons). So much of what is here I derived from amit's work. diff --git a/res/wall_closed.png b/res/wall_closed.png new file mode 100644 index 0000000..befe8fd Binary files /dev/null and b/res/wall_closed.png differ diff --git a/res/wall_open.png b/res/wall_open.png new file mode 100644 index 0000000..df42421 Binary files /dev/null and b/res/wall_open.png differ diff --git a/src/colors.lua b/src/colors.lua index 1653e88..5b22f72 100644 --- a/src/colors.lua +++ b/src/colors.lua @@ -1,15 +1,17 @@ COLORS = { TRANSPARENT = vec4(0.4), - --TRANSPARENT = vec4(0.6), + -- tones WHITE = vec4(0.8, 0.8, 0.7, 1), BLACK = vec4(0, 0, 0, 1), + TRUEBLACK = vec4(0, 0, 0, 1), -- hues BLUE_STONE = vec4(0.12, 0.3, 0.3, 1), MYRTLE = vec4(0.10, 0.25, 0.10, 1), BROWN_POD = vec4(0.25, 0.20, 0.10, 1), - BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1) + BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1), + MAGENTA = vec4(1, 0, 1, 1) } diff --git a/src/grid.lua b/src/grid.lua index 0751a71..6c09fc6 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -1,83 +1,94 @@ require "colors" +require "gui" -WORLD_GRID_DIMENSIONS = vec2(46, 32) -CELL_SIZE = 20 -local world_grid_map - --- ensure home-base is somewhat of an open area. -function find_home(preferred_radius) - home = spiral_map(vec2(23, 4), preferred_radius or 2) - local home_node = am.group() - - repeat - local happy = true - - for i,h in pairs(home) do - local elevation = map[h.x][h.y] - - if not elevation then -- hex not in map - elseif elevation > 0.5 or elevation < -0.5 then - happy = false - - elseif not happy then - home = spiral_map(h, preferred_radius or 1) - home_node = am.group() - - else - local center = hex_to_pixel(h) - local color = vec4(1, 0, 0.5, 1) - local node = am.circle(center, 4, color, 4) - home_node:append(node) - end - end - until happy - return home_node -end +HEX_SIZE = 20 +HEX_GRID_WIDTH = 65 +HEX_GRID_HEIGHT = 33 +HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT) --- map elevation to appropriate tile color. -function color_at(elevation) - if elevation < -0.5 then -- lowest elevation : impassable - return COLORS.BLUE_STONE{ a = (elevation + 1.4) / 2 + 0.2 } +-- @NOTE no idea why the y coordinate doesn't need to be transformed here +HEX_GRID_CENTER = vec2(math.floor(HEX_GRID_DIMENSIONS.x/2), 0) - elseif elevation < 0 then -- med-low elevation : passable - return COLORS.MYRTLE{ a = (elevation + 1.8) / 2 + 0.2 } +-- index is hex coordinates [x][y] +-- { { elevation, sprite, tile } } +HEX_MAP = {} - elseif elevation < 0.5 then -- med-high elevation : passable - return COLORS.BROWN_POD{ a = (elevation + 1.6) / 2 + 0.2 } +function grid_pixel_dimensions() + local hhs = hex_horizontal_spacing(HEX_SIZE) + local hvs = hex_vertical_spacing(HEX_SIZE) - elseif elevation < 1 then -- highest elevation : impassable - return COLORS.BOTTLE_GREEN{ a = (elevation + 1.0) / 2 + 0.2 } - end + -- number of 'spacings' on the grid == number of cells - 1 + return vec2((HEX_GRID_DIMENSIONS.x - 1) * hhs + , (HEX_GRID_DIMENSIONS.y - 1) * hvs) end -function worldspace_coordinate_offset() - return vec2(-hex_height(CELL_SIZE)) +GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions() +WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2 + +-- convience function for when getting a tile at x,y could fail +function get_tile(x, y) + return HEX_MAP[x] and HEX_MAP[x][y] end -function random_map(seed) - world_grid_map = rectangular_map(WORLD_GRID_DIMENSIONS.x, WORLD_GRID_DIMENSIONS.y, seed); - math.randomseed(world_grid_map.seed) - local world = am.translate(worldspace_coordinate_offset()) ^ am.group(am.circle(vec2(0), 32, COLORS.WHITE)):tag"world" - - for i,_ in pairs(world_grid_map) do - for j,elevation in pairs(world_grid_map[i]) do - - -- subtly shade map edges - local off = hex_to_offset(vec2(i, j)) - local mask = vec4(0, 0, 0, math.max(((off.x - WORLD_GRID_DIMENSIONS.x/2) / WORLD_GRID_DIMENSIONS.x) ^ 2, - ((-off.y - WORLD_GRID_DIMENSIONS.y/2) / WORLD_GRID_DIMENSIONS.y) ^ 2)) - local color = color_at(elevation) - mask - - local node = am.circle(hex_to_pixel(vec2(i, j)), CELL_SIZE, vec4(0), 6) - :action(am.tween(2, { color=color }, am.ease.out(am.ease.hyperbola))) - - world:append(node) - end - end - --world:append(find_home(2)) - --world:action(spawner) - return world:tag"world" +-- map elevation to appropriate tile color. +function color_at(elevation) + if elevation < -0.5 then -- lowest elevation : impassable + return COLORS.BLUE_STONE{ a = (elevation + 1.4) / 2 + 0.2 } + + elseif elevation < 0 then -- med-low elevation : passable + return COLORS.MYRTLE{ a = (elevation + 1.8) / 2 + 0.2 } + + elseif elevation < 0.5 then -- med-high elevation : passable + return COLORS.BROWN_POD{ a = (elevation + 1.6) / 2 + 0.2 } + + elseif elevation < 1 then -- highest elevation : impassable + return COLORS.BOTTLE_GREEN{ a = (elevation + 1.0) / 2 + 0.2 } + else + log('bad elevation') + return vec4(0) + end end +function random_map(seed, do_seed_rng) + local elevation_map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, seed) + + if do_seed_rng then math.randomseed(elevation_map.seed) end + + HEX_MAP = {} + 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 + + 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 + , ((-off.y - HEX_GRID_DIMENSIONS.y/2) / HEX_GRID_DIMENSIONS.y) ^ 2)) + local color = color_at(elevation) - mask + + local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6) + + HEX_MAP[i][j] = { + elevation = elevation, + sprite = node, + tile = {} + } + + world:append(node) + end + end + + -- the center of the map in some radius is always considered 'passable' terrain and is home base + -- terraform this area to ensure it's passable + -- @NOTE no idea why the y-coord doesn't need to be transformed + local home = spiral_map(HEX_GRID_CENTER, 3) + 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) + world:append(am.circle(hex_to_pixel(vec2(hex.x, hex.y)), HEX_SIZE/2, COLORS.MAGENTA, 4)) + end + + return am.translate(WORLDSPACE_COORDINATE_OFFSET) + ^ world:tag"world" +end diff --git a/src/gui.lua b/src/gui.lua index ff62ef2..b456892 100644 --- a/src/gui.lua +++ b/src/gui.lua @@ -1,9 +1,53 @@ -local hot -local active +local hot, active = false, false +local widgets = {} -function button(x, y) - local color = (x + y) % 2 == 0 and vec4(0.4, 0.4, 0.5, 1) or vec4(0.5, 0.4, 0.4, 1) - return am.translate(x * 80, y * 80) ^ am.rect(-40, 40, 40, -40, color) +function get_widgets() return widgets end + +function register_widget(id, poll) + widgets[id] = { id = id, poll = poll } +end + +function point_in_rect(point, rect) + return point.x > rect.x1 and point.x < rect.x2 and point.y > rect.y1 and point.y < rect.y2 +end + +function set_hot(id) + if not active then hot = { id = id } end +end + +function register_button_widget(id, rect) + register_widget(id, function() + local click = false + + if active and active.id == id then + if win:mouse_released"left" then + if hot and hot.id == id then click = true end + active = false + end + elseif hot and hot.id == id then + if win:mouse_pressed"left" then active = { id = id } end + end + + if point_in_rect(win:mouse_position(), rect) then set_hot(id) end + + return click + end) +end + +function make_button_widget(id, position, dimensions, text) + local rect = am.rect( + -dimensions.x/2, + dimensions.y/2, + dimensions.x/2, + -dimensions.y/2, + vec4(1, 0, 0, 1) + ) + + register_button_widget(id, rect) + return am.group{ + rect, + am.text(text) + }:tag(id) end diff --git a/src/hexyz.lua b/src/hexyz.lua index 90dcffa..f13ff96 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -1,4 +1,5 @@ +math.round = function(n) return math.floor(n + 0.5) end --============================================================================ -- HEX CONSTANTS AND UTILITY FUNCTIONS @@ -7,15 +8,14 @@ ORIENTATION = { -- Forward & Inverse Matrices used for the Flat Orientation FLAT = { - M = mat2(3.0/2.0, 0.0, 3.0^0.5/2.0, 3.0^0.5 ), - W = mat2(2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0), + M = mat2(3.0/2.0, 0.0, 3.0^0.5/2.0, 3.0^0.5 ), + W = mat2(2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0), angle = 0.0 }, - -- Forward & Inverse Matrices used for the Pointy Orientation POINTY = { - M = mat2(3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0), - W = mat2(3.0^0.5/3.0, -1.0/3.0, 0.0, 2.0/3.0), + M = mat2(3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0), + W = mat2(3.0^0.5/3.0, -1.0/3.0, 0.0, 2.0/3.0), angle = 0.5 } } @@ -23,8 +23,9 @@ ORIENTATION = { -- whenver |orientation| appears as an argument, if it isn't provided, this is used instead. local DEFAULT_ORIENTATION = ORIENTATION.FLAT +-- whenever |size| for a hexagon appears as an argument, if it isn't provided, use this -- 'size' here is distance from the centerpoint to any vertex in pixel -local DEFAULT_HEX_SIZE = 20 +local DEFAULT_HEX_SIZE = vec2(20) -- actual width (longest contained horizontal line) of the hexagon function hex_width(size, orientation) @@ -42,8 +43,12 @@ end function hex_height(size, orientation) local orientation = orientation or DEFAULT_ORIENTATION - -- hex_width in one orientation == the height in the opposite orientation - return hex_width(size, orientation == ORIENTATION.FLAT and ORIENTATION.POINTY or ORIENTATION.FLAT) + if orientation == ORIENTATION.FLAT then + return math.sqrt(3) * size + + elseif orientation == ORIENTATION.POINTY then + return size * 2 + end end -- returns actual width and height of a hexagon given it's |size| which is the distance from the centerpoint to any vertex in pixels @@ -59,7 +64,7 @@ function hex_horizontal_spacing(size, orientation) if orientation == ORIENTATION.FLAT then return hex_width(size, orientation) * 3/4 - elseif orietnation == ORIENTATION.POINTY then + elseif orientation == ORIENTATION.POINTY then return hex_height(size, orientation) end end @@ -68,8 +73,12 @@ end function hex_vertical_spacing(size, orientation) local orientation = orientation or DEFAULT_ORIENTATION - -- hex_horizontal_spacing in one orientation == the vertical spacing in the opposite orientation - return hex_horizontal_spacing(size, orientation == ORIENTATION.FLAT and ORIENTATION.POINTY or ORIENTATION.FLAT) + if orientation == ORIENTATION.FLAT then + return hex_height(size, orientation) + + elseif orientation == ORIENTATION.POINTY then + return hex_width(size, orientation) * 3/4 + end end -- returns the distance between adjacent hexagon centers in a grid @@ -101,15 +110,13 @@ end -- Returns a vec2 Which is the Nearest |x, y| to Float Trio |x, y, z| local function hex_round(x, y, z) - local function round(n) return math.floor(n + 0.5) end - - local rx = round(x) - local ry = round(y) - local rz = round(z) or round(-x - y) + local rx = math.round(x) + local ry = math.round(y) + local rz = math.round(z) or math.round(-x - y) local xdelta = math.abs(rx - x) local ydelta = math.abs(ry - y) - local zdelta = math.abs(rz - z or round(-x - y)) + local zdelta = math.abs(rz - z or math.round(-x - y)) if xdelta > ydelta and xdelta > zdelta then rx = -ry - rz @@ -126,8 +133,8 @@ end function hex_to_pixel(hex, size, orientation) local M = orientation and orientation.M or DEFAULT_ORIENTATION.M - local x = (M[1][1] * hex[1] + M[1][2] * hex[2]) * (size and size[1] or DEFAULT_HEX_SIZE) - local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * (size and size[2] or DEFAULT_HEX_SIZE) + local x = (M[1][1] * hex[1] + M[1][2] * hex[2]) * (size and size[1] or DEFAULT_HEX_SIZE[1]) + local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * (size and size[2] or DEFAULT_HEX_SIZE[2]) return vec2(x, y) end @@ -163,11 +170,13 @@ function hex_corners(hex, size, orientation) return corners end -function hex_to_offset(hex) - return vec2(hex[1], -hex[1] - hex[2] + (hex[1] + (hex[1] % 2)) / 2) end +function hex_to_evenq(hex) + return vec2(hex.x, (-hex.x - hex.y) + (hex.x + (hex.x % 2)) / 2) +end -function offset_to_hex(off) - return vec2(off[1], off[2] - math.floor((off[1] - 1 * (off[1] % 2))) / 2) end +function evenq_to_hex(off) + return vec2(off.x, -off.x - (off.y - (off.x + (off.x % 2)) / 2)) +end --============================================================================ -- MAPS & STORAGE @@ -285,9 +294,9 @@ function rectangular_map(width, height, seed) local seed = seed or math.random(width * height) local map = {} - for i = 0, width do + for i = 0, width - 1 do map[i] = {} - for j = 0, height do + for j = 0, height - 1 do -- Begin to Calculate Noise local idelta = i / width diff --git a/src/main.lua b/src/main.lua index 203d5b5..93cd056 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,125 +1,96 @@ + math.randomseed(os.time()); math.random(); math.random(); math.random() ---[[=I==========================================================================]] +--============================================================================ -- Imports require "hexyz" require "grid" +require "mob" +require "util" - ---[[============================================================================]] +--============================================================================ -- Globals -win = am.window{ - width = 1920, - height = 1080, - resizable = false -} ---[[============================================================================]] --- Local 'Globals' -local home - - -function poll_mouse() - if win:mouse_position().x > -268 then -- mouse is inside game map - - local hex = pixel_to_hex(win:mouse_position() - vec2(-278, -318)) - local off = hex_to_offset(hex) - - -- check if cursor location outside of map bounds - if off.x <= 1 or -off.y <= 1 or off.x >= 46 or -off.y >= 32 then - win.scene"coords".text = "" - - else - if win:mouse_down"left" then - if map[hex.x][hex.y] <= -0.5 or map[hex.x][hex.y] >= 0.5 then - - else - map[hex.x][hex.y] = 2 - win.scene"world":append(am.circle(hex_to_pixel(hex), CELL_SIZE, COLORS.BLACK, 6)) - end - end - win.scene"coords".text = string.format("%2d,%2d", off.x, -off.y) - win.scene"hex_cursor".center = hex_to_pixel(hex) + vec2(-278, -318) - end - else -- mouse is over background bar, (or outside window!) - if win:key_pressed"escape" then - init() - end - end -end +win = am.window{ width = 1920, height = 1080 } -function update_score() - win.scene"score".text = string.format("SCORE: %.2f", am.current_time()) +function mask() + return am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRANSPARENT):tag"mask" end -function main_action(main_scene) - update_score() - poll_mouse() +function get_menu_for_tile(x, y, tile) + local pos = hex_to_pixel(vec2(x, y)) + WORLDSPACE_COORDINATE_OFFSET + return am.translate(pos) ^ am.group{ + am.rect(-50, -50, 50, 50, COLORS.TRANSPARENT), + make_button_widget(x .. y, pos, vec2(100, 37), "close") + } end -function game_init() - local score = am.translate(win.left, win.top - 50) ^ am.text("", "left"):tag"score" - local coords = am.translate(win.right, win.top - 50) ^ am.text("", "right"):tag"coords" - local hex_cursor = am.circle(vec2(win.left, win.top), CELL_SIZE, vec4(0.4), 6):tag"hex_cursor" - local curtain = am.rect(win.left, win.top, win.right, win.bottom, COLORS.BLUE_STONE):tag"curtain" +function invoke_tile_menu(x, y, tile) + win.scene:append(get_menu_for_tile(x, y, tile)) +end - local main_scene = am.group{ - random_map(), - score, - coords, - curtain, - hex_cursor - } +function game_action(scene) + local time = am.current_time() - main_scene:action(am.series - { - am.tween(curtain, 0.8, { x2 = win.left }, am.ease.bounce), - main_action - }) + local mouse = win:mouse_position() + local hex = pixel_to_hex(mouse - WORLDSPACE_COORDINATE_OFFSET) + local _off = hex_to_evenq(hex) + local off = _off{ y = -_off.y } - vec2(math.floor(HEX_GRID_WIDTH/2) + , math.floor(HEX_GRID_HEIGHT/2)) + local off2 = evenq_to_hex(_off) + local tile = get_tile(hex.x, hex.y) - win.scene = main_scene -end + if tile and win:mouse_pressed"left" then + log(tile) + --invoke_tile_menu(hex.x, hex.y, tile) + end -function draw_menu() - local map = hexagonal_map(15, 9) - local backdrop = am.group() + if win:key_pressed"f1" then end - for i,_ in pairs(map) do - for j,e in pairs(map[i]) do - backdrop:append(am.circle(hex_to_pixel(vec2(i, j)), 11, color_at(e), 6)) + for wid,widget in pairs(get_widgets()) do + if widget.poll() then + log('we clicked button with id %s!', wid) end end - local title_text = am.group - { - am.translate(0, 200) ^ am.scale(5) ^ am.text("hexyz", COLORS.WHITE, "right"), - am.translate(0, 130) ^ am.scale(4) ^ am.text("a tower defense", COLORS.WHITE, 1), - am.circle(vec2(0), 100, vec4(0.6), 6):tag"button", am.scale(4) ^ am.text("START", COLORS.BLACK) - } + do_mob_updates() + do_mob_spawning() + + -- draw stuff + win.scene"hex_cursor".center = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET + win.scene"score".text = string.format("SCORE: %.2f", time) + win.scene"coords".text = string.format("%d,%d", off.x, off.y) + win.scene"rev".text = string.format("%d,%d", off2.x, off2.y) +end - win.scene = am.group - { - backdrop, - title_text +function game_scene() + local score = am.translate(win.left + 10, win.top - 20) ^ am.text("", "left"):tag"score" + local coords = am.translate(win.right - 10, win.top - 20) ^ am.text("", "right"):tag"coords" + local coords2 = am.translate(win.right - 10, win.top - 40) ^ am.text("", "right"):tag"rev" + local hex_cursor = am.circle(vec2(-6969), HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor" + + local curtain = am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRUEBLACK) + curtain:action(coroutine.create(function() + am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola))) + win.scene:remove(curtain) + end)) + + local scene = am.group{ + random_map(), + curtain, + hex_cursor, + score, + coords, + coords2 } - :action(function(self) - local mouse = win:mouse_position() - if math.length(mouse) < 100 then - self"button":action(am.series - { - am.tween(0.1, { color = COLORS.WHITE }), - am.tween(0.1, { color = vec4(0.6) }) - }) - - if win:mouse_pressed"left" then - game_init() - end - end - end) + + scene:action(game_action) + + return scene end function init() - draw_menu() + win.scene = am.scale(1) ^ game_scene() end init() diff --git a/src/mob.lua b/src/mob.lua index 0cfa0b6..c127925 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -1,79 +1,108 @@ +MOBS = {} -MOB_HURTBOX_RADIUS = 4 - - --- determines when, where, and how often to spawn mobs. -function spawner(world) - local SPAWN_CHANCE = 25 - if math.random(SPAWN_CHANCE) == 1 then -- chance to spawn - local spawn_position - repeat - -- ensure we spawn on an random tile along the map's edges - local x,y = math.random(46), math.random(33) - if math.random() < 0.5 then - x = math.random(0, 1) * 46 - else - y = math.random(0, 1) * 33 - end - spawn_position = offset_to_hex(vec2(x, y)) - - -- ensure that we spawn somewhere that is passable: mid-elevation - local e = map[spawn_position.x][spawn_position.y] - until e and e < 0.5 and e > -0.5 - - local mob = am.translate(-278, -318) ^ am.circle(hex_to_pixel(spawn_position), MOB_HURTBOX_RADIUS) - world:append(mob"circle":action(coroutine.create(live))) - end +-- check if a |tile| is passable by |mob| +function can_pass_through(mob, tile) + return tile and tile.elevation and tile.elevation < 0.5 and tile.elevation > -0.5 end --- this function is the coroutine that represents the life-cycle of a mob. -function live(mob) - local dead = false +function get_path(mob, starting_hex, goal_hex) + local moves = {} local visited = {} - visited[mob.center.x] = {}; visited[mob.center.x][mob.center.y] = true + visited[starting_hex.x] = {} + visited[starting_hex.x][starting_hex.y] = true - -- begin life repeat - local neighbours = hex_neighbours(pixel_to_hex(mob.center)) + local neighbours = hex_neighbours(pixel_to_hex(mob.position)) local candidates = {} -- get list of candidates: hex positions to consider moving to. - for _,h in pairs(neighbours) do - - local e - if map[h.x] then - e = map[h.x][h.y] - end - - if e and e < 0.5 and e > -0.5 then - if visited[h.x] then - if not visited[h.x][h.y] then - table.insert(candidates, h) - end + for _,neighbour in pairs(neighbours) do + if can_pass_through(mob, get_tile(neighbour.x, neighbour.y)) then + if not (visited[neighbour.h] and visited[neighbour.x][neighbour.y]) then + table.insert(candidates, neighbour) else - table.insert(candidates, h) + --table.insert(candidates, neighbour) end end end - -- choose where to move. manhattan distance closest to goal is chosen. + -- choose where to move local move = candidates[1] - for _,h in pairs(candidates) do - if math.distance(h, home.center) < math.distance(move, home.center) then - move = h + for _,hex in pairs(candidates) do + if math.distance(hex, goal_hex) < math.distance(move, goal_hex) then + move = hex end end - if not move then print("can't find anywhere to move to"); return - end -- bug + if move then + table.insert(moves, move) + visited[move.x] = {} + visited[move.x][move.y] = true + end + + --if move == goal then log('made it!') return end + until move == goal_hex + + return moves +end + +function get_spawn_hex(mob) + local spawn_hex + repeat + -- ensure we spawn on an random tile along the map's edges + local roll = math.random(HEX_GRID_WIDTH * 2 + HEX_GRID_HEIGHT * 2) - 1 + local x, y + + if roll < HEX_GRID_HEIGHT then + x, y = 0, roll + + elseif roll < (HEX_GRID_WIDTH + HEX_GRID_HEIGHT) then + x, y = roll - HEX_GRID_HEIGHT, HEX_GRID_HEIGHT - 1 + + elseif roll < (HEX_GRID_HEIGHT * 2 + HEX_GRID_WIDTH) then + x, y = HEX_GRID_WIDTH - 1, roll - HEX_GRID_WIDTH - HEX_GRID_HEIGHT + + else + x, y = roll - (HEX_GRID_HEIGHT * 2) - HEX_GRID_WIDTH, 0 + end + + -- @NOTE negate 'y' because hexyz algorithms assume south is positive, in amulet north is positive + spawn_hex = evenq_to_hex(vec2(x, -y)) + local tile = HEX_MAP[spawn_hex.x][spawn_hex.y] + + until can_pass_through(mob, tile) + + return spawn_hex +end + +function make_mob() + local mob = {} + + local spawn_hex = get_spawn_hex(mob) + log(spawn_hex) + local spawn_position = hex_to_pixel(spawn_hex) + WORLDSPACE_COORDINATE_OFFSET - local speed = map[move.x][move.y] ^ 2 + 0.5 - am.wait(am.tween(mob, speed, {center=hex_to_pixel(move)})) - visited[move.x] = {}; visited[move.x][move.y] = true - if move == home.center then dead = true end - until dead - win.scene:remove(mob) + mob.position = spawn_position + --mob.path = get_path(mob, spawn_hex, HEX_GRID_CENTER) + mob.sprite = am.circle(spawn_position, 18, COLORS.WHITE, 4) + win.scene:append(mob.sprite) + + return mob +end + +local SPAWN_CHANCE = 25 +function do_mob_spawning() + if win:key_pressed"space" then + --if math.random(SPAWN_CHANCE) == 1 then + table.insert(MOBS, make_mob()) + end +end + +function do_mob_updates() + for _,mob in pairs(MOBS) do + + end end diff --git a/src/util.lua b/src/util.lua new file mode 100644 index 0000000..b36c3eb --- /dev/null +++ b/src/util.lua @@ -0,0 +1,14 @@ + +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 +