require("game") function love.load() -- the level data is a matrix! -- limitation: there MUST be walls limiting the level levels = { { { '#', '#', '#', '#', '#', '#', '#' }, { '#', '.', '@', ' ', '#', ' ', '#' }, { '#', '$', '*', ' ', '$', ' ', '#' }, { '#', ' ', ' ', ' ', '$', ' ', '#' }, { '#', ' ', '.', '.', ' ', ' ', '#' }, { '#', ' ', ' ', '*', ' ', ' ', '#' }, { '#', '#', '#', '#', '#', '#', '#' } }, { { '#', '#', '#', '#', '#', '#', 'x', 'x' }, { '#', ' ', ' ', ' ', ' ', '#', '#', '#' }, { '#', ' ', '#', ' ', '$', ' ', ' ', '#' }, { '#', '.', '.', '.', '*', '$', '@', '#' }, { '#', ' ', '#', ' ', '$', ' ', ' ', '#' }, { '#', ' ', ' ', ' ', '#', '#', '#', '#' }, { '#', '#', '#', '#', '#', 'x', 'x', 'x' } } } -- use a monospaced font (TODO: licence) font = love.graphics.newFont("data/Monocraft.otf", 12 * game.scale) -- load graphics ground = love.graphics.newImage("data/ground.png") crate = love.graphics.newImage("data/crate.png") wall = love.graphics.newImage("data/wall.png") goal = love.graphics.newImage("data/goal.png") crate_goal = love.graphics.newImage("data/crate_goal.png") -- load sounds move_sound = love.audio.newSource("data/move.wav", "static") crate_sound = love.audio.newSource("data/crate_move.wav", "static") undo_sound = love.audio.newSource("data/undo.wav", "static") restart_sound = love.audio.newSource("data/restart.wav", "static") win_sound = love.audio.newSource("data/win.wav", "static") player_frames = { love.graphics.newImage("data/player_01.png"), love.graphics.newImage("data/player_02.png") } player_frame = 1 player_delay = 0 -- where the player is player_x = 0 player_y = 0 moves = 0 state = "play" current_level = 1 set_level(current_level) end -- return true if all the crates are in storage function is_level_complete() for _, row in ipairs(level) do for _, cell in ipairs(row) do if cell == '$' then return false end end end return true end -- set current level based on the index in the levels table function set_level(number) state_undo = {} level = {} for y, row in ipairs(levels[number]) do level[y] = {} for x, cell in ipairs(row) do level[y][x] = cell if cell == '@' or cell == '+' then player_x = x player_y = y end end end moves = 0 end -- perform a move (if possible) function move(target_x, target_y) -- can't push walls if level[player_y + target_y][player_x + target_x] == '#' then return end local current = level[player_y][player_x] local destination = level[player_y + target_y][player_x + target_x] -- copy current level and player position to use it for undo local undo = {} local undo_x = 0 local undo_y = 0 for y, row in ipairs(level) do undo[y] = {} for x, cell in ipairs(row) do undo[y][x] = cell if cell == '@' or cell == '+' then undo_x = x undo_y = y end end end local playing = false -- pushing a box if destination == '$' or destination == '*' then local destination_crate = level[player_y + target_y * 2][player_x + target_x * 2] -- can we move it? if destination_crate == ' ' or destination_crate == '.' then -- first erase the current position if destination == '$' then level[player_y + target_y][player_x + target_x] = ' ' destination = ' ' else level[player_y + target_y][player_x + target_x] = '.' destination = '.' end -- second draw on the new one if destination_crate == ' ' then level[player_y + target_y * 2][player_x + target_x * 2] = '$' else level[player_y + target_y * 2][player_x + target_x * 2] = '*' end playing = true crate_sound:play() else -- we can't, so we don't move return end end -- first erase the current position if current == '@' then level[player_y][player_x] = ' ' else level[player_y][player_x] = '.' end -- second draw on the new one if destination == ' ' then level[player_y + target_y][player_x + target_x] = '@' else level[player_y + target_y][player_x + target_x] = '+' end player_x = player_x + target_x player_y = player_y + target_y moves = moves + 1 if playing == false then move_sound:play() end -- save the previous move for UNDO table.insert(state_undo, 1, {undo, undo_x, undo_y}) end -- keypressed handler when the level is compplete function keypressed_win(key) if key == "space" then -- loop if we run out of levels if current_level == #levels then current_level = 1 else current_level = current_level + 1 end -- back to play! set_level(current_level) state = "play" end end -- keypressed handler during play function keypressed_play(key) if key == "r" then -- restart level set_level(current_level) restart_sound:play() return elseif key == "u" and #state_undo > 0 then -- undo move local state = table.remove(state_undo, 1) level = state[1] player_x = state[2] player_y = state[3] moves = moves - 1 undo_sound:play() end -- where are we moving local target_x = 0 local target_y = 0 if key == "up" then target_y = -1 elseif key == "down" then target_y = 1 elseif key == "left" then target_x = -1 elseif key=="right" then target_x = 1 end -- only if we move if target_x ~= 0 or target_y ~= 0 then move(target_x, target_y) if is_level_complete() then win_delay = 0 state = "win" end end end function love.keypressed(key) -- quit game if key == "escape" then print("bye bye!") love.event.quit() end if state == "play" then keypressed_play(key) elseif state == "win" then keypressed_win(key) end end function love.update(dt) player_delay = player_delay + dt * 3 if player_delay > 1 then player_delay = 0 if player_frame == 1 then player_frame = 2 else player_frame = 1 end end end function love.draw() love.graphics.setFont(font) love.graphics.print("Level " .. current_level .. " Moves " .. moves, 20, 5 * game.scale) -- these options anly exist when we are playing if state == "play" then love.graphics.print("R:restart", 20, 205 * game.scale) -- change colour if there's not undo if #state_undo == 0 then love.graphics.setColor(0.5, 0.3, 0.3) end love.graphics.print("U:undo", 20 + 10 * 8 * game.scale, 205 * game.scale) love.graphics.setColor(1, 1, 1) love.graphics.print("ESC:exit", 20 + 17 * 8 * game.scale, 205 * game.scale) end love.graphics.print("Storage Chaos", 20, 220 * game.scale) for y, row in ipairs(level) do for x, cell in ipairs(row) do -- for testing -- love.graphics.print(cell, 10 + x * 12 * game.scale, 10 + y * 12 * game.scale) draw_x = 10 + x * 64 draw_y = 10 + y * 64 -- draw ground unless in empty if cell ~= 'x' then love.graphics.draw(ground, draw_x, draw_y) end if cell == '#' then love.graphics.draw(wall, draw_x, draw_y) elseif cell == '$' then love.graphics.draw(crate, draw_x, draw_y) elseif cell == '.' then love.graphics.draw(goal, draw_x, draw_y) elseif cell == '*' then love.graphics.draw(crate_goal, draw_x, draw_y) end if cell == '@' then love.graphics.draw(player_frames[player_frame], draw_x, draw_y) elseif cell == '+' then love.graphics.draw(goal, draw_x, draw_y) love.graphics.draw(player_frames[player_frame], draw_x, draw_y) end end end -- if the level is completed, show an verlay if state == "win" then if win_delay < 64 then win_delay = win_delay + 1 if win_delay == 64 then win_sound:play() end else love.graphics.setColor(0.3, 0.3, 0.5) love.graphics.rectangle("fill", 0, 80 * game.scale, game.width * game.scale, 60 * game.scale) love.graphics.setColor(1.0, 1.0, 1.0) love.graphics.print("Level completed!", 96 * game.scale, 90 * game.scale) love.graphics.print("SPACE:next ESC:quit", 80 * game.scale, 115 * game.scale) end end end function love.keyreleased(key) end