diff --git a/hex.lua b/hex.lua index 5579111..7433d12 100644 --- a/hex.lua +++ b/hex.lua @@ -1,31 +1,14 @@ ------ [[ AXIAL/CUBE COORDINATE SYSTEM FOR AMULET/LUA]] ------------------------- +----- [[ AXIAL/CUBE COORDINATE HEXAGON LIBRARY FOR AMULET/LUA]] ---------------- --[[ author@churchianity.ca -- INTRODUCTION this is a hexagonal grid library for amulet/lua. it uses axial coordinates or cube/hex coordinates when necessary. by amulet convention, hexes are either vec2(s, t) or vec3(s, t, z) but nearly always the former. - - in some rare cases, coordinates will be passed individually, usually - because they are only passed internally and should never be adjusted - directly. - - in amulet, vector arithmetic already works via: + - * / - additional things such as equality, and distance are implemented here. - - +support for parallelogram, triangular, hexagonal and rectangular maps. - +support for arbitrary maps with gaps via hashmaps-like storage - +support for simple irregular hexagons (horizontal and vertical stretching). - - classes are used sparsely. maps implement a few constructors for storing - your maps elsewhere, and should be the only field that is necessarily - visible outside the library. - -- RESOURCES USED TO DEVELOP THIS LIBRARY + -- RESOURCES USED TO DEVELOP THIS LIBRARY, AND FOR WHICH I AM GRATEFUL https://redblobgames.com/grid/hexagons - simply amazing. http://amulet.xyz/doc - amulet documentation - TODO that place that had the inner circle/outer circle ratio?? - ]] ----- [[ GENERALLY USEFUL FUNCTIONS ]] ----------------------------------------- @@ -35,7 +18,7 @@ local function round(n) return n % 1 >= 0.5 and math.ceil(n) or math.floor(n) end ------ [[ HEX CONSTANTS ]] ------------------------------------------------------ +----- [[ HEX CONSTANTS & UTILITY FUNCTIONS ]] ---------------------------------- -- all possible vector directions from a given hex by edge local HEX_DIRECTIONS = {vec2( 1 , 0), @@ -45,20 +28,19 @@ local HEX_DIRECTIONS = {vec2( 1 , 0), vec2(-1 , 1), vec2( 0 , 1)} ------ [[ HEX UTILITY FUNCTIONS ]] ---------------------------------------------- - -function hex_equals(a, b) - return a.s == b.s and a.t == b.t +-- return hex vector direction via index |direction|. +function hex_direction(direction) + return HEX_DIRECTIONS[direction] end -function hex_length(hex) - return round(math.abs(hex.s) + math.abs(hex.r) + math.abs(-hex.s - hex.t)/2) -end - -function hex_distance(a, b) - return hex_length(a - b) +-- return hexagon adjacent to |hex| in |direction| +function hex_neighbour(hex, direction) + return hex + HEX_DIRECTION[direction] end +-- rounds hexes. without this, pixel_to_hex returns fractional coordinates. +-- using single coordinates instead of a vector, because this should only +-- ever be called internally. function hex_round(s, t) local rs = round(s) local rt = round(t) @@ -82,15 +64,16 @@ end ----- [[ LAYOUT, ORIENTATION & COORDINATE CONVERSION ]] ----------------------- -- forward & inverse matrices used for the flat orientation. -local FLAT = {3.0/2.0, 0.0, 3.0^0.5/2.0, 3.0^0.5, - 2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0} +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)} --- forward & inverse matrices used for the pointy orientation. -local POINTY = {3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0, - 3.0^0.5/3.0, -1.0/3.0, 0.0, 2.0/3.0} +-- forward & inverse matrices used for the pointy orientation. +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)} +-- TODO encapsulate hex_to_pixel and pixel_to_hex in layout table. -- stores layout information that does not pertain to map shape -function layout_init(origin, size, orientation) +function hex_layout(origin, size, orientation) return {origin = origin or vec2(0), size = size or vec2(11), orientation = orientation or FLAT} @@ -98,117 +81,66 @@ end -- hex to screen function hex_to_pixel(hex, layout) - local M = layout.orientation + local M = layout.orientation.M - local x = (M[1] * hex.s + M[2] * hex.t) * layout.size.x - local y = (M[3] * hex.s + M[4] * hex.t) * layout.size.y + local x = (M[1][1] * hex.s + M[1][2] * hex.t) * layout.size.x + local y = (M[2][1] * hex.s + M[2][2] * hex.t) * layout.size.y return vec2(x + layout.origin.x, y + layout.origin.y) end -- screen to hex function pixel_to_hex(pix, layout) - local M = layout.orientation + local W = layout.orientation.W local pix = (pix - layout.origin) / layout.size - local s = M[5] * pix.x + M[6] * pix.y - local t = M[7] * pix.x + M[8] * pix.y + local s = W[1][1] * pix.x + W[1][2] * pix.y + local t = W[2][1] * pix.x + W[2][2] * pix.y return hex_round(s, t) end ----- [[ MAP STORAGE & RETRIEVAL ]] -------------------------------------------- - ---[[ _init functions return a table of tables; - a map of points in a chosen shape and specified layout. - - grammap_init - parallelogram map - trimap_init - triangular map - hexmap_init - hexagonal map - rectmap_init - rectangular map - - calling .retrieve(pix) on your map will get the hexagon at that pixel. - calling .store(hex) on your map will store that hex as pixel coords. - - maps store coordinates like this: - - map[hex] = hex_to_pixel(hex) - - this means you should be able to get all the information you need about - various coordinates completely within the map 'class', without calling - any internal functions. indeed, *map_init, map.retrieve, and map.store - is all you need. +--[[ ]] +-- TODO make all functions work regardless of layout. --- returns parallelogram-shaped map. -function grammap_init(layout, width, height) +-- returns unordered parallelogram-shaped map of |width| and |height|. +function hex_parallelogram_map(width, height) local map = {} - local mt = {__index={layout=layout, - - -- get hex in map from pixel coordinate - retrieve=function(pix) - return pixel_to_hex(pix, layout) - end, - - -- store pixel in map from hex coordinate - store=function(hex) - map[hex]=hex_to_pixel(hex, layout) - end - }} + local mt = {__index={width=width, height=height}} setmetatable(map, mt) for s = 0, width do for t = 0, height do - table.insert(map, hex_to_pixel(vec2(s, t), layout)) + map[vec2(s, t)] = true end end return map end --- returns triangular map. -function trimap_init(layout, size) +-- returns unordered triangular map of |size|. +function hex_triangular_map(size) local map = {} - local mt = {__index={layout=layout, - - -- get hex in map from pixel coordinate - retrieve=function(pix) - return pixel_to_hex(pix, layout) - end, - - -- store pixel in map from hex coordinate - store=function(hex) - map[hex]=hex_to_pixel(hex, layout) - end - }} + local mt = {__index={size=size}} setmetatable(map, mt) for s = 0, size do for t = size - s, size do - map.store(vec2(s, t)) + map[vec2(s, t)] = true end end return map end --- returns hexagonal map. length of map is radius * 2 + 1 -function hexmap_init(layout, radius) +-- returns unordered hexagonal map of |radius|. +function hex_hexagonal_map(radius) local map = {} - local mt = {__index={layout=layout, + local mt = {__index={radius=radius}} - -- get hex in map from pixel coordinate - retrieve=function(pix) - return pixel_to_hex(pix, layout) - end, - - -- store pixel in map from hex coordinate - store=function(hex) - map[hex]=hex_to_pixel(hex, layout) - end - }} - setmetatable(map, mt) for s = -radius, radius do @@ -216,33 +148,22 @@ function hexmap_init(layout, radius) local t2 = math.min(radius, -s + radius) for t = t1, t2 do - table.insert(map, hex_to_pixel(vec2(s, t), layout)) + map[vec2(s, t)] = true end end return map end --- returns rectangular map. -function rectmap_init(layout, width, height) +-- returns unordered rectangular map of |width| and |height|. +function hex_rectangular_map(width, height) local map = {} - local mt = {__index={layout=layout, + local mt = {__index={width=width, height=height}} - -- get hex in map from pixel coordinate - retrieve=function(pix) - return pixel_to_hex(pix, layout) - end, - - -- store pixel in map from hex coordinate - store=function(hex) - map[hex]=hex_to_pixel(hex - vec2(0, math.floor(hex.s/2)), layout) - end - }} - setmetatable(map, mt) for s = 0, width do for t = 0, height do - map.store(vec2(s, t)) + map[vec2(s, t)] = true end end return map diff --git a/main.lua b/main.lua index b4f2617..ff61c21 100644 --- a/main.lua +++ b/main.lua @@ -1,107 +1,57 @@ ------ [[ WARZONE 2 - HEXAGONAL GRID RESOURCE BASED TOWER DEFENSE GAME]] ------- +----- [[ WARZONE 2 - HEXAGONAL GRID RESOURCE BASED TOWER DEFENSE GAME]] -------- --[[ author@churchianity.ca ]] require "hex" +require "util" ------ [[ DUMMY FUNCTIONS ]] ---------------------------------------------------- +local world -function show_hex_coords() - grid:action(function() - grid:remove("text") - - mouse_position = vec2(win:mouse_position().x, win:mouse_position().y) - if mouse_position.x < 268 then - hex = map.retrieve(mouse_position) - grid:append(am.translate(win.left + 30, win.top - 10) - ^ am.text(string.format("%d,%d", hex.s, hex.t))) - end - end) -end - -function rcolor() - return vec4(math.random(20, 80) / 100, - math.random(20, 80) / 100, - math.random(20, 80) / 100, - 1) -end +local guibgcolor = vec4(0.5, 0.5, 0.2, 0) -SPRITES = {"BoulderHills1.png", "BoulderHills2.png", "BoulderHills2.png", - "Brambles1.png", "Brambles2.png", "Brambles3.png", "Brambles4.png", - "BrownHills1.png", "BrownHills2.png", "BrownHills3.png", - "Grass1.png", "Grass2.png", "Grass3.png", "Grass4.png", "Grass5.png", "Hills1.png", "Hills2.png", "Hills3.png", "Hills4.png", "Hills5.png", - "HillsGreen1.png", "HillsGreen2.png", "HillsGreen3.png", - "LightGrass1.png", "LightGrass2.png", "LightGrass3.png", - "LowHills1.png", "LowHills2.png", "LowHills3.png", "LowHills4.png", - "Mountains1.png", "Mountains2.png", "Mountains3.png", - "Mud1.png", "Mud2.png", "Mud3.png", "Mud4.png", "Mud5.png", - "Orchards1.png", "Orchards2.png", "Orchards3.png", "Orchards4.png", - "PineForest1.png", "PineForest2.png", "PineForest3.png", - "Woods1.png", "Woods2.png", "Woods3.png", "Woods4.png"} +local win = am.window{ + -- BASE RESOLUTION = 3/4 * WXGA Standard 16:10 + width = 1280 * 3 / 4, -- 960px + height = 800 * 3 / 4, -- 600px + + title = "Warzone 2: Electric Boogaloo"} -function rsprite() - return string.format("res/%s", SPRITES[math.random(#SPRITES)]) +function show_axes() + xaxis = am.line(vec2(win.left, 0), vec2(win.right, 0)) + yaxis = am.line(vec2(0, win.top), vec2(0, win.bottom)) + world:append(am.group{xaxis, yaxis}:tag("axes")) end ------ [[ BLAH BLAH LBAH ]] ----------------------------------------------- - -win = am.window { - title = "Warzone 2: Electric Boogaloo", - - -- BASE RESOLUTION = 3/4 * WXGA Standard 16:10 Aspect Ratio - width = 1280 * 3 / 4, -- 960 - height = 800 * 3 / 4} -- 600 - ------ [[ MAP RENDERING ]] ------------------------------------------------ - -function grid_init() - grid = am.group() - - map = rectmap_init(layout_init(vec2(win.left, win.bottom)), 45, 31) - - grid:action(function() - for hex,pix in pairs(map) do - grid:append(am.circle(pix, map.layout.size.x, rcolor(), 6)) +function world_init() + world = am.group() + local layout = layout_init(vec2(-402, win.bottom)) + local map = rectmap_init(45, 31) + local lgui = am.group( + am.rect(win.left, win.top, -402, win.bottom, guibgcolor)) + local rgui = am.group( + am.rect(win.right, win.top, 402, win.bottom, guibgcolor)) + + world:append(lgui) + world:append(rgui) + + world:action(coroutine.create(function() + for hex,_ in pairs(map) do + world:append(am.circle(hex_to_pixel(hex, layout), 11, rrgb(1), 6)) + am.wait(am.delay(0.01)) end - return true - end) + end)) - grid:append(am.translate(350, 200) - ^ am.scale(2) - ^ am.sprite("2.png")) - - show_hex_coords() - - return grid end -function toolbar_init() - local toolbar = am.group() - local toolbar_bg = vec4(0.4, 0.6, 0.8, 1) - - toolbar:append(am.rect(268, win.top, win.right, win.bottom, toolbar_bg)) - - toolbar:append(am.particles2d({source_pos=vec2(win.width/2-268, win.top), - source_pos_var=vec2(0, 600), - angle=math.pi/4, - start_color=vec4(0.9), - gravity=vec2(100), - start_color_var=rcolor(), - start_size=3})) - return toolbar +function init() + world_init() + show_axes() + win.scene = world end -function game_init() - return am.group{grid_init(), toolbar_init()} -end - ------ [[ MAIN ]] ----------------------------------------------------------- - -local map = {} - -win.scene = game_init() +----- [[ MAIN ]] --------------------------------------------------------------- -show_hex_coords() +init() diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..50a1147 --- /dev/null +++ b/util.lua @@ -0,0 +1,9 @@ + +function rrgb(a) + local R = math.random(20, 80) / 100 + local G = math.random(20, 80) / 100 + local B = math.random(20, 80) / 100 + local A = a or math.random() + + return vec4(R, G, B, A) +end