Skip to content

Instantly share code, notes, and snippets.

@jonbro
Created December 19, 2015 21:14
Show Gist options
  • Select an option

  • Save jonbro/509b44703420e74658b2 to your computer and use it in GitHub Desktop.

Select an option

Save jonbro/509b44703420e74658b2 to your computer and use it in GitHub Desktop.
lua code that starts running slowly
local middleclass = {
_VERSION = 'middleclass v3.2.0',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
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 _metamethods = {}
for m in ([[ add band bor bxor bnot call concat div eq
gc ipairs idiv le len lt metatable mod mode
mul pairs pow shl shr sub tostring unm ]]):gmatch("%S+") do
_metamethods['__' .. m] = true
end
local function _setClassDictionariesMetatables(aClass)
local dict = aClass.__instanceDict
dict.__index = dict
local super = aClass.super
if super then
local superStatic = super.static
setmetatable(dict, { __index = super.__instanceDict })
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or superStatic[k] end })
else
setmetatable(aClass.static, { __index = function(_,k) return dict[k] end })
end
end
local function _propagateMetamethod(aClass, name, f)
for subclass in pairs(aClass.subclasses) do
if not subclass.__metamethods[name] then
subclass.__instanceDict[name] = f
_propagateMetamethod(subclass, name, f)
end
end
end
local function _updateClassDict(aClass, key, value)
if _metamethods[key] then
if value == nil then
aClass.__metamethods[key] = nil
if aClass.super then
value = aClass.super.__instanceDict[key]
end
else
aClass.__metamethods[key] = true
end
_propagateMetamethod(aClass, key, value)
end
aClass.__instanceDict[key] = value
end
local function _setClassMetatable(aClass)
setmetatable(aClass, {
__tostring = function() return "class " .. aClass.name end,
__index = aClass.static,
__newindex = _updateClassDict,
__call = function(self, ...) return self:new(...) end
})
end
local function _createClass(name, super)
local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict = {}, __metamethods = {} }
aClass.subclasses = setmetatable({}, {__mode = "k"})
_setClassDictionariesMetatables(aClass)
_setClassMetatable(aClass)
return aClass
end
local function _setSubclassMetamethods(aClass, subclass)
for m in pairs(_metamethods) do
subclass.__instanceDict[m] = aClass.__instanceDict[m]
end
end
local function _setDefaultInitializeMethod(aClass, super)
aClass.initialize = function(instance, ...)
return super.initialize(instance, ...)
end
end
local function _includeMixin(aClass, mixin)
assert(type(mixin)=='table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
if mixin.static then
for name,method in pairs(mixin.static) do
aClass.static[name] = method
end
end
if type(mixin.included)=="function" then mixin:included(aClass) end
aClass.__mixins[mixin] = true
end
local Object = _createClass("Object", nil)
function Object.static:allocate()
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end
function Object.static:new(...)
local instance = self:allocate()
instance:initialize(...)
return instance
end
function Object.static:subclass(name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
_setSubclassMetamethods(self, subclass)
_setDefaultInitializeMethod(subclass, self)
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end
function Object.static:subclassed(other) end
function Object.static:isSubclassOf(other)
return type(other) == 'table' and
type(self) == 'table' and
type(self.super) == 'table' and
( self.super == other or
type(self.super.isSubclassOf) == 'function' and
self.super:isSubclassOf(other)
)
end
function Object.static:include( ... )
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
function Object.static:includes(mixin)
return type(mixin) == 'table' and
type(self) == 'table' and
type(self.__mixins) == 'table' and
( self.__mixins[mixin] or
type(self.super) == 'table' and
type(self.super.includes) == 'function' and
self.super:includes(mixin)
)
end
function Object:initialize() end
function Object:__tostring() return "instance of " .. tostring(self.class) end
function Object:isInstanceOf(aClass)
return type(self) == 'table' and
type(self.class) == 'table' and
type(aClass) == 'table' and
( aClass == self.class or
type(aClass.isSubclassOf) == 'function' and
self.class:isSubclassOf(aClass)
)
end
function middleclass.class(name, super, ...)
super = super or Object
return super:subclass(name, ...)
end
middleclass.Object = Object
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
local random = function()
return js.global:Random()
end
local abs = function(n)
return js.global:abs(n)
end
local floor = function(n)
return js.global:floor(n)
end
local PI = js.global:PI()
local cos = function(n) return js.global:cos(n) end
local sin = function(n) return js.global:sin(n) end
local atan2 = function(a,b) return js.global:atan2(a,b) end
local Screen = {width=480, height=480}
local class = middleclass
local Vector = class('Vector')
function Vector:initialize(_x, _y)
self.x = _x or 0
self.y = _y or 0
end
local drawRect = function(r,g,b,x,y,w,h)
js.global:drawRect(r, g, b, x*Screen.width, y*Screen.height, Screen.width*w, Screen.height*w)
end
function Vector:set(v)
if not v then return end
self.x = v.x
self.y = v.y
end
function Vector:addDirection(degrees, speed)
local s = speed or 100
local rad = degrees * js.global:PI()/180
self.x = self.x + js.global:sin(rad)*s
self.y = self.y - js.global:cos(rad)*s
end
function Vector:directionTo(v)
return atan2(v.x - self.x, -(v.y - self.y)) * 180 / PI
end
function Vector:add(v)
self.x = self.x + v.x
self.y = self.y + v.y
return self
end
function Vector:div(n)
if n == 0 then return self; end
self.x = self.x / n
self.y = self.y / n
return self
end
function Vector:onScreen()
return self.x >= 0 and self.x <= 1 and self.y >= 0 and self.y <= 1
end
function Vector:rotate(amt)
if amt == 0 then return; end
amt = amt * PI / 180
local ox = self.x
self.x = self.x*cos(amt) - self.y * sin(amt)
self.y = ox*sin(amt) + self.y * cos(amt)
end
local Color = class('Color')
function Color:initialize(_r,_g,_b)
self.r = _r or 0
self.g = _g or 0
self.b = _b or 0
end
function Color:copy()
return Color:new(self.r, self.g, self.b)
end
function Color:set(r,g,b)
self.r = r
self.g = g
self.b = b
end
Color.static.white = Color:new(1,1,1)
Color.static.red = Color:new(1,0,0)
local DrawingRect = class('DrawingRect')
function DrawingRect:initialize(_color, offset, _size)
self.color = _color or Color:new()
self.offset = offset or Vector:new()
self.size = _size or Vector:new()
self.currentPosition = Vector:new()
end
function DrawingRect:draw(drawing)
self:updateState(drawing)
local xPos = Screen.width*(self.currentPosition.x)
local yPos = Screen.height*(self.currentPosition.y)
js.global:drawRect(self.color.r, self.color.g, self.color.b, xPos, yPos, Screen.width*self.size.x, Screen.height*self.size.y)
end
function DrawingRect:updateState(drawing)
-- copy out the information that we need to draw with
self.currentPosition:set(self.offset)
self.currentPosition:rotate(drawing.rotation)
self.currentPosition:add(drawing.position)
end
function DrawingRect:isOverlapping(rect)
return
abs(self.currentPosition.x - rect.currentPosition.x) <
(self.size.x + rect.size.x) / 2 and
abs(self.currentPosition.y - rect.currentPosition.y) <
(self.size.y + rect.size.y) / 2
end
local Drawing = class('Drawing');
function Drawing:initialize()
self.color = Color:new(1,1,1);
self.rects = {}
self.position = Vector:new()
self.rotation = 0
end
function Drawing:setColor(r, g, b)
self.color:set(r,g,b)
return self
end
function Drawing:addRect(_x,_y,_w,_h)
table.insert(self.rects, DrawingRect:new(self.color:copy(), Vector:new(_x,_y), Vector:new(_w, _h)))
return self
end
-- generates a set of rects that can look like a rotated square
function Drawing:addRects(width, height)
local directionAmount = 0
local ox = 0
local oy = 0
if width > height then
directionAmount = directionAmount + PI / 2
local tw = width
width = height
height = tw
end
local n = floor(height / width)
local o = -width * (n - 1) / 2
local vo = width
width = width * 1.05
for i=1,n do
local rp = Vector:new(sin(directionAmount) * o + ox, cos(directionAmount) * o + oy)
table.insert(self.rects, DrawingRect:new(
self.color:copy(),
rp,
Vector:new(width, width)
))
o = o + vo
end
return self
end
function Drawing:draw()
for i,v in ipairs(self.rects) do
v:draw(self)
end
end
function Drawing:setPosition(position)
self.position:set(position)
return self
end
function Drawing:setRotation(rotation)
self.rotation = rotation
return self
end
function Drawing:isOverlapping(otherDrawing)
for i,ourRect in ipairs(self.rects) do
for j,otherRect in ipairs(otherDrawing.rects) do
if ourRect:isOverlapping(otherRect) then return true end
end
end
return false
end
local Fiber = class('Fiber')
Fiber.static.fibers = {}
Fiber.static.update = function()
for i,v in ipairs(Fiber.fibers) do
v:update()
end
end
function Fiber:initialize(fiberGroup)
self.functions = {}
self.functionCount = 0
self.currentFunction = 1
self.waitTicks = 0
fiberGroup = fiberGroup or Fiber.fibers
table.insert(fiberGroup, self)
end
function Fiber:doOnce(func)
table.insert(self.functions, function()
func()
self:gotoNext()
end)
self.functionCount = self.functionCount + 1
return self
end
function Fiber:doRepeat(func)
table.insert(self.functions, func)
self.functionCount = self.functionCount + 1
return self
end
function Fiber:gotoNext()
self.currentFunction = self.currentFunction + 1
if self.currentFunction > self.functionCount then
self.currentFunction = 1
end
end
function Fiber:wait(ticks)
table.insert(self.functions, function()
self.waitTicks = ticks
self:gotoNext()
end)
self.functionCount = self.functionCount + 1
return self
end
function Fiber:update()
if self.functionCount == 0 then
return
end
if self.waitTicks > 0 then
self.waitTicks = self.waitTicks - 1
return
end
self.functions[self.currentFunction]()
end
local Actor = class('Actor')
Actor.static.groups = {}
Actor.static.number = 10
Actor.static.update = function()
Actor.static.number = 0
for i,group in ipairs(Actor.groups) do
for j, actor in ipairs(group.contents) do
Actor.static.number = Actor.static.number + 1
actor:preupdate()
actor:update()
actor:draw()
end
end
end
Actor.static.scroll = function(GroupName, ox, oy, minX, minY, maxX, maxY)
for i,group in ipairs(Actor.groups) do
if group.groupName == GroupName then
for j, actor in ipairs(group.contents) do
if actor.position.x < minX then
actor.position.x = maxX
end
if actor.position.y < minY then
actor.position.y = maxY
end
if actor.position.x > maxX then
actor.position.x = minX
end
if actor.position.y > maxY then
actor.position.y = minY
end
actor.position.x = actor.position.x + ox
actor.position.y = actor.position.y + oy
end
break;
end
end
end
function Actor:initialize(...)
arg = {...}
local myClass = self.class.name
for i,v in pairs(Actor.groups) do
if v.groupName == myClass then
self.group = v.contents
end
end
if not self.group then
local tempGroup = {groupName = myClass, contents={}}
table.insert(Actor.groups, tempGroup)
self.group = tempGroup.contents
end
self.fibers = {}
self.position = Vector:new(0.5,0.5)
self.velocity = Vector:new(0,0)
self.drawing = Drawing:new()
self.rotation = 0
self.ticks = 0
table.insert(self.group, self)
if self.init then
if ... then
self:init(unpack(arg))
else
self:init()
end
end
end
function Actor:newFiber()
return Fiber:new(self.fibers)
end
function Actor:draw()
self.drawing:draw(self.position)
end
function Actor:preupdate()
self.ticks = self.ticks + 1
self.position:add(self.velocity)
self.drawing:setPosition(self.position):setRotation(self.rotation);
-- update the objects that are attached to this actor
for i,v in pairs(self.fibers) do
v:update()
end
end
function Actor:update()
end
function Actor:remove()
for i,v in ipairs(self.group) do
if v == self then
table.remove(self.group, i)
break;
end
end
end
function Actor:onCollision(otherClass, callback)
-- get the group we are colliding with
for i,group in ipairs(Actor.groups) do
if group.groupName == otherClass then
for j,actor in ipairs(group.contents) do
if self.drawing:isOverlapping(actor.drawing) then
-- if not callback then return true end
if not callback then
return true;
else
end
end
end
return false;
end
end
end
local ParticleActor = class('ParticleActor', Actor)
function ParticleActor:init()
self.duration = 2
self.color = Color:new(1,1,1)
self.size = Vector:new(0.01, 0.01)
end
function ParticleActor:update()
-- if this particle is requesting a spawn
if self.particleSpawner then
local spawner = self.particleSpawner
self:remove()
if spawner.number < 1 then return end
for i=1,spawner.number do
-- generate all the particles we need
local pActor = ParticleActor:new()
pActor.position:set(spawner.position)
pActor.size = Vector:new(spawner.size, spawner.size)
-- calculate velocity based on direction
local d = spawner.direction + (random()*spawner.directionSpread)-(spawner.directionSpread*0.5)
pActor.velocity:addDirection(d, spawner.speed)
pActor.duration = spawner.duration
pActor.color = spawner.color
end
return
end
-- otherwise, just update the particle
-- we have to manually draw, since particles don't have graphics attached to them
local xPos = Screen.width*(self.position.x)
local yPos = Screen.height*(self.position.y)
js.global:drawRect(self.color.r, self.color.g, self.color.b, xPos, yPos, Screen.width*self.size.x, Screen.height*self.size.y)
if self.ticks >= self.duration then
self:remove()
end
end
local Particle = class('Particle')
function Particle:initialize()
self.actor = ParticleActor:new()
self.actor.particleSpawner = self
self.duration = 10
self.direction = 0
self.size = 0.02
self.directionSpread = 360
self.speed = 0.01
self.number = 5
self.position = Vector:new()
self.color = Color:new()
end
function Particle:setNumber(count)
self.number = count
return self
end
function Particle:duration(time)
self.duration = time
return self
end
function Particle:setPosition(position)
self.position:set(position)
return self
end
function Particle:setDirection(direction, spread)
self.direction = direction
self.directionSpread = spread or 30
return self
end
function Particle:setSpeed(speed)
self.speed = speed
return self
end
function Particle:setColor(r,g,b)
self.color:set(r,g,b)
return self
end
function Actor:newParticle()
return Particle:new():setPosition(self.position)
end
local Mouse = class('Mouse')
Mouse.static.position = Vector:new()
function mouseMove(x,y)
Mouse.position.x = x
Mouse.position.y = y
end
local Letter = class('Letter')
Letter.static.dots = {}
Letter.static.drawDots = function(letter, x,y,color,size)
if not Letter.dots[letter] then
Letter.dots[letter] = {}
local letterBitCount = js.global:getDotCountForLetter(letter)
for i=0,letterBitCount-1 do
local px = js.global:getDotPositionForLetter(letter,i,0)
local py = js.global:getDotPositionForLetter(letter,i,1)
table.insert(Letter.dots[letter], {x = px, y=py})
end
end
for i,v in ipairs(Letter.dots[letter]) do
drawRect(color.r, color.b, color.g,
x + v.x * size,
y + v.y * size,
size,
size)
end
end
Letter.static.draw = function(text, x,y, color, scale)
local letterWidth = scale * 5
local tx = x
local ty = y
for i=1,#text do
local c = text:sub(i,i)
Letter.drawDots(c, tx, ty, color, scale)
tx = tx+letterWidth
end
end
local TextActor = class('TextActor', Actor)
function TextActor:init(text)
self.text = text
self.duration = 1
self.size = 0.02
end
function TextActor:draw()
if self.ticks == 0 then
self.velocity:div(self.duration)
end
Letter.draw(self.text, self.position.x, self.position.y, self.color, self.size)
if self.ticks >= self.duration then
self:remove()
end
end
-- END STDLIB
-- this is the thing that runs the whole game
function drawFrame()
js.global:drawRect(0,0,0,240,240,480,480)
Actor.update()
Fiber.update()
Actor.scroll('Star', 0,0, 0,0, 1,1)
print(Actor.number)
Letter.draw("ACTORS: " .. Actor.static.number, 0,0,Color.white, 0.005)
-- Letter.draw("BRO", 0.5, 0.5, Color.red, 0.02)
-- we are just going to force draw a letter now, just for fun
end
local Star = class('Star', Actor)
function Star:init()
self.position.x = js.global:Random()
self.position.y = js.global:Random()
local c = js.global:Random()
self.speed = c*0.005
self.drawing:setColor(c*1.5,c*1.5,c*1.5):addRect(0,0, 0.01*c, 0.01*c)
end
function Star:update()
self.position.y = self.position.y+self.speed
end
local Shot = class('Shot', Actor)
function Shot:initialize(...)
Actor.initialize(self, ...) -- invoking the superclass' initializer
end
function Shot:init(position)
self.drawing:setColor(1, 0,0)
:addRect(-0.02, 0.02, 0.02, 0.04)
:addRect(0.02, 0.02, 0.02, 0.04)
self.position:set(position)
self.velocity = Vector:new(0,-0.03)
end
function Shot:update()
if not self.position:onScreen() then
self:remove()
end
end
local Ship = class('Ship', Actor)
function Ship:init()
self.position.x = 0.5
self.position.y = 0.8
self.drawing:setColor(1, 0,0)
:addRect(-0.02, 0.02, 0.02, 0.04)
:addRect(0.02, 0.02, 0.02, 0.04)
:setColor(1, 1, 1)
:addRect(0,0, 0.02, 0.05)
self:newFiber()
:doOnce(function()
Shot:new(self.position)
self:addMuzzleFlash(-0.02)
self:addMuzzleFlash(0.02)
end)
:wait(3)
end
function Ship:addMuzzleFlash(offsetX)
self:newParticle()
:setPosition(Vector:new(self.position.x+offsetX, self.position.y))
:setNumber(3)
:setDirection(0, 30)
:setSpeed(0.02)
:setColor(1, 0, 0)
end
function Ship:update()
-- self.position.y = self.position.y + 0.01
self.position.x = Mouse.position.x
self.position.y = Mouse.position.y
end
for i=0,30 do
local s = Star:new()
end
local ship = Ship:new()
local Bullet = class('Bullet', Actor)
function Bullet:init(position)
self.position:set(position)
local angle = self.position:directionTo(ship.position)
self.velocity:addDirection(angle, 0.015)
self.drawing:setColor(1,1,1)
:addRects(0.02, 0.04)
:addRects(0.04, 0.02)
end
function Bullet:update()
self.rotation = self.rotation + 3
if not self.position:onScreen() then self:remove() end
end
local Enemy = class('Enemy', Actor)
function Enemy:init()
self.position.x = js.global:Random()
self.position.y = 0
self.drawing:setColor(1, 1,0)
:addRect(-0.02, -0.02, 0.02, 0.04)
:addRect(0.02, -0.02, 0.02, 0.04)
:addRect(0,0, 0.02, 0.05)
self:newFiber()
:doOnce(function()
Bullet:new(self.position)
end)
:wait(30)
end
function Enemy:update()
self.position.y = self.position.y + 0.01
if self.position.y > 1 then
self:remove()
end
if self:onCollision('Shot') then
local ta = TextActor:new("+1")
ta.color = Color:new(1,1,1)
ta.duration = 30
ta.size = 0.005
ta.position:set(self.position)
ta.velocity:set(Vector:new(0,-0.01))
self:remove()
self:newParticle()
:setColor(1,1,0)
:setNumber(5)
end
end
Fiber:new()
:doOnce(function()
Enemy:new()
end)
:wait(30)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment