diff --git a/README.md b/README.md index f62d3ab..8766519 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,21 @@ 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. +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. ## CONVENTIONS & TERMINOLOGY -If you have read amit's guide to hexagon grid, a lot of the terminology will be familiar to you - I utilize many conventions he does in his guide. That being said, -because so many similar kinds of data structures with different goals are used in this library it can be hard to remember precisely what they all refer to. -The following table shows what each table/vector/array refers to in the code: +If you have read amit's guide to hexagon grids, a lot of the terminology will be familiar to you - I utilize many conventions he does in his guide. That being said, I use some he doesn't, and because so many similar kinds of data structures with different goals are used in this library it can be hard to remember precisely what they all refer to. The following table shows what each table/vector/array refers to in the code: | NAME | REFERS TO | | ---- | ------------------------------------------------------------ | | hex | xyz, *vector* used for most tasks, with constraint x+y+z=0 | | pix | xy, *vector* true screen pixel coordinates | | off | xy, 'offset', *vector* used for UI implementations | -| map | xy, *table* of unit hexagon centerpoints arranged in a shape | +| map | *table* of unit hexagon centerpoints arranged in a shape | - * note that 'hex' here is a catch-all term for cube/axial, as they can often be used interchangeably. +* note that 'hex' here is a catch-all term for cube/axial coordinates, as they can often be used interchangeably. ## MAPS & MAP STORAGE @@ -36,7 +33,7 @@ The storage system used is based on the map shape - see chart: | hexagonal | unordered, hash-like | vec2(i, j) | simplex noise | | triangular | unordered, hash-like | vec2(i, j) | simplex noise | - * note that a spiral map is just a hexagonal one with a particular order. +* note that a spiral map is just a hexagonal one with a particular order. The noise values on the hashmaps are seeded. You can optionally provide a seed after the map's dimensions as an argument, otherwise it's a random seed. @@ -44,8 +41,6 @@ The noise values on the hashmaps are seeded. You can optionally provide a seed a ## RESOURCES * [Hex Map 1](https://catlikecoding.com/unity/tutorials/hex-map/) - unity tutorial for hexagon grids with some useful generalized math. - * [Hexagonal Grids](https://redblobgames.com/grids/hexagons) - THE resource on hexagonal grids on the internet. - * [Amulet Docs](http://amulet.xyz/doc) - amulet documentation. diff --git a/src/hexyz.lua b/src/hexyz.lua index 8ef8eee..f29dc55 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -1,8 +1,6 @@ -- Rounds Numbers. -local function round(n) - return n % 1 >= 0.5 and math.ceil(n) or math.floor(n) -end +local function round(n) return n % 1 >= 0.5 and math.ceil(n) or math.floor(n) end --[[============================================================================ ----- HEX CONSTANTS AND UTILITY FUNCTIONS ----- @@ -13,59 +11,53 @@ function hex_equals(a, b) return a[1] == b[1] and a[2] == b[2] end function hex_not_equals(a, b) return not hex_equals(a, b) end --- All Possible Vector Directions from a Given Hex by Edge -local HEX_DIRECTIONS = {vec2( 0 , 1), vec2( 1 , 0), vec2( 1 , -1), - vec2( 0 , -1), vec2(-1 , 0), vec2(-1 , 1)} +-- All Non-Diagonal Vector Directions from a Given Hex by Edge +HEX_DIRECTIONS = {vec2( 1 , -1), vec2( 1 , 0), vec2(0 , 1), + vec2(-1 , 1), vec2(-1 , 0), vec2(0 , -1)} --- Return Hex Vector Direction via Integer Index |direction|. +-- Return Hex Vector Direction via Integer Index |direction| function hex_direction(direction) - return HEX_DIRECTIONS[(direction % 6) % 6 + 1] -end + return HEX_DIRECTIONS[(direction % 6) % 6 + 1] end --- Return Hexagon Adjacent to |hex| in Integer Index |direction|. +-- Return Hexagon Adjacent to |hex| in Integer Index |direction| function hex_neighbour(hex, direction) - return hex + HEX_DIRECTIONS[(direction % 6) % 6 + 1] -end - - --- Return Hex 60deg away to the Left; Counter-Clockwise -function hex_rotate_left(hex) - return vec2(hex.x + hex.y, -hex.x) -end + return hex + HEX_DIRECTIONS[(direction % 6) % 6 + 1] end --- Return Hex 60deg away to the Right; Clockwise -function hex_rotate_right(hex) - return vec2(-hex.y, hex.x + hex.y) +-- Collect All 6 Neighbours in a Table +function hex_neighbours(hex) + local neighbours = {} + for i = 1, 6 do + table.insert(neighbours, hex_neighbour(hex, i)) + end + return neighbours end --- NOT a General 3D Vector Round - Only Returns a vec2! +-- Returns a vec2 Which is the Nearest |x, y| to Float Trio |x, y, z| local function hex_round(x, y, z) - local rx = round(x) - local ry = round(y) - local rz = round(z) or 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)) - - if xdelta > ydelta and xdelta > zdelta then - rx = -ry - rz - elseif ydelta > zdelta then - ry = -rx - rz - else - rz = -rx - ry - end - return vec2(rx, ry) + local rx = round(x) + local ry = round(y) + local rz = round(z) or 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)) + + if xdelta > ydelta and xdelta > zdelta then + rx = -ry - rz + elseif ydelta > zdelta then + ry = -rx - rz + else + rz = -rx - ry end + return vec2(rx, ry) end --[[==========================================================================-- ----- ORIENTATION & LAYOUT ----- ============================================================================]]-- - -- Forward & Inverse Matrices used for the Flat Orientation local 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), @@ -76,266 +68,221 @@ local 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), angle = 0.5} - -- Hex to Screen -- Orientation Must be Either POINTY or FLAT function hex_to_pixel(hex, size, orientation_M) - local M = orientation_M or FLAT.M + local M = orientation_M or FLAT.M - local x = (M[1][1] * hex[1] + M[1][2] * hex[2]) * size[1] - local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * size[2] + local x = (M[1][1] * hex[1] + M[1][2] * hex[2]) * size[1] + local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * size[2] - return vec2(x, y) + return vec2(x, y) end -- Screen to Hex -- Orientation Must be Either POINTY or FLAT function pixel_to_hex(pix, size, orientation_W) - local W = orientation_W or FLAT.W + local W = orientation_W or FLAT.W - local pix = pix / size + local pix = pix / size - local x = W[1][1] * pix[1] + W[1][2] * pix[2] - local y = W[2][1] * pix[1] + W[2][2] * pix[2] + local x = W[1][1] * pix[1] + W[1][2] * pix[2] + local y = W[2][1] * pix[1] + W[2][2] * pix[2] - return hex_round(x, y, -x - y) + return hex_round(x, y, -x - y) end -- TODO test, learn am.draw function hex_corner_offset(corner, size, orientation_angle) - local angle = 2.0 * math.pi * orientation_angle or FLAT.angle + corner / 6 - return vec2(size[1] * math.cos(angle), - size[2] * math.sin(angle)) + local angle = 2.0 * math.pi * orientation_angle or FLAT.angle + corner / 6 + return vec2(size[1] * math.cos(angle), size[2] * math.sin(angle)) end --- TODO this thing +-- TODO test this thing function hex_corners(hex, size, orientation) - local corners = {} + local corners = {} + local center = hex_to_pixel(hex, size, orientation) + for i = 0, 5 do + local offset = hex_corner_offset(i, size, orientation) + table.insert(corners, center + offset) + end + return corners end --- Offset Coordinates are Useful for UI-Implementations +-- Offset Coordinates Look Nice / are Useful for UI-Implementations function hex_to_offset(hex) - return vec2(hex[1], -hex[1] - hex[2] + (hex[1] + (hex[1] % 2)) / 2) -end + return vec2(hex[1], -hex[1]-hex[2] + (hex[1] + (hex[1] % 2)) / 2) end --- back to cube coordinates +-- ... Back to Cube Coordinates function offset_to_hex(off) - return vec2(off[1], off[2] - off[1] * (off[1] % 2) / 2) -end + return vec2(off[1], off[2] - math.floor((off[1] - 1 * (off[1] % 2))) / 2) end + --[[============================================================================ ----- MAPS & STORAGE ----- - - You are not to draw using the coordinates stored in your map. - You are to draw using the hex_to_pixel of those coordinates. - - If you wish to draw a hexagon to the screen, you must first use hex_to_pixel - to retrieve the center of the hexagon on each set of cube coordinates stored - in your map. Then, depending on how you are going to draw, either call - am.circle with |sides| = 6, or gather the vertices with hex_corners and - use am.draw - TODO, haven't used am.draw yet. - - Maps have metatables containing information about their dimensions, and - seed (if applicable), so you can retrieve information about maps after they - are created. - - ----- NOISE ----- - To simplify terrain generation, unordered, hash-like maps automatically - calculate and store seeded simplex noise as their values. You can provide - a seed if you wish. The default is a randomized seed. - - TODO Pointy Hex testing and support. - ============================================================================]]-- - -- Returns Ordered Ring-Shaped Map of |radius| from |center| function ring_map(center, radius) - local map = {} - - local walk = center + HEX_DIRECTIONS[6] * radius - - for i = 1, 6 do - for j = 1, radius do - table.insert(map, walk) - walk = hex_neighbour(walk, i) - end - end - setmetatable(map, {__index={center=center, radius=radius}}) - return map + local map = {} + + local walk = center + HEX_DIRECTIONS[6] * radius + + for i = 1, 6 do + for j = 1, radius do + table.insert(map, walk) + walk = hex_neighbour(walk, i) + end + end + setmetatable(map, {__index={center=center, radius=radius}}) + return map end -- Returns Ordered Spiral Hexagonal Map of |radius| Rings from |center| function spiral_map(center, radius) - local map = {center} + local map = {center} + + for i = 1, radius do + table.append(map, ring_map(center, i)) + end + setmetatable(map, {__index={center=center, radius=radius}}) + return map +end - for i = 1, radius do - table.append(map, ring_map(center, i)) - end - setmetatable(map, {__index={center=center, radius=radius}}) - return map + +-- Used to Retrieve Noise Values in Hashmap; t[vec2(x, y)] will always find nil +function hash_retrieve(map, hex) + for h,n in pairs(map) do + if hex_equals(hex, h) then + return n + end + end + return nil end -- Returns Unordered Parallelogram-Shaped Map of |width| and |height| with Simplex Noise function parallelogram_map(width, height, seed) - local seed = seed or math.random(width * height) - - local map = {} - for i = 0, width do - for j = 0, height do - - -- Calculate Noise - local idelta = i / width - local jdelta = j / height - local noise = 0 - - for oct = 1, 6 do - local f = 1/4^oct - local l = 2^oct - local pos = vec2(idelta + seed * width, jdelta + seed * height) - noise = noise + f * math.simplex(pos * l) - end - map[vec2(i, j)] = noise -- Straightforward Iteration Produces a Parallelogram - end - end - setmetatable(map, {__index={width=width, height=height, seed=seed}}) - return map + local seed = seed or math.random(width * height) + + local map = {} + for i = 0, width do + for j = 0, height do + + -- Calculate Noise + local idelta = i / width + local jdelta = j / height + local noise = 0 + + for oct = 1, 6 do + local f = 1/4^oct + local l = 2^oct + local pos = vec2(idelta + seed * width, jdelta + seed * height) + noise = noise + f * math.simplex(pos * l) + end + map[vec2(i, j)] = noise + end + end + setmetatable(map, {__index={width=width, height=height, seed=seed}}) + return map end --- Returns Unordered Triangular Map of |size| with Simplex Noise +-- Returns Unordered Triangular (Equilateral) Map of |size| with Simplex Noise function triangular_map(size, seed) - local seed = seed or math.random(size) - - local map = {} - for i = 0, size do - for j = size - i, size do - - -- Generate Noise - local idelta = i / size - local jdelta = j / size - local noise = 0 - - for oct = 1, 6 do - local f = 1/3^oct - local l = 2^oct - local pos = vec2(idelta + seed * size, jdelta + seed * size) - noise = noise + f * math.simplex(pos * l) - end - - map[vec2(i, j)] = noise - end - end - setmetatable(map, {__index={size=size, seed=seed}}) - return map + local seed = seed or math.random(size * math.cos(size) / 2) + + local map = {} + for i = 0, size do + for j = size - i, size do + + -- Generate Noise + local idelta = i / size + local jdelta = j / size + local noise = 0 + + for oct = 1, 6 do + local f = 1/3^oct + local l = 2^oct + local pos = vec2(idelta + seed * size, jdelta + seed * size) + noise = noise + f * math.simplex(pos * l) + end + map[vec2(i, j)] = noise + end + end + setmetatable(map, {__index={size=size, seed=seed}}) + return map end -- Returns Unordered Hexagonal Map of |radius| with Simplex Noise function hexagonal_map(radius, seed) - local seed = seed or math.random(radius * 2 + 1) - - local map = {} - for i = -radius, radius do - local j1 = math.max(-radius, -i - radius) - local j2 = math.min(radius, -i + radius) - - for j = j1, j2 do - - -- Calculate Noise - local idelta = i / radius - local jdelta = j / radius - local noise = 0 - - for oct = 1, 6 do - - local f = 2/3^oct -- NOTE, for some reason, I found 2/3 produces better looking noise maps. As far as I am aware, this is weird. - local l = 2^oct - local pos = vec2(idelta + seed * radius, jdelta + seed * radius) - - noise = noise + f * math.simplex(pos * l) - end - map[vec2(i, j)] = noise - end - end - setmetatable(map, {__index={radius=radius, seed=seed}}) - return map + local seed = seed or math.random(radius * 2 * math.pi) + + local map = {} + local mt = {__index={radius=radius, seed=seed}} + for i = -radius, radius do + local j1 = math.max(-radius, -i - radius) + local j2 = math.min(radius, -i + radius) + + for j = j1, j2 do + + -- Calculate Noise + local idelta = i / radius + local jdelta = j / radius + local noise = 0 + + for oct = 1, 6 do + local f = 2/3^oct + local l = 2^oct + local pos = vec2(idelta + seed * radius, jdelta + seed * radius) + noise = noise + f * math.simplex(pos * l) + end + map[vec2(i, j)] = noise + end + end + setmetatable(map, mt) + return map end -- Returns Unordered Rectangular Map of |width| and |height| with Simplex Noise function rectangular_map(width, height, seed) - local seed = seed or math.random(width * height) - - local map = {} - for i = 0, width do - for j = 0, height do - - -- Calculate Noise - local idelta = i / width - local jdelta = j / height - local noise = 0 - - for oct = 1, 6 do - - local f = 2/3^oct - local l = 2^oct - local pos = vec2(idelta + seed * width, jdelta + seed * height) - - noise = noise + f * math.simplex(pos * l) - end - -- Store Hex in the Map Paired with its Associated Noise Value - map[vec2(i, j - math.floor(i/2))] = noise - end - end - setmetatable(map, {__index={width=width, height=height, seed=seed}}) - return map + local seed = seed or math.random(width * height) + + local map = {} + local mt = {__index={width=width, height=height, seed=seed}} + + for i = 0, width do + for j = 0, height do + + -- Begin to Calculate Noise + local idelta = i / width + local jdelta = j / height + local noise = 0 + + for oct = 1, 6 do + local f = 2/3^oct + local l = 2^oct + local pos = vec2(idelta + seed * width, jdelta + seed * height) + noise = noise + f * math.simplex(pos * l) + end + map[vec2(i, j - math.floor(i/2))] = noise + end + end + setmetatable(map, mt) + return map end --[[==========================================================================-- ----- PATHFINDING ----- ============================================================================]]-- +-- big ol' TODO --- first try -function search(map, start) - local neighbours - for i = 1, 6 do - neighbours[#neighbours + 1] = hex_neighbour(start, i) - end -end - - - - - - - - - --- -function breadth_first_search(map, start) - local frontier = {start} - - local visited = {start = true} - - while next(frontier) ~= nil do - local current = next(frontier) - local neighbours - for i = 1, 6 do - neighbours[#neighnours + 1] = hex_neighbour(current, i) - end - for _,n in neighbours do - if visited[n] ~= true then - visited[n] = true - end - end - end -end diff --git a/src/main.lua b/src/main.lua index 43a379b..906b215 100644 --- a/src/main.lua +++ b/src/main.lua @@ -3,297 +3,136 @@ require"hexyz" math.randomseed(os.time()) ---[[============================================================================ - ----- GLOBALS ----- -============================================================================]]-- - --- Ethan Schoonover Solarized Colorscheme w/ Eigengrau -local EIGENGRAU = vec4(0.08, 0.08, 0.11, 1) -local BASE03 = vec4(0 , 0.16, 0.21, 1) -local BASE02 = vec4(0.02, 0.21, 0.25, 1) -local BASE01 = vec4(0.34, 0.43, 0.45, 1) -local BASE00 = vec4(0.39, 0.48, 0.51, 1) -local BASE0 = vec4(0.51, 0.58, 0.58, 1) -local BASE1 = vec4(0.57, 0.63, 0.63, 1) -local BASE2 = vec4(0.93, 0.90, 0.83, 1) -local BASE3 = vec4(0.99, 0.96, 0.89, 1) -local YELLOW = vec4(0.70, 0.53, 0, 1) -local ORANGE = vec4(0.79, 0.29, 0.08, 1) -local RED = vec4(0.86, 0.19, 0.18, 1) -local MAGENTA = vec4(0.82, 0.21, 0.50, 1) -local VIOLET = vec4(0.42, 0.44, 0.76, 1) -local BLUE = vec4(0.14, 0.54, 0.82, 1) -local CYAN = vec4(0.16, 0.63, 0.59, 1) -local GREEN = vec4(0.52, 0.60, 0 , 1) - local win = am.window { - -- Base Resolution = 3/4 * WXGA standard 16:10 - width = 1280 * 3/4, -- 960px - height = 800 * 3/4, -- 600px - - clear_color = EIGENGRAU + -- Base Resolution = 3/4 * WXGA standard 16:10 + width = 1280 * 3/4, -- 960px + height = 800 * 3/4, -- 600px } -local curtain = am.rect(win.left, win.top, win.right, win.bottom - , vec4(0, 0, 0, 0.7)) -- Color +local map +local world +local home +local home_node ---local log = io.open(os.date("log %c.txt"), "w") ---[[============================================================================ - ----- ROUTINES ----- -============================================================================]]-- +function find_home() + home = spiral_map(vec2(23, 4), 2) + home_node = am.group() + repeat + local happy = true --- Template for Buttons -function rect_button(text) - return am.group - { - am.rect(-150, -20, 150, 20, vec4(0)):action(am.tween(1, {color=BASE2{a=0.4}})), - am.text(text, vec4(0)):action(am.tween(1, {color=BASE02})) - } -end + for i,h in pairs(home) do + local elevation = hash_retrieve(map, h) --- -function text_field(tag) - local field = am.text(""):action(function() - - -- Special Cases - if win:key_pressed("backspace") then - field.text = field.text:sub(1, -2) - - elseif win:key_pressed("enter") then - field.text = "" - - -- I Only Use This To Get Seeds At The Moment - elseif win:key_pressed("0") then field.text = field.text .. 0 - elseif win:key_pressed("1") then field.text = field.text .. 1 - elseif win:key_pressed("2") then field.text = field.text .. 2 - elseif win:key_pressed("3") then field.text = field.text .. 3 - elseif win:key_pressed("4") then field.text = field.text .. 4 - elseif win:key_pressed("5") then field.text = field.text .. 5 - elseif win:key_pressed("6") then field.text = field.text .. 6 - elseif win:key_pressed("7") then field.text = field.text .. 7 - elseif win:key_pressed("8") then field.text = field.text .. 8 - elseif win:key_pressed("9") then field.text = field.text .. 9 - end - end) - return field:tag(tag) -end + 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, 2) + home_node = am.group() --- Tween Multiple Nodes of the Same Type -function multitween(nodes, time, values) - for _,node in pairs(nodes) do - node:action(am.tween(time, values)) - end + else + local center = hex_to_pixel(h, vec2(11)) + local color = vec4(0.5) + local node = am.circle(center, 4, color, 4) + home_node:append(node) + end + end + until happy + return home_node end --- Returns Appropriate Tile Color for Specified Elevation function color_at(elevation) - if elevation < -0.5 then -- Lowest Elevation : Impassable - return vec4(0.10, 0.30, 0.40, (elevation + 1.2) / 2) - - elseif elevation < 0 then -- Med-Low Elevation : Passable - return vec4(0.10, 0.25, 0.10, (elevation + 1.8) / 2) - - elseif elevation < 0.5 then -- Med-High Elevation : Passable - return vec4(0.25, 0.20, 0.10, (elevation + 1.6) / 2) - - else -- Highest Elevation : Impassable - return vec4(0.15, 0.30, 0.20, (elevation + 1.0) / 2) - end -end - - --- Handler for Scoring -function keep_score() - local offset = am.current_time() - - win.scene:action(function() - win.scene:remove("time") + if elevation < -0.5 then -- Lowest Elevation : Impassable + return vec4(0.10, 0.30, 0.40, (elevation + 1.4) / 2 + 0.2) - local time_str = string.format("%.2f", am.current_time() - offset) - - win.scene:append(am.translate(-374, win.top - 10) - ^ am.text(time_str):tag"time") - end) -end + elseif elevation < 0 then -- Med-Low Elevation : Passable + return vec4(0.10, 0.25, 0.10, (elevation + 1.8) / 2 + 0.2) + elseif elevation < 0.5 then -- Med-High Elevation : Passable + return vec4(0.25, 0.20, 0.10, (elevation + 1.6) / 2 + 0.2) --- In-Game Pause Menu -function pause() - win.scene:append(curtain:tag"curtain") - win.scene:append(am.group - { - am.rect(-200, 150, 200, -150, BASE03{a=0.9}) - }:tag"menu") + elseif elevation < 1 then -- Highest Elevation : Impassable + return vec4(0.15, 0.30, 0.20, (elevation + 1.0) / 2 + 0.2) - -- Event Handler - win.scene:action(function() - - -- Back to Main Game - if win:key_pressed("escape") then - win.scene:remove("curtain"); win.scene:remove("menu") - game_action(); return true - end - end) + else + return vec4(0.5, 0.5, 0.5, 1) + end end - - - --- Returns Node function draw_(map) - local world = am.group():tag"world" - - for hex,elevation in pairs(map) do - - local off = hex_to_offset(hex) - local mask = vec4(0, 0, 0, math.max(((off.x - 23) / 45) ^ 2, - ((-off.y - 16) / 31) ^ 2)) - - local color = color_at(elevation) - mask - local node = am.circle(hex_to_pixel(hex, vec2(11)), 11, vec4(0), 6) - node:action(am.tween(0.3, {color = color})) -- fade in - - world:append(node:tag(tostring(hex))) -- unique identifier - end - return world -end - - --- -function ghandler(game) - win.scene:remove("coords"); win.scene:remove("selected") - - -- Pause Game - if win:key_pressed("escape") then - pause(); return true - end - - local hex = pixel_to_hex(win:mouse_position(), vec2(11)) - vec2(10, 10) - local mouse = hex_to_offset(hex) - - -- Check if Mouse is Within Bounds of Game Map - if mouse.x > 0 and mouse.x < map.width and - -mouse.y > 0 and -mouse.y < map.height then -- North is Positive - - local text = am.text(string.format("%d,%d", mouse.x, mouse.y)) - game:append(am.translate(450, 290) ^ text:tag"coords") - - local color = vec4(0, 0, 0, 0.4) - local pix = hex_to_pixel(hex, vec2(11)) - game:append(am.circle(pix, 11, color, 6):tag"selected") - end -end - - --- Begin Game - From Seed or Random Seed (if ommitted) -function game_init(seed) - local game = am.group() + world = am.group() - game:append(am.rect(win.left, win.top, -268, win.bottom, BASE03)) + for hex,elevation in pairs(map) do + local off = hex_to_offset(hex) + local mask = vec4(0, 0, 0, math.max(((off.x - 23.5) / 46) ^ 2, + ((-off.y - 16.5) / 32) ^ 2)) + local color = color_at(elevation) - mask + local node = am.circle(hex_to_pixel(hex, vec2(11)), 11, vec4(0), 6) + :action(am.tween(5, {color = color}, am.ease.out(am.ease.hyperbola))) + world:append(node:tag(tostring(hex))) + end + world:append(find_home()) - - map = rectangular_map(45, 31, seed) - game:action(ghandler) - win.scene = am.group(am.translate(-268, -300) ^ draw_(map), game) + return am.translate(-278, -318) ^ world:tag"world" end --- Title Action -function thandler(title) - local mouse = win:mouse_position() - - if mouse.x > -150 and mouse.x < 150 then - - -- Button 1 - if mouse.y > -70 and mouse.y < -30 then - - title"button1""rect":action(am.tween(0.5, {color = BASE2{a=1}})) - - if win:mouse_pressed("left") then - win.scene:action(am.play(am.sfxr_synth(57784609))) - game_init(map.seed) - return true - end - - -- Button 2 - elseif mouse.y > -120 and mouse.y < -80 then - - title"button2""rect":action(am.tween(0.5, {color = BASE2{a=1}})) - - if win:mouse_pressed("left") then - win.scene:action(am.play(am.sfxr_synth(91266909))) +function spawner(world) + + if math.random(10) == 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) * 47 + 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 = hash_retrieve(map, spawn_position) + until e and e < 0.5 and e > -0.5 + + local mob = am.circle(hex_to_pixel(spawn_position, vec2(11)), 4, vec4(1), 6) + :action(coroutine.create(function(mob) + local dead = false + repeat + local neighbours = hex_neighbours(pixel_to_hex(mob.center, vec2(11))) + local candidates = {} + for _,h in pairs(neighbours) do + + local e = hash_retrieve(map, h) + if e and e < 0.5 and e > -0.5 then + table.insert(candidates, h) + end end - - -- Button 3 - elseif mouse.y > -170 and mouse.y < -130 then - - title"button3""rect":action(am.tween(0.5, {color = BASE2{a=1}})) - - if win:mouse_pressed("left") then - local synth_seed = math.random(100000000) - win.scene:action(am.play(am.sfxr_synth(synth_seed))) - print(synth_seed) - end - - -- Button 4 - elseif mouse.y > -220 and mouse.y < -180 then - - title"button4""rect":action(am.tween(0.5, {color = BASE2{a=1}})) - - if win:mouse_pressed("left") then - win.scene:action(am.play(am.sfxr_synth(36002209))) - end - else - multitween({title"button1""rect", title"button2""rect", - title"button3""rect", title"button4""rect"}, - 0.5, {color = BASE2{a=0.4}}) - end - else - multitween({title"button1""rect", title"button2""rect", - title"button3""rect", title"button4""rect"}, - 0.5, {color = BASE2{a=0.4}}) - end + local move = candidates[math.random(#candidates)] + am.wait(am.tween(mob, 1, {center=hex_to_pixel(move, vec2(11))})) + until dead + end)) + world:append(mob) + end end --- Setup and Display Title Screen -function title_init() - local title = am.group() - - title:append(am.translate(0, -50) ^ rect_button("NEW SCENARIO"):tag"button1") - title:append(am.translate(0, -100) ^ rect_button("LOREMIPSUM"):tag"button2") - title:append(am.translate(0, -150) ^ rect_button("FUN BUTTON"):tag"button3") - title:append(am.translate(0, -200) ^ rect_button("SETTINGS"):tag"button4") - - map = hexagonal_map(45) - backdrop = am.scale(1.3) ^ am.rotate(0) ^ draw_(map) - - backdrop:action(function() - backdrop"rotate".angle = am.frame_time / 40 + 45 - end) - - -- Event Handler - title:action(thandler) - - win.scene = am.group(backdrop, title) -end - +function game_init(seed) + local bg = am.rect(-480, 300, -268, -300, vec4(0.12, 0.3, 0.3, 1)) + map = rectangular_map(46, 33) --- Alias -function init() - title_init() + math.randomseed(map.seed) + win.scene = am.group(draw_(map):action(spawner), bg) end ---============================================================================= - ----- Main ----- - -init() +-- +game_init() diff --git a/src/sprites.lua b/src/sprites.lua deleted file mode 100644 index 8e46d14..0000000 --- a/src/sprites.lua +++ /dev/null @@ -1,16 +0,0 @@ - - -settings = -[[ -. -. -. -. -. -. -. -. -]] - - -