diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c327dde --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lua", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/main.lua" + } + ] +} \ No newline at end of file diff --git a/fonts/font.ttf b/fonts/font.ttf new file mode 100644 index 0000000..fe4328b Binary files /dev/null and b/fonts/font.ttf differ diff --git a/game.game b/game.game new file mode 100644 index 0000000..c21ddd8 Binary files /dev/null and b/game.game differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/brown.png b/graphics/PixelUIpack/9-Slice/Ancient/brown.png new file mode 100755 index 0000000..8c33cee Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/brown.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/brown_inlay.png b/graphics/PixelUIpack/9-Slice/Ancient/brown_inlay.png new file mode 100755 index 0000000..7918798 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/brown_inlay.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/brown_pressed.png b/graphics/PixelUIpack/9-Slice/Ancient/brown_pressed.png new file mode 100755 index 0000000..c75812f Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/brown_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/grey.png b/graphics/PixelUIpack/9-Slice/Ancient/grey.png new file mode 100755 index 0000000..07eea7b Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/grey.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/grey_inlay.png b/graphics/PixelUIpack/9-Slice/Ancient/grey_inlay.png new file mode 100755 index 0000000..6153654 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/grey_inlay.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/grey_pressed.png b/graphics/PixelUIpack/9-Slice/Ancient/grey_pressed.png new file mode 100755 index 0000000..9f66040 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/grey_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/tan.png b/graphics/PixelUIpack/9-Slice/Ancient/tan.png new file mode 100755 index 0000000..3e2075e Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/tan.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/tan_inlay.png b/graphics/PixelUIpack/9-Slice/Ancient/tan_inlay.png new file mode 100755 index 0000000..fbe425e Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/tan_inlay.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/tan_pressed.png b/graphics/PixelUIpack/9-Slice/Ancient/tan_pressed.png new file mode 100755 index 0000000..bc4a0b8 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/tan_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/white.png b/graphics/PixelUIpack/9-Slice/Ancient/white.png new file mode 100755 index 0000000..ca6ea48 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/white.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/white_inlay.png b/graphics/PixelUIpack/9-Slice/Ancient/white_inlay.png new file mode 100755 index 0000000..d9507de Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/white_inlay.png differ diff --git a/graphics/PixelUIpack/9-Slice/Ancient/white_pressed.png b/graphics/PixelUIpack/9-Slice/Ancient/white_pressed.png new file mode 100755 index 0000000..49ffc01 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Ancient/white_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/blue.png b/graphics/PixelUIpack/9-Slice/Colored/blue.png new file mode 100755 index 0000000..e2fe503 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/blue.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/blue_pressed.png b/graphics/PixelUIpack/9-Slice/Colored/blue_pressed.png new file mode 100755 index 0000000..a2708bd Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/blue_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/green.png b/graphics/PixelUIpack/9-Slice/Colored/green.png new file mode 100755 index 0000000..38f64a0 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/green.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/green_pressed.png b/graphics/PixelUIpack/9-Slice/Colored/green_pressed.png new file mode 100755 index 0000000..83be734 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/green_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/grey.png b/graphics/PixelUIpack/9-Slice/Colored/grey.png new file mode 100755 index 0000000..17bfa37 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/grey.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/grey_pressed.png b/graphics/PixelUIpack/9-Slice/Colored/grey_pressed.png new file mode 100755 index 0000000..e5a72da Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/grey_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/red.png b/graphics/PixelUIpack/9-Slice/Colored/red.png new file mode 100755 index 0000000..b569496 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/red.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/red_pressed.png b/graphics/PixelUIpack/9-Slice/Colored/red_pressed.png new file mode 100755 index 0000000..ff9d28d Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/red_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/yellow.png b/graphics/PixelUIpack/9-Slice/Colored/yellow.png new file mode 100755 index 0000000..fdae21b Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/yellow.png differ diff --git a/graphics/PixelUIpack/9-Slice/Colored/yellow_pressed.png b/graphics/PixelUIpack/9-Slice/Colored/yellow_pressed.png new file mode 100755 index 0000000..9afe399 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Colored/yellow_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/blue.png b/graphics/PixelUIpack/9-Slice/Outline/blue.png new file mode 100755 index 0000000..6b36d20 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/blue.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/blue_pressed.png b/graphics/PixelUIpack/9-Slice/Outline/blue_pressed.png new file mode 100755 index 0000000..8719dfe Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/blue_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/green.png b/graphics/PixelUIpack/9-Slice/Outline/green.png new file mode 100755 index 0000000..7c6bb30 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/green.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/green_pressed.png b/graphics/PixelUIpack/9-Slice/Outline/green_pressed.png new file mode 100755 index 0000000..cfe86d9 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/green_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/red.png b/graphics/PixelUIpack/9-Slice/Outline/red.png new file mode 100755 index 0000000..a28790c Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/red.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/red_pressed.png b/graphics/PixelUIpack/9-Slice/Outline/red_pressed.png new file mode 100755 index 0000000..0e82d1d Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/red_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/yellow.png b/graphics/PixelUIpack/9-Slice/Outline/yellow.png new file mode 100755 index 0000000..ff070ee Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/yellow.png differ diff --git a/graphics/PixelUIpack/9-Slice/Outline/yellow_pressed.png b/graphics/PixelUIpack/9-Slice/Outline/yellow_pressed.png new file mode 100755 index 0000000..c735c8b Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/Outline/yellow_pressed.png differ diff --git a/graphics/PixelUIpack/9-Slice/list.png b/graphics/PixelUIpack/9-Slice/list.png new file mode 100755 index 0000000..1065270 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/list.png differ diff --git a/graphics/PixelUIpack/9-Slice/space.png b/graphics/PixelUIpack/9-Slice/space.png new file mode 100755 index 0000000..e45fbe9 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/space.png differ diff --git a/graphics/PixelUIpack/9-Slice/space_inlay.png b/graphics/PixelUIpack/9-Slice/space_inlay.png new file mode 100755 index 0000000..3ba1910 Binary files /dev/null and b/graphics/PixelUIpack/9-Slice/space_inlay.png differ diff --git a/graphics/PixelUIpack/Donate.url b/graphics/PixelUIpack/Donate.url new file mode 100755 index 0000000..7fdd00e --- /dev/null +++ b/graphics/PixelUIpack/Donate.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://donate.kenney.nl/ \ No newline at end of file diff --git a/graphics/PixelUIpack/Facebook.url b/graphics/PixelUIpack/Facebook.url new file mode 100755 index 0000000..2744b4b --- /dev/null +++ b/graphics/PixelUIpack/Facebook.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://www.facebook.com/KenneyNL \ No newline at end of file diff --git a/graphics/PixelUIpack/License.txt b/graphics/PixelUIpack/License.txt new file mode 100755 index 0000000..938613c --- /dev/null +++ b/graphics/PixelUIpack/License.txt @@ -0,0 +1,23 @@ + +############################################################################### + + + Pixel UI pack + by Kenney Vleugels for Kenney (www.kenney.nl) + with help by Lynn Evers (Twitter: @EversLynn) + + ------------------------------ + + License (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + You may use these graphics in personal and commercial projects. + Credit (Kenney or www.kenney.nl) would be nice but is not mandatory. + + ------------------------------ + + Donate: http://donate.kenney.nl/ + Request: http://request.kenney.nl/ + + +############################################################################### \ No newline at end of file diff --git a/graphics/PixelUIpack/Preview.png b/graphics/PixelUIpack/Preview.png new file mode 100755 index 0000000..e1237c5 Binary files /dev/null and b/graphics/PixelUIpack/Preview.png differ diff --git a/graphics/PixelUIpack/Spritesheet/UIpackSheet_magenta.png b/graphics/PixelUIpack/Spritesheet/UIpackSheet_magenta.png new file mode 100755 index 0000000..782c1a4 Binary files /dev/null and b/graphics/PixelUIpack/Spritesheet/UIpackSheet_magenta.png differ diff --git a/graphics/PixelUIpack/Spritesheet/UIpackSheet_transparent.png b/graphics/PixelUIpack/Spritesheet/UIpackSheet_transparent.png new file mode 100755 index 0000000..723ed94 Binary files /dev/null and b/graphics/PixelUIpack/Spritesheet/UIpackSheet_transparent.png differ diff --git a/graphics/PixelUIpack/Spritesheet/spritesheetInfo.txt b/graphics/PixelUIpack/Spritesheet/spritesheetInfo.txt new file mode 100755 index 0000000..24ffe93 --- /dev/null +++ b/graphics/PixelUIpack/Spritesheet/spritesheetInfo.txt @@ -0,0 +1,6 @@ +The tiles are 16 x 16px and have a 2px margin between them. + +--- + +TILE SIZE: 16 x 16 +MARGIN: 2 \ No newline at end of file diff --git a/graphics/entities.png b/graphics/entities.png new file mode 100644 index 0000000..eac82f3 Binary files /dev/null and b/graphics/entities.png differ diff --git a/graphics/entities_numbered.png b/graphics/entities_numbered.png new file mode 100644 index 0000000..e2271a7 Binary files /dev/null and b/graphics/entities_numbered.png differ diff --git a/graphics/pokemon/aardart-back.png b/graphics/pokemon/aardart-back.png new file mode 100644 index 0000000..d3acc0c Binary files /dev/null and b/graphics/pokemon/aardart-back.png differ diff --git a/graphics/pokemon/aardart-front.png b/graphics/pokemon/aardart-front.png new file mode 100644 index 0000000..e21a49b Binary files /dev/null and b/graphics/pokemon/aardart-front.png differ diff --git a/graphics/pokemon/agnite-back.png b/graphics/pokemon/agnite-back.png new file mode 100644 index 0000000..08048c1 Binary files /dev/null and b/graphics/pokemon/agnite-back.png differ diff --git a/graphics/pokemon/agnite-front.png b/graphics/pokemon/agnite-front.png new file mode 100644 index 0000000..d2ebdac Binary files /dev/null and b/graphics/pokemon/agnite-front.png differ diff --git a/graphics/pokemon/anoleaf-back.png b/graphics/pokemon/anoleaf-back.png new file mode 100644 index 0000000..07cbd9e Binary files /dev/null and b/graphics/pokemon/anoleaf-back.png differ diff --git a/graphics/pokemon/anoleaf-front.png b/graphics/pokemon/anoleaf-front.png new file mode 100644 index 0000000..70c1e44 Binary files /dev/null and b/graphics/pokemon/anoleaf-front.png differ diff --git a/graphics/pokemon/bamboon-back.png b/graphics/pokemon/bamboon-back.png new file mode 100644 index 0000000..a2a559f Binary files /dev/null and b/graphics/pokemon/bamboon-back.png differ diff --git a/graphics/pokemon/bamboon-front.png b/graphics/pokemon/bamboon-front.png new file mode 100644 index 0000000..703ebb8 Binary files /dev/null and b/graphics/pokemon/bamboon-front.png differ diff --git a/graphics/pokemon/cardiwing-back.png b/graphics/pokemon/cardiwing-back.png new file mode 100644 index 0000000..00c5e2c Binary files /dev/null and b/graphics/pokemon/cardiwing-back.png differ diff --git a/graphics/pokemon/cardiwing-front.png b/graphics/pokemon/cardiwing-front.png new file mode 100644 index 0000000..a576f12 Binary files /dev/null and b/graphics/pokemon/cardiwing-front.png differ diff --git a/graphics/sheet.png b/graphics/sheet.png new file mode 100644 index 0000000..05325c7 Binary files /dev/null and b/graphics/sheet.png differ diff --git a/graphics/sheet_numbered.png b/graphics/sheet_numbered.png new file mode 100644 index 0000000..efc0aab Binary files /dev/null and b/graphics/sheet_numbered.png differ diff --git a/lib/class.lua b/lib/class.lua new file mode 100644 index 0000000..8d1a17c --- /dev/null +++ b/lib/class.lua @@ -0,0 +1,100 @@ +-- require('mobdebug').start() + +--[[ +Copyright (c) 2010-2013 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local function include_helper(to, from, seen) + if from == nil then + return to + elseif type(from) ~= 'table' then + return from + elseif seen[from] then + return seen[from] + end + + seen[from] = to + for k,v in pairs(from) do + k = include_helper({}, k, seen) -- keys might also be tables + if to[k] == nil then + to[k] = include_helper({}, v, seen) + end + end + return to +end + +-- deeply copies `other' into `class'. keys in `other' that are already +-- defined in `class' are omitted +local function include(class, other) + return include_helper(class, other, {}) +end + +-- returns a deep copy of `other' +local function clone(other) + return setmetatable(include({}, other), getmetatable(other)) +end + +local function new(class) + -- mixins + class = class or {} -- class can be nil + local inc = class.__includes or {} + if getmetatable(inc) then inc = {inc} end + + for _, other in ipairs(inc) do + if type(other) == "string" then + other = _G[other] + end + include(class, other) + end + + -- class implementation + class.__index = class + class.init = class.init or class[1] or function() end + class.include = class.include or include + class.clone = class.clone or clone + + -- constructor call + return setmetatable(class, {__call = function(c, ...) + local o = setmetatable({}, c) + o:init(...) + return o + end}) +end + +-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons). +if class_commons ~= false and not common then + common = {} + function common.class(name, prototype, parent) + return new{__includes = {prototype, parent}} + end + function common.instance(class, ...) + return class(...) + end +end + + +-- the module +return setmetatable({new = new, include = include, clone = clone}, + {__call = function(_,...) return new(...) end}) diff --git a/lib/knife/base.lua b/lib/knife/base.lua new file mode 100644 index 0000000..55f72a4 --- /dev/null +++ b/lib/knife/base.lua @@ -0,0 +1,15 @@ +return { + extend = function (self, subtype) + subtype = subtype or {} + local meta = { __index = subtype } + return setmetatable(subtype, { + __index = self, + __call = function (self, ...) + local instance = setmetatable({}, meta) + return instance, instance:constructor(...) + end + }) + end, + constructor = function () end, +} + diff --git a/lib/knife/behavior.lua b/lib/knife/behavior.lua new file mode 100644 index 0000000..b825365 --- /dev/null +++ b/lib/knife/behavior.lua @@ -0,0 +1,78 @@ +-- behavior.lua -- a state manager + +-- internal/external api + +local function getCurrentFrame (behavior) + return behavior.states[behavior.state][behavior.index] +end + +local function advanceFrame (behavior) + local nextState = behavior.frame.after + local nextIndex = behavior.index + 1 + local maxIndex = #behavior.states[behavior.state] + + if nextState then + behavior.state = nextState + nextIndex = 1 + elseif nextIndex > maxIndex then + nextIndex = 1 + end + + behavior.index = nextIndex + behavior.frame = behavior:getCurrentFrame() +end + +local function performAction (behavior) + local act = behavior.frame.action + + if act then + act(behavior, behavior.subject) + end +end + +-- external api + +local function update (behavior, dt) + behavior.elapsed = behavior.elapsed + dt + + while behavior.elapsed >= behavior.frame.duration do + behavior.elapsed = behavior.elapsed - behavior.frame.duration + behavior:advanceFrame() + behavior:performAction() + end + + return behavior +end + +local function setState (behavior, state, index) + behavior.state = state + behavior.index = index or 1 + behavior.elapsed = 0 + behavior.frame = behavior:getCurrentFrame() + behavior:performAction() + return behavior +end + +-- behavior factory + +return function (states, subject) + local behavior = { + states = states, + subject = subject, + elapsed = 0, + state = 'default', + index = 1, + -- internal api + getCurrentFrame = getCurrentFrame, + advanceFrame = advanceFrame, + performAction = performAction, + -- external api + update = update, + setState = setState + } + + behavior.frame = behavior:getCurrentFrame() + behavior:performAction() + + return behavior +end diff --git a/lib/knife/bind.lua b/lib/knife/bind.lua new file mode 100644 index 0000000..da5fbbd --- /dev/null +++ b/lib/knife/bind.lua @@ -0,0 +1,27 @@ +local loadstring = _G.loadstring or _G.load +local tconcat = table.concat + +local helperCache = {} + +local function buildHelper (argCount) + if helperCache[argCount] then + return helperCache[argCount] + end + local argList1 = { 'f' } + local argList2 = {} + for index = 1, argCount do + argList1[index + 1] = 'a' .. index + argList2[index] = 'a' .. index + end + argList2[argCount + 1] = '...' + local source = 'return function(' .. tconcat(argList1, ', ') .. + ') return function(...) return f(' .. tconcat(argList2, ', ') .. + ') end end' + local helper = loadstring(source)() + helperCache[argCount] = helper + return helper +end + +return function (func, ...) + return buildHelper(select('#', ...))(func, ...) +end diff --git a/lib/knife/chain.lua b/lib/knife/chain.lua new file mode 100644 index 0000000..5401d43 --- /dev/null +++ b/lib/knife/chain.lua @@ -0,0 +1,31 @@ +local function Invoker (links, index) + return function (...) + local link = links[index] + if not link then + return + end + local continue = Invoker(links, index + 1) + local returned = link(continue, ...) + if returned then + returned(function (_, ...) continue(...) end) + end + end +end + +return function (...) + local links = { ... } + + local function chain (...) + if not (...) then + return Invoker(links, 1)(select(2, ...)) + end + local offset = #links + for index = 1, select('#', ...) do + links[offset + index] = select(index, ...) + end + return chain + end + + return chain +end + diff --git a/lib/knife/convoke.lua b/lib/knife/convoke.lua new file mode 100644 index 0000000..b01f8c4 --- /dev/null +++ b/lib/knife/convoke.lua @@ -0,0 +1,54 @@ +return function (routine) + local routines = { routine } + local routineIndex = 1 + local isFinished = false + + local function execute () + local continueCount = 0 + local run + + local function continue () + continueCount = continueCount + 1 + return function (...) + continueCount = continueCount - 1 + if continueCount == 0 then + return run(...) + end + end + end + + local function wait (...) + return coroutine.yield(...) + end + + local r = coroutine.create(function () + isFinished = false + while routineIndex <= #routines do + routines[routineIndex](continue, wait) + continueCount = 0 + routineIndex = routineIndex + 1 + end + isFinished = true + end) + + run = function (...) + return coroutine.resume(r, ...) + end + + run() + end + + local function appendOrExecute (routine) + if routine then + routines[#routines + 1] = routine + if isFinished then + execute() + end + return appendOrExecute + else + execute() + end + end + + return appendOrExecute +end diff --git a/lib/knife/event.lua b/lib/knife/event.lua new file mode 100644 index 0000000..d698508 --- /dev/null +++ b/lib/knife/event.lua @@ -0,0 +1,111 @@ +-- require('mobdebug').start() + +-- Event module +local Event = {} + +-- Event handler registry +Event.handlers = {} + +-- Remove an event handler from the registry +local function remove (self) + if not self.isRegistered then + return self + end + if self.prevHandler then + self.prevHandler.nextHandler = self.nextHandler + end + if self.nextHandler then + self.nextHandler.prevHandler = self.prevHandler + end + if Event.handlers[self.name] == self then + Event.handlers[self.name] = self.nextHandler + end + self.prevHandler = nil + self.nextHandler = nil + self.isRegistered = false + + return self +end + +-- Insert an event handler into the registry +local function register (self) + if self.isRegistered then + return self + end + self.nextHandler = Event.handlers[self.name] + if self.nextHandler then + self.nextHandler.prevHandler = self + end + Event.handlers[self.name] = self + self.isRegistered = true + + return self +end + +-- Create an event handler +local function Handler (name, callback) + return { + name = name, + callback = callback, + isRegistered = false, + remove = remove, + register = register + } +end + + + +-- Create and register a new event handler +function Event.on (name, callback) + return register(Handler(name, callback)) +end + +-- Dispatch an event +function Event.dispatch (name, ...) + local handler = Event.handlers[name] + + while handler do + if handler.callback(...) == false then + return handler + end + handler = handler.nextHandler + end +end + +local function isCallable (value) + return type(value) == 'function' or + getmetatable(value) and getmetatable(value).__call +end + +-- Inject a dispatcher into a table. +local function hookDispatcher (t, key) + local original = t[key] + + if isCallable(original) then + t[key] = function (...) + original(...) + return Event.dispatch(key, ...) + end + else + t[key] = function (...) + return Event.dispatch(key, ...) + end + end +end + +-- Inject dispatchers into a table. Examples: +-- Event.hook(love.handlers) +-- Event.hook(love, { 'load', 'update', 'draw' }) +function Event.hook (t, keys) + if keys then + for _, key in ipairs(keys) do + hookDispatcher(t, key) + end + else + for key in pairs(t) do + hookDispatcher(t, key) + end + end +end + +return Event diff --git a/lib/knife/gun.lua b/lib/knife/gun.lua new file mode 100644 index 0000000..9a445ae --- /dev/null +++ b/lib/knife/gun.lua @@ -0,0 +1,2 @@ +-- GUN (Give Up Now) +os.exit() diff --git a/lib/knife/memoize.lua b/lib/knife/memoize.lua new file mode 100644 index 0000000..bcace75 --- /dev/null +++ b/lib/knife/memoize.lua @@ -0,0 +1,77 @@ +local loadstring = _G.loadstring or _G.load +local weakKeys = { __mode = 'k' } +local cache = setmetatable({}, weakKeys) +local resultsKey = {} +local nilKey = {} + +local function getMetaCall (callable) + local meta = getmetatable(callable) + return meta and meta.__call +end + +local tupleConstructorCache = {} + +local function buildTupleConstructor (n) + if tupleConstructorCache[n] then + return tupleConstructorCache[n] + end + local t = {} + for i = 1, n do + t[i] = "a" .. i + end + local args = table.concat(t, ',') + local ctor = loadstring('return function(' .. args .. + ') return function() return ' .. args .. ' end end')() + tupleConstructorCache[n] = ctor + return ctor +end + +local function tuple (...) + return buildTupleConstructor(select('#', ...))(...) +end + +return function (callable) + local metaCall = getMetaCall(callable) + + if type(callable) ~= 'function' and not metaCall then + error 'Attempted to memoize a non-callable value.' + end + + cache[callable] = setmetatable({}, weakKeys) + + local function run (...) + local node = cache[callable] + local argc = select('#', ...) + for i = 1, argc do + local key = select(i, ...) + if key == nil then + key = nilKey + end + if not node[key] then + node[key] = setmetatable({}, weakKeys) + end + node = node[key] + end + + if not node[resultsKey] then + node[resultsKey] = tuple(callable(...)) + end + + return node[resultsKey]() + end + + if metaCall then + return function (...) + local call = getMetaCall(callable) + + if call ~= metaCall then + cache[callable] = setmetatable({}, weakKeys) + metaCall = call + end + + return run(...) + end, cache, resultsKey, nilKey + end + + return run, cache, resultsKey, nilKey +end diff --git a/lib/knife/serialize.lua b/lib/knife/serialize.lua new file mode 100644 index 0000000..92d7c95 --- /dev/null +++ b/lib/knife/serialize.lua @@ -0,0 +1,78 @@ +local tinsert, tconcat, infinity = table.insert, table.concat, math.huge + +return function (value) + local intro, outro, ready, known = {}, {}, {}, {} + local knownCount = 0 + local writer = {} + + -- get writer delegate for this value's type + local function getWriter (value) + return writer[type(value)] + end + + -- check if a value has a representation yet + local function isReady (value) + return type(value) ~= 'table' or ready[value] + end + + -- serialize tables + function writer.table (value) + if known[value] then + return known[value] + end + + knownCount = knownCount + 1 + local variable = ('v%i'):format(knownCount) + known[value] = variable + + local parts = {} + for k, v in pairs(value) do + local writeKey, writeValue = getWriter(k), getWriter(v) + if writeKey and writeValue then + local key, value = writeKey(k), writeValue(v) + if isReady(k) and isReady(v) then + tinsert(parts, ('[%s]=%s'):format(key, value)) + else + tinsert(outro, ('%s[%s]=%s'):format(variable, key, value)) + end + end + end + + local fields = tconcat(parts, ',') + tinsert(intro, ('local %s={%s}'):format(variable, fields)) + ready[value] = true + + return variable + end + + -- preserve sign bit on NaN, since Lua prints "nan" or "-nan" + local function writeNan (n) + return tostring(n) == tostring(0/0) and '0/0' or '-(0/0)' + end + + -- serialize numbers + function writer.number (value) + return value == infinity and '1/0' + or value == -infinity and '-1/0' + or value ~= value and writeNan(value) + or ('%.17G'):format(value) + end + + -- serialize strings + function writer.string (value) + return ('%q'):format(value) + end + + -- serialize booleans + writer.boolean = tostring + + -- concatenate array, joined by and terminated with line break + local function lines (t) + return #t == 0 and '' or tconcat(t, '\n') .. '\n' + end + + -- generate serialized result + local write = getWriter(value) + local result = write and write(value) or 'nil' + return lines(intro) .. lines(outro) .. 'return ' .. result +end diff --git a/lib/knife/system.lua b/lib/knife/system.lua new file mode 100644 index 0000000..ddb6060 --- /dev/null +++ b/lib/knife/system.lua @@ -0,0 +1,69 @@ +local loadstring = _G.loadstring or _G.load +local tconcat = table.concat +local type = type + +local function hasSigil (sigil, value) + return type(value) == 'string' and sigil:byte() == value:byte() +end + +return function (aspects, process) + local args = {} + local cond = {} + local results = {} + local localIndex = 0 + local choicePattern = '([^|]+)' + + local function suppress (aspect, condition) + cond[#cond + 1] = 'if nil' + for option in aspect:gmatch(choicePattern) do + cond[#cond + 1] = condition:format(option) + end + cond[#cond + 1] = 'then return end' + end + + local function supply (aspect, isOptional, isReturned) + localIndex = localIndex + 1 + cond[#cond + 1] = ('local l%d = nil'):format(localIndex) + for option in aspect:gmatch(choicePattern) do + cond[#cond + 1] = ('or _entity[%q]'):format(option) + end + if not isOptional then + cond[#cond + 1] = ('if not l%d then return end'):format(localIndex) + end + if isReturned then + results[#results + 1] = ('_entity[%q]'):format(aspect) + end + args[#args + 1] = ('l%d'):format(localIndex) + end + + for index = 1, #aspects do + local aspect = aspects[index] + if hasSigil('_', aspect) then + args[#args + 1] = aspect + elseif hasSigil('!', aspect) or hasSigil('~', aspect) then + suppress(aspect:sub(2), 'or _entity[%q]') + elseif hasSigil('-', aspect) then + suppress(aspect:sub(2), 'or not _entity[%q]') + elseif hasSigil('?', aspect) then + supply(aspect:sub(2), true) + elseif hasSigil('=', aspect) then + supply(aspect:sub(2), false, true) + else + supply(aspect, false) + end + end + + local source = ([[ +local _aspects, _process = ... +return function (_entity, ...) + %s + %s _process(%s ...) + return true +end]]):format( + tconcat(cond, ' '), + results[1] and (tconcat(results, ',') .. ' = ') or '', + args[1] and (tconcat(args, ', ') .. ', ') or '') + + return loadstring(source)(aspects, process) +end + diff --git a/lib/knife/test.lua b/lib/knife/test.lua new file mode 100644 index 0000000..2a6aecd --- /dev/null +++ b/lib/knife/test.lua @@ -0,0 +1,128 @@ +local test, testAssert, testError + +-- Create a node representing a test section +local function createNode (parent, description, process) + return setmetatable({ + parent = parent, + description = description, + process = process, + nodes = {}, + activeNodeIndex = 1, + currentNodeIndex = 0, + assert = testAssert, + error = testError, + }, { __call = test }) +end + +-- Run a node +local function runNode (node) + node.currentNodeIndex = 0 + return node:process() +end + +-- Get the root node for a given node +local function getRootNode (node) + local parent = node.parent + return parent and getRootNode(parent) or node +end + +-- Update the active child node of the given node +local function updateActiveNode (node, description, process) + local activeNodeIndex = node.activeNodeIndex + local nodes = node.nodes + local activeNode = nodes[activeNodeIndex] + + if not activeNode then + activeNode = createNode(node, description, process) + nodes[activeNodeIndex] = activeNode + else + activeNode.process = process + end + + getRootNode(node).lastActiveLeaf = activeNode + + return activeNode +end + +-- Run the active child node of the given node +local function runActiveNode (node, description, process) + local activeNode = updateActiveNode(node, description, process) + return runNode(activeNode) +end + +-- Get ancestors of a node, including the node +local function getAncestors (node) + local ancestors = { node } + for ancestor in function () return node.parent end do + ancestors[#ancestors + 1] = ancestor + node = ancestor + end + return ancestors +end + +-- Print a message describing one execution path in the test scenario +local function printScenario (node) + local ancestors = getAncestors(node) + for i = #ancestors, 1, -1 do + io.stderr:write(ancestors[i].description or '') + io.stderr:write('\n') + end +end + +-- Print a message and stop the test scenario when an assertion fails +local function failAssert (node, description, message) + io.stderr:write(message or '') + io.stderr:write('\n\n') + printScenario(node) + io.stderr:write(description or '') + io.stderr:write('\n\n') + error(message or '', 2) +end + +-- Create a branch node for a test scenario +test = function (node, description, process) + node.currentNodeIndex = node.currentNodeIndex + 1 + if node.currentNodeIndex == node.activeNodeIndex then + return runActiveNode(node, description, process) + end +end + +-- Test an assertion +testAssert = function (self, value, description) + if not value then + return failAssert(self, description, 'Test failed: assertion failed') + end + return value +end + +-- Expect function f to fail +testError = function (self, f, description) + if pcall(f) then + return failAssert(self, description, 'Test failed: expected error') + end +end + +-- Create the root node for a test scenario +local function T (description, process) + local root = createNode(nil, description, process) + + runNode(root) + while root.activeNodeIndex <= #root.nodes do + local lastActiveBranch = root.lastActiveLeaf.parent + lastActiveBranch.activeNodeIndex = lastActiveBranch.activeNodeIndex + 1 + runNode(root) + end + + return root +end + +-- Run any other files passed from CLI. +if arg and arg[0] and arg[0]:gmatch('test.lua') then + _G.T = T + for i = 1, #arg do + dofile(arg[i]) + end + _G.T = nil +end + +return T diff --git a/lib/knife/timer.lua b/lib/knife/timer.lua new file mode 100644 index 0000000..de1e439 --- /dev/null +++ b/lib/knife/timer.lua @@ -0,0 +1,246 @@ +-- require('mobdebug').start() + +local Timer = {} + +-- group management + +local function detach (group, item) + local index = item.index + + group[index] = group[#group] + group[index].index = index + group[#group] = nil + item.groupField = nil +end + +local function attach (group, item) + if item.groupField then + detach (item.groupField, item) + end + + local index = #group + 1 + + item.index = index + group[index] = item + item.groupField = group + item.lastGroup = group +end + +-- instance update methods + +local function updateContinuous (self, dt) + local cutoff = self.cutoff + local elapsed = self.elapsed + dt + + if self:callback(dt) == false or elapsed >= cutoff then + if self.finishField then + self:finishField(elapsed - cutoff) + end + self:remove() + end + + self.elapsed = elapsed + + return +end + +local function updateIntermittent (self, dt) + local duration = self.delay or self.interval + local elapsed = self.elapsed + dt + + while elapsed >= duration do + elapsed = elapsed - duration + if self.limitField then + self.limitField = self.limitField - 1 + end + if self:callback(elapsed) == false + or self.delay or self.limitField == 0 then + if self.finishField then + self:finishField(elapsed) + end + self:remove() + return + end + end + + self.elapsed = elapsed +end + +local function updateTween (self, dt) + local elapsed = self.elapsed + dt + local plan = self.plan + local duration = self.duration + + self.elapsed = elapsed + + if elapsed >= duration then + for index = 1, #plan do + local task = plan[index] + + task.target[task.key] = task.final + end + if self.finishField then + self:finishField(elapsed - duration) + end + self:remove() + return + end + + local ease = self.easeField + + for index = 1, #plan do + local task = plan[index] + local target, key = task.target, task.key + local initial, change = task.initial, task.change + + target[key] = ease(elapsed, initial, change, duration) + end + +end + +-- shared instance methods + +local defaultGroup = {} + +local function group (self, group) + if not group then + group = defaultGroup + end + + attach(group, self) + + return self +end + +local function remove (self) + if self.groupField then + detach(self.groupField, self) + end + + return self +end + +local function register (self) + attach(self.lastGroup, self) + + return self +end + +local function limit (self, limitField) + self.limitField = limitField + + return self +end + +local function finish (self, finishField) + self.finishField = finishField + + return self +end + +local function ease (self, easeField) + self.easeField = easeField + + return self +end + +-- tweening helper functions + +local function planTween (definition) + local plan = {} + + for target, values in pairs(definition) do + for key, final in pairs(values) do + local initial = target[key] + + plan[#plan + 1] = { + target = target, + key = key, + initial = initial, + final = final, + change = final - initial, + } + end + end + + return plan +end + +local function easeLinear (elapsed, initial, change, duration) + return change * elapsed / duration + initial +end + +-- instance initializer + +local function initialize (timer) + timer.elapsed = 0 + timer.group = group + timer.remove = remove + timer.register = register + + attach(defaultGroup, timer) + + return timer +end + +-- static api + +function Timer.after (delay, callback) + return initialize { + delay = delay, + callback = callback, + update = updateIntermittent, + } +end + +function Timer.every (interval, callback) + return initialize { + interval = interval, + callback = callback, + update = updateIntermittent, + limit = limit, + finish = finish, + } +end + +function Timer.prior (cutoff, callback) + return initialize { + cutoff = cutoff, + callback = callback, + update = updateContinuous, + finish = finish, + } +end + +function Timer.tween (duration, definition) + return initialize { + duration = duration, + plan = planTween(definition), + update = updateTween, + easeField = easeLinear, + ease = ease, + finish = finish, + } +end + +function Timer.update (dt, group) + if not group then + group = defaultGroup + end + for index = #group, 1, -1 do + group[index]:update(dt) + end +end + +function Timer.clear (group) + if not group then + group = defaultGroup + end + for i = 1, #group do + group[i] = nil + end +end + +Timer.defaultGroup = defaultGroup + +return Timer diff --git a/lib/push.lua b/lib/push.lua new file mode 100644 index 0000000..8580c3a --- /dev/null +++ b/lib/push.lua @@ -0,0 +1,281 @@ +-- push.lua v0.4 + +-- Copyright (c) 2020 Ulysse Ramage +-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +local love11 = love.getVersion() == 11 +local getDPI = love11 and love.window.getDPIScale or love.window.getPixelScale +local windowUpdateMode = love11 and love.window.updateMode or function(width, height, settings) + local _, _, flags = love.window.getMode() + for k, v in pairs(settings) do flags[k] = v end + love.window.setMode(width, height, flags) +end + +local push = { + + defaults = { + fullscreen = false, + resizable = false, + pixelperfect = false, + highdpi = true, + canvas = true, + stencil = true + } + +} +setmetatable(push, push) + +function push:applySettings(settings) + for k, v in pairs(settings) do + self["_" .. k] = v + end +end + +function push:resetSettings() return self:applySettings(self.defaults) end + +function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings) + + settings = settings or {} + + self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT + self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT + + self:applySettings(self.defaults) --set defaults first + self:applySettings(settings) --then fill with custom settings + + windowUpdateMode(self._RWIDTH, self._RHEIGHT, { + fullscreen = self._fullscreen, + resizable = self._resizable, + highdpi = self._highdpi + }) + + self:initValues() + + if self._canvas then + self:setupCanvas({ "default" }) --setup canvas + end + + self._borderColor = {0, 0, 0} + + self._drawFunctions = { + ["start"] = self.start, + ["end"] = self.finish + } + + return self +end + +function push:setupCanvas(canvases) + table.insert(canvases, { name = "_render", private = true }) --final render + + self._canvas = true + self.canvases = {} + + for i = 1, #canvases do + push:addCanvas(canvases[i]) + end + + return self +end +function push:addCanvas(params) + table.insert(self.canvases, { + name = params.name, + private = params.private, + shader = params.shader, + canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT), + stencil = params.stencil or self._stencil + }) +end + +function push:setCanvas(name) + if not self._canvas then return true end + local canvasTable = self:getCanvasTable(name) + return love.graphics.setCanvas({ canvasTable.canvas, stencil = canvasTable.stencil }) +end +function push:getCanvasTable(name) + for i = 1, #self.canvases do + if self.canvases[i].name == name then + return self.canvases[i] + end + end +end +function push:setShader(name, shader) + if not shader then + self:getCanvasTable("_render").shader = name + else + self:getCanvasTable(name).shader = shader + end +end + +function push:initValues() + self._PSCALE = (not love11 and self._highdpi) and getDPI() or 1 + + self._SCALE = { + x = self._RWIDTH/self._WWIDTH * self._PSCALE, + y = self._RHEIGHT/self._WHEIGHT * self._PSCALE + } + + if self._stretched then --if stretched, no need to apply offset + self._OFFSET = {x = 0, y = 0} + else + local scale = math.min(self._SCALE.x, self._SCALE.y) + if self._pixelperfect then scale = math.floor(scale) end + + self._OFFSET = {x = (self._SCALE.x - scale) * (self._WWIDTH/2), y = (self._SCALE.y - scale) * (self._WHEIGHT/2)} + self._SCALE.x, self._SCALE.y = scale, scale --apply same scale to X and Y + end + + self._GWIDTH = self._RWIDTH * self._PSCALE - self._OFFSET.x * 2 + self._GHEIGHT = self._RHEIGHT * self._PSCALE - self._OFFSET.y * 2 +end + +function push:apply(operation, shader) + self._drawFunctions[operation](self, shader) +end + +function push:start() + if self._canvas then + love.graphics.push() + love.graphics.setCanvas({ self.canvases[1].canvas, stencil = self.canvases[1].stencil }) + + else + love.graphics.translate(self._OFFSET.x, self._OFFSET.y) + love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE.x, self._WHEIGHT*self._SCALE.y) + love.graphics.push() + love.graphics.scale(self._SCALE.x, self._SCALE.y) + end +end + +function push:applyShaders(canvas, shaders) + local _shader = love.graphics.getShader() + if #shaders <= 1 then + love.graphics.setShader(shaders[1]) + love.graphics.draw(canvas) + else + local _canvas = love.graphics.getCanvas() + + local _tmp = self:getCanvasTable("_tmp") + if not _tmp then --create temp canvas only if needed + self:addCanvas({ name = "_tmp", private = true, shader = nil }) + _tmp = self:getCanvasTable("_tmp") + end + + love.graphics.push() + love.graphics.origin() + local outputCanvas + for i = 1, #shaders do + local inputCanvas = i % 2 == 1 and canvas or _tmp.canvas + outputCanvas = i % 2 == 0 and canvas or _tmp.canvas + love.graphics.setCanvas(outputCanvas) + love.graphics.clear() + love.graphics.setShader(shaders[i]) + love.graphics.draw(inputCanvas) + love.graphics.setCanvas(inputCanvas) + end + love.graphics.pop() + + love.graphics.setCanvas(_canvas) + love.graphics.draw(outputCanvas) + end + love.graphics.setShader(_shader) +end + +function push:finish(shader) + love.graphics.setBackgroundColor(unpack(self._borderColor)) + if self._canvas then + local _render = self:getCanvasTable("_render") + + love.graphics.pop() + + local white = love11 and 1 or 255 + love.graphics.setColor(white, white, white) + + --draw canvas + love.graphics.setCanvas(_render.canvas) + for i = 1, #self.canvases do --do not draw _render yet + local _table = self.canvases[i] + if not _table.private then + local _canvas = _table.canvas + local _shader = _table.shader + self:applyShaders(_canvas, type(_shader) == "table" and _shader or { _shader }) + end + end + love.graphics.setCanvas() + + --draw render + love.graphics.translate(self._OFFSET.x, self._OFFSET.y) + local shader = shader or _render.shader + love.graphics.push() + love.graphics.scale(self._SCALE.x, self._SCALE.y) + self:applyShaders(_render.canvas, type(shader) == "table" and shader or { shader }) + love.graphics.pop() + + --clear canvas + for i = 1, #self.canvases do + love.graphics.setCanvas(self.canvases[i].canvas) + love.graphics.clear() + end + + love.graphics.setCanvas() + love.graphics.setShader() + else + love.graphics.pop() + love.graphics.setScissor() + end +end + +function push:setBorderColor(color, g, b) + self._borderColor = g and {color, g, b} or color +end + +function push:toGame(x, y) + x, y = x - self._OFFSET.x, y - self._OFFSET.y + local normalX, normalY = x / self._GWIDTH, y / self._GHEIGHT + + x = (x >= 0 and x <= self._WWIDTH * self._SCALE.x) and normalX * self._WWIDTH or nil + y = (y >= 0 and y <= self._WHEIGHT * self._SCALE.y) and normalY * self._WHEIGHT or nil + + return x, y +end + +function push:toReal(x, y) + local realX = self._OFFSET.x + (self._GWIDTH * x)/self._WWIDTH + local realY = self._OFFSET.y + (self._GHEIGHT * y)/self._WHEIGHT + return realX, realY +end + +function push:switchFullscreen(winw, winh) + self._fullscreen = not self._fullscreen + local windowWidth, windowHeight = love.window.getDesktopDimensions() + + if self._fullscreen then --save windowed dimensions for later + self._WINWIDTH, self._WINHEIGHT = self._RWIDTH, self._RHEIGHT + elseif not self._WINWIDTH or not self._WINHEIGHT then + self._WINWIDTH, self._WINHEIGHT = windowWidth * .5, windowHeight * .5 + end + + self._RWIDTH = self._fullscreen and windowWidth or winw or self._WINWIDTH + self._RHEIGHT = self._fullscreen and windowHeight or winh or self._WINHEIGHT + + self:initValues() + + love.window.setFullscreen(self._fullscreen, "desktop") + if not self._fullscreen and (winw or winh) then + windowUpdateMode(self._RWIDTH, self._RHEIGHT) --set window dimensions + end +end + +function push:resize(w, h) + if self._highdpi then w, h = w / self._PSCALE, h / self._PSCALE end + self._RWIDTH = w + self._RHEIGHT = h + self:initValues() +end + +function push:getWidth() return self._WWIDTH end +function push:getHeight() return self._WHEIGHT end +function push:getDimensions() return self._WWIDTH, self._WHEIGHT end + +return push \ No newline at end of file diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..67cefa0 --- /dev/null +++ b/main.lua @@ -0,0 +1,78 @@ +--[[ + GD50 + + Author: Angel C Monroy + angelchecamonroy@outlook.com + + Credit for art: + Buch - https://opengameart.org/users/buch (tile sprites) + + Monster sprites: + http://creativecommons.org/licenses/by-sa/4.0/ + Aardart - Magic-Purple-Hermit + https://wiki.tuxemon.org/index.php?title=Magic-Purple-Hermit + Agnite - Leo, Sanglorian, and extyrannomon + https://wiki.tuxemon.org/index.php?title=Leo + https://wiki.tuxemon.org/index.php?title=Sanglorian + https://wiki.tuxemon.org/index.php?title=Extyrannomon&action=edit&redlink=1 + Anoleaf - Spalding004 + https://wiki.tuxemon.org/index.php?title=Spalding004 + Bamboon - Mike Bramson + mnbramson@gmail.com + Cardiwing - Spalding004 + https://wiki.tuxemon.org/index.php?title=Spalding004 + + Credit for music: + Field: https://freesound.org/people/Mrthenoronha/sounds/371843/ + Battle: https://freesound.org/people/Sirkoto51/sounds/414214/ +]] + +-- require('mobdebug').start() + +require 'src/Dependencies' + +function love.load() + love.window.setTitle('BallCatch50') + love.graphics.setDefaultFilter('nearest', 'nearest') + math.randomseed(os.time()) + + push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, { + fullscreen = false, + vsync = false, + resizable = false + }) + + gStateStack = StateStack() + gStateStack:push(StartState()) + + love.keyboard.keysPressed = {} +end + +function love.resize(w, h) + push:resize(w, h) +end + +function love.keypressed(key) + if key == 'escape' then + love.event.quit() + end + + love.keyboard.keysPressed[key] = true +end + +function love.keyboard.wasPressed(key) + return love.keyboard.keysPressed[key] +end + +function love.update(dt) + Timer.update(dt) + gStateStack:update(dt) + + love.keyboard.keysPressed = {} +end + +function love.draw() + push:start() + gStateStack:render() + push:finish() +end \ No newline at end of file diff --git a/sounds/battle_music.mp3 b/sounds/battle_music.mp3 new file mode 100644 index 0000000..6c1dd20 Binary files /dev/null and b/sounds/battle_music.mp3 differ diff --git a/sounds/blip.wav b/sounds/blip.wav new file mode 100644 index 0000000..5d4f8e4 Binary files /dev/null and b/sounds/blip.wav differ diff --git a/sounds/exp.wav b/sounds/exp.wav new file mode 100644 index 0000000..d930a37 Binary files /dev/null and b/sounds/exp.wav differ diff --git a/sounds/field_music.wav b/sounds/field_music.wav new file mode 100644 index 0000000..a72ee51 Binary files /dev/null and b/sounds/field_music.wav differ diff --git a/sounds/heal.wav b/sounds/heal.wav new file mode 100644 index 0000000..e90d47f Binary files /dev/null and b/sounds/heal.wav differ diff --git a/sounds/hit.wav b/sounds/hit.wav new file mode 100644 index 0000000..74e7e30 Binary files /dev/null and b/sounds/hit.wav differ diff --git a/sounds/intro.mp3 b/sounds/intro.mp3 new file mode 100644 index 0000000..63287fb Binary files /dev/null and b/sounds/intro.mp3 differ diff --git a/sounds/intro.ogg b/sounds/intro.ogg new file mode 100644 index 0000000..736e7fc Binary files /dev/null and b/sounds/intro.ogg differ diff --git a/sounds/kill.wav b/sounds/kill.wav new file mode 100644 index 0000000..ade49d8 Binary files /dev/null and b/sounds/kill.wav differ diff --git a/sounds/levelup.wav b/sounds/levelup.wav new file mode 100644 index 0000000..3b4eab7 Binary files /dev/null and b/sounds/levelup.wav differ diff --git a/sounds/music.wav b/sounds/music.wav new file mode 100644 index 0000000..34dcde2 Binary files /dev/null and b/sounds/music.wav differ diff --git a/sounds/powerup.wav b/sounds/powerup.wav new file mode 100644 index 0000000..d04fdeb Binary files /dev/null and b/sounds/powerup.wav differ diff --git a/sounds/run.wav b/sounds/run.wav new file mode 100644 index 0000000..ec4515d Binary files /dev/null and b/sounds/run.wav differ diff --git a/sounds/victory.wav b/sounds/victory.wav new file mode 100644 index 0000000..e93cd8d Binary files /dev/null and b/sounds/victory.wav differ diff --git a/src/Animation.lua b/src/Animation.lua new file mode 100644 index 0000000..bd5c0a3 --- /dev/null +++ b/src/Animation.lua @@ -0,0 +1,57 @@ +--[[ + GD50 + Legend of Zelda + + -- Animation Class -- + + Author: Colton Ogden + cogden@cs50.harvard.edu +]] + +Animation = Class{} + +function Animation:init(def) + self.frames = def.frames + self.interval = def.interval + self.texture = def.texture + self.looping = def.looping or true + + self.timer = 0 + self.currentFrame = 1 + + -- used to see if we've seen a whole loop of the animation + self.timesPlayed = 0 +end + +function Animation:refresh() + self.timer = 0 + self.currentFrame = 1 + self.timesPlayed = 0 +end + +function Animation:update(dt) + -- if not a looping animation and we've played at least once, exit + if not self.looping and self.timesPlayed > 0 then + return + end + + -- no need to update if animation is only one frame + if #self.frames > 1 then + self.timer = self.timer + dt + + if self.timer > self.interval then + self.timer = self.timer % self.interval + + self.currentFrame = math.max(1, (self.currentFrame + 1) % (#self.frames + 1)) + + -- if we've looped back to the beginning, record + if self.currentFrame == 1 then + self.timesPlayed = self.timesPlayed + 1 + end + end + end +end + +function Animation:getCurrentFrame() + return self.frames[self.currentFrame] +end \ No newline at end of file diff --git a/src/Ball.lua b/src/Ball.lua new file mode 100644 index 0000000..ff78618 --- /dev/null +++ b/src/Ball.lua @@ -0,0 +1,69 @@ + +Ball = Class{} + +function Ball:init(x, y, dx, dy) + self.x = x + self.y = y + self.width = 8 + self.height = 8 + + -- these variables are for keeping track of our velocity on both the + -- X and Y axis, since the ball can move in two dimensions + self.dy = dx + self.dx = dy + self.remove = false + self.hitPlayer = false +end +function Ball:reset() + self.x = VIRTUAL_WIDTH / 2 - 2 + self.y = VIRTUAL_HEIGHT / 2 - 2 + self.dx = 0 + self.dy = 0 +end + +function Ball:update(dt,level) + local tempX = self.x + self.dx * dt + local tempY = self.y + self.dy * dt + + local trailSeg = level:pointOnEdge(tempX,tempY,level.player.trailSegments) + if trailSeg then + self.hitPlayer = true + gSounds['hit']:play() + end + + local i, segment = level:getTouchingSegment(tempX,tempY) + if segment then + local direction = self:getCollisionDirection(segment, tempX, tempY) + if direction == 'up' or direction == 'down' then + self.dy = - self.dy + elseif direction == 'right' or direction == 'left' then + self.dx = -self.dx + elseif level:pointOnEdge(tempX,tempY) then + self.dx = -self.dx + self.dy = -self.dy + elseif not level:insideBounds(tempX,tempY) then + self.dx = -self.dx + self.dy = -self.dy + end + gSounds['blip']:stop() + gSounds['blip']:play() + end + + if not (level:insideBounds(tempX,tempY) or level:pointOnEdge(tempX,tempY)) then + self.remove = true + end + + self.x = self.x + self.dx * dt + self.y = self.y + self.dy * dt + + +end + +function Ball:render() + love.graphics.setColor(255,255,255,255) + love.graphics.circle('fill', self.x, self.y, self.width/2) +end + +function Ball:getCollisionDirection(segment,x,y) + return segment:sideOfSegment(x,y) +end \ No newline at end of file diff --git a/src/Dependencies.lua b/src/Dependencies.lua new file mode 100644 index 0000000..6751313 --- /dev/null +++ b/src/Dependencies.lua @@ -0,0 +1,64 @@ +-- +-- libraries +-- + +Class = require 'lib/class' +Event = require 'lib/knife.event' +push = require 'lib/push' +Timer = require 'lib/knife.timer' + +require 'src/Animation' +require 'src/constants' +require 'src/StateMachine' +require 'src/Util' + +require 'src/Level' +require 'src/Segment' +require 'src/Player' +require 'src/Ball' + +require 'src/entity/entity_defs' +require 'src/entity/Entity' + +require 'src/states/BaseState' +require 'src/states/StateStack' + +require 'src/states/entity/EntityBaseState' +require 'src/states/entity/EntityIdleState' +require 'src/states/entity/EntityWalkState' +require 'src/states/entity/EntityWalkEdgeState' + +require 'src/states/game/StartState' +require 'src/states/game/FadeInState' +require 'src/states/game/FadeOutState' +require 'src/states/game/PlayState' +require 'src/states/game/GameOverState' + +gTextures = { + ['entities'] = love.graphics.newImage('graphics/entities.png') +} + +gFrames = { + ['entities'] = GenerateQuads(gTextures['entities'], 16, 16) +} + +gFonts = { + ['small'] = love.graphics.newFont('fonts/font.ttf', 8), + ['medium'] = love.graphics.newFont('fonts/font.ttf', 16), + ['large'] = love.graphics.newFont('fonts/font.ttf', 32) +} + +gSounds = { + ['music'] = love.audio.newSource('sounds/music.wav', 'static'), + ['field-music'] = love.audio.newSource('sounds/field_music.wav', 'static'), + ['battle-music'] = love.audio.newSource('sounds/battle_music.mp3', 'static'), + ['blip'] = love.audio.newSource('sounds/blip.wav', 'static'), + ['powerup'] = love.audio.newSource('sounds/powerup.wav', 'static'), + ['hit'] = love.audio.newSource('sounds/hit.wav', 'static'), + ['run'] = love.audio.newSource('sounds/run.wav', 'static'), + ['heal'] = love.audio.newSource('sounds/heal.wav', 'static'), + ['exp'] = love.audio.newSource('sounds/exp.wav', 'static'), + ['levelup'] = love.audio.newSource('sounds/levelup.wav', 'static'), + ['victory-music'] = love.audio.newSource('sounds/victory.wav', 'static'), + ['intro-music'] = love.audio.newSource('sounds/intro.mp3', 'static') +} \ No newline at end of file diff --git a/src/Level.lua b/src/Level.lua new file mode 100644 index 0000000..e209ad6 --- /dev/null +++ b/src/Level.lua @@ -0,0 +1,728 @@ + +-- require('mobdebug').start() + +Level = Class{} + +function Level:init() + + self.segments = self:createLevel() + self.polygon = self:createPolygon() + self.player = {} + +end + +function Level:update(dt) +end + +function Level:render() + self:renderOuterSegments() +end + +function Level:renderOuterSegments() + for k,segment in pairs(self.segments) do + segment:render() + end +end + +function Level:createLevel() + local level = { + [1] = Segment({LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},.5,'down'), + [2] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5,'left'), + [3] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5,'up'), + [4] = Segment({LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},.5,'right') + } + -- local level = { + -- [1] = Segment({LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},.5), + -- [2] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, LEVEL_RENDER_OFFSET_TOP},{VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5), + -- [3] = Segment({VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},.5), + -- [4] = Segment({VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET},{VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT / 2},.5), + -- [5] = Segment({VIRTUAL_WIDTH / 2 + 10, VIRTUAL_HEIGHT / 2},{LEVEL_RENDER_OFFSET,VIRTUAL_HEIGHT / 2},.5), + -- [6] = Segment({LEVEL_RENDER_OFFSET,VIRTUAL_HEIGHT / 2},{LEVEL_RENDER_OFFSET,LEVEL_RENDER_OFFSET_TOP},.5) + -- } + return level +end + +function Level:insideBounds2(x,y) + local shape = love.physics.newPolygonShape(self.polygonPoints) + return shape:testPoint(x,y) +end + +function Level:insideBounds(x,y, segments) + if not segments then + segments = self.segments + end + + local up,down,left,right = false + local upSeg, downSeg, leftSeg, rightSeg = nil + + -- find closest segments + for i, segment in pairs(segments) do + -- check raycast on y axis + if segment.vertical then + if y <= math.max(segment.firstPointY, segment.secondPointY) and + y >= math.min(segment.firstPointY, segment.secondPointY) then + if x <= segment.firstPointX then + if not rightSeg then + rightSeg = segment + elseif math.abs(segment.firstPointX - x) < math.abs(rightSeg.firstPointX - x) then + rightSeg = segment + end + else + if not leftSeg then + leftSeg = segment + elseif math.abs(segment.firstPointX - x) < math.abs(leftSeg.firstPointX - x) then + leftSeg = segment + end + end + end + end + if segment.horizontal then + if x <= math.max(segment.firstPointX, segment.secondPointX) and + x >= math.min(segment.firstPointX, segment.secondPointX) then + if y <= segment.firstPointY then + if not downSeg then + downSeg = segment + elseif math.abs(segment.firstPointY - y) < math.abs(downSeg.firstPointY - y) then + downSeg = segment + end + else + if not upSeg then + upSeg = segment + elseif math.abs(segment.firstPointY - y) < math.abs(upSeg.firstPointY - y) then + upSeg = segment + end + end + end + end + end + if not rightSeg or not leftSeg or not upSeg or not downSeg then + return false + end + + if rightSeg.face ~= 'left' or leftSeg.face ~= 'right' or upSeg.face ~= 'down' or downSeg.face ~= 'up' then + return false + end + return true +end + +function Level:insideBounds3(x,y) + x = math.floor(x) + y = math.floor(y) + local oddNodes = false + local j = #self.polygon + local polygon = self.polygon + for i = 1, #polygon do + if (polygon[i].y < y and polygon[j].y >= y or polygon[j].y < y and polygon[i].y >= y) then + if (polygon[i].x + ( y - polygon[i].y ) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) < x) then + oddNodes = not oddNodes + end + end + j = i + end + return oddNodes +end + +function Level:createPolygon() + local polygon = {} + local polygonPoints = {} + local j = 1 + for i,segment in ipairs(self.segments) do + polygon[i] = {} + polygon[i+1] = {} + polygon[i].x, polygon[i].y, polygon[i+1].x, polygon[i+1].y = segment:segmentToPoints() + polygonPoints[j] = polygon[i].x + polygonPoints[j+1] = polygon[i].y + polygonPoints[j+2] = polygon[i+1].x + polygonPoints[j+3] = polygon[i+1].y + j = j + 4 + i = i + 1 + end + self.polygonPoints = polygonPoints + return polygon +end + +function Level:pointOnEdge(x,y,segments) + if not segments then + segments = self.segments + end + local margin = 2 + for i,segment in pairs(segments) do + if segment.vertical then --vertical line + if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and + y <= math.max(segment.firstPointY, segment.secondPointY) and + y >= math.min(segment.firstPointY, segment.secondPointY) + then + return true + end + elseif segment.horizontal then --horizontal line + if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and + x <= math.max(segment.firstPointX, segment.secondPointX) and + x >= math.min(segment.firstPointX, segment.secondPointX) + then + return true + end + end + end + return false +end + +function Level:getTouchingSegment(x,y) + local margin = 2 + for i,segment in pairs(self.segments) do + if segment.vertical then --vertical line + if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and + y <= math.max(segment.firstPointY, segment.secondPointY) and + y >= math.min(segment.firstPointY, segment.secondPointY) + then + return i, segment + end + elseif segment.horizontal then --horizontal line + if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and + x <= math.max(segment.firstPointX, segment.secondPointX) and + x >= math.min(segment.firstPointX, segment.secondPointX) + then + return i, segment + end + end + end + return nil +end + +function Level:cutLevel() + + local newSegs = self.player.trailSegments + local startSegi, startSeg = self.player.trailStartSegment[1],self.player.trailStartSegment[2] + local endSegi, endSeg = self.player.trailFinishSegment[1],self.player.trailFinishSegment[2] + local first = 0 + local last = 0 + local firstFace = '' + + --check if it is same start and finish segment + if startSegi == endSegi then + -- print("cutlevel same start and end segment: "..tostring(startSegi)..' - '..tostring(endSegi)) + local s = {} + -- split the start/end segment + local new = {} + local split = self.segments[startSegi]:copy() + + newSegs[1]:joinPerpendicular(split) + newSegs[#newSegs]:joinPerpendicular(split) + -- print("-- joined perpendicular start and end segment from new segments") + newSegs[1]:debug() + newSegs[#newSegs]:debug() + local previous = #self.segments + if startSegi > 1 and startSegi < #self.segments then + previous = startSegi - 1 + end + local part1, part2, temp = {} + local inOrder = false + local j,k = 1,1 + local insertedNewSegs = false + -- print("-- Create new segments table") + -- create the new set of segments + while k <= #self.segments do + -- print(" segment to be inserted "..tostring(k)) + if k == startSegi and not insertedNewSegs then + -- print("Reached the segment being cut") + -- print_r(self.segments[k]) + -- print("split the segment") + part1, temp, part2 = self.segments[k]:splitInThreeWithSegments(newSegs[1],newSegs[#newSegs]) + -- print("part1") + part1:debug() + -- print("part2") + part2:debug() + + -- print("insert first part") + new[j] = part1 + -- print_r(new) + j = j + 1 + -- insert new segs + -- print("-- insert new segments into new set") + for i, segment in pairs(newSegs) do + if i + 1 < #newSegs then + newSegs[i+1]:joinPerpendicular(segment) + end + new[j] = segment:copy() + j = j + 1 + end + insertedNewSegments = true + new[j] = part2 + j = j + 1 + -- print_r(new) + -- print("-- finished inserting new segments and parts") + else + new[j] = self.segments[k]:copy() + j = j + 1 + end + k = k + 1 + end + + -- print("order segments in cutlevel same start and end segment") + new = self:orderSegments(new) + -- print_r(new) + -- print("calculate faces") + self:calculateSegmentFaces(new,new[1].face) + -- print_r(new) + self.segments = new + else + -- create two set of segments, one to one side of the cut, one to the other + local board1 = {} + local board2 = {} + + -- create first board + local k = 1 + local j = 1 + local last = #self.segments + local insertedNewSegments = false + local savedStart,savedEnd = {} + local board2StartSegi,board2EndSegi = 0 + -- create first board + while k <= #self.segments do + -- print("-----start Loop for "..tostring(k).."-----") + if k == startSegi and not insertedNewSegments then + board2StartSegi = k + -- print("-- this segment is the start segment and not inserted new segments yet") + -- print("newsegs[1] is joined perpendicular to k segment:") + newSegs[1]:debug() + newSegs[1]:joinPerpendicular(self.segments[k]) + newSegs[1]:debug() + -- print("newSegs[1] is joined -- finished") + local startPart1, startPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[1]) + -- print("-- Segment k split into 2 segments by newsegs[1]: ") + startPart1:debug() + startPart2:debug() + + if self.segments[last]:endEqualsStartOf(self.segments[k]) or + self.segments[last]:startEqualsStartOf(self.segments[k]) then -- last one is linked to the start of this one + --keep first part of segment + -- print("-- keep first part of the k segment") + self.segments[k] = startPart1 + savedStart = startPart2 + else -- keep second part of the segment (which is the first one touching the last segment) + -- print("-- keep second part of the k segment") + self.segments[k] = startPart2 + savedStart = startPart1 + end + + board1[j] = self.segments[k] + j = j + 1 + + -- print("-- before checking for inserted new segments - Board1") + -- print_r(board1) + if not insertedNewSegments then + k = endSegi -- skip to last segment to cut and insert it as well + last = k - 1 + board2EndSegi = k + newSegs[#newSegs]:joinPerpendicular(self.segments[k]) + + -- print("-- not inserted items, proceed to insert in order new segments") + for i, newseg in ipairs(newSegs) do + board1[j] = newseg:copy() + j = j + 1 + end + -- print_r(board1) + + local endPart1, endPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[#newSegs]) + -- print("-- proceed to insert the second split segment") + if self.segments[last]:endEqualsEndOf(self.segments[k]) or + self.segments[last]:startEqualsEndOf(self.segments[k]) then -- next one is linked to the start of this one + self.segments[k] = endPart1 + savedEnd = endPart2 + -- print("-- keep first part of the k segment") + else + self.segments[k] = endPart2 + savedEnd = endPart1 + -- print("-- keep second part of the k segment") + end + + board1[j] = self.segments[k]:copy() + j = j + 1 + -- print_r(board1) + insertedNewSegments = true + end + elseif k == endSegi and not insertedNewSegments then + board2StartSegi = k + -- print("-- this segment is the end segment and not inserted new segments yet") + -- print("newsegs[#newSegs] is joined perpendicular to k segment:") + newSegs[#newSegs]:debug() + newSegs[#newSegs]:joinPerpendicular(self.segments[k]) + newSegs[#newSegs]:debug() + -- print("newSegs[#newSegs] is joined -- finished") + local endPart1, endPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[#newSegs]) + -- print("-- Segment k split into 2 segments by newsegs[#newSegs]: ") + endPart1:debug() + endPart2:debug() + + if self.segments[last]:endEqualsStartOf(self.segments[k]) or + self.segments[last]:startEqualsStartOf(self.segments[k]) then -- last one is linked to the start of this one + -- keep first part of segment + self.segments[k] = endPart1 + savedEnd = endPart2 + else -- keep second part of the segment (which is the first one touching the last segment) + self.segments[k] = endPart2 + savedEnd = endPart1 + end + + board1[j] = self.segments[k] + j = j + 1 + + if not insertedNewSegments then + k = startSegi -- skip to last segment to cut and insert it as well + last = k - 1 + board2EndSegi = k + newSegs[1]:joinPerpendicular(self.segments[k]) + -- print("-- not inserted items, proceed to insert in reverse order new segments") + + local i = #newSegs + while i >= 1 do + board1[j] = newSegs[i]:copy() + board1[j]:switchDirection() + j = j + 1 + i = i - 1 + end + -- print_r(board1) + + local startPart1, startPart2 = self.segments[k]:splitInTwoWithSegment(newSegs[1]) + -- print("-- Segment k split into 2 segments by newsegs[1]: ") + startPart1:debug() + startPart2:debug() + + if self.segments[last]:endEqualsEndOf(self.segments[k]) or + self.segments[last]:startEqualsEndOf(self.segments[k]) then -- next one is linked to the start of this one + self.segments[k] = startPart1 + savedstart = startPart2 + else + self.segments[k] = startPart2 + savedStart= startPart1 + end + + board1[j] = self.segments[k]:copy() + j = j + 1 + -- print_r(board1) + insertedNewSegments = true + end + + elseif k ~= startSegi and k ~= endSegi then + board1[j] = self.segments[k]:copy() + j = j + 1 + end + + last = k + k = k + 1 + end + -- print("order segments in cutlevel board1") + board1 = self:orderSegments(board1) + self:calculateSegmentFaces(board1,board1[1].face) + -- print("PREPARE BOARD 1 -- FINAL") + -- print_r(board1) + + -- create second board + -- print('---- Create Second board from '..tostring(board2StartSegi)..' to '..tostring(board2EndSegi)) + j = 1 + if board2StartSegi < board2EndSegi then + board2[j] = savedStart + j = j + 1 + -- print_r(board2) + + -- print('-- inserted first segment, proceed with skipped segments in first board, start < end') + k = board2StartSegi + 1 + + while k < board2EndSegi do + board2[j] = self.segments[k]:copy() + k = k + 1 + j = j + 1 + end + -- print_r(board2) + -- print('-- inserted original skipped segments') + board2[j] = savedEnd + -- print('-- inserted saved end segment') + -- print_r(board2) + j = j + 1 + -- insert new segments in order + -- print("-- proceed to insert in order new segments") + for i, newseg in ipairs(newSegs) do + board2[j] = newseg:copy() + j = j + 1 + end + -- print_r(board2) + + else + -- print('-- inserted first segment, proceed with skipped segments in first board, end < start') + board2[j] = savedEnd + j = j + 1 + -- print('inserted saved end') + -- print_r(board2) + + k = board2EndSegi + 1 + -- print('-- insert the skipped segments') + while k < board2StartSegi do + board2[j] = self.segments[k]:copy() + k = k + 1 + j = j + 1 + end + -- print_r(board2) + -- print('insert the saved start segment') + -- insert new segments reverse order + board2[j] = savedStart + j = j + 1 + -- print_r(board2) + -- print('-- insert the new segments in reverse order') + local i = #newSegs + while i >= 1 do + board2[j] = newSegs[i]:copy() + board2[j]:switchDirection() + j = j + 1 + i = i - 1 + end + -- print('-- inserted new segments, final board2 before ordering') + -- print_r(board2) + end + + -- print("order segments in cutlevel board2") + board2 = self:orderSegments(board2) + self:calculateSegmentFaces(board2,board2[1].face) + -- print("PREPARE BOARD 2 -- FINAL") + -- print_r(board2) + + if self:containsBalls(board1) == 0 then + self.segments = board2 + elseif self:containsBalls(board2) == 0 then + self.segments = board1 + else + self.segments = self:getPerimeter(board1) > self:getPerimeter(board2) and board1 or board2 + end + end + +end + +function Level:orderSegments(segs) + --returns the list of segments in the level linked to each other in order and perpendicular to each other + local new = {} + -- -- print('----- order Segments : ') + -- print_s(segs) + + new[1] = segs[1] + table.remove(segs,1) + + local found = false + local k = #segs + local i = 1 + while i <= k do + -- -- print('----- segment i = '..tostring(i) .. '#segs '..tostring(#segs)) + -- print_s(new) + -- -- print(' --new search') + -- print_s(segs) + for j, s in ipairs(segs) do + -- -- print("test if j is next segment to i, j = "..tostring(j)) + if new[i]:endEqualsStartOf(s) then + found = true + + -- -- print("new[i] end equals start of ") + -- s:debug() + if new[i].vertical == s.vertical and new[i].horizontal == s.horizontal then + -- -- print("join contiguous "..tostring(i).. 'with'..tostring(j)) + new[i]:joinContiguous(s) + table.remove(segs,j) + i = i - 1 + k = k - 1 + break + else + -- -- print("join perpendicular "..tostring(i).. 'with'..tostring(j)) + s:joinPerpendicular(new[i]) + new[i+1] = s + table.remove(segs,j) + break + end + elseif new[i]:endEqualsEndOf(s) then + found = true + + -- -- print("new[i] end equals end of ") + -- s:debug() + s:switchDirection() + if new[i].vertical == s.vertical and new[i].horizontal == s.horizontal then + -- -- print("join contiguous "..tostring(i).. 'with'..tostring(j)) + new[i]:joinContiguous(s) + table.remove(segs,j) + i = i - 1 + k = k - 1 + break + else + -- -- print("join perpendicular "..tostring(i).. 'with'..tostring(j)) + s:joinPerpendicular(new[i]) + new[i+1] = s + table.remove(segs,j) + break + end + end + end + + local fi, fs = 0,{} + + if not found then + -- -- print("search didnt yield any segment") + local margin = 2 + while not found and margin < 5 do + fi, fs = self:findNearestSegment(new[i].secondPointX,new[i].secondPointY,segs,margin) + + if not fs then + fi, fs = self:findNearestSegment(new[i].firstPointX,new[i].firstPointY,segs,margin) + + if not fs then + margin = margin + 1 + else + found = true + end + else + found = true + end + end + + if found then + if new[i].vertical == fs.vertical then + -- -- print("join contiguous "..tostring(i).. 'with'..tostring(fi)) + new[i]:joinContiguous(fs) + table.remove(segs,fi) + i = i - 1 + k = k - 1 + else + -- -- print("join perpendicular "..tostring(i).. 'with'..tostring(fi)) + fs:joinPerpendicular(new[i]) + new[i+1] = fs + table.remove(segs,fi) + end + end + end + if not found then + -- print("ERROR -- order segments does not find next segment to ") + new[i]:debug("number: " ..tostring(i)) + break + else + found = false + end + + i = i + 1 + end + -- -- print("finished ordering: #new " ..tostring(#new)) + return new +end + +function Level:calculateSegmentFaces(segments, firstFace) + -- start segment, end segment, list of segments to calculate faces for (when cutting the map) + local face = firstFace + for k, s in ipairs(segments) do + s.face = face + if k + 1 <= #segments then + if face == 'up' then + if s.direction == 'right' then + if segments[k+1].direction == 'up' then + face = 'left' + else + face = 'right' + end + else + if segments[k+1].direction == 'up' then + face = 'right' + else + face = 'left' + end + end + elseif face == 'down' then + if s.direction == 'right' then + if segments[k+1].direction == 'up' then + face = 'right' + else + face = 'left' + end + else + if segments[k+1].direction == 'up' then + face = 'left' + else + face = 'right' + end + end + elseif face == 'right' then + if s.direction == 'up' then + if segments[k+1].direction == 'right' then + face = 'down' + else + face = 'up' + end + else + if segments[k+1].direction == 'right' then + face = 'up' + else + face = 'down' + end + end + elseif face == 'left' then + if s.direction == 'up' then + if segments[k+1].direction == 'right' then + face = 'up' + else + face = 'down' + end + else + if segments[k+1].direction == 'right' then + face = 'down' + else + face = 'up' + end + end + end + end + end +end + +function Level:getPerimeter(segments) + local p = 0 + if not segments then + segments = self.segments + end + for k, s in pairs(segments) do + p = p + s:length() + end + return p +end + +function Level:findNearestSegment(x,y,segments,margin) + if not segments then + segments = self.segments + end + if not margin then + margin = 2 + end + -- find a touching segment within margins + for i,segment in pairs(segments) do + if segment.vertical then --vertical line + if x >= segment.firstPointX - margin and x <= segment.firstPointX + margin and + y <= math.max(segment.firstPointY, segment.secondPointY) and + y >= math.min(segment.firstPointY, segment.secondPointY) + then + return i, segment + end + elseif segment.horizontal then --horizontal line + if y >= segment.firstPointY - margin and y <= segment.firstPointY + margin and + x <= math.max(segment.firstPointX, segment.secondPointX) and + x >= math.min(segment.firstPointX, segment.secondPointX) + then + return i, segment + end + end + end + return nil, nil +end + +function Level:containsBalls(segments) + -- returns numbere of balls inside the bounds passed in or self.segments + if not segments then + segments = self.segments + end + local counter = 0 + + for k,b in pairs(self.balls) do + if self:insideBounds(b.x + 4, b.y + 4,segments) or self:pointOnEdge(b.x+4, b.y+4, seegments) then + counter = counter + 1 + end + end + return counter +end \ No newline at end of file diff --git a/src/Player.lua b/src/Player.lua new file mode 100644 index 0000000..8ac3763 --- /dev/null +++ b/src/Player.lua @@ -0,0 +1,213 @@ + +-- require('mobdebug').start() + +Player = Class{__includes = Entity} + +function Player:init(def, level ) + Entity.init(self,def) + + self.x = 1 + self.y = LEVEL_RENDER_OFFSET_TOP - TILE_SIZE / 2 + 1 + + self.trail = {} + self.level = level + self.trailSegments = {} + self.trailStartSegment = {} + self.trailFinishSegment = {} + self.score = 0 + self.multiplier = 1 + + Event.on('on-edge',function() + self:onEdge() + -- self:playerDebug("after on-edge event ") + end) + + Event.on('walking',function() + self:onWalking() + -- self:playerDebug("after walking event ") + end) + + Event.on('change-direction', function() + self:onChangeDirection() + -- self:playerDebug("after change-direction event ") + end) + + Event.on('back-to-wall', function () + self:onBackToWall() + -- self:playerDebug("after back-to-wall event ") + end) +end + +function Player:changeState(state) + Entity.changeState(self,state) +end + +function Player:update(dt) + Entity.update(self, dt) + +end + +function Player:createAnimations(animations) + return Entity.createAnimations(self,animations) +end + +function Player:changeAnimation(name) + Entity.changeAnimation(self,name) +end + +function Player:render() + Entity.render(self) + self:renderTrailSegments() + + -- love.graphics.setColor(255, 0, 255, 255) + -- love.graphics.rectangle('line', self.x, self.y, self.width, self.height) + -- love.graphics.setColor(255, 255, 255, 255) +end + +function Player:renderTrailSegments() + for i,segment in pairs (self.trailSegments) do + segment:render() + -- print_r(segment) + end +end + +function Player:insideBounds() + return self.level:insideBounds(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2) +end + +function Player:onEdge() + self.trail = {} + self.trailSegments = {} + self.trailFinishSegment = {} + self.trailStartSegment = {} + self.trailStartSegment[1], self.trailStartSegment[2] = self.level:getTouchingSegment(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2) + table.insert(self.trail, + {['x'] = self.x + self.width/2,['y'] = self.y + self.height/2} + ) + +end + +function Player:onWalking() + if #self.trail > 1 then + table.remove(self.trail) + table.remove(self.trailSegments) + end + table.insert(self.trail, + {['x'] = self.x + self.width/2,['y'] = self.y + self.height/2} + ) + if #self.trail == 2 then + table.insert(self.trailSegments, + Segment( + {self.trail[1].x, self.trail[1].y}, + {self.trail[2].x, self.trail[2].y}, + .5,'',{r=250,g=150,b=150} + ) + ) + end +end + +function Player:onChangeDirection() + if self:insideBounds() then + if #self.trail > 1 then + table.remove(self.trail) + table.remove(self.trailSegments) + end + + local k = #self.trailSegments + local j = #self.trail + 1 + + table.insert(self.trail, + {['x'] = self.x + self.width/2,['y'] = self.y + self.height/2} + ) + if #self.trail == 2 then + + table.insert(self.trailSegments, + Segment( + {self.trail[1].x, self.trail[1].y}, + {self.trail[2].x, self.trail[2].y}, + .5,'',{r=250,g=150,b=150} + ) + ) + end + self.trail = {} + table.insert(self.trail, + {['x'] = self.x + self.width/2,['y'] = self.y + self.height/2} + ) + end +end + +function Player:onBackToWall() + if self:onTheEdge() and #self.trail > 0 then + self.trailFinishSegment[1], self.trailFinishSegment[2] = self.level:getTouchingSegment(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2) + local k = #self.trailSegments + local j = #self.trail + 1 + if #self.trail > 1 then + table.remove(self.trail) + end + table.insert(self.trail, + {['x'] = self.x + self.width/2,['y'] = self.y + self.height/2} + ) + if #self.trail == 2 then + table.remove(self.trailSegments) + table.insert(self.trailSegments, + Segment( + {self.trail[1].x, self.trail[1].y}, + {self.trail[2].x, self.trail[2].y}, + .5,'',{r=250,g=150,b=150} + ) + ) + end + self:resetTrail() + end +end + +function Player:reset() + self.x = 1 + self.y = LEVEL_RENDER_OFFSET_TOP - TILE_SIZE / 2 + 1 + self.trail = {} + self.trailSegments = {} + self.trailStartSegment = {} + self.trailFinishSegment = {} +end + +function Player:resetTrail() + self:playerDebug('Player:resetTrail') + if #self.trailStartSegment > 0 and #self.trailFinishSegment > 0 then + self.level:cutLevel() + end + self.trail = {} + self.trailStartSegment = {} + self.trailFinishSegment = {} + self.trailSegments = {} +end + +function Player:onTheEdge() + return self.level:pointOnEdge(self.x + TILE_SIZE / 2, self.y + TILE_SIZE / 2) +end + +function Player:playerDebug(msg) + -- print('------------------------------------') + if msg then + -- print(msg) + end + -- print("Player Position x,y = " ..tostring(self.x)..','..tostring(self.y)) + -- print("Player Center x,y = " ..tostring(self.x+8)..','..tostring(self.y+8)) + -- print("Player direction : "..self.direction .. ' previous: '..self.previousDirection) + -- print('......................') + -- print("Player trail points: ") + -- print_r(self.trail) + -- print('......................') + -- print("Player segments: ") + -- print_s(self.trailSegments) + -- print('......................') + -- print("Trail Start") + if self.trailStartSegment[2] ~= nil then + self.trailStartSegment[2]:debug(tostring(self.trailStartSegment[1])) + end + -- print('......................') + -- print("Trail Finish") + if self.trailFinishSegment[2] ~= nil then + self.trailFinishSegment[2]:debug(tostring(self.trailFinishSegment[1])) + end + -- print('------------------------------------') +end \ No newline at end of file diff --git a/src/Segment.lua b/src/Segment.lua new file mode 100644 index 0000000..5f59725 --- /dev/null +++ b/src/Segment.lua @@ -0,0 +1,294 @@ +-- require('mobdebug').start() + +Segment = Class{} + +local symbols = {['up'] = 207, ['down'] = 209, ['right'] = 199, ['left'] = 182} + +function Segment:init(firstPoint, secondPoint,lineWidth, face, color) + self.firstPointX = firstPoint[1] + self.firstPointY = firstPoint[2] + self.secondPointX = secondPoint[1] + self.secondPointY = secondPoint[2] + self.lineWidth = lineWidth or 3 + self.vertical = self.firstPointX == self.secondPointX + self.horizontal = self.firstPointY == self.secondPointY + self.face = face or '' + self.direction = self:calculateDirection() + self.color = color or {r=255,g=255,b=255} +end + +function Segment:render() + love.graphics.setColor(self.color.r,self.color.g, self.color.b,255) + love.graphics.setLineWidth(self.lineWidth) + love.graphics.line(self.firstPointX,self.firstPointY,self.secondPointX, self.secondPointY) + + love.graphics.setColor(255,255, 255, 255) + + -- debug + -- love.graphics.setFont(gFonts['small']) + -- love.graphics.printf(self.face,self.firstPointX + 2, self.firstPointY + 2 / 2,25,'left' ) + +end + +function Segment:segmentToPoints() + return self.firstPointX, self.firstPointY, self.secondPointX, self.secondPointY +end + +function Segment:length() + return math.sqrt( (self.secondPointX - self.firstPointX) * (self.secondPointX - self.firstPointX) + (self.secondPointY - self.firstPointY) * (self.secondPointY - self.firstPointY) ) +end + +function Segment:sideOfSegment(x,y) + if y < self.firstPointY and y < self.secondPointY then -- above + return 'up' + end + if y > self.firstPointY and y > self.secondPointY then -- below + return 'down' + end + if x < self.firstPointX and x < self.secondPointX then -- left + return 'left' + end + if x > self.firstPointX and x > self.secondPointX then -- right + return 'right' + end + +end + +function Segment:debug(msg) + if not msg then + msg = '' + end + -- print("Segment:debug ".. msg .." ...............") + -- print("FirstPoint: "..tostring(self.firstPointX)..','..tostring(self.firstPointY)) + -- print("SecondPoint: "..tostring(self.secondPointX)..','..tostring(self.secondPointY)) + -- print("face: "..self.face.. ' - Direction: '..self.direction) +end + +function Segment:copy() + local copy = {} + copy.firstPointX = self.firstPointX + copy.firstPointY = self.firstPointY + copy.secondPointX = self.secondPointX + copy.secondPointY = self.secondPointY + copy.lineWidth = self.lineWidth + copy.face = self.face + return Segment({copy.firstPointX,copy.firstPointY},{copy.secondPointX,copy.secondPointY},copy.lineWidth, copy.face) +end + +function Segment:endEqualsStartOf(s) + -- segments are linked at the end of this one + if self.secondPointX == s.firstPointX and + self.secondPointY == s.firstPointY then + return true + end + return false +end +function Segment:endEqualsEndOf(s) + -- segments are linked at the end of this one + if self.secondPointX == s.secondPointX and self.secondPointY == s.secondPointY then + return true + end + return false +end + +function Segment:startEqualsEndOf(s) + -- segments are linked at the beginning of this one + if self.firstPointX == s.secondPointX and self.firstPointY == s.secondPointY then + return true + end + return false +end +function Segment:startEqualsStartOf(s) + -- segments are linked at the beginning of this one + if self.firstPointX == s.firstPointX and self.firstPointY == s.PointY then + return true + end + return false +end + +function Segment:calculateDirection() + local dir = '' + + if self.vertical then + if self.firstPointY < self.secondPointY then + dir = 'down' + else + dir = 'up' + end + else + if self.firstPointX < self.secondPointX then + dir = 'right' + else + dir = 'left' + end + end + return dir +end + +function Segment:splitInTwoWithPoint(point) -- number + local s1, s2 = {} + if point > 0 then + if self.vertical then + s1 = Segment( + {self.firstPointX, self.firstPointY}, + {self.secondPointX, point}, + self.lineWidth, self.face + ) + s2 = Segment( + {self.firstPointX, point}, + {self.secondPointX,self.secondPointY}, + self.lineWidth, self.face + ) + else + s1 = Segment( + {self.firstPointX, self.firstPointY}, + {point, self.secondPointY}, + self.lineWidth, self.face + ) + s2 = Segment( + {point, self.firstPointY}, + {self.secondPointX,self.secondPointY}, + self.lineWidth, self.face + ) + end + else + return nil, nil + end + + return s1, s2 +end + +function Segment:splitInTwoWithSegment(segment) + local point = 0 + if self.vertical then + if self.firstPointY == segment.firstPointY then + point = segment.firstPointY + else + point = segment.secondPointY + end + elseif self.horizontal then + if self.firstPointX == segment.firstPointX then + point = segment.firstPointX + else + point = segment.secondPointX + end + end + + return self:splitInTwoWithPoint(point) +end + +function Segment:joinPerpendicular(s) + -- changes the self, not the parameter + if s.vertical then + local sx = s.firstPointX + if math.abs(self.firstPointX - sx) < + math.abs(self.secondPointX - sx) then + self.firstPointX = sx + else + self.secondPointX = sx + end + else + local sy = s.firstPointY + if math.abs(self.firstPointY - sy) < + math.abs(self.secondPointY - sy) then + self.firstPointY = sy + else + self.secondPointY = sy + end + end +end + +function Segment:switchDirection() + local temp = {} + temp.firstPointX = self.firstPointX + temp.firstPointY = self.firstPointY + self.firstPointX = self.secondPointX + self.firstPointY = self.secondPointY + self.secondPointX = temp.firstPointX + self.secondPointY = temp.firstPointY + self:notDirection() +end + +function Segment:notDirection() + if self.direction == 'down' then + self.direction = 'up' + elseif self.direction == 'up' then + self.direction = 'down' + elseif self.direction == 'right' then + self.direction = 'left' + elseif self.direction == 'left' then + self.direction = 'right' + end +end + +function Segment:notFace() + if self.face == 'up' then + self.face = 'down' + elseif self.face == 'down' then + self.face = 'up' + elseif self.face == 'right' then + self.face = 'left' + elseif self.face == 'left' then + self.face = 'right' + end +end + +function Segment:joinContiguous(s) + -- second segment becomes part of first one + if self.vertical then + self.secondPointY = s.secondPointY + else + self.secondPointX = s.secondPointX + end +end + +function Segment:compare(s) + -- returns >0 if self is to the right or below + -- returns <0 if self is to the left or above + -- returns 0 if both are same point + if self.vertical and s.vertical then + if self.firstPointX == s.firstPointX then + return 0 + else + return self.firstPointX - s.firstPointX + end + elseif self.horizontal and s.horizontal then + if self.firstPointY == s.firstPointY then + return 0 + else + return self.firstPointY - s.firstPointY + end + else + return (self.firstPointX + self.firstPointY + + self.secondPointX + self.secondPointY) - ( + s.firstPointX + s.firstPointY + s.secondPointX + +s.secondPointY) + end +end + +function Segment:splitInThreeWithSegments(s1,s2) + local p1, p2, p3, t = {} + if self.direction == 'down' or self.direction == 'right' then + if s1:compare(s2) < 0 then + p1, t = self:splitInTwoWithSegment(s1) + p2, p3 = t:splitInTwoWithSegment(s2) + else + p1, t = self:splitInTwoWithSegment(s2) + p2, p3 = t:splitInTwoWithSegment(s1) + end + else + if s1:compare(s2) < 0 then + p1, t = self:splitInTwoWithSegment(s2) + p2, p3 = t:splitInTwoWithSegment(s1) + else + p1, t = self:splitInTwoWithSegment(s1) + p2, p3 = t:splitInTwoWithSegment(s2) + end + end + + return p1,p2,p3 +end + +function Segment:insert(list) + +end diff --git a/src/StateMachine.lua b/src/StateMachine.lua new file mode 100644 index 0000000..19ee81b --- /dev/null +++ b/src/StateMachine.lua @@ -0,0 +1,37 @@ +-- require('mobdebug').start() + +StateMachine = Class{} + +function StateMachine:init(states) + self.empty = { + render = function() end, + update = function() end, + processAI = function() end, + enter = function() end, + exit = function() end + } + self.states = states or {} -- [name] -> [function that returns states] + self.current = self.empty +end + +function StateMachine:change(stateName, enterParams) + assert(self.states[stateName]) -- state must exist! + self.current:exit() + self.current = self.states[stateName]() + self.current:enter(enterParams) +end + +function StateMachine:update(dt) + self.current:update(dt) +end + +function StateMachine:render() + self.current:render() +end + +--[[ + Used for states that can be controlled by the AI to influence update logic. +]] +function StateMachine:processAI(params, dt) + self.current:processAI(params, dt) +end diff --git a/src/Util.lua b/src/Util.lua new file mode 100644 index 0000000..c4f9002 --- /dev/null +++ b/src/Util.lua @@ -0,0 +1,73 @@ + +-- require('mobdebug').start() +--[[ + Given an "atlas" (a texture with multiple sprites), as well as a + width and a height for the tiles therein, split the texture into + all of the quads by simply dividing it evenly. +]] +function GenerateQuads(atlas, tilewidth, tileheight) + local sheetWidth = atlas:getWidth() / tilewidth + local sheetHeight = atlas:getHeight() / tileheight + + local sheetCounter = 1 + local spritesheet = {} + + for y = 0, sheetHeight - 1 do + for x = 0, sheetWidth - 1 do + spritesheet[sheetCounter] = + love.graphics.newQuad(x * tilewidth, y * tileheight, tilewidth, + tileheight, atlas:getDimensions()) + sheetCounter = sheetCounter + 1 + end + end + + return spritesheet +end + +--[[ + Recursive table printing function. + https://coronalabs.com/blog/2014/09/02/tutorial-printing-table-contents/ +]] +function print_r ( t ) + local print_r_cache={} + local function sub_print_r(t,indent) + if (print_r_cache[tostring(t)]) then + print(indent.."*"..tostring(t)) + else + print_r_cache[tostring(t)]=true + if (type(t)=="table") then + for pos,val in pairs(t) do + if (type(val)=="table") then + print(indent.."["..pos.."] => "..tostring(t).." {") + sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) + print(indent..string.rep(" ",string.len(pos)+6).."}") + elseif (type(val)=="string") then + print(indent.."["..pos..'] => "'..val..'"') + else + print(indent.."["..pos.."] => "..tostring(val)) + end + end + else + print(indent..tostring(t)) + end + end + end + if (type(t)=="table") then + print(tostring(t).." {") + sub_print_r(t," ") + print("}") + else + sub_print_r(t," ") + end + print() +end + +function math.round(x,precision) + return x + precision - (x + precision) % 1 +end + +function print_s(segments) + for k, s in ipairs(segments) do + s:debug(tostring(k)) + end +end \ No newline at end of file diff --git a/src/constants.lua b/src/constants.lua new file mode 100644 index 0000000..4be6a3d --- /dev/null +++ b/src/constants.lua @@ -0,0 +1,12 @@ +-- require('mobdebug').start() + +VIRTUAL_WIDTH = 384 +VIRTUAL_HEIGHT = 216 + +WINDOW_WIDTH = 1280 +WINDOW_HEIGHT = 720 + +LEVEL_RENDER_OFFSET_TOP = 30 +LEVEL_RENDER_OFFSET = 10 + +TILE_SIZE = 16 \ No newline at end of file diff --git a/src/entity/Entity.lua b/src/entity/Entity.lua new file mode 100644 index 0000000..72aac96 --- /dev/null +++ b/src/entity/Entity.lua @@ -0,0 +1,57 @@ + +Entity = Class{} + +function Entity:init(def) + self.direction = 'down' + self.previousDirection = '' + self.walkingSpeed = def.walkingSpeed + self.animations = self:createAnimations(def.animations) + + self.currentAnimation = self.animations['idle-down'] + + self.stateMachine = StateMachine { + ['walk'] = function() return EntityWalkState(self) end, + ['edge'] = function() return EntityWalkEdgeState(self) end, + ['idle'] = function() return EntityIdleState(self) end + } + self.stateMachine:change('idle') + self.width = def.width + self.height = def.height + +end + +function Entity:changeState(name) + self.stateMachine:change(name) + -- printChangeState to "..name) +end + +function Entity:changeAnimation(name) + self.currentAnimation = self.animations[name] +end + +function Entity:createAnimations(animations) + local animationsReturned = {} + + for k, animationDef in pairs(animations) do + animationsReturned[k] = Animation { + texture = animationDef.texture or 'entities', + frames = animationDef.frames, + interval = animationDef.interval + } + end + + return animationsReturned +end + +function Entity:processAI(params, dt) + self.stateMachine:processAI(params, dt) +end + +function Entity:update(dt) + self.currentAnimation:update(dt) + self.stateMachine:update(dt) +end + +function Entity:render() + self.stateMachine:render() +end \ No newline at end of file diff --git a/src/entity/entity_defs.lua b/src/entity/entity_defs.lua new file mode 100644 index 0000000..ca40069 --- /dev/null +++ b/src/entity/entity_defs.lua @@ -0,0 +1,47 @@ + + +ENTITY_DEFS = { + ['player'] = { + width = 16, + height = 16, + walkingSpeed = 100, + animations = { + ['walk-left'] = { + frames = {67, 68, 69}, + interval = 0.15, + texture = 'entities' + }, + ['walk-right'] = { + frames = {79, 80, 81}, + interval = 0.15, + texture = 'entities' + }, + ['walk-down'] = { + frames = {55, 56, 57}, + interval = 0.15, + texture = 'entities' + }, + ['walk-up'] = { + frames = {91, 92, 93}, + interval = 0.15, + texture = 'entities' + }, + ['idle-left'] = { + frames = {67}, + texture = 'entities' + }, + ['idle-right'] = { + frames = {79}, + texture = 'entities' + }, + ['idle-down'] = { + frames = {55}, + texture = 'entities' + }, + ['idle-up'] = { + frames = {91}, + texture = 'entities' + } + } + } +} \ No newline at end of file diff --git a/src/states/BaseState.lua b/src/states/BaseState.lua new file mode 100644 index 0000000..7f4668a --- /dev/null +++ b/src/states/BaseState.lua @@ -0,0 +1,9 @@ +-- require('mobdebug').start() + +BaseState = Class{} + +function BaseState:init() end +function BaseState:enter() end +function BaseState:exit() end +function BaseState:update(dt) end +function BaseState:render() end \ No newline at end of file diff --git a/src/states/StateStack.lua b/src/states/StateStack.lua new file mode 100644 index 0000000..173c649 --- /dev/null +++ b/src/states/StateStack.lua @@ -0,0 +1,36 @@ +-- require('mobdebug').start() + +StateStack = Class{} + +function StateStack:init() + self.states = {} +end + +function StateStack:update(dt) + self.states[#self.states]:update(dt) +end + +function StateStack:processAI(params, dt) + self.states[#self.states]:processAI(params, dt) +end + +function StateStack:render() + for i, state in ipairs(self.states) do + state:render() + end +end + +function StateStack:clear() + self.states = {} +end + +function StateStack:push(state) + table.insert(self.states, state) + state:enter() +end + +function StateStack:pop() + -- print("pop state: " .. self.states[#self.states].name) + self.states[#self.states]:exit() + table.remove(self.states) +end \ No newline at end of file diff --git a/src/states/entity/EntityBaseState.lua b/src/states/entity/EntityBaseState.lua new file mode 100644 index 0000000..7a4f7f2 --- /dev/null +++ b/src/states/entity/EntityBaseState.lua @@ -0,0 +1,18 @@ +-- require('mobdebug').start() + +EntityBaseState = Class{} + +function EntityBaseState:init(entity) + self.entity = entity +end + +function EntityBaseState:update(dt) end +function EntityBaseState:enter() end +function EntityBaseState:exit() end +function EntityBaseState:processAI(params, dt) end + +function EntityBaseState:render() + local anim = self.entity.currentAnimation + love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()], + math.floor(self.entity.x), math.floor(self.entity.y)) +end \ No newline at end of file diff --git a/src/states/entity/EntityIdleState.lua b/src/states/entity/EntityIdleState.lua new file mode 100644 index 0000000..f0af98d --- /dev/null +++ b/src/states/entity/EntityIdleState.lua @@ -0,0 +1,32 @@ +-- require('mobdebug').start() + +EntityIdleState = Class{__includes = BaseState} + +function EntityIdleState:init(entity) + self.entity = entity + + self.entity:changeAnimation('idle-' .. self.entity.direction) + +end + +function EntityIdleState:update(dt) + if love.keyboard.isDown('left') or love.keyboard.isDown('right') or + love.keyboard.isDown('up') or love.keyboard.isDown('down') then + if not self.entity:onTheEdge() then + Event.dispatch('walking') + self.entity:changeState('walk') + else + Event.dispatch('on-edge') + self.entity:changeState('edge') + end + end +end + +function EntityIdleState:render() + local anim = self.entity.currentAnimation + love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],self.entity.x, self.entity.y) + + -- love.graphics.setColor(255, 0, 255, 255) + -- love.graphics.rectangle('line', self.entity.x, self.entity.y, self.entity.width, self.entity.height) + -- love.graphics.setColor(255, 255, 255, 255) +end \ No newline at end of file diff --git a/src/states/entity/EntityWalkEdgeState.lua b/src/states/entity/EntityWalkEdgeState.lua new file mode 100644 index 0000000..faf3761 --- /dev/null +++ b/src/states/entity/EntityWalkEdgeState.lua @@ -0,0 +1,100 @@ +-- require('mobdebug').start() + + +EntityWalkEdgeState = Class{__includes = BaseState} + +function EntityWalkEdgeState:init(entity) + self.entity = entity + self.entity:changeAnimation('walk-'..self.entity.direction) +end + +function EntityWalkEdgeState:update(dt) + + local dy = self.entity.walkingSpeed * dt + local dx = self.entity.walkingSpeed * dt + + if self.entity.direction == 'left' then + if self.entity.level:pointOnEdge(self.entity.x - dx + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2) then + self.entity.x = self.entity.x - dx + Event.dispatch('on-edge') + else + if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2 - dx, self.entity.y + TILE_SIZE / 2) then + self.entity:changeState('walk') + Event.dispatch('walking') + self.entity.x = self.entity.x - dx + end + end + elseif self.entity.direction == 'right' then + if self.entity.level:pointOnEdge(self.entity.x + dx + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2) then + self.entity.x = self.entity.x + dx + Event.dispatch('on-edge') + else + if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2 + dx, self.entity.y + TILE_SIZE / 2) then + self.entity:changeState('walk') + Event.dispatch('walking') + self.entity.x = self.entity.x + dx + end + end + elseif self.entity.direction == 'up' then + if self.entity.level:pointOnEdge(self.entity.x + TILE_SIZE / 2, self.entity.y - dy + TILE_SIZE / 2) then + self.entity.y = self.entity.y - dy + Event.dispatch('on-edge') + else + if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2 - dy) then + self.entity:changeState('walk') + Event.dispatch('walking') + self.entity.y = self.entity.y - dy + end + end + elseif self.entity.direction == 'down' then + if self.entity.level:pointOnEdge(self.entity.x + TILE_SIZE / 2, self.entity.y + dy + TILE_SIZE / 2) then + self.entity.y = self.entity.y + dy + Event.dispatch('on-edge') + else + if self.entity.level:insideBounds(self.entity.x + TILE_SIZE / 2, self.entity.y + TILE_SIZE / 2 + dy) then + self.entity:changeState('walk') + Event.dispatch('walking') + self.entity.y = self.entity.y + dy + end + end + end + + if love.keyboard.isDown('left') then + if self.entity.direction ~= 'left' then + self.entity.previousDirection = self.entity.direction + end + self.entity.direction = 'left' + self.entity:changeAnimation('walk-left') + elseif love.keyboard.isDown('right') then + if self.entity.direction ~= 'right' then + self.entity.previousDirection = self.entity.direction + end + self.entity.direction = 'right' + self.entity:changeAnimation('walk-right') + elseif love.keyboard.isDown('up') then + if self.entity.direction ~= 'up' then + self.entity.previousDirection = self.entity.direction + end + self.entity.direction = 'up' + self.entity:changeAnimation('walk-up') + elseif love.keyboard.isDown('down') then + if self.entity.direction ~= 'down' then + self.entity.previousDirection = self.entity.direction + end + self.entity.direction = 'down' + self.entity:changeAnimation('walk-down') + else + self.entity:changeState('idle') + end + + +end + +function EntityWalkEdgeState:render() + local anim = self.entity.currentAnimation + love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],self.entity.x, self.entity.y) + + -- love.graphics.setColor(255, 0, 255, 255) + -- love.graphics.rectangle('line', self.entity.x, self.entity.y, self.entity.width, self.entity.height) + -- love.graphics.setColor(255, 255, 255, 255) +end diff --git a/src/states/entity/EntityWalkState.lua b/src/states/entity/EntityWalkState.lua new file mode 100644 index 0000000..9ec19bd --- /dev/null +++ b/src/states/entity/EntityWalkState.lua @@ -0,0 +1,93 @@ +-- require('mobdebug').start() + + +EntityWalkState = Class{__includes = BaseState} + +function EntityWalkState:init(entity) + self.entity = entity + self.entity:changeAnimation('walk-'..self.entity.direction) +end + +function EntityWalkState:update(dt) + if self.entity.direction == 'up' then + self.entity.y = self.entity.y - self.entity.walkingSpeed * dt + if self.entity.y + TILE_SIZE / 2 < LEVEL_RENDER_OFFSET_TOP - 2 then + self.entity.y = self.entity.y + self.entity.walkingSpeed * dt + Event.dispatch('back-to-wall') + self.entity:changeState('edge') + end + elseif self.entity.direction == 'down' then + self.entity.y = self.entity.y + self.entity.walkingSpeed * dt + if self.entity.y + TILE_SIZE / 2 > VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET + 2 then + self.entity.y = self.entity.y - self.entity.walkingSpeed * dt + Event.dispatch('back-to-wall') + self.entity:changeState('edge') + end + elseif self.entity.direction == 'right' then + self.entity.x = self.entity.x + self.entity.walkingSpeed * dt + if self.entity.x + TILE_SIZE / 2 > VIRTUAL_WIDTH - TILE_SIZE / 2 + 2 or self.entity.previousdirection == 'left' then + self.entity.x = self.entity.x - self.entity.walkingSpeed * dt + Event.dispatch('back-to-wall') + self.entity:changeState('edge') + end + elseif self.entity.direction == 'left' then + self.entity.x = self.entity.x - self.entity.walkingSpeed * dt + if self.entity.x < 0 or self.entity.previousdirection == 'right' then + self.entity.x = self.entity.x + self.entity.walkingSpeed * dt + Event.dispatch('back-to-wall') + self.entity:changeState('edge') + end + end + if love.keyboard.isDown('left') then + if self.entity.direction ~= 'left' then + self.entity.previousDirection = self.entity.direction + Event.dispatch('change-direction') + else + Event.dispatch('walking') + end + self.entity.direction = 'left' + self.entity:changeAnimation('walk-left') + elseif love.keyboard.isDown('right') then + if self.entity.direction ~= 'right' then + self.entity.previousDirection = self.entity.direction + Event.dispatch('change-direction') + else + Event.dispatch('walking') + end + self.entity.direction = 'right' + self.entity:changeAnimation('walk-right') + elseif love.keyboard.isDown('up') then + if self.entity.direction ~= 'up' then + self.entity.previousDirection = self.entity.direction + Event.dispatch('change-direction') + else + Event.dispatch('walking') + end + self.entity.direction = 'up' + self.entity:changeAnimation('walk-up') + elseif love.keyboard.isDown('down') then + if self.entity.direction ~= 'down' then + self.entity.previousDirection = self.entity.direction + Event.dispatch('change-direction') + else + Event.dispatch('walking') + end + self.entity.direction = 'down' + self.entity:changeAnimation('walk-down') + else + self.entity:changeState('idle') + end + if not self.entity:insideBounds() then + Event.dispatch('back-to-wall') + self.entity:changeState('edge') + end +end + +function EntityWalkState:render() + local anim = self.entity.currentAnimation + love.graphics.draw(gTextures[anim.texture], gFrames[anim.texture][anim:getCurrentFrame()],self.entity.x, self.entity.y) + + -- love.graphics.setColor(255, 0, 255, 255) + -- love.graphics.rectangle('line', self.entity.x, self.entity.y, self.entity.width, self.entity.height) + -- love.graphics.setColor(255, 255, 255, 255) +end diff --git a/src/states/game/FadeInState.lua b/src/states/game/FadeInState.lua new file mode 100644 index 0000000..551eaed --- /dev/null +++ b/src/states/game/FadeInState.lua @@ -0,0 +1,33 @@ +--[[ + GD50 + Pokemon + + Author: Colton Ogden + cogden@cs50.harvard.edu +]] + +FadeInState = Class{__includes = BaseState} + +function FadeInState:init(color, time, onFadeComplete) + self.name = 'FadeInState' + self.r = color.r + self.g = color.g + self.b = color.b + self.opacity = 0 + self.time = time + + Timer.tween(self.time, { + [self] = {opacity = 255} + }) + :finish(function() + gStateStack:pop() + onFadeComplete() + end) +end + +function FadeInState:render() + love.graphics.setColor(self.r, self.g, self.b, self.opacity) + love.graphics.rectangle('fill', 0, 0, VIRTUAL_WIDTH, VIRTUAL_HEIGHT) + + love.graphics.setColor(255, 255, 255, 255) +end \ No newline at end of file diff --git a/src/states/game/FadeOutState.lua b/src/states/game/FadeOutState.lua new file mode 100644 index 0000000..91930bf --- /dev/null +++ b/src/states/game/FadeOutState.lua @@ -0,0 +1,37 @@ +--[[ + GD50 + Pokemon + + Author: Colton Ogden + cogden@cs50.harvard.edu +]] + +FadeOutState = Class{__includes = BaseState} + +function FadeOutState:init(color, time, onFadeComplete) + self.name = 'FadeOutState' + self.opacity = 255 + self.r = color.r + self.g = color.g + self.b = color.b + self.time = time + + Timer.tween(self.time, { + [self] = {opacity = 0} + }) + :finish(function() + gStateStack:pop() + onFadeComplete() + end) +end + +function FadeOutState:update(dt) + +end + +function FadeOutState:render() + love.graphics.setColor(self.r, self.g, self.b, self.opacity) + love.graphics.rectangle('fill', 0, 0, VIRTUAL_WIDTH, VIRTUAL_HEIGHT) + + love.graphics.setColor(255, 255, 255, 255) +end \ No newline at end of file diff --git a/src/states/game/GameOverState.lua b/src/states/game/GameOverState.lua new file mode 100644 index 0000000..ad39926 --- /dev/null +++ b/src/states/game/GameOverState.lua @@ -0,0 +1,44 @@ +-- require('mobdebug').start() + +GameOverState = Class{__includes = BaseState} + +function GameOverState:init(score) + self.name = 'GameOverState' + gSounds['intro-music']:play() + self.score = score or 0 +end + +function GameOverState:update(dt) + if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then + gStateStack:push(FadeInState({ + r = 0, g = 0, b = 0 + }, 1, + function() + gSounds['intro-music']:stop() + + gStateStack:push(StartState(),function()end, true) + gStateStack:push(FadeOutState({ + r = 0, g = 0, b = 0 + }, 1, + function() end)) + end)) + end +end + +function GameOverState:render() + love.graphics.clear(0, 0, 0, 255) + + love.graphics.setColor(56, 56, 56, 255) + love.graphics.setFont(gFonts['large']) + love.graphics.printf('GAME OVER', 0, VIRTUAL_HEIGHT / 2 - 72, VIRTUAL_WIDTH, 'center') + love.graphics.setFont(gFonts['medium']) + + love.graphics.printf('Score: '..tostring(self.score), 0, VIRTUAL_HEIGHT / 2, VIRTUAL_WIDTH, 'center') + + love.graphics.printf('Try keeping away from the balls but do not lose them all!!!', 0, VIRTUAL_HEIGHT / 2 + 48, VIRTUAL_WIDTH, 'center') + love.graphics.printf('Press Enter', 0, VIRTUAL_HEIGHT / 2 + 88, VIRTUAL_WIDTH, 'center') + love.graphics.setFont(gFonts['small']) + + love.graphics.setColor(255, 255, 255, 255) + -- love.graphics.draw(gTextures[self.sprite], self.spriteX, self.spriteY) +end \ No newline at end of file diff --git a/src/states/game/PlayState.lua b/src/states/game/PlayState.lua new file mode 100644 index 0000000..bec0d8e --- /dev/null +++ b/src/states/game/PlayState.lua @@ -0,0 +1,115 @@ +-- require('mobdebug').start() + +PlayState = Class{__includes = BaseState} + +function PlayState:init() + self.name = 'PlayState' + self.stage = 1 + self.level = Level() + self.player = Player ( ENTITY_DEFS['player'] , self.level) + self.level.player = self.player + self.balls = {} + self:createBalls() + self.level.balls = self.balls + + gSounds['music']:setLooping(true) + gSounds['music']:play() + +end + +function PlayState:enter() +end + +function PlayState:update(dt) + -- self.level:update(dt) + self.player:update(dt) + self:updateBalls(dt, self.level) +end + +function PlayState:render() + love.graphics.clear(0, 0, 0, 255) + love.graphics.setColor(40, 45, 52,255) + + love.graphics.setFont(gFonts['small']) + love.graphics.printf('Score '..tostring(self.player.score), + 0, 5, VIRTUAL_WIDTH, 'left') + + love.graphics.setFont(gFonts['medium']) + love.graphics.printf('Stage '..tostring(self.stage), + 0, 5, VIRTUAL_WIDTH, 'center') + + self.level:render() + self.player:render() + self:renderBalls(dt) +end + +function PlayState:createBalls() + self.balls = {} + + for i = 1, self.stage do + table.insert(self.balls,Ball( + math.random(LEVEL_RENDER_OFFSET, VIRTUAL_WIDTH - LEVEL_RENDER_OFFSET), --X + math.random(LEVEL_RENDER_OFFSET_TOP, VIRTUAL_HEIGHT - LEVEL_RENDER_OFFSET), --Y + math.random(20,90),math.random(20,90) -- speed + )) + end +end + +function PlayState:updateBalls(dt, level) + for k,ball in pairs(self.balls) do + ball:update(dt,level) + end + -- remove balls outside bounds + for k,ball in pairs(self.balls) do + if ball.remove then + table.remove(self.balls,k) + self.player.score = self.player.score - 1 + elseif ball.hitPlayer then + -- check if any ball hit the player trail segments + self:gameOver() + end + end + + if #self.balls <= 0 then + -- game over - reset level + self:gameOver() + end + + -- check perimeter of level, if low enough, create new level move on to the next stage + if level:getPerimeter() < 500 then + self:nextStage() + end +end + +function PlayState:renderBalls(dt) + for k,ball in pairs(self.balls) do + ball:render() + end +end + +function PlayState:gameOver() + gStateStack:pop() + gSounds['music']:stop() + gStateStack:push(GameOverState(self.player.score),function() end) +end + +function PlayState:nextStage() + gStateStack:push(FadeOutState({ + r = 255, g = 255, b = 255}, 1,function() + self.stage = self.stage + 1 + self.level = Level() + self.player:reset() + self.player.score = self.player.score + self.player.multiplier * #self.balls + self.player.multiplier = self.player.multiplier + #self.balls + self.balls = {} + self:createBalls() + self.level.player = self.player + self.player.level = self.level + self.level.balls = self.balls + gSounds['music']:stop() + gSounds['heal']:play() + gStateStack:push(FadeOutState({r = 255, g = 255, b = 255}, 1,function() + gSounds['music']:play() + end)) + end)) +end \ No newline at end of file diff --git a/src/states/game/StartState.lua b/src/states/game/StartState.lua new file mode 100644 index 0000000..c0937c1 --- /dev/null +++ b/src/states/game/StartState.lua @@ -0,0 +1,40 @@ +-- require('mobdebug').start() + +StartState = Class{__includes = BaseState} + +function StartState:init() + self.name = 'StartState' + gSounds['intro-music']:play() + +end + +function StartState:update(dt) + if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then + gStateStack:push(FadeInState({ + r = 255, g = 255, b = 255 + }, 1, + function() + gSounds['intro-music']:stop() + + gStateStack:push(PlayState(),function()end, true) + -- gStateStack:push(FadeOutState({ + -- r = 255, g = 255, b = 255 + -- }, 1, + -- function() end)) + end)) + end +end + +function StartState:render() + -- print("StartSate : setting colors") + love.graphics.clear(0, 0, 0, 255) + love.graphics.setColor(56, 56, 56, 255) + love.graphics.setFont(gFonts['large']) + love.graphics.printf('Spider Cut!', 0, VIRTUAL_HEIGHT / 2 - 72, VIRTUAL_WIDTH, 'center') + love.graphics.setFont(gFonts['medium']) + love.graphics.printf('Press Enter', 0, VIRTUAL_HEIGHT / 2 + 68, VIRTUAL_WIDTH, 'center') + love.graphics.setFont(gFonts['small']) + -- print("StartState : end") + -- love.graphics.setColor(255, 255, 255, 255) + -- love.graphics.draw(gTextures[self.sprite], self.spriteX, self.spriteY) +end \ No newline at end of file