first commit

This commit is contained in:
2021-12-18 20:10:30 +01:00
parent 4d04f63b99
commit 215bed9faf
108 changed files with 3630 additions and 0 deletions

14
.vscode/launch.json vendored Normal file
View File

@@ -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"
}
]
}

BIN
fonts/font.ttf Normal file

Binary file not shown.

BIN
game.game Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

View File

@@ -0,0 +1,2 @@
[InternetShortcut]
URL=http://donate.kenney.nl/

View File

@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.facebook.com/KenneyNL

View File

@@ -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/
###############################################################################

BIN
graphics/PixelUIpack/Preview.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,6 @@
The tiles are 16 x 16px and have a 2px margin between them.
---
TILE SIZE: 16 x 16
MARGIN: 2

BIN
graphics/entities.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
graphics/sheet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
graphics/sheet_numbered.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

100
lib/class.lua Normal file
View File

@@ -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})

15
lib/knife/base.lua Normal file
View File

@@ -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,
}

78
lib/knife/behavior.lua Normal file
View File

@@ -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

27
lib/knife/bind.lua Normal file
View File

@@ -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

31
lib/knife/chain.lua Normal file
View File

@@ -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

54
lib/knife/convoke.lua Normal file
View File

@@ -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

111
lib/knife/event.lua Normal file
View File

@@ -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

2
lib/knife/gun.lua Normal file
View File

@@ -0,0 +1,2 @@
-- GUN (Give Up Now)
os.exit()

77
lib/knife/memoize.lua Normal file
View File

@@ -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

78
lib/knife/serialize.lua Normal file
View File

@@ -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

69
lib/knife/system.lua Normal file
View File

@@ -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

128
lib/knife/test.lua Normal file
View File

@@ -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

246
lib/knife/timer.lua Normal file
View File

@@ -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

281
lib/push.lua Normal file
View File

@@ -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

78
main.lua Normal file
View File

@@ -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

BIN
sounds/battle_music.mp3 Normal file

Binary file not shown.

BIN
sounds/blip.wav Normal file

Binary file not shown.

BIN
sounds/exp.wav Normal file

Binary file not shown.

BIN
sounds/field_music.wav Normal file

Binary file not shown.

BIN
sounds/heal.wav Normal file

Binary file not shown.

BIN
sounds/hit.wav Normal file

Binary file not shown.

BIN
sounds/intro.mp3 Normal file

Binary file not shown.

BIN
sounds/intro.ogg Normal file

Binary file not shown.

BIN
sounds/kill.wav Normal file

Binary file not shown.

BIN
sounds/levelup.wav Normal file

Binary file not shown.

BIN
sounds/music.wav Normal file

Binary file not shown.

BIN
sounds/powerup.wav Normal file

Binary file not shown.

BIN
sounds/run.wav Normal file

Binary file not shown.

BIN
sounds/victory.wav Normal file

Binary file not shown.

57
src/Animation.lua Normal file
View File

@@ -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

69
src/Ball.lua Normal file
View File

@@ -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

64
src/Dependencies.lua Normal file
View File

@@ -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')
}

728
src/Level.lua Normal file
View File

@@ -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

213
src/Player.lua Normal file
View File

@@ -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

294
src/Segment.lua Normal file
View File

@@ -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

37
src/StateMachine.lua Normal file
View File

@@ -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

73
src/Util.lua Normal file
View File

@@ -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

12
src/constants.lua Normal file
View File

@@ -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

57
src/entity/Entity.lua Normal file
View File

@@ -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

View File

@@ -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'
}
}
}
}

9
src/states/BaseState.lua Normal file
View File

@@ -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

36
src/states/StateStack.lua Normal file
View File

@@ -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

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More