Last active
May 9, 2017 05:28
-
-
Save emendoza2/4bb7bd49989de52db82bc1bd78e772ec to your computer and use it in GitHub Desktop.
A Slither.io Clone Created using Codea
This file contains 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
--# Button | |
Button = class() | |
-- Fast Mesh Button Class courtesy of @Vega | |
function Button:init(text,x,y,width,height) | |
self.state = "normal" | |
self.text = text | |
self.textColor = color(255,255,255,192) | |
self.x = x | |
self.y = y | |
self.width = width | |
self.height = height | |
self.visible = true | |
self.fontSize = 28 | |
self.font = "ArialRoundedMTBold" | |
self.color1 = color(255, 255, 255, 96) | |
self.color2 = color(128,128,128,32) | |
self.presscolor1 = color(192, 224, 224, 128) | |
self.presscolor2 = color(96, 192, 224, 128) | |
self.verts = self:createVerts(self.width, self.height) | |
self.myMesh = mesh() | |
self.myMesh.vertices = triangulate(self.verts) | |
self.vertColor = {} | |
self:recolor() | |
self.action = nil | |
self.tapped = false | |
end | |
function Button:setColors(c1,c2,p1,p2) | |
self.color1 = c1 | |
self.color2 = c2 | |
self.presscolor1 = p1 | |
self.presscolor2 = p2 | |
self:recolor() | |
end | |
function Button:textOptions(fn, sz, col) | |
self.font = fn | |
self.fontSize = sz | |
self.textColor = col | |
end | |
function Button:draw() | |
if self.visible == true then | |
pushStyle() | |
pushMatrix() | |
translate(self.x,self.y) | |
self.myMesh:draw() | |
fill(self.textColor) | |
fontSize(self.fontSize) | |
font(self.font) | |
text(self.text, self.width/2,self.height/2) | |
self:drawLines(self.verts) | |
popMatrix() | |
popStyle() | |
end | |
end | |
function Button:touched(touch) | |
self.tapped = false | |
if self.visible then | |
if pointInRect(touch.x, touch.y, self.x, self.y, self.width, self.height) then | |
if touch.state == BEGAN then | |
self.tapped = true | |
self.state = "pressing" | |
self:recolor() | |
elseif touch.state == ENDED then | |
if self.state == "pressing" then | |
self.state = "normal" | |
self.tapped = true | |
self:recolor() | |
end | |
if self.action then | |
self.action() | |
end | |
end | |
else | |
self.state = "normal" | |
self:recolor() | |
end | |
end | |
end | |
function Button:createVerts(w,h) | |
local r | |
local v = {} | |
if w > 100 or h > 100 then | |
if w>=h then r = math.round(h/100) else r = math.round(w/100) end | |
else | |
r = 1 | |
end | |
v[1] = vec2(w,6*r) | |
v[2] = vec2(w-r,4*r) | |
v[3] = vec2(w-2*r,2*r) | |
v[4] = vec2(w-4*r,r) | |
v[5] = vec2(w-6*r,0) | |
v[6] = vec2(6*r,0) | |
v[7] = vec2(4*r,r) | |
v[8] = vec2(2*r,2*r) | |
v[9] = vec2(r,4*r) | |
v[10] = vec2(0,6*r) | |
v[11] = vec2(0,h-6*r) | |
v[12] = vec2(r,h-4*r) | |
v[13] = vec2(2*r,h-2*r) | |
v[14] = vec2(4*r,h-r) | |
v[15] = vec2(6*r,h) | |
v[16] = vec2(w-6*r,h) | |
v[17] = vec2(w-4*r,h-r) | |
v[18] = vec2(w-2*r,h-2*r) | |
v[19] = vec2(w-r,h-4*r) | |
v[20] = vec2(w,h-6*r) | |
return v | |
end | |
function Button:drawLines(v) | |
noSmooth() | |
strokeWidth(1) | |
stroke(0, 0, 0, 192) | |
for i=1, #v-1 do | |
line(v[i].x,v[i].y,v[i+1].x,v[i+1].y) | |
end | |
line(v[#v].x,v[#v].y,v[1].x,v[1].y) | |
end | |
function Button:recolor() | |
local lt, dk | |
if self.state == "normal" then | |
lt = self.color1 | |
dk = self.color2 | |
else | |
lt = self.presscolor1 | |
dk = self.presscolor2 | |
end | |
for i=1,3 * #self.verts - 6 do | |
if self.myMesh.vertices[i].y > self.height/2 then | |
self.vertColor[i] = lt | |
else | |
self.vertColor[i] = dk | |
end | |
end | |
self.myMesh.colors = self.vertColor | |
end | |
-- Math Utilities | |
function math.round(value) | |
-- math.round function courtesy of Vega. | |
return math.floor(value + 0.5) | |
end | |
function pointInRect(pointX, pointY, x, y, w, h) | |
-- Returns true if point (pointX, pointY) is within the rectangle | |
-- with lower left corner at (x, y) with a width of w and a | |
-- height of h. | |
-- | |
-- Reefwing Software (www.reefwing.com.au) | |
-- Version 1.0 | |
if pointX >= x and pointX <= x + w and pointY >= y and pointY <= y + h then | |
return true | |
else | |
return false | |
end | |
end | |
--# Generatename | |
vw = {"a","e","i","o","u","oo","ee","ae","ea","oi","io","y","ei","ie"} | |
cn = {"b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z","ts","th","ph","gh"} | |
defname = {"snakey","snaker","sneaky","slipper"} | |
function genname() | |
local startwith = math.floor(math.random(2)) | |
local name = "" | |
if startwith == 1 then | |
name = name..vw[math.ceil(math.random(14))] | |
else | |
name = name..cn[math.ceil(math.random(25))] | |
end | |
local namelength = math.floor(math.random(10)) | |
local rname = {} | |
for i = 0, namelength do | |
if startwith == 1 then | |
if i%2 == 0 then | |
rname[i] = vw[math.ceil(math.random(14))] | |
else | |
rname[i] = cn[math.ceil(math.random(25))] | |
end | |
else | |
if i%2 == 0 then | |
rname[i] = cn[math.ceil(math.random(25))] | |
else | |
rname[i] = vw[math.ceil(math.random(14))] | |
end | |
end | |
end | |
for i,v in ipairs(rname) do | |
name = name..rname[i] | |
end | |
local randomPct = 0.8 | |
if math.random() < randomPct then | |
return name | |
else | |
return defname[math.random(1,#defname)] | |
end | |
return name | |
end | |
--# Main | |
-- Snake | |
WORLDWIDTH = 10000 | |
WORLDHEIGHT = 10000 | |
-- Use this function to perform your initial setup | |
function setup() | |
print("Hello World!") | |
socket = require("socket") | |
s = Snake(WIDTH/2,HEIGHT/2,50) | |
food = {} | |
frames = 0 | |
for i = 1,5000 do | |
table.insert(food,vec3(math.random(WORLDWIDTH),math.random(WORLDHEIGHT),math.random(2,10))) | |
end | |
ss = {} | |
table.insert(ss,Playersnake(math.random(WORLDWIDTH),math.random(WORLDHEIGHT),math.random(5,10))) | |
for i = 1, 75 do | |
table.insert(ss,Snake(math.random(WORLDWIDTH),math.random(WORLDHEIGHT),math.random(5,70))) | |
end | |
print("done setting up") | |
print(getLocalIP()) | |
cam = vec2(0,0) | |
draw = DrawGame | |
topten = {{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0},{name="",score=0}} | |
end | |
-- This function gets called once every frame | |
function DrawDead() | |
background(255, 255, 255, 255) | |
fill(0) | |
text("You Died",WIDTH/2,HEIGHT/2) | |
end | |
function DrawGame() | |
-- This sets a dark background color | |
background(40, 40, 50) | |
-- This sets the line thickness | |
strokeWidth(5) | |
translate(cam.x,cam.y) | |
--s:draw() | |
fill(255) | |
rect(0,-1000,WORLDWIDTH,1000) | |
rect(-1000,0,1000,WORLDHEIGHT) | |
rect(0,WORLDHEIGHT,WORLDWIDTH,1000) | |
rect(WORLDWIDTH,0,1000,WORLDHEIGHT) | |
noStroke() | |
for i,v in ipairs(food) do | |
if inView(v) then | |
ellipse(v.x,v.y,v.z) | |
end | |
end | |
for i,s in ipairs(ss) do | |
for i=#topten,1,-1 do | |
v = topten[i] | |
if #s.segments > v.score then | |
v.score = #s.segments | |
v.name = s.name | |
topten[i] =v | |
break | |
end | |
end | |
if (s.segments[1].pos - vec2(-cam.x+WIDTH/2,-cam.y+HEIGHT/2)):len() < WIDTH/2 + #s.segments*s.segLen or s.player then | |
s:draw() | |
for j,t in ipairs(ss) do | |
if s ~= t then | |
s:collide(t) | |
end | |
end | |
--[[ for i,v in ipairs(s.segments) do | |
if v.pos.x > WIDTH then | |
v.offset.x = v.offset.x + 1 | |
elseif v.pos.x < 0 then | |
v.offset.x = v.offset.x - 1 | |
else | |
v.offset.x = 0 | |
end | |
v.pos.y = v.pos.y%HEIGHT | |
end | |
]] | |
if s.dead then | |
if s.player then | |
draw = DrawDead | |
end | |
table.remove(ss,i) | |
end | |
end | |
end | |
textMode(CORNER) | |
fill(255) | |
fontSize(20) | |
for i,v in ipairs(topten) do | |
text(v.name.." "..v.score,WIDTH-200,HEIGHT-i*20-20) | |
end | |
frames = frames + 1 | |
if frames%100 == 0 then | |
table.insert(food,vec3(math.random(WORLDWIDTH),math.random(WORLDHEIGHT),math.random(2,10))) | |
end | |
if frames%1000 == 0 then | |
table.insert(ss,Snake(math.random(WORLDWIDTH),math.random(WORLDHEIGHT),math.random(5,10))) | |
end | |
cam = vec2(-ss[1].segments[1].pos.x+WIDTH/2,-ss[1].segments[1].pos.y+HEIGHT/2) | |
end | |
function touched(touch) | |
for i,v in ipairs(ss) do | |
v:touched(vec2(touch.x-cam.x,touch.y-cam.y)) | |
end | |
end | |
function bearingOf(v) | |
return math.deg(math.atan2(v.x, v.y)) | |
end | |
function bearingToVector(degrees) | |
local radians = math.rad(degrees) | |
return vec2(math.sin(radians), math.cos(radians)) -- bearings are relative to "up" | |
end | |
function inView(point) | |
if point.x > -cam.x and point.y > -cam.y and point.x < -cam.x+WIDTH and point.y < -cam.y+HEIGHT then | |
return true | |
end | |
return false | |
end | |
function getLocalIP() | |
--you input your ip here | |
local randomIP = "" | |
--this port below is just a place holder it does nothing but it needs to be there | |
local randomPort = "" | |
local randomSocket = socket.udp() | |
randomSocket:setpeername(randomIP,randomPort) | |
local localIP, _ = randomSocket:getsockname() | |
randomSocket:close() | |
randomSocket = nil | |
return localIP | |
end | |
--# Playersnake | |
Playersnake = class() | |
function Playersnake:init(x,y,startlen) | |
-- you can accept and set parameters here | |
self.segments = {} | |
self.segLen = 20 | |
for i = 1, startlen do | |
self.segments[i] = {steer = 0, offset = vec2(0,0), desiredBearing = 0, pos = vec2(x,y), len = 25} | |
end | |
self.frames = 0 | |
self.foodCount = 0 | |
self.width = #self.segments | |
self.player = true | |
self.near = vec2(x,y) | |
self.far = vec2(x,y) | |
local function randomColor() | |
return color(math.random(0,255),math.random(0,255),math.random(0,255)) | |
end | |
self.randomColors = {} | |
for i = 1 , math.random(1,4) do | |
table.insert(self.randomColors,randomColor()) | |
end | |
self.name = "mr. snake" | |
end | |
function Playersnake:draw() | |
local seg = self.segments[1] | |
-- Codea does not automatically call this method | |
for i,v in ipairs(food) do | |
if v.x > self.segments[1].pos.x - self.width/2 then | |
if (self.segments[1].pos - vec2(v.x,v.y)):len() < self.width/2 + v.z/2 then | |
self.foodCount = self.foodCount + 1 | |
table.remove(food,i) | |
end | |
end | |
end | |
pushStyle() | |
resetStyle() | |
local buff = self.segments | |
for i = #buff, 1, -1 do | |
v = buff[i] | |
local prev = buff[i-1] or v | |
if inView(v.pos) then | |
strokeWidth(self.width) | |
stroke(self.randomColors[i%#self.randomColors+1]) | |
lineCapMode(ROUND) | |
line(v.pos.x - v.offset.x*WIDTH,v.pos.y,prev.pos.x-prev.offset.x*WIDTH,prev.pos.y) | |
end | |
if buff[i-1] then | |
local prev = buff[i-1] | |
local d = prev.pos - v.pos | |
local angle = bearingOf(d) | |
v.pos = prev.pos - | |
(bearingToVector(angle)) | |
* v.len | |
-- v.steer = bearingOf(segments[i-1].pos - v.pos) | |
end | |
-- segments[i].pos = segments[i].pos + vec2(math.sin(math.rad(v.steer)),math.cos(math.rad(v.steer))) | |
end | |
popStyle() | |
pushMatrix() | |
pushStyle() | |
noStroke() | |
translate(seg.pos.x,seg.pos.y) | |
rotate(-seg.desiredBearing) | |
fill(255, 255, 255, 255) | |
ellipse(-self.width/6,self.width/5,self.width/3) | |
ellipse(self.width/6,self.width/5,self.width/3) | |
fill(0) | |
ellipse(-self.width/6,self.width/5+self.width/16,self.width/6) | |
ellipse(self.width/6,self.width/5+self.width/16,self.width/6) | |
popStyle() | |
popMatrix() | |
--[[ v = segments[1] | |
steer = wrap(v.desiredBearing - v.bearing,-180,180) | |
maxsteer = clampMagnitude(steer,180) | |
v.bearing = v.bearing + maxsteer | |
]] | |
self.near,self.far = seg.pos, seg.pos | |
local maxDeltaBearing = (270-#self.segments)*DeltaTime | |
local desiredDeltaBearing = wrap( | |
seg.desiredBearing - seg.steer, -180, 180) | |
local deltaBearing = clampMagnitude( | |
desiredDeltaBearing, maxDeltaBearing) | |
seg.steer = seg.steer + deltaBearing | |
local vel = bearingToVector(seg.steer) * 120 | |
local newPos = seg.pos + vel*DeltaTime | |
seg.pos = newPos | |
if self.foodCount > 5 then | |
self:grow() | |
end | |
self.width = math.min(#self.segments,75) | |
end | |
function Playersnake:grow() | |
self.startedGrowing = true | |
local growingSegment = {steer = 0, offset = vec2(0,0), desiredBearing = 0, pos = self.segments[#self.segments].pos, len = 0} | |
table.insert(self.segments,growingSegment) | |
tween(2,growingSegment,{len = 25},tween.easing.cubicOut) | |
self.foodCount = 0 | |
end | |
function Playersnake:collide(snake) | |
for i,v in ipairs(snake.segments) do | |
local dist = (self.segments[1].pos - v.pos):len() | |
if dist < self.width/2 + snake.width/2 then | |
self:die() | |
end | |
end | |
end | |
function Playersnake:die() | |
for i,v in ipairs(self.segments) do | |
for i = 1, math.random(1,5) do | |
local pellet = vec3(math.random(-25,25) + v.pos.x,math.random(-25,25) + v.pos.y,0) | |
table.insert(food,pellet) | |
tween(1,pellet,{z = math.random(10,30)},tween.easing.cubicInOut) | |
end | |
end | |
self.dead = true | |
end | |
function Playersnake:touched(touch) | |
-- Codea does not automatically call this method | |
self.segments[1].desiredBearing = bearingOf(vec2(touch.x,touch.y) - self.segments[1].pos) | |
end | |
--# Snake | |
Snake = class() | |
function Snake:init(x,y,startlen) | |
-- you can accept and set parameters here | |
self.segments = {} | |
self.segLen = 20 | |
for i = 1, startlen do | |
self.segments[i] = {steer = 0, offset = vec2(0,0), desiredBearing = 0, pos = vec2(x,y), len = 25} | |
end | |
self.width = 50 | |
self.frames = 0 | |
self.foodCount = 0 | |
self.far = vec2(x,y) | |
self.near = vec2(x,y) | |
local function randomColor() | |
return color(math.random(0,255),math.random(0,255),math.random(0,255)) | |
end | |
self.randomColors = {} | |
for i = 1 , math.random(1,4) do | |
table.insert(self.randomColors,randomColor()) | |
end | |
self.override = false | |
self.name = genname() | |
end | |
function Snake:draw() | |
-- Codea does not automatically call this method | |
local seg = self.segments[1] | |
local closestFood = vec2(0,0) | |
for i,v in ipairs(food) do | |
local dist = (self.segments[1].pos - vec2(v.x,v.y)):len() | |
if dist < self.width/2 + v.z/2 then | |
self.foodCount = self.foodCount + 1 | |
table.remove(food,i) | |
elseif dist < (self.segments[1].pos - vec2(closestFood.x,closestFood.y)):len() then | |
closestFood = v -- find closest food to snake head | |
end | |
end | |
if (seg.pos-vec2(closestFood.x,closestFood.y)):len() < 100 and | |
not self.override then | |
self.override = 1 | |
seg.desiredBearing = bearingOf(vec2(closestFood.x,closestFood.y) - self.segments[1].pos) | |
else | |
self.override = false -- return to random walk | |
end | |
local buff = self.segments | |
for i = #buff, 1, -1 do | |
v = buff[i] | |
local prev = buff[i-1] or v | |
strokeWidth(self.width) | |
stroke(self.randomColors[i%#self.randomColors+1]) | |
lineCapMode(ROUND) | |
line(v.pos.x - v.offset.x*WIDTH,v.pos.y,prev.pos.x-prev.offset.x*WIDTH,prev.pos.y) | |
if buff[i-1] then | |
local prev = buff[i-1] | |
local d = prev.pos - v.pos | |
local angle = bearingOf(d) | |
v.pos = prev.pos - | |
(bearingToVector(angle)) | |
* v.len | |
-- v.steer = bearingOf(segments[i-1].pos - v.pos) | |
end | |
-- segments[i].pos = segments[i].pos + vec2(math.sin(math.rad(v.steer)),math.cos(math.rad(v.steer))) | |
end | |
pushMatrix() | |
pushStyle() | |
noStroke() | |
translate(seg.pos.x,seg.pos.y) | |
rotate(-seg.steer) | |
fill(255, 255, 255, 255) | |
ellipse(-self.width/6,self.width/5,self.width/3) | |
ellipse(self.width/6,self.width/5,self.width/3) | |
fill(0) | |
ellipse(-self.width/6,self.width/5+self.width/16,self.width/6) | |
ellipse(self.width/6,self.width/5+self.width/16,self.width/6) | |
popStyle() | |
popMatrix() | |
--[[ v = segments[1] | |
steer = wrap(v.desiredBearing - v.bearing,-180,180) | |
maxsteer = clampMagnitude(steer,180) | |
v.bearing = v.bearing + maxsteer | |
]] | |
local maxDeltaBearing = 270*DeltaTime | |
local desiredDeltaBearing = wrap( | |
seg.desiredBearing - seg.steer, -180, 180) | |
local deltaBearing = clampMagnitude( | |
desiredDeltaBearing, maxDeltaBearing) | |
seg.steer = seg.steer + deltaBearing | |
local vel = bearingToVector(seg.steer) * 100 | |
local newPos = seg.pos + vel*DeltaTime | |
seg.pos = newPos | |
if not self.override then -- do random movement when override is off | |
if self.frames%60 == 0 then | |
seg.desiredBearing = seg.desiredBearing + math.random(-50,50) | |
end | |
end | |
if self.foodCount > 5 then | |
self:grow() | |
end | |
self.width = math.min(#self.segments,75) | |
end | |
function Snake:grow() | |
self.startedGrowing = true | |
local growingSegment = {steer = 0, offset = vec2(0,0), desiredBearing = 0, pos = self.segments[#self.segments].pos, len = 0} | |
table.insert(self.segments,growingSegment) | |
tween(2,growingSegment,{len = 25},tween.easing.cubicOut) | |
self.foodCount = 0 | |
end | |
function Snake:collide(snake) | |
local closestSegment = {pos = vec2(0,0)} | |
for i,v in ipairs(snake.segments) do | |
local dist = (self.segments[1].pos - v.pos):len() | |
if dist < (self.segments[1].pos - closestSegment.pos):len() then | |
closestSegment = v | |
end | |
if dist < self.width/2 + snake.width/2 then | |
self:die() | |
break | |
end | |
end | |
if not self.steerLocked then | |
if (self.segments[1].pos - closestSegment.pos):len() < 80 + self.width/2 + snake.width/2 then | |
self.override = 2 -- override controls with level 2 if snake is near other snake | |
self.segments[1].desiredBearing = v.steer -90 +(math.random()-0.5)*10 -- leave perpendicular from snake with random vec | |
self.steerLocked = true | |
self._=0 | |
tween(1,self,{_=0},nil,function() | |
self.steerLocked=false | |
print("@") | |
end) | |
elseif (self.segments[1].pos - closestSegment.pos):len() < 150 then | |
self.override = false | |
end | |
end | |
end | |
function Snake:die() | |
for i,v in ipairs(self.segments) do | |
for i = 1, math.random(1,2) do | |
local pellet = vec3(math.random(-25,25) + v.pos.x,math.random(-25,25) + v.pos.y,0) | |
table.insert(food,pellet) | |
tween(1,pellet,{z = math.random(10,30)},tween.easing.cubicInOut) | |
end | |
end | |
self.dead = true | |
end | |
function Snake:touched(touch) | |
-- Codea does not automatically call this method | |
end | |
--# Vector | |
function wrap(x, min, max) | |
if x < min then | |
return max - (min - x) | |
elseif x >= max then | |
return min + (x - max) | |
else | |
return x | |
end | |
end | |
function clamp(x, min, max) | |
return math.max(min, math.min(max, x)) | |
end | |
function clampMagnitude(x, maxMagnitude) | |
return clamp(x, -maxMagnitude, maxMagnitude) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment