Last active
August 29, 2015 14:17
-
-
Save tarrouye/e3eb73282d7590b503f2 to your computer and use it in GitHub Desktop.
Codea GAMEJAM 2015 Entry
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
-- Wrinkle | |
displayMode(FULLSCREEN) | |
function setup() | |
makeAssets() | |
highscore = readLocalData("highscore", 0) | |
reset() | |
end | |
function lose() | |
lost = true | |
score = math.round(score) | |
if score > highscore then | |
saveLocalData("highscore", score) | |
highscore = score | |
end | |
end | |
function reset() | |
wrinkle = Wrinkle() | |
objects = Objects() | |
multiplier, score = 1, 0 | |
speedmod = 1 | |
lost = false | |
invincible = false | |
end | |
function draw() | |
background(255, 255, 255, 255) | |
if not lost then | |
objects:setSpeedMod(speedmod) | |
objects:draw() | |
wrinkle:draw() | |
for i = 1, #wrinkle.points - 1 do | |
local p, p2 = wrinkle.points[i], wrinkle.points[i+1] | |
for j, k in pairs(objects.objs) do | |
for v, o in ipairs(k) do | |
if not o.hit and intersectsCircle(o.position, o:halfSize(), p, p2) then | |
o:die() | |
if o.kills then | |
if not invincible then | |
lose() | |
end | |
goto rdone | |
end | |
if powerup then | |
tween.stop(powerup) | |
end | |
score = score + (o.bonus or 0) | |
speedmod = (o.speedMod or 1) | |
invincible = (o.invincibilizes or false) | |
wrinkle.colour = o.colour | |
powerup = tween.delay(o.lasts, function() | |
invincible = false | |
speedmod = 1 | |
wrinkle.colour = wrinkle.ocolour | |
end) | |
goto done | |
end | |
end | |
end | |
end | |
::done:: | |
multiplier = math.max(#wrinkle.points - 1, 0) | |
score = score + (multiplier / 60) | |
smooth() | |
fill(wrinkle.colour.r, wrinkle.colour.g, wrinkle.colour.b, 127) fontSize(HEIGHT / 2) | |
text(math.round(score), WIDTH / 2, HEIGHT / 2) | |
fill(255, 0, 0, 127) fontSize(HEIGHT / 8) | |
text("x" .. multiplier, WIDTH / 2, HEIGHT * 7.5/8) | |
else | |
fill(0) fontSize(HEIGHT / 8) | |
text("game over\n\nscore: " .. score .. "\n\nhighscore: " .. highscore, WIDTH / 2, HEIGHT / 2) | |
end | |
::rdone:: | |
end | |
function touched(t) | |
if not lost then | |
wrinkle:touched(t) | |
else | |
if t.state == ENDED then | |
reset() | |
end | |
end | |
end | |
--# Objects | |
Objects = class() | |
function Objects:init() | |
self.m = mesh() | |
self.m.texture = assets.object | |
self.objs = {} | |
self.types = {} | |
for k, v in pairsByKeys(subclasses(Object)) do | |
table.insert(self.types, v) | |
self.objs[v.id] = {} | |
end | |
self:spawnObj() | |
end | |
function Objects:spawnObj() | |
local possibilities = {} | |
for i, t in ipairs(self.types) do | |
for j = 1, t.probability do | |
table.insert(possibilities, t) | |
end | |
end | |
local kind = possibilities[math.random(1, #possibilities)] | |
local dead | |
for i, obj in ipairs(self.objs[kind.id]) do | |
if not obj.alive then | |
dead = obj | |
break | |
end | |
end | |
if dead then | |
dead:undeactivate() | |
else | |
local speed = math.random(5, 10) | |
local size = WIDTH / 20 | |
local new = kind(speed, size) | |
new.mId = self.m:addRect(0, 0, 0, 0, 0) | |
table.insert(self.objs[kind.id], new) | |
end | |
self.lastSpawnTime = ElapsedTime | |
self.nextSpawn = math.random(15, 35) / 100 | |
end | |
function Objects:setSpeedMod(s) | |
for v, kind in pairs(self.objs) do | |
for i, obj in ipairs(kind) do | |
obj.speedModifier = s | |
end | |
end | |
end | |
function Objects:draw() | |
-- Spawn Objects | |
if self.lastSpawnTime + self.nextSpawn < ElapsedTime then | |
self:spawnObj() | |
end | |
-- Update mesh | |
for v, kind in pairs(self.objs) do | |
for i, obj in ipairs(kind) do | |
obj:update() | |
self.m:setRect(obj.mId, obj:getX(), obj:getY(), obj:getSize(), obj:getSize(), obj.angle) | |
self.m:setRectColor(obj.mId, obj.colour.r, obj.colour.g, obj.colour.b, obj.alpha or 255) | |
end | |
end | |
self.m:draw() | |
end | |
function Objects:touched(touch) | |
end | |
--# Object | |
Object = class() | |
function Object:init(speed, size) | |
self.speed = speed | |
self.size = size | |
self.speedModifier = 1 | |
self.scale = 1 | |
self.angle = 0 | |
self.alpha = 255 | |
self.alive = true | |
self:randomDirAndPos() | |
end | |
function Object:randomDirAndPos() | |
local dirs = { vec2(-1,0), vec2(1,0), vec2(0,-1), vec2(0,1), vec2(1,1), vec2(-1,-1) } | |
self.direction = dirs[math.random(1, #dirs)] | |
local trx = WIDTH / 2 + ((WIDTH / 2 + self:halfSize()) * (self.direction.x * -1)) | |
local try = HEIGHT / 2 + ((HEIGHT / 2 + self:halfSize()) * (self.direction.y * -1)) | |
local ranx = math.max(math.min(math.random(0, WIDTH), WIDTH - self:halfSize()), self:halfSize()) | |
local rany = math.max(math.min(math.random(0, HEIGHT), HEIGHT - self:halfSize()), self:halfSize()) | |
if self.direction.x == 0 then | |
self.position = vec2(ranx, try) | |
else | |
self.position = vec2(trx, rany) | |
end | |
end | |
function Object:update() | |
if self.alive then | |
local move = self.direction * self.speed * self.speedModifier | |
self.position = vec2(self:getX() + move.x, self:getY() + move.y) | |
if self:offscreen() then | |
self:deactivate() | |
end | |
end | |
end | |
function Object:deactivate() | |
self.alive = false | |
self:randomDirAndPos() | |
end | |
function Object:undeactivate() | |
self.alive = true | |
self.hit = false | |
end | |
function Object:die() | |
if self.hit or (not self.alive) then return end | |
self.hit = true | |
self.speed = 0 | |
self.fadeAlpha = 255 | |
tween(0.25, self, { scale = 1.5 }, tween.easing.quadOut, function() | |
tween(0.2, self, { scale = 0, alpha = 0 }, tween.easing.quadIn, function() self:deactivate() end) | |
end) | |
end | |
function Object:offscreen() | |
return (self.position.x > WIDTH + self:halfSize() or self.position.x < -self:halfSize() | |
or self.position.y > HEIGHT + self:halfSize() or self.position.y < -self:halfSize()) | |
end | |
function Object:getSize() | |
return self.size * self.scale | |
end | |
function Object:getX() | |
return self.position.x | |
end | |
function Object:getY() | |
return self.position.y | |
end | |
function Object:halfSize() | |
return self:getSize() / 2 | |
end | |
function Object:pointInside(p) | |
return (p:dist(self.position) <= self:halfSize()) | |
end | |
--# Types | |
-- Enemy | |
Enemy = class(Object) | |
Enemy.id = "enemy" | |
Enemy.colour = color(255, 0, 0) | |
Enemy.probability = 20 | |
Enemy.kills = true | |
function Enemy:init(...) | |
Object.init(self, ...) | |
end | |
-- Bonus points | |
Bonus = class(Object) | |
Bonus.id = "points" | |
Bonus.colour = color(0, 255, 0) | |
Bonus.probability = 1 | |
Bonus.bonus = 50 | |
Bonus.lasts = 0.001 | |
function Bonus:init(...) | |
Object.init(self, ...) | |
end | |
-- Shield | |
Shield = class(Object) | |
Shield.id = "shield" | |
Shield.colour = color(0, 204, 255, 255) | |
Shield.probability = 5 | |
Shield.lasts = 1 | |
Shield.invincibilizes = true | |
function Shield:init(...) | |
Object.init(self, ...) | |
end | |
-- Slowdown | |
Slow = class(Object) | |
Slow.id = "slow" | |
Slow.colour = color(127, 0, 255) | |
Slow.probability = 5 | |
Slow.lasts = 2 | |
Slow.speedMod = 0.5 | |
function Slow:init(...) | |
Object.init(self, ...) | |
end | |
-- Speedup | |
Fast = class(Object) | |
Fast.id = "fast" | |
Fast.colour = color(245, 0, 255, 255) | |
Fast.probability = 5 | |
Fast.lasts = 2 | |
Fast.speedMod = 2 | |
function Fast:init(...) | |
Object.init(self, ...) | |
end | |
--# Wrinkle | |
Wrinkle = class() | |
function Wrinkle:init() | |
self.points = {} | |
self.trailing = {} | |
self.locked = false | |
self.moving = false | |
self.colour = color(245, 255, 0, 255) --color(math.random(0,255), math.random(0,255), math.random(0,255)) | |
self.ocolour = self.colour | |
end | |
function Wrinkle:draw() | |
stroke(self.colour) strokeWidth(5) noSmooth() | |
if self.locked and #self.points >= 2 then | |
local change = (self.points[2] - self.points[1]) | |
local newPoint = self.points[#self.points] + change | |
newPoint.x, newPoint.y = newPoint.x % WIDTH, newPoint.y % HEIGHT | |
table.insert(self.points, newPoint) | |
table.remove(self.points, 1) | |
end | |
local long = vec2(WIDTH * 3/4, HEIGHT * 3/4) | |
for i = 1, #self.points - 1 do | |
local p1 = self.points[i] | |
local p2 = self.points[i + 1] | |
if math.abs(p1.x - p2.x) < long.x and math.abs(p1.y - p2.y) < long.y then | |
line(p1.x, p1.y, p2.x, p2.y) | |
end | |
end | |
end | |
function Wrinkle:stopTrail() | |
for i = #self.trailing, 1, -1 do | |
tween.stop(self.trailing[i]) | |
end | |
end | |
function Wrinkle:resumeTrail() | |
for i = 1, #self.points do | |
self.trailing[i].running = 0 | |
self.trailing[i].time = 0.175 + (i/10) | |
tween.play(self.trailing[i]) | |
end | |
end | |
function Wrinkle:lock() | |
self.locked = true | |
self:stopTrail() | |
end | |
function Wrinkle:unlock() | |
self.locked = false | |
self:resumeTrail() | |
end | |
function Wrinkle:touched(t) | |
if t.state == BEGAN then | |
self:unlock() | |
end | |
if (#self.points == 0 or vec2(t.x, t.y):dist(self.points[#self.points]) > 15) then | |
table.insert(self.points, vec2(t.x, t.y)) | |
table.insert(self.trailing, tween.delay(0.175, function() table.remove(self.points, 1) end)) | |
end | |
if t.state == ENDED then | |
self:lock() | |
end | |
end | |
--# Assets | |
assets = {} | |
function makeAssets() | |
assets.object = image(100, 100) setContext(assets.object) | |
fill(255, 255, 255, 255) noStroke() | |
ellipse(50, 50, 100) | |
setContext() | |
end | |
--# Functions | |
function subclasses(c) | |
local t = {} | |
for k, v in pairs(_G) do | |
if type(v) == "table" and v._base and v._base == c then | |
t[k] = v | |
end | |
end | |
return t | |
end | |
function pairsByKeys (t, f) | |
local a = {} | |
for n in pairs(t) do table.insert(a, n) end | |
table.sort(a, f) | |
local i = 0 -- iterator variable | |
local iter = function () -- iterator function | |
i = i + 1 | |
if a[i] == nil then return nil | |
else return a[i], t[a[i]] | |
end | |
end | |
return iter | |
end | |
-- Rounds a number | |
function math.round(num, idp) | |
return tonumber(string.format("%." .. (idp or 0) .. "f", num)) | |
end | |
-- Check if line intersects a circle | |
function intersectsCircle(center, rad, lp1, lp2) | |
local d = lp1 - lp2 | |
local f = lp2 - center | |
local r = rad | |
local a = d:dot( d ) | |
local b = 2*f:dot( d ) | |
local c = f:dot( f ) - r*r | |
local discriminant = b*b-4*a*c | |
if( discriminant < 0 ) then | |
return false | |
else | |
discriminant = math.sqrt( discriminant ) | |
local t1 = (-b + discriminant)/(2*a) | |
local t2 = (-b - discriminant)/(2*a) | |
if( t1 >= 0 and t1 <= 1 ) or ( t2 >= 0 and t2 <= 1 ) then | |
return true | |
else | |
return false | |
end | |
end | |
end | |
-- Tween colors | |
local cmt = getmetatable(color()) | |
cmt.__add = function (c1, c2) | |
return color(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b, c1.a + c2.a) | |
end | |
cmt.__sub = function (c1, c2) | |
return color(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b, c1.a - c2.a) | |
end | |
cmt.__mul = function (c, s) | |
if type(c) ~= "userdata" then | |
c, s = s, c | |
end | |
return color(c.r * s, c.g * s, c.b * s, c.a * s) | |
end | |
cmt.__div = function (c, s) | |
if type(c) ~= "userdata" then | |
c, s = s, c | |
end | |
return color(c.r / s, c.g / s, c.b / s, c.a / s) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment