diff --git a/src/hexyz.lua b/src/hexyz.lua index e366ac0..6af574d 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -3,9 +3,9 @@ local function round(n) return n % 1 >= 0.5 and math.ceil(n) or math.floor(n) end --[[============================================================================ - ----- HEX CONSTANTS AND UTILITY FUNCTIONS ----- -============================================================================]]-- + -- HEX CONSTANTS AND UTILITY FUNCTIONS +]] -- 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)} @@ -48,11 +48,10 @@ local function hex_round(x, y, z) rz = -rx - ry end return vec2(rx, ry) end - --[[==========================================================================-- - ----- ORIENTATION & LAYOUT ----- -============================================================================]]-- + -- 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), @@ -64,11 +63,11 @@ local POINTY = {M = mat2(3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0), angle = 0.5} -- Hex to Screen -- Orientation Must be Either POINTY or FLAT -function hex_to_pixel(hex, size, orientation_M, offset) +function hex_to_pixel(hex, size, orientation_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 and size[1] or 11) + local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * (size and size[2] or 11) return vec2(x, y) end @@ -78,7 +77,7 @@ end function pixel_to_hex(pix, size, orientation_W) local W = orientation_W or FLAT.W - local pix = pix / size + local pix = pix / (size or vec2(11)) local x = W[1][1] * pix[1] + W[1][2] * pix[2] local y = W[2][1] * pix[1] + W[2][2] * pix[2] @@ -111,15 +110,14 @@ function hex_to_offset(hex) 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] - math.floor((off[1] - 1 * (off[1] % 2))) / 2) end - --[[============================================================================ - ----- MAPS & STORAGE ----- -============================================================================]]-- + -- MAPS & STORAGE +]] -- Returns Ordered Ring-Shaped Map of |radius| from |center| function ring_map(center, radius) local map = {} @@ -149,23 +147,13 @@ function spiral_map(center, radius) end -function hash_retrieve(map, entry) - for k,v in pairs(map) do - if k == entry then - return v - 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 + map[i] = {} for j = 0, height do -- Calculate Noise @@ -179,7 +167,7 @@ function parallelogram_map(width, height, seed) local pos = vec2(idelta + seed * width, jdelta + seed * height) noise = noise + f * math.simplex(pos * l) end - map[vec2(i, j)] = noise + map[i][j] = noise end end setmetatable(map, {__index={width=width, height=height, seed=seed}}) @@ -193,6 +181,7 @@ function triangular_map(size, seed) local map = {} for i = 0, size do + map[i] = {} for j = size - i, size do -- Generate Noise @@ -206,7 +195,7 @@ function triangular_map(size, seed) local pos = vec2(idelta + seed * size, jdelta + seed * size) noise = noise + f * math.simplex(pos * l) end - map[vec2(i, j)] = noise + map[i][j] = noise end end setmetatable(map, {__index={size=size, seed=seed}}) @@ -220,6 +209,8 @@ function hexagonal_map(radius, seed) local map = {} for i = -radius, radius do + map[i] = {} + local j1 = math.max(-radius, -i - radius) local j2 = math.min(radius, -i + radius) @@ -234,9 +225,10 @@ function hexagonal_map(radius, seed) 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 + map[i][j] = noise end end setmetatable(map, {__index={radius=radius, seed=seed}}) diff --git a/src/main.lua b/src/main.lua index 860b860..04f0373 100644 --- a/src/main.lua +++ b/src/main.lua @@ -2,30 +2,30 @@ require"hexyz" math.randomseed(os.time()); math.random(); math.random(); math.random() +--[[============================================================================ -function explosion(position, size, color, color_var, sound) end +]] +-- win = am.window -{ -- Base Resolution = 3/4 * WXGA standard 16:10 - width = 1280 * 3/4, height = 800 * 3/4, -- 960px -- 600px -} - -bias = "right" -state = { - score = 0, +{ -- Base Resolution = 3/4 * WXGA standard 16:10 -- 960px, 600px + width = 1280 * 3/4, height = 800 * 3/4, + clear_color = vec4(0.08, 0.08, 0.11, 1) } local map -local world local home -local home_node +local spawn_chance = 25 + +--[[============================================================================ +]] -- ensure home-base is somewhat of an open area. function find_home(preferred_radius) home = spiral_map(vec2(23, 4), preferred_radius or 2) - home_node = am.group() + local home_node = am.group() repeat local happy = true @@ -42,7 +42,7 @@ function find_home(preferred_radius) home_node = am.group() else - local center = hex_to_pixel(h, vec2(11)) + 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) @@ -55,26 +55,25 @@ end -- map elevation to appropriate tile color. function color_at(elevation) - if elevation < -0.5 then -- Lowest Elevation : Impassable + if elevation < -0.5 then -- lowest elevation : impassable return vec4(0.10, 0.30, 0.40, (elevation + 1.4) / 2 + 0.2) - elseif elevation < 0 then -- Med-Low Elevation : Passable + 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 + elseif elevation < 0.5 then -- med-high elevation : passable return vec4(0.25, 0.20, 0.10, (elevation + 1.6) / 2 + 0.2) - elseif elevation < 1 then -- Highest Elevation : Impassable + elseif elevation < 1 then -- highest elevation : impassable return vec4(0.15, 0.30, 0.20, (elevation + 1.0) / 2 + 0.2) - else - return vec4(0.6) end end -function cartograph() - map = rectangular_map(46, 33); math.randomseed(map.seed) - world = am.translate(vec2(-278, -318)) ^ am.group():tag"world" +-- +function random_map(seed) + map = rectangular_map(46, 33, seed); math.randomseed(map.seed) + local world = am.translate(vec2(-278, -318)) ^ am.group():tag"world" for i,_ in pairs(map) do for j,elevation in pairs(map[i]) do @@ -85,25 +84,24 @@ function cartograph() ((-off.y - 16.5) / 32) ^ 2)) local color = color_at(elevation) - mask - local node = am.circle(hex_to_pixel(vec2(i, j), vec2(11)), 11, vec4(0), 6) - :action(am.tween(1, {color=color}, am.ease.out(am.ease.hyperbola))) + local node = am.circle(hex_to_pixel(vec2(i, j)), 11, vec4(0), 6) + :action(am.tween(2, {color=color}, am.ease.out(am.ease.hyperbola))) world:append(node) end end - world:append(find_home()) + world:append(find_home(2)) world:action(spawner) - - return world + return world:tag"world" end -- determines when, where, and how often to spawn mobs. function spawner(world) - if math.random(25) == 1 then -- chance to spawn + 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) + local x,y = math.random(46), math.random(33) if math.random() < 0.5 then x = math.random(0, 1) * 46 else @@ -115,13 +113,7 @@ function spawner(world) local e = map[spawn_position.x][spawn_position.y] until e and e < 0.5 and e > -0.5 - local mob - if bias == "right" then - mob = am.translate(-278, -318) ^ am.circle(hex_to_pixel(spawn_position, vec2(11)), 4) - - elseif bias == "left" then - mob = am.translate(-480, -318) ^ am.circle(hex_to_pixel(spawn_position, vec2(11)), 4) - end + local mob = am.translate(-278, -318) ^ am.circle(hex_to_pixel(spawn_position), 4) world:append(mob"circle":action(coroutine.create(live))) end end @@ -131,11 +123,12 @@ end function live(mob) local dead = false - local visited = {}; visited[mob.center] = true + local visited = {} + visited[mob.center.x] = {}; visited[mob.center.x][mob.center.y] = true -- begin life repeat - local neighbours = hex_neighbours(pixel_to_hex(mob.center, vec2(11))) + local neighbours = hex_neighbours(pixel_to_hex(mob.center)) local candidates = {} -- get list of candidates: hex positions to consider moving to. @@ -147,7 +140,11 @@ function live(mob) end if e and e < 0.5 and e > -0.5 then - if not hash_retrieve(visited, h) then + if visited[h.x] then + if not visited[h.x][h.y] then + table.insert(candidates, h) + end + else table.insert(candidates, h) end end @@ -165,62 +162,134 @@ function live(mob) end -- bug local speed = map[move.x][move.y] ^ 2 + 0.5 - am.wait(am.tween(mob, speed, {center=hex_to_pixel(move, vec2(11))})) - visited[move] = true + 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 - explosion(mob.center) + win.scene:remove(mob) end --- -function init() - local score = am.translate(-264, 290) ^ am.text("0", "left"):tag"score" - local coords = am.translate(440, 290) ^ am.text():tag"coords" - local bg = am.rect(win.left, win.top, win.right, win.bottom, vec4(0.12, 0.3, 0.3, 1)):tag"curtain" - -- -480, 300, -268, -300 - - main_scene = am.group - { - cartograph(), - bg, - score, - coords, - } - - main_scene:append(am.circle(home.center, 11, vec4(0.4), 6):tag"selected") - - function main_action(scene) +-- POLL MOUSE +function poll_mouse() + if win:mouse_position().x > -268 then -- mouse is inside game map -- get info about mouse position - local hex = pixel_to_hex(win:mouse_position() - vec2(-278, -318), vec2(11)) + 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 map[hex.x] == nil or map[hex.x][hex.y] == nil or off.x <= 1 or -off.y <= 1 or off.x >= 46 or -off.y >= 32 then - scene"coords".text = "" + if off.x <= 1 or -off.y <= 1 or off.x >= 46 or -off.y >= 32 then + win.scene"coords".text = "" else - -- check if mouse clicked - if win:mouse_down"left" then + if win:mouse_down"left" then -- check if mouse clicked if map[hex.x][hex.y] <= -0.5 or map[hex.x][hex.y] >= 0.5 then else map[hex.x][hex.y] = 2 - world:append(am.circle(hex_to_pixel(hex, vec2(11)), 11, vec4(0.2, 0.2, 0.2, 1), 6)) + win.scene"world":append(am.circle(hex_to_pixel(hex), 11, vec4(0, 0, 0, 1), 6)) end end - scene"coords".text = string.format("%d,%d", off.x, -off.y) - scene"selected".center = hex_to_pixel(hex, vec2(11)) + vec2(-278, -318) + win.scene"coords".text = string.format("%2d,%2d", off.x, -off.y) + win.scene"selected".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 - scene"score".text = string.format("SCORE: %.2f", am.current_time() + state.score) end - main_scene:action(am.series +end + + +-- +function update_score() + win.scene"score".text = string.format("SCORE: %.2f", am.current_time()) +end + + +function update_mobs() + + +end + + + +-- +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) +end + + +-- GAME INITIALIZATION FUNCTION +function game_init() + local score = am.translate(-264, 290) ^ am.text("", "left"):tag"score" + local coords = am.translate(440, 290) ^ am.text(""):tag"coords" + local selected = am.circle(vec2(win.left, win.top), 11, vec4(0.4), 6):tag"selected" + local bg = am.rect(win.left, win.top, win.right, win.bottom, vec4(0.12, 0.3, 0.3, 1)):tag"curtain" + + local buttons = am.translate(-500, -300) ^ am.group() + for i = 1, 2 do + for j = 1, 6 do + buttons:append(button(i, j)) + end + end + + local main_scene = am.group{random_map(9), bg, buttons, score, coords, selected} + :action(am.series { - am.tween(bg, 1, {x2 = -268}, am.ease.bounce), - main_action + am.tween(bg, 0.8, {x2 = -268}, am.ease.bounce), -- INTRO TRANSITION + + function(scene) -- MAIN ACTION + update_score() + -- update mobs + -- update towers + -- update environment + poll_mouse() -- check if player did anything + end }) win.scene = main_scene end + +-- TITLE SCREEN +function init() + local map = hexagonal_map(15, 9) + local backdrop = am.group() + + 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)) + end + end + + local title_text = am.group + { + am.translate(0, 200) ^ am.scale(5) ^ am.text("hexyz", vec4(0.8, 0.8, 0.7, 1), "right"), + am.translate(0, 130) ^ am.scale(4) ^ am.text("a tower defense", vec4(0.8, 0.8, 0.7, 1)), + am.circle(vec2(0), 100, vec4(0.6), 6):tag"b", am.scale(4) ^ am.text("START", vec4(0, 0, 0, 1)) + } + + win.scene = am.group + { + backdrop, + title_text + } + :action(function(s) + local mouse = win:mouse_position() + if math.length(mouse) < 100 then + s"b":action(am.series + { + am.tween(0.1, {color = vec4(0.8, 0.8, 0.7, 1)}), + am.tween(0.1, {color = vec4(0.6)}) + }) + if win:mouse_pressed"left" then + game_init() + end + end + end) +end + init() +noglobals()