Skip to content

Instantly share code, notes, and snippets.

@NathanFlurry
Created December 16, 2013 01:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save NathanFlurry/7981135 to your computer and use it in GitHub Desktop.
Save NathanFlurry/7981135 to your computer and use it in GitHub Desktop.
December 2013 Ludum Dare entry.
--# Main
displayMode(FULLSCREEN_NO_BUTTONS)
function setup()
parameter.boolean("rotateCamera",true)
lastScore = readLocalData("lastScore",0)
highscore = readLocalData("highscore",0)
StateMachine:init()
StateMachine:registerStates({
start = Start,
game = Game
})
StateMachine:setState("start")
end
function draw()
background(0, 0, 0, 255)
physics.gravity(0,0)
font("Didot")
fontSize(25)
fill(255, 255, 255, 255)
StateMachine:draw()
end
function touched(t)
StateMachine:touched(t)
end
function collide(c)
if StateMachine.currentState.player.collide then
state.player:collide(c)
end
end
--# Start
Start = class()
function Start:init()
end
function Start:draw()
pushStyle()
if lastScore > 0 then
fontSize(20)
text("Last Score: "..lastScore,WIDTH/2,HEIGHT/4)
text("Highscore: "..highscore,WIDTH/2,HEIGHT/4-fontSize()*2)
end
fontSize(100)
text("SnakeChase",WIDTH/2,HEIGHT/3*2)
fontSize(50)
text("Touch to start",WIDTH/2,HEIGHT/3)
popStyle()
end
function Start:touched(t)
if t.state == ENDED then
StateMachine:setState("game")
end
end
--# Game
Game = class()
function Game:init()
self.score = 0
self.scoreTimer = Timer(.1,function()
self.score = self.score + #self.player.bodyParts
end)
self.lives = {}
self.addLife = function()
table.insert(self.lives,{size = 0})
tween(.2,self.lives[#self.lives],{size = 1},tween.easing.sineOut)
end
self.subtractLife = function()
if #self.lives > 0 then
tween(.2,self.lives[#self.lives],{size = 0},tween.easing.sineOut,function()
table.remove(self.lives)
end)
end
end
for i = 1,5 do
self:addLife()
end
self.player = Player()
self.enemies = Enemies()
end
function Game:draw()
self.scoreTimer:update()
self.trans = vec2(-self.player.head.x+WIDTH/2,-self.player.head.y+HEIGHT/2)
local scoreStr = tostring(self.score)
local scoreTable = {}
for i = 6,1,-1 do
local num = string.sub(scoreStr,-i,-i)
if type(tonumber(num)) ~= "number" then
num = "0"
end
table.insert(scoreTable,num)
end
for i,v in ipairs(scoreTable) do
text(v,fontSize()*i*.7,HEIGHT-fontSize())
end
local sMStr = tostring(#self.player.bodyParts)
local sMTable = {}
for i = 1,string.len(#self.player.bodyParts) do
local num = string.sub(sMStr,-i,-i)
if type(tonumber(num)) ~= "number" then
num = "0"
end
table.insert(sMTable,num)
end
table.insert(sMTable,"x")
for i,v in ipairs(sMTable) do
text(v,fontSize()*(#scoreTable-i+1)*.7,HEIGHT-fontSize()*2)
end
for i,v in ipairs(self.lives) do
pushMatrix()
pushStyle()
tint(255, 255, 255, 255)
translate((WIDTH-i*25)+0,HEIGHT-20)
scale(v.size)
text("♡",0,0)
popStyle()
popMatrix()
end
pushMatrix()
if rotateCamera then
translate(WIDTH/2,HEIGHT/2)
rotate(-self.player.head.angle+90)
translate(-WIDTH/2,-HEIGHT/2)
end
translate(self.trans.x,self.trans.y)
pushStyle()
fill(76, 76, 76, 255)
popStyle()
self.player:draw()
self.enemies:draw()
popMatrix()
if #self.lives <= 0 then
lastScore = self.score
saveLocalData("lastScore",self.score)
if self.score > highscore then
highscore = self.score
saveLocalData("highscore",self.score)
end
-- StateMachine:setState("start") -- UNKNOWN BUG
restart()
end
end
function Game:exit()
end
--# Player
Player = class()
function Player:init()
self.partSize = 15
self.headSize = self.partSize
self.rotateSpeed = 250
self.moveSpeed = 150
self.oDistance = self.partSize
self.head = physics.body(CIRCLE,self.headSize)
self.head.sensor = true
self.head.type = KINEMATIC
self.head.position = vec2(WIDTH/2,HEIGHT/2)
self.color = color(255, 255, 255, 255)
self.m = mesh()
self.m:setColors(self.color)
self.bodyParts = {}
for i = 1,1 do
self:addBodyPart()
end
end
function Player:draw()
self:control()
--debugDraw(self.head)
for i,v in ipairs(self.bodyParts) do
local nextObj = self.bodyParts[i-1] or self.head
local ang = math.deg(vec2(0,0):angleBetween((v.position-nextObj.position)))-180
v.angle = ang
v.linearVelocity = vec2(
math.cos(math.rad(ang)),
math.sin(math.rad(ang)))*self.moveSpeed
--debugDraw(v)
end
local snakeWidth = self.partSize
local points = {}
local capPoints = {}
local capRes = 20
for i = 1,capRes do
local pos = vec2(math.cos(math.rad((360/capRes)*i)),
math.sin(math.rad((360/capRes)*i)))
if pos.y >= -capRes then
table.insert(capPoints,pos)
end
end
for i = 1,#capPoints do
local pos = capPoints[i]
pos = pos:rotate(math.rad(self.head.angle-90))
pos = pos * snakeWidth + self.head.position
table.insert(points,pos)
end
for i = 0,#self.bodyParts do
local v = self.bodyParts[i] or self.head
table.insert(
points,
vec2(
v.x+math.cos(math.rad(v.angle+90))*snakeWidth,
v.y+math.sin(math.rad(v.angle+90))*snakeWidth
)
)
end
for i = #capPoints,1,-1 do
local pos = capPoints[i]
local part = self.bodyParts[#self.bodyParts] or self.head
pos.x = pos.x
pos.y = -pos.y
pos = pos:rotate(math.rad(part.angle-90))
pos = pos * snakeWidth + part.position
table.insert(points,pos)
end
for i = #self.bodyParts,0,-1 do
local v = self.bodyParts[i] or self.head
table.insert(
points,
vec2(
v.x+math.cos(math.rad(v.angle-90))*snakeWidth,
v.y+math.sin(math.rad(v.angle-90))*snakeWidth
)
)
end
self.m.vertices = triangulate(points)
self.m:setColors(self.color)
self.m:draw()
local eyeSize = 3
pushStyle()
pushMatrix()
translate(self.head.x,self.head.y)
rotate(self.head.angle+90)
fill(0, 0, 0, 255)
ellipse(-4,2,eyeSize)
ellipse(4,2,eyeSize)
popMatrix()
popStyle()
end
function Player:control()
self.head.angle = self.head.angle+(-Gravity.x*self.rotateSpeed*DeltaTime)
self.head.linearVelocity = vec2(
math.cos(math.rad(self.head.angle)),
math.sin(math.rad(self.head.angle)))*self.moveSpeed
end
function Player:addBodyPart()
local o = physics.body(CIRCLE,self.partSize)
o.sensor = true
local nextObj;
if self.bodyParts[#self.bodyParts] then
nextObj = self.bodyParts[#self.bodyParts]
else
nextObj = self.head
end
o.x = nextObj.x - math.cos(math.rad(nextObj.angle)) * self.oDistance
o.y = nextObj.y - math.sin(math.rad(nextObj.angle)) * self.oDistance
o.id = "bodyPart"
o.j = physics.joint(DISTANCE,o,nextObj,o.position,nextObj.position)
o.j.frequency = 0
table.insert(self.bodyParts,o)
end
function Player:collide(c)
if c.bodyA == self.head or c.bodyB == self.head then
local destroyBody;
for i,v in ipairs(state.enemies.enemies) do
if c.bodyA == v or c.bodyB == v then
destroyBody = i
end
end
if destroyBody then
self:addBodyPart()
state.enemies.enemies[destroyBody]:destroy()
table.remove(state.enemies.enemies,destroyBody)
end
elseif (c.bodyA.id == "bodyPart" and c.bodyB.id ~= "bodyPart") or
(c.bodyB.id == "bodyPart" and c.bodyA.id ~= "bodyPart") then
local stopLoop = false
local i = 1
while not stopLoop do
local v = self.bodyParts[i]
if c.bodyA == v or c.bodyB == v then
stopLoop = true
local destroyBody;
for i,v in ipairs(state.enemies.enemies) do
if c.bodyA == v or c.bodyB == v then
destroyBody = i
end
end
if destroyBody then
self.color = color(255, 0, 0, 255)
tween(1,
self.color,
{g = 255, b = 255},
{easing = tween.easing.sineOut})
state:subtractLife()
state.enemies.enemies[destroyBody]:destroy()
table.remove(state.enemies.enemies,destroyBody)
local destI = i
for i = #self.bodyParts,i,-1 do
local v = self.bodyParts[i]
v:destroy()
table.remove(self.bodyParts,i)
end
end
end
i = i+1
if i > #self.bodyParts then
stopLoop = true
end
end
end
end
--# Enemies
Enemies = class()
function Enemies:init()
self.size = 20
self.moveSpeed = 50
self.angleRange = 30
self.turnRange = 100
self.enemies = {}
self.enemyTimer = Timer(.2,function() self:addEnemy() end)
self.speedUpTimer = Timer(1,
function()
self.enemyTimer.interval = self.enemyTimer.interval * .95
end)
end
function Enemies:draw()
self.speedUpTimer:update()
self.enemyTimer:update()
for i = #self.enemies,1,-1 do
local v = self.enemies[i]
v.angle = v.angle + v.turnAmount * DeltaTime
v.linearVelocity = vec2(
math.cos(math.rad(v.angle)),
math.sin(math.rad(v.angle)))*v.speed
--debugDraw(v)
pushMatrix()
pushStyle()
fill(v.fill)
translate(v.x,v.y)
scale(v.offsetSize)
ellipse(0,0,v.radius*2)
popMatrix()
popStyle()
if v.x < state.player.head.x-WIDTH or
v.x > state.player.head.x+WIDTH or
v.y < state.player.head.y-HEIGHT or
v.y > state.player.head.y+HEIGHT then
v:destroy()
table.remove(self.enemies,i)
end
end
end
function Enemies:addEnemy()
local e = physics.body(CIRCLE,randomInRange(self.size,10))
local location = math.random(1,4)
e.sensor = true
local positionRange = vec2(WIDTH,HEIGHT)
e.speed = randomInRange(self.moveSpeed,30)
if location == 1 then
e.position = vec2(
state.player.head.x-positionRange.x/2,
randomInRange(state.player.head.y,positionRange.y))
e.angle = randomInRange(0,self.angleRange)
elseif location == 2 then
e.position = vec2(
randomInRange(state.player.head.x,positionRange.x),
state.player.head.y+positionRange.y/2)
e.angle = randomInRange(270,self.angleRange)
elseif location == 3 then
e.position = vec2(
state.player.head.x+positionRange.x/2,
randomInRange(state.player.head.y,positionRange.y))
e.angle = randomInRange(180,self.angleRange)
elseif location == 4 then
e.position = vec2(
randomInRange(state.player.head.x,positionRange.x),
state.player.head.y-positionRange.y/2)
e.angle = randomInRange(90,self.angleRange)
end
e.turnAmount = (math.random()-.5)*self.turnRange
e.fill = color(255)
tween(.25,e.fill,{g = 200, b = 200},{loop = "pingpong",easing = tween.easing.sineInOut})
e.offsetSize = 1.1
tween(.4,e,{offsetSize = .9},{loop = "pingpong",easing = tween.easing.sineInOut})
table.insert(self.enemies,e)
end
--# StateMachine
StateMachine = class()
function StateMachine:init()
self.states = {}
self.currentStateIndex = nil
self.currentState = nil
end
function StateMachine:draw()
if self.currentState then
self.currentState:draw()
end
end
function StateMachine:setState(index)
if self.currentState and self.currentState.exit then
self.currentState:exit()
end
if self.states[index] then
self.currentStateIndex = index
self.currentState = self.states[index]()
state = self.currentState
else
error("Non-existent state")
end
end
function StateMachine:registerStates(states)
for i,v in pairs(states) do
self.states[i] = v
print(i,v)
end
end
function StateMachine:touched(t)
if self.currentState and self.currentState.touched then
self.currentState:touched(t)
end
end
--# Timer
Timer = class()
function Timer:init(interval,callback)
self.interval = interval
self.callback = callback
self.time = 0
self.occurances = 0
self.paused = false
end
function Timer:update()
if self.paused ~= true then
self.time = self.time + DeltaTime
if self.time >= self.interval then
self.occurances = self.occurances + 1
self.time = 0
if type(self.callback) == "function" then
self.callback(self.occurances)
end
return true
else
return false
end
end
end
function Timer:reset()
self.time = 0
self.occurances = 0
end
function Timer:pause()
self.paused = true
end
function Timer:resume()
self.paused = false
end
function Timer:isPaused()
return self.paused
end
function Timer:getTime()
return self.time
end
function Timer:getCount()
return self.occurances
end
--# Functions
function randomInRange(n,r)
return n+((math.random()-.5)*2*r)
end
function debugDraw(b)
pushStyle()
pushMatrix()
noFill()
translate(b.x,b.y)
rotate(b.angle)
if b.type == STATIC then
stroke(255,255,255,255)
elseif b.type == DYNAMIC then
stroke(150,255,150,255)
elseif b.type == KINEMATIC then
stroke(150,150,255,255)
end
if b.shapeType == POLYGON then
strokeWidth(3)
local points = b.points
for j = 1,#points do
local a = points[j]
local b = points[(j % #points)+1]
line(a.x,a.y,b.x,b.y)
end
elseif b.shapeType == CHAIN or b.shapeType == EDGE then
strokeWidth(3)
local points = b.points
for j = 1,#points-1 do
local a = points[j]
local b = points[j+1]
line(a.x,a.y,b.x,b.y)
end
elseif b.shapeType == CIRCLE then
strokeWidth(3)
line(0,0,b.radius-3,0)
ellipse(0,0,b.radius*2)
end
popStyle()
popMatrix()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment