local debug = false local cavern = {} local tiletypes = {unknown = 0, empty = 1, wall = 2, orb = 3, slime = 4} 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, inplace = 8} local player_x = nil local player_y = nil local player_direction = nil local hp = nil local flash_player_crit_counter = 0 local flash_player_damaged_counter = 0 local player_sprite = nil local player_sprite_crit = nil local player_sprite_damaged = nil local player_animation_flip = false local heart_sprite = nil local slimes = {} local last_key_pressed = nil local move_repeat_counter = nil local gamemodes = {normal = 0, won = 1, lost = 2, config = 3} local game_mode = nil local configuration_steps = {quit = -4, restart = -3, configure = -2, autoexplore = -1} local configuration_step = nil local direction_keys = {} direction_keys['i'] = directions.up direction_keys['u'] = directions.upleft direction_keys['j'] = directions.left direction_keys['m'] = directions.downleft direction_keys['k'] = directions.down direction_keys['.'] = directions.downright direction_keys['l'] = directions.right direction_keys['o'] = directions.upright direction_keys['space'] = directions.inplace local control_keys = {quit = 'q', restart = 'r', configure = 'c', autoexplore = 'a'} local win_image = nil local win_is_raster = true -- ------------------------------------------------------------------ -- 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 passable(player_x, player_y + 1) then -- There's space under, spawn facing up player_direction = directions.up elseif passable(player_x + 1, player_y) then -- There's space to the right, spawn facing left player_direction = directions.left elseif passable(player_x - 1, player_y) then -- There's space to the left, spawn facing right player_direction = directions.right elseif passable(player_x, player_y - 1) then -- There's space above, spawn facing down player_direction = directions.down else error("Cavern is weirdly generated and player cannot spawn") end hp = 3 end function spawnOrb() local orb_x = nil local orb_y = nil repeat orb_x = math.random(2, cavern.width - 1) orb_y = math.random(2, cavern.height - 1) until cavern[orb_x][orb_y] == tiletypes.empty cavern[orb_x][orb_y] = tiletypes.orb end function spawnSlimes() -- Try spawning 20 slimes, but if they don't hit any location then don't care slimes = {} for i = 1, 20 do local x = math.random(2, cavern.width - 1) local y = math.random(2, cavern.height - 1) if cavern[x][y] == tiletypes.empty then cavern[x][y] = tiletypes.slime table.insert(slimes, {x = x, y = y}) end 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 = {} visibility_map[player_x][player_y] = true 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() visibility_map[body_x][body_y] = true 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}) end end elseif item.direction == directions.upright then -- See under item.direction == directions.upleft if seethrough(item.x - 1, item.y) or seethrough(item.x, item.y + 1) then visibility_map[item.x][item.y] = true if seethrough(item.x, item.y) 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}) end end else error("Visibility floodfill item travelling in an impossible direction") 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 -- ------------------------------------------------------------------ -- Collision -- ------------------------------------------------------------------ function passable(x, y) return cavern[x][y] == tiletypes.empty or cavern[x][y] == tiletypes.orb end function seethrough(x, y) return cavern[x][y] == tiletypes.empty or cavern[x][y] == tiletypes.orb or cavern[x][y] == tiletypes.slime end function collidedPlayerOrb() local body_x, body_y = getBodyLocation() return cavern[player_x][player_y] == tiletypes.orb or cavern[body_x][body_y] == tiletypes.orb 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 passable(player_x + dx, player_y + dy) and passable(body_x, body_y) then player_x = player_x + dx player_y = player_y + dy player_direction = new_direction end end -- ------------------------------------------------------------------ -- Gameloop functions -- ------------------------------------------------------------------ function newGame() game_mode = gamemodes.normal generateCavern() spawnPlayer() spawnOrb() spawnSlimes() generateVisibilityMap() initializeRememberedCavern() if collidedPlayerOrb() then game_mode = gamemodes.won end end function moveSlimes() for i, slime in pairs(slimes) do local dx = 0 local dy = 0 local to_player_dx = player_x - slime.x local to_player_dy = player_y - slime.y if math.abs(to_player_dx) + math.abs(to_player_dy) <= 5 and math.random() < 0.7 then -- If at max 5 moves away from player, book for it (likely) if math.abs(to_player_dx) > math.abs(to_player_dy) then -- Needs to move more in the horizontal direction → move horizontally if to_player_dx < 0 then dx = -1 else dx = 1 end else -- Otherwise move vertically if to_player_dy < 0 then dy = -1 else dy = 1 end end else local movement = math.random(0, 3) if movement == 0 then -- Up dy = -1 elseif movement == 1 then -- Left dx = -1 elseif movement == 2 then -- Down dy = 1 elseif movement == 3 then -- Right dx = 1 end end -- We test for empty instead of using passable(), since we are overwriting a tile if cavern[slime.x + dx][slime.y + dy] == tiletypes.empty then cavern[slime.x + dx][slime.y + dy] = tiletypes.slime cavern[slime.x][slime.y] = tiletypes.empty slimes[i] = {x = slime.x + dx, y = slime.y + dy} end end end function killSlime() local body_x, body_y = getBodyLocation() for i, slime in pairs(slimes) do if slime.x == player_x and slime.y == player_y or slime.x == body_x and slime.y == body_y then cavern[slime.x][slime.y] = tiletypes.empty slimes[i] = nil end end end function step(direction) if game_mode == gamemodes.normal then rememberVisible() if direction ~= directions.inplace then movePlayer(direction) end moveSlimes() generateVisibilityMap() if collidedPlayerOrb() then game_mode = gamemodes.won end local body_x, body_y = getBodyLocation() if cavern[body_x][body_y] == tiletypes.slime then killSlime() hp = hp - 1 if hp == 0 then flash_player_damaged_counter = 1 else flash_player_damaged_counter = 0.2 end elseif cavern[player_x][player_y] == tiletypes.slime then killSlime() if math.random() < 0.2 then flash_player_crit_counter = 0.2 else hp = hp - 1 if hp == 0 then flash_player_damaged_counter = 1 else flash_player_damaged_counter = 0.2 end end end player_animation_flip = not player_animation_flip end end -- ------------------------------------------------------------------ -- Autoexplore -- ------------------------------------------------------------------ function autoexplore() -- Use a pathfinding algo where each unknown but accessible space gets labelled 0, and -- then each known accessible space gets labelled with min(neighbours) + 1. Once we have -- computed that table, we can then move towards closest unknown accessible space by -- moving to our neighbouring space with lowest score local distances = {} for x = 1, cavern.width do local list = {} for y = 1, cavern.height do table.insert(list, {value = nil, direction = directions.inplace}) end table.insert(distances, list) end local queue = {} function enqueue(queue, x, y, value, direction) if 1 < x and x < cavern.width and 1 < y and y < cavern.height and cavern[x][y] ~= tiletypes.wall then table.insert(queue, {x = x, y = y, value = value, direction = direction}) end end function neighbors(q, x, y, v) -- The directions are reversed, because they tell where to go to get to the -- unexplored areas enqueue(q, x, y - 1, v, directions.down) enqueue(q, x - 1, y, v, directions.right) enqueue(q, x, y + 1, v, directions.up) enqueue(q, x + 1, y, v, directions.left) end for x, list in ipairs(visibility_map) do for y, visible in ipairs(list) do if not visible and remembered_cavern[x][y] == nil then distances[x][y] = {value = 0, direction = directions.inplace} neighbors(queue, x, y, 1) end end end repeat local new_queue = {} for i, item in ipairs(queue) do -- Write the lowest distance to each tile if distances[item.x][item.y].value == nil or distances[item.x][item.y].value > item.value then distances[item.x][item.y] = {value = item.value, direction = item.direction} neighbors(new_queue, item.x, item.y, item.value + 1) end end queue = new_queue until #queue == 0 local direction = distances[player_x][player_y].direction local body_x, body_y = getBodyLocation() -- If we are backing up, consider feet if player_direction == directions.up and direction == directions.down then direction = distances[body_x][body_y].direction if direction == directions.left then direction = directions.downleft elseif direction == directions.right then direction = directions.downright else direction = directions.down end elseif player_direction == directions.left and direction == directions.right then direction = distances[body_x][body_y].direction if direction == directions.up then direction = directions.upright elseif direction == directions.down then direction = directions.downright else direction = directions.right end elseif player_direction == directions.down and direction == directions.up then direction = distances[body_x][body_y].direction if direction == directions.left then direction = directions.upleft elseif direction == directions.right then direction = directions.upright else direction = directions.up end elseif player_direction == directions.right and direction == directions.left then direction = distances[body_x][body_y].direction if direction == directions.up then direction = directions.upleft elseif direction == directions.down then direction = directions.downleft else direction = directions.left end end step(direction) end -- ------------------------------------------------------------------ -- Drawing -- ------------------------------------------------------------------ function drawCavern() local screen_width = love.graphics.getWidth() local screen_height = love.graphics.getHeight() local width_scale = screen_width / cavern.width local height_scale = screen_height / cavern.height local scale = math.min(width_scale, height_scale) local x_offset = (screen_width - cavern.width * scale) / 2 local y_offset = (screen_height - cavern.height * scale) / 2 for tile_x, list in ipairs(visibility_map) do local x = (tile_x - 1) * scale + x_offset for tile_y, visible in ipairs(list) do local y = (tile_y - 1) * scale + y_offset if visible or debug then tile = cavern[tile_x][tile_y] if tile == tiletypes.empty then love.graphics.setColor(0, 0, 0) love.graphics.rectangle('fill', x, y, scale, scale) elseif tile == tiletypes.wall then love.graphics.setColor(1, 1, 1) love.graphics.rectangle('fill', x, y, scale, scale) elseif tile == tiletypes.orb then love.graphics.setColor(0.5, 0.3, 0.3) love.graphics.rectangle('fill', x, y, scale, scale) love.graphics.setColor(0, 1, 0) love.graphics.circle('fill', x + 0.5 * scale, y + 0.5 * scale, 0.7 * scale/2) elseif tile == tiletypes.slime then love.graphics.setColor(0, 0, 0) love.graphics.rectangle('fill', x, y, scale, scale) love.graphics.setColor(1, 1, 0) love.graphics.circle('fill', x + 0.5 * scale, y + 0.5 * scale, 0.8 * scale/2) love.graphics.rectangle('fill', x + 0.1 * scale , y + 0.5 * scale, 0.8 * scale, 0.8 * scale/2) elseif tile == tiletypes.unknown then love.graphics.setColor(1, 0.5, 0.5) love.graphics.rectangle('fill', x, y, scale, scale) else print("Impossible tile!!!") love.graphics.setColor(1, 1, 0) love.graphics.rectangle('fill', x, y, scale, scale) end else tile = remembered_cavern[tile_x][tile_y] if tile == tiletypes.empty then love.graphics.setColor(0.2, 0.2, 0.2) love.graphics.rectangle('fill', x, y, scale, scale) elseif tile == tiletypes.wall then love.graphics.setColor(0.9, 0.9, 0.9) love.graphics.rectangle('fill', x, y, scale, scale) elseif tile == tiletypes.orb then love.graphics.setColor(0.4, 0.4, 0.4) love.graphics.rectangle('fill', x, y, scale, scale) love.graphics.setColor(0.2, 0.7, 0.2) love.graphics.circle('fill', x + 0.5 * scale, y + 0.5 * scale, 0.7 * scale/2) elseif tile == tiletypes.slime then love.graphics.setColor(0.2, 0.2, 0.2) love.graphics.rectangle('fill', x, y, scale, scale) love.graphics.setColor(0.7, 0.7, 0.2) love.graphics.circle('fill', x + 0.5 * scale, y + 0.5 * scale, 0.8 * scale/2) love.graphics.rectangle('fill', x + 0.1 * scale , y + 0.5 * scale, 0.8 * scale, 0.8 * scale/2) else love.graphics.setColor(0.7, 0.5, 0.5) love.graphics.rectangle('fill', x, y, scale, scale) end end if debug and tile_x == player_x and tile_y == player_y then love.graphics.setColor(1, 1, 0) love.graphics.rectangle('fill', x + 0.5 * scale, y + 0.5 * scale, scale/2, scale/2) end end end end function drawPlayer() local screen_width = love.graphics.getWidth() local screen_height = love.graphics.getHeight() local width_scale = screen_width / cavern.width local height_scale = screen_height / cavern.height local scale = math.min(width_scale, height_scale) local x_offset = (screen_width - cavern.width * scale) / 2 local y_offset = (screen_height - cavern.height * scale) / 2 local x = (player_x - 1) * scale + x_offset local y = (player_y - 1) * scale + y_offset local sprite = nil if flash_player_damaged_counter > 0 then sprite = player_sprite_damaged elseif flash_player_crit_counter > 0 then sprite = player_sprite_crit else sprite = player_sprite end local rotation = nil if player_direction == directions.up then rotation = 0 if player_animation_flip then x = x + scale end elseif player_direction == directions.left then rotation = math.pi * 1.5 if not player_animation_flip then y = y + scale end elseif player_direction == directions.down then rotation = math.pi if not player_animation_flip then x = x + scale y = y + scale else y = y + scale end elseif player_direction == directions.right then rotation = math.pi * 0.5 if not player_animation_flip then x = x + scale else x = x + scale y = y + scale end else error("Player facing a direction without usable sprite") end -- Our sprite image is drawn assuming 16 pixels per tile love.graphics.setColor(1, 1, 1) if not player_animation_flip then love.graphics.draw(sprite, x, y, rotation, scale/16, scale/16) else love.graphics.draw(sprite, x, y, rotation, -scale/16, scale/16) end end function drawHP() local screen_width = love.graphics.getWidth() local screen_height = love.graphics.getHeight() local width_scale = screen_width / cavern.width local height_scale = screen_height / cavern.height local scale = math.min(width_scale, height_scale) local x_offset = (screen_width - cavern.width * scale) / 2 local y_offset = (screen_height - cavern.height * scale) / 2 for i = 1, hp do local x = (i - 1) * scale + x_offset local y = y_offset love.graphics.setColor(1, 1, 1) love.graphics.draw(heart_sprite, x, y, 0, scale/16, scale/16) end end function drawWin() if win_is_raster then local img_width, img_height = win_image:getDimensions() local screen_width = love.graphics.getWidth() local screen_height = love.graphics.getHeight() local width_scale = screen_width / img_width local height_scale = screen_height / img_height local scale = math.min(width_scale, height_scale) local x_offset = (screen_width - img_width * scale) / 2 local y_offset = (screen_height - img_height * scale) / 2 love.graphics.setColor(1, 1, 1) love.graphics.draw(win_image, x_offset, y_offset, 0, scale, scale) else local width = 20 local height = 20 local screen_width = love.graphics.getWidth() local screen_height = love.graphics.getHeight() local width_scale = screen_width / width local height_scale = screen_height / height local scale = math.min(width_scale, height_scale) local x_offset = (screen_width - width * scale) / 2 local y_offset = (screen_height - height * scale) / 2 local vertices = {10, 7, 6, 8, 5, 20, 15, 20, 14, 8} local transformed_vertices = {} for i = 1, #vertices, 2 do local x = vertices[i] * scale + x_offset local y = vertices[i + 1] * scale + y_offset transformed_vertices[i] = x transformed_vertices[i + 1] = y end love.graphics.setColor(0.1, 0.1, 0.3) love.graphics.polygon('fill', transformed_vertices) love.graphics.setColor(0.6, 0.3, 0.2) love.graphics.circle('fill', 10 * scale + x_offset, 5 * scale + y_offset, 3 * scale) love.graphics.setColor(0.5, 0.2, 0.1) love.graphics.rectangle('fill', x_offset, 15 * scale + y_offset, 20 * scale, 10 * scale) love.graphics.setColor(0, 1, 0) love.graphics.circle('fill', 10 * scale + x_offset, 16 * scale + y_offset, 2 * scale) end end function drawLoss() local width = 54 local height = 140 local screen_width = love.graphics.getWidth() local screen_height = love.graphics.getHeight() local width_scale = screen_width / width local height_scale = screen_height / height local scale = math.min(width_scale, height_scale) local x_offset = (screen_width - width * scale) / 2 local y_offset = (screen_height - height * scale) / 2 local vertices = {18, 20, 0, 40, 0, 60, 6, 80, 12, 100, 18, 120, 36, 120, 42, 100, 48, 80, 54, 60, 54, 40, 36, 20} local transformed_vertices = {} for i = 1, #vertices, 2 do local x = vertices[i] * scale + x_offset local y = vertices[i + 1] * scale + y_offset transformed_vertices[i] = x transformed_vertices[i + 1] = y end love.graphics.setColor(0.5, 0.2, 0.1) love.graphics.polygon('fill', transformed_vertices) love.graphics.setColor(1, 1, 1) local lines = {27, 40, 27, 80, 18, 50, 36, 50} for i = 1, #lines, 4 do local sx = lines[i] * scale + x_offset local sy = lines[i + 1] * scale + y_offset local ex = lines[i + 2] * scale + x_offset local ey = lines[i + 3] * scale + y_offset love.graphics.line(sx, sy, ex, ey) end end function drawConfig() local text = nil if configuration_step == configuration_steps.quit then text = "Quit" elseif configuration_step == configuration_steps.restart then text = "Restart" elseif configuration_step == configuration_steps.configure then text = "Button configuration" elseif configuration_step == configuration_steps.autoexplore then text = "Autoexplore" elseif configuration_step == directions.up then text = "Up" elseif configuration_step == directions.upleft then text = "Up-Left" elseif configuration_step == directions.left then text = "Left" elseif configuration_step == directions.downleft then text = "Down-Left" elseif configuration_step == directions.down then text = "Down" elseif configuration_step == directions.downright then text = "Down-Right" elseif configuration_step == directions.right then text = "Right" elseif configuration_step == directions.upright then text = "Up-Right" elseif configuration_step == directions.inplace then text = "Do nothing for a turn" else error("Impossible configuration step") end love.graphics.setColor(1, 1, 1) love.graphics.print(text) end -- ------------------------------------------------------------------ -- Callbacks -- ------------------------------------------------------------------ function love.load() math.randomseed(os.time()) newGame() if win_is_raster then win_image = love.graphics.newImage("win_image.png") end player_sprite = love.graphics.newImage("sprite.png") player_sprite_crit = love.graphics.newImage("sprite_crit.png") player_sprite_damaged = love.graphics.newImage("sprite_damaged.png") heart_sprite = love.graphics.newImage("heart.png") end function love.update(dt) if move_repeat_counter ~= nil and move_repeat_counter > 0 then move_repeat_counter = move_repeat_counter - dt if move_repeat_counter <= 0 and last_key_pressed ~= nil then love.keypressed(last_key_pressed) end end if flash_player_crit_counter > 0 then flash_player_crit_counter = flash_player_crit_counter - dt end if flash_player_damaged_counter > 0 then flash_player_damaged_counter = flash_player_damaged_counter - dt if hp == 0 and flash_player_damaged_counter <= 0 and game_mode ~= gamemodes.won then game_mode = gamemodes.lost end end end function love.keypressed(key) if game_mode == gamemodes.config then -- This is a hack. Each "enum value" corresponds to an integer, and we are iterating -- through them. -3 to -1 are control keys, and 0 to 8 are direction keys if configuration_step >= directions.up and configuration_step <= directions.inplace then direction_keys[key] = configuration_step elseif configuration_step == configuration_steps.quit then control_keys.quit = key elseif configuration_step == configuration_steps.restart then control_keys.restart = key elseif configuration_step == configuration_steps.configure then control_keys.configure = key elseif configuration_step == configuration_steps.autoexplore then control_keys.autoexplore = key end configuration_step = configuration_step + 1 if configuration_step > directions.inplace then game_mode = gamemodes.normal end else if last_key_pressed == key then -- Repeat faster after the initial threshold for repetition has been met move_repeat_counter = 0.1 else move_repeat_counter = 0.3 end if key == control_keys.configure and game_mode == gamemodes.normal then direction_keys = {} game_mode = gamemodes.config configuration_step = configuration_steps.quit elseif key == control_keys.quit then love.event.quit() elseif key == control_keys.restart then newGame() elseif key == control_keys.autoexplore then autoexplore() last_key_pressed = key elseif direction_keys[key] ~= nil then step(direction_keys[key]) last_key_pressed = key elseif key == 'pause' then debug = not debug win_is_raster = not win_is_raster end end end function love.keyreleased(key) if last_key_pressed == key then last_key_pressed = nil move_repeat_counter = nil end end function love.draw() if game_mode == gamemodes.normal then drawCavern() if not debug then drawPlayer() end drawHP() elseif game_mode == gamemodes.won then drawWin() elseif game_mode == gamemodes.lost then drawLoss() elseif game_mode == gamemodes.config then drawConfig() else error("Impossible game mode") end end