local cavern = {} local tiletypes = {unknown = 0, empty = 1, wall = 2} local visibility_map = {} local remembered_cavern = {} local spawn_x = nil local spawn_y = nil local directions = {up = 0, upleft = 1, left = 2, downleft = 3, down = 4, downright = 5, right = 6, upright = 7} local player_x = nil local player_y = nil local player_direction = nil -- ------------------------------------------------------------------ -- Cavern generation -- ------------------------------------------------------------------ function generateCavern() repeat cavern = {} cavern.width = 80 cavern.height = 60 -- Create a cavern.width × cavern.height array filled with tiletypes.unknown for x = 1, cavern.width do local list = {} for y = 1, cavern.height do table.insert(list, tiletypes.unknown) end table.insert(cavern, list) end -- Fill top and bottom edges with walls for x = 1, cavern.width do cavern[x][1] = tiletypes.wall cavern[x][cavern.height] = tiletypes.wall end -- Fill the left and right edges with walls for y = 1, cavern.height do cavern[1][y] = tiletypes.wall cavern[cavern.width][y] = tiletypes.wall end local queue = {} local size = 1 -- Limit spawn to locations not on the edge walls spawn_x = math.random(2, cavern.width - 1) spawn_y = math.random(2, cavern.height - 1) cavern[spawn_x][spawn_y] = tiletypes.empty table.insert(queue, {x = spawn_x, y = spawn_y - 1}) table.insert(queue, {x = spawn_x, y = spawn_y + 1}) table.insert(queue, {x = spawn_x - 1, y = spawn_y}) table.insert(queue, {x = spawn_x + 1, y = spawn_y}) repeat queue, size = stepCavernGeneration(queue, size) until #queue == 0 until size > 200 end function stepCavernGeneration(queue, size) -- Tuning this parameter will change how open and cavernous vs. closed and corridory the -- cavern will be local spread_probability = 0.6 local new_queue = {} local new_size = size for i, location in ipairs(queue) do if cavern[location.x][location.y] == tiletypes.unknown then -- We haven't covered this tile yet if math.random() < spread_probability then -- Empty cavern[location.x][location.y] = tiletypes.empty size = size + 1 -- Up if location.y - 1 >= 1 then table.insert(new_queue, {x = location.x, y = location.y - 1}) end -- Down if location.y + 1 <= cavern.height then table.insert(new_queue, {x = location.x, y = location.y + 1}) end -- Left if location.x - 1 >= 1 then table.insert(new_queue, {x = location.x - 1, y = location.y}) end -- Right if location.x + 1 <= cavern.width then table.insert(new_queue, {x = location.x + 1, y = location.y}) end else -- Wall cavern[location.x][location.y] = tiletypes.wall end end end return new_queue, size end -- ------------------------------------------------------------------ -- Spawning objects -- ------------------------------------------------------------------ function spawnPlayer() player_x = spawn_x player_y = spawn_y -- Check what direction we can spawn the player if cavern[player_x][player_y + 1] == tiletypes.empty then -- There's space under, spawn facing up player_direction = directions.up elseif cavern[player_x + 1][player_y] == tiletypes.empty then -- There's space to the right, spawn facing left player_direction = directions.left elseif cavern[player_x - 1][player_y] == tiletypes.empty then -- There's space to the left, spawn facing right player_direction = directions.right elseif cavern[player_x][player_y - 1] == tiletypes.empty then -- There's space above, spawn facing down player_direction = directions.down else error("Cavern is weirdly generated and player cannot spawn") end end -- ------------------------------------------------------------------ -- Visibility -- ------------------------------------------------------------------ function generateVisibilityMap() -- Start by creating a new visibility map (since we'll have to recompute everything anyways) visibility_map = {} for x = 1, cavern.width do local list = {} for y = 1, cavern.height do table.insert(list, false) end table.insert(visibility_map, list) end -- Handle visibility from the head local queue = {} if player_direction == directions.up then -- ⌜^⌝ -- table.insert(queue, {x = player_x - 1, y = player_y - 1, direction = directions.upleft}) table.insert(queue, {x = player_x, y = player_y - 1, direction = directions.up}) table.insert(queue, {x = player_x + 1, y = player_y - 1, direction = directions.upright}) table.insert(queue, {x = player_x - 1, y = player_y, direction = directions.left}) table.insert(queue, {x = player_x + 1, y = player_y, direction = directions.right}) elseif player_direction == directions.left then -- ⌜^ -- -- ⌞v⌟ table.insert(queue, {x = player_x - 1, y = player_y, direction = directions.left}) table.insert(queue, {x = player_x + 1, y = player_y, direction = directions.right}) table.insert(queue, {x = player_x - 1, y = player_y + 1, direction = directions.downleft}) table.insert(queue, {x = player_x, y = player_y + 1, direction = directions.down}) table.insert(queue, {x = player_x + 1, y = player_y + 1, direction = directions.downright}) elseif player_direction == directions.right then -- ^⌝ -- o> -- v⌟ table.insert(queue, {x = player_x, y = player_y - 1, direction = directions.up}) table.insert(queue, {x = player_x + 1, y = player_y - 1, direction = directions.upright}) table.insert(queue, {x = player_x + 1, y = player_y, direction = directions.right}) table.insert(queue, {x = player_x, y = player_y + 1, direction = directions.down}) table.insert(queue, {x = player_x + 1, y = player_y + 1, direction = directions.downright}) else error("Player facing an impossible direction") end repeat queue = stepVisibilityMapGeneration(queue) until #queue == 0 -- Handle "visibility" from the feet local body_x, body_y = getBodyLocation() if player_direction == directions.up then -- -- v visibility_map[body_x - 1][body_y] = true visibility_map[body_x + 1][body_y] = true visibility_map[body_x][body_y + 1] = true elseif player_direction == directions.left then -- ^ -- x> -- v visibility_map[body_x][body_y - 1] = true visibility_map[body_x + 1][body_y] = true visibility_map[body_x][body_y + 1] = true elseif player_direction == directions.down then -- ^ -- visibility_map[body_x][body_y - 1] = true visibility_map[body_x - 1][body_y] = true visibility_map[body_x + 1][body_y] = true elseif player_direction == directions.right then -- ^ -- -- v⌟ table.insert(new_queue, {x = item.x + 1, y = item.y, direction = directions.right}) table.insert(new_queue, {x = item.x, y = item.y + 1, direction = directions.down}) table.insert(new_queue, {x = item.x + 1, y = item.y + 1, direction = directions.downright}) elseif item.direction == directions.upright then -- ^⌝ -- ⌝ x> table.insert(new_queue, {x = item.x, y = item.y - 1, direction = directions.up}) table.insert(new_queue, {x = item.x + 1, y = item.y - 1, direction = directions.upright}) table.insert(new_queue, {x = item.x + 1, y = item.y, direction = directions.right}) else error("Visibility floodfill item travelling in an impossible direction") end end end return new_queue end function initializeRememberedCavern() remembered_cavern = {} for x = 1, cavern.width do local list = {} for y = 1, cavern.height do table.insert(list, nil) end table.insert(remembered_cavern, list) end end function rememberVisible() for x, list in ipairs(visibility_map) do for y, visible in ipairs(list) do if visible then remembered_cavern[x][y] = cavern[x][y] end end end end -- ------------------------------------------------------------------ -- Player helper functions -- ------------------------------------------------------------------ function getBodyLocation(x, y, facing) if x == nil then x = player_x end if y == nil then y = player_y end if facing == nil then facing = player_direction end if facing == directions.up then -- Facing up, body is down return x, y + 1 elseif facing == directions.left then -- Facing left, body is right return x + 1, y elseif facing == directions.down then -- Facing down, body is up return x, y - 1 elseif facing == directions.right then -- Facing right, body is left return x - 1, y else error("Player facing an impossible direction") end end function movePlayer(direction) local dx = 0 local dy = 0 local new_direction = direction -- If we are moving backwards, keep the old direction of facing if player_direction == directions.up and direction == directions.down or player_direction == directions.down and direction == directions.up or player_direction == directions.left and direction == directions.right or player_direction == directions.right and direction == directions.left then new_direction = player_direction end if direction == directions.up then dy = -1 elseif direction == directions.down then dy = 1 elseif direction == directions.left then dx = -1 elseif direction == directions.right then dx = 1 elseif direction == directions.upleft then if player_direction == directions.up then dx = -1 new_direction = directions.left elseif player_direction == directions.left then dy = -1 new_direction = directions.up elseif player_direction == directions.down then -- ## ## -- x# xo# -- #o# # # dy = -1 new_direction = directions.right elseif player_direction == directions.right then -- # # #x# -- #xo #o -- ## ## dx = -1 new_direction = directions.down else error("Player facing an impossible direction") end elseif direction == directions.downleft then if player_direction == directions.up then -- #o# # # -- x# xo# -- ## ## dy = 1 new_direction = directions.right elseif player_direction == directions.left then dy = 1 new_direction = directions.down elseif player_direction == directions.down then dx = -1 new_direction = directions.left elseif player_direction == directions.right then -- ## ## -- #xo #o -- # # #x# dx = -1 new_direction = directions.up else error("Player facing an impossible direction") end elseif direction == directions.downright then if player_direction == directions.up then -- #o# # # -- #x #ox -- ## ## dy = 1 new_direction = directions.left elseif player_direction == directions.left then -- ## ## -- ox# o# -- # # #x# dx = 1 new_direction = directions.up elseif player_direction == directions.down then dx = 1 new_direction = directions.right elseif player_direction == directions.right then dy = 1 new_direction = directions.down else error("Player facing an impossible direction") end elseif direction == directions.upright then if player_direction == directions.up then dx = 1 new_direction = directions.right elseif player_direction == directions.left then -- # # #x# -- ox# o# -- ## ## dx = 1 new_direction = directions.down elseif player_direction == directions.down then -- ## ## -- #x #ox -- #o# # # dy = -1 new_direction = directions.left elseif player_direction == directions.right then dy = -1 new_direction = directions.up else error("Player facing an impossible direction") end else error("Player moving in an impossible direction") end local body_x, body_y = getBodyLocation(player_x + dx, player_y + dy, new_direction) if cavern[player_x + dx][player_y + dy] == tiletypes.empty and cavern[body_x][body_y] == tiletypes.empty then player_x = player_x + dx player_y = player_y + dy player_direction = new_direction end end -- ------------------------------------------------------------------ -- Callbacks -- ------------------------------------------------------------------ function love.load() math.randomseed(os.time()) generateCavern() spawnPlayer() generateVisibilityMap() initializeRememberedCavern() end function love.update(dt) end function love.keypressed(key) if key == 'r' then generateCavern() spawnPlayer() generateVisibilityMap() initializeRememberedCavern() elseif key == 'i' then rememberVisible() movePlayer(directions.up) generateVisibilityMap() elseif key == 'j' then rememberVisible() movePlayer(directions.left) generateVisibilityMap() elseif key == 'k' then rememberVisible() movePlayer(directions.down) generateVisibilityMap() elseif key == 'l' then rememberVisible() movePlayer(directions.right) generateVisibilityMap() elseif key == 'u' then rememberVisible() movePlayer(directions.upleft) generateVisibilityMap() elseif key == 'm' then rememberVisible() movePlayer(directions.downleft) generateVisibilityMap() elseif key == '.' then rememberVisible() movePlayer(directions.downright) generateVisibilityMap() elseif key == 'o' then rememberVisible() movePlayer(directions.upright) generateVisibilityMap() elseif key == 'q' then love.event.quit() else display_inputinfo_counter = 1 end end function love.draw() local x_scale = love.graphics.getWidth() / cavern.width local y_scale = love.graphics.getHeight() / cavern.height -- Draw the cavern for tile_x, list in ipairs(visibility_map) do local x = (tile_x - 1) * x_scale for tile_y, visible in ipairs(list) do local y = (tile_y - 1) * y_scale if visible then tile = cavern[tile_x][tile_y] if tile == tiletypes.empty then love.graphics.setColor(0, 0, 0) elseif tile == tiletypes.wall then love.graphics.setColor(1, 1, 1) else love.graphics.setColor(1, 0.5, 0.5) end else tile = remembered_cavern[tile_x][tile_y] if tile == tiletypes.empty then love.graphics.setColor(0.2, 0.2, 0.2) elseif tile == tiletypes.wall then love.graphics.setColor(0.8, 0.8, 0.8) else love.graphics.setColor(0.3, 0.3, 0.3) end end love.graphics.rectangle('fill', x, y, x_scale, y_scale) end end -- Draw the player local player_head_center_x = (player_x - 1) * x_scale + 0.5 * x_scale local player_head_center_y = (player_y - 1) * y_scale + 0.5 * y_scale love.graphics.setColor(0.7, 0.3, 0.3) love.graphics.ellipse('fill', player_head_center_x, player_head_center_y, x_scale/2, y_scale/2) local player_body_x, player_body_y = getBodyLocation() local player_body_x = (player_body_x - 1) * x_scale local player_body_y = (player_body_y - 1) * y_scale love.graphics.setColor(0, 0, 1) love.graphics.rectangle('fill', player_body_x, player_body_y, x_scale, y_scale) end