/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Orb class var Orb = Container.expand(function () { var self = Container.call(this); // Randomize orb color and size var orbColors = [0xffe066, 0x66e0ff, 0xff66b3, 0x8cf58c, 0xffb366, 0xc299fc, 0xfff266]; var color = orbColors[Math.floor(Math.random() * orbColors.length)]; var minSize = 55, maxSize = 90; var size = minSize + Math.random() * (maxSize - minSize); var orb = self.attachAsset('orb', { anchorX: 0.5, anchorY: 0.5, width: size, height: size, color: color }); // Animate orb with a pulsing effect tween(orb, { scaleX: 1.2, scaleY: 1.2 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(orb, { scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { // Loop if (self.parent) { orb.scaleX = 1; orb.scaleY = 1; tween(orb, { scaleX: 1.2, scaleY: 1.2 }, { duration: 600, easing: tween.easeInOut, onFinish: arguments.callee }); } } }); } }); return self; }); // Snake Segment (body or head) var SnakeSegment = Container.expand(function () { var self = Container.call(this); // By default, body segment var assetId = self.isHead ? 'snakeHead' : 'snakeBody'; var seg = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c2c }); /**** * Game Code ****/ // Orb: glowing yellow // Snake body: ellipse, lighter green // Snake head: ellipse, bright green // Game constants var GAME_W = 2048; var GAME_H = 2732; var ARENA_MARGIN = 60; // px, keep snake/orbs away from edge var SNAKE_INIT_LEN = 6; var SNAKE_SPEED = 13; // px per tick var SNAKE_TURN_SPEED = 0.16; // radians per tick var ORB_COUNT = 3; // State var snake = []; var snakeDirs = []; // Array of directions (radians) for each segment var snakeTargetDir = 0; // radians, where the head is steering var snakeCurDir = 0; // radians, current head direction var snakePendingGrowth = 0; var orbs = []; var dragging = false; var dragTarget = { x: 0, y: 0 }; var score = 0; var gameOver = false; // GUI var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Helper: clamp function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Helper: distance function dist(ax, ay, bx, by) { var dx = ax - bx; var dy = ay - by; return Math.sqrt(dx * dx + dy * dy); } // Helper: angle between two points function angleTo(ax, ay, bx, by) { return Math.atan2(by - ay, bx - ax); } // Helper: wrap angle to [-PI, PI] function wrapAngle(a) { while (a > Math.PI) a -= Math.PI * 2; while (a < -Math.PI) a += Math.PI * 2; return a; } // Helper: random position inside arena function randomArenaPos() { var x = ARENA_MARGIN + Math.random() * (GAME_W - 2 * ARENA_MARGIN); var y = ARENA_MARGIN + Math.random() * (GAME_H - 2 * ARENA_MARGIN); return { x: x, y: y }; } // Place orbs, avoiding snake function placeOrb(orb) { var tries = 0; while (tries < 20) { var pos = randomArenaPos(); var ok = true; for (var i = 0; i < snake.length; ++i) { if (dist(pos.x, pos.y, snake[i].x, snake[i].y) < 180) { ok = false; break; } } if (ok) { orb.x = pos.x; orb.y = pos.y; return; } tries++; } // fallback orb.x = ARENA_MARGIN + Math.random() * (GAME_W - 2 * ARENA_MARGIN); orb.y = ARENA_MARGIN + Math.random() * (GAME_H - 2 * ARENA_MARGIN); } // Initialize snake function initSnake() { // Remove old for (var i = 0; i < snake.length; ++i) { snake[i].destroy(); } snake = []; snakeDirs = []; // Center start var startX = GAME_W / 2; var startY = GAME_H / 2; var dir = -Math.PI / 2; // Up for (var i = 0; i < SNAKE_INIT_LEN; ++i) { var seg = new SnakeSegment(); seg.isHead = i === 0; // Re-attach correct asset for head if (seg.isHead) { seg.removeChildren(); seg.attachAsset('snakeHead', { anchorX: 0.5, anchorY: 0.5 }); } seg.x = startX - Math.cos(dir) * i * 85; seg.y = startY - Math.sin(dir) * i * 85; game.addChild(seg); snake.push(seg); snakeDirs.push(dir); } snakeCurDir = dir; snakeTargetDir = dir; snakePendingGrowth = 0; } // Initialize orbs function initOrbs() { for (var i = 0; i < orbs.length; ++i) { orbs[i].destroy(); } orbs = []; for (var i = 0; i < ORB_COUNT; ++i) { var orb = new Orb(); placeOrb(orb); game.addChild(orb); orbs.push(orb); } } // Reset game state function resetGame() { score = 0; scoreTxt.setText(score); gameOver = false; initSnake(); initOrbs(); window._orbsEaten = 0; } // Start game resetGame(); // Touch/drag controls game.down = function (x, y, obj) { // Don't allow drag in top left 100x100 if (x < 100 && y < 100) return; dragging = true; dragTarget.x = x; dragTarget.y = y; // Set target direction immediately var head = snake[0]; snakeTargetDir = angleTo(head.x, head.y, x, y); }; game.move = function (x, y, obj) { if (!dragging) return; dragTarget.x = x; dragTarget.y = y; var head = snake[0]; snakeTargetDir = angleTo(head.x, head.y, x, y); }; game.up = function (x, y, obj) { dragging = false; }; // Main update loop game.update = function () { if (gameOver) return; // Move snake head var head = snake[0]; var angleDiff = wrapAngle(snakeTargetDir - snakeCurDir); // Limit turn speed if (Math.abs(angleDiff) > SNAKE_TURN_SPEED) { snakeCurDir += SNAKE_TURN_SPEED * (angleDiff > 0 ? 1 : -1); } else { snakeCurDir = snakeTargetDir; } // Move head forward var newHeadX = head.x + Math.cos(snakeCurDir) * SNAKE_SPEED; var newHeadY = head.y + Math.sin(snakeCurDir) * SNAKE_SPEED; // Check wall collision if (newHeadX < ARENA_MARGIN || newHeadX > GAME_W - ARENA_MARGIN || newHeadY < ARENA_MARGIN || newHeadY > GAME_H - ARENA_MARGIN) { // Game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); gameOver = true; return; } // Move body: each segment follows the previous var prevX = head.x, prevY = head.y; var prevDir = snakeCurDir; // Move head head.x = newHeadX; head.y = newHeadY; snakeDirs[0] = snakeCurDir; // Rotate head to face direction if (head.children && head.children.length > 0) { head.children[0].rotation = snakeCurDir; } for (var i = 1; i < snake.length; ++i) { var seg = snake[i]; var dx = prevX - seg.x; var dy = prevY - seg.y; var d = Math.sqrt(dx * dx + dy * dy); var desiredDist = 85; if (d > 1) { var moveDist = clamp(d - desiredDist, 0, SNAKE_SPEED * 1.2); if (moveDist > 0) { var moveX = seg.x + dx / d * moveDist; var moveY = seg.y + dy / d * moveDist; seg.x = moveX; seg.y = moveY; } } // Face the direction of movement with smooth turning var targetDir = Math.atan2(seg.y - prevY, seg.x - prevX); var curDir = snakeDirs[i]; var diff = wrapAngle(targetDir - curDir); if (Math.abs(diff) > SNAKE_TURN_SPEED) { curDir += SNAKE_TURN_SPEED * (diff > 0 ? 1 : -1); } else { curDir = targetDir; } snakeDirs[i] = curDir; // Rotate segment to face movement direction if (seg.children && seg.children.length > 0) { seg.children[0].rotation = curDir; } prevX = seg.x; prevY = seg.y; prevDir = snakeDirs[i]; } // Check self-collision (head with body, skip first 3 segments for leniency) for (var i = 3; i < snake.length; ++i) { var seg = snake[i]; if (dist(head.x, head.y, seg.x, seg.y) < 60) { // Game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); gameOver = true; return; } } // Check orb collision for (var i = 0; i < orbs.length; ++i) { var orb = orbs[i]; if (dist(head.x, head.y, orb.x, orb.y) < 80) { // Eat orb score += 1; scoreTxt.setText(score); // Grow much slower: require 5 orbs per new segment if (!window._orbsEaten) window._orbsEaten = 0; window._orbsEaten += 1; if (window._orbsEaten >= 5) { snakePendingGrowth += 1; window._orbsEaten = 0; } // Animate orb tween(orb, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 180, easing: tween.easeOut, onFinish: function (orb, i) { return function () { orb.scaleX = 1; orb.scaleY = 1; orb.alpha = 1; placeOrb(orb); }; }(orb, i) }); } } // Grow snake if needed while (snakePendingGrowth > 0) { var last = snake[snake.length - 1]; var dir = snakeDirs[snake.length - 1]; var seg = new SnakeSegment(); seg.isHead = false; seg.removeChildren(); seg.attachAsset('snakeBody', { anchorX: 0.5, anchorY: 0.5 }); // Place at last segment's position, trailing behind seg.x = last.x - Math.cos(dir) * 85; seg.y = last.y - Math.sin(dir) * 85; // Rotate new segment to match direction if (seg.children && seg.children.length > 0) { seg.children[0].rotation = dir; } game.addChild(seg); snake.push(seg); snakeDirs.push(dir); snakePendingGrowth--; } }; // On game over, reset state for replay LK.on('gameover', function () { resetGame(); }); LK.on('youWin', function () { resetGame(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Orb class
var Orb = Container.expand(function () {
var self = Container.call(this);
// Randomize orb color and size
var orbColors = [0xffe066, 0x66e0ff, 0xff66b3, 0x8cf58c, 0xffb366, 0xc299fc, 0xfff266];
var color = orbColors[Math.floor(Math.random() * orbColors.length)];
var minSize = 55,
maxSize = 90;
var size = minSize + Math.random() * (maxSize - minSize);
var orb = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5,
width: size,
height: size,
color: color
});
// Animate orb with a pulsing effect
tween(orb, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(orb, {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Loop
if (self.parent) {
orb.scaleX = 1;
orb.scaleY = 1;
tween(orb, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: arguments.callee
});
}
}
});
}
});
return self;
});
// Snake Segment (body or head)
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
// By default, body segment
var assetId = self.isHead ? 'snakeHead' : 'snakeBody';
var seg = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Orb: glowing yellow
// Snake body: ellipse, lighter green
// Snake head: ellipse, bright green
// Game constants
var GAME_W = 2048;
var GAME_H = 2732;
var ARENA_MARGIN = 60; // px, keep snake/orbs away from edge
var SNAKE_INIT_LEN = 6;
var SNAKE_SPEED = 13; // px per tick
var SNAKE_TURN_SPEED = 0.16; // radians per tick
var ORB_COUNT = 3;
// State
var snake = [];
var snakeDirs = []; // Array of directions (radians) for each segment
var snakeTargetDir = 0; // radians, where the head is steering
var snakeCurDir = 0; // radians, current head direction
var snakePendingGrowth = 0;
var orbs = [];
var dragging = false;
var dragTarget = {
x: 0,
y: 0
};
var score = 0;
var gameOver = false;
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: clamp
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Helper: distance
function dist(ax, ay, bx, by) {
var dx = ax - bx;
var dy = ay - by;
return Math.sqrt(dx * dx + dy * dy);
}
// Helper: angle between two points
function angleTo(ax, ay, bx, by) {
return Math.atan2(by - ay, bx - ax);
}
// Helper: wrap angle to [-PI, PI]
function wrapAngle(a) {
while (a > Math.PI) a -= Math.PI * 2;
while (a < -Math.PI) a += Math.PI * 2;
return a;
}
// Helper: random position inside arena
function randomArenaPos() {
var x = ARENA_MARGIN + Math.random() * (GAME_W - 2 * ARENA_MARGIN);
var y = ARENA_MARGIN + Math.random() * (GAME_H - 2 * ARENA_MARGIN);
return {
x: x,
y: y
};
}
// Place orbs, avoiding snake
function placeOrb(orb) {
var tries = 0;
while (tries < 20) {
var pos = randomArenaPos();
var ok = true;
for (var i = 0; i < snake.length; ++i) {
if (dist(pos.x, pos.y, snake[i].x, snake[i].y) < 180) {
ok = false;
break;
}
}
if (ok) {
orb.x = pos.x;
orb.y = pos.y;
return;
}
tries++;
}
// fallback
orb.x = ARENA_MARGIN + Math.random() * (GAME_W - 2 * ARENA_MARGIN);
orb.y = ARENA_MARGIN + Math.random() * (GAME_H - 2 * ARENA_MARGIN);
}
// Initialize snake
function initSnake() {
// Remove old
for (var i = 0; i < snake.length; ++i) {
snake[i].destroy();
}
snake = [];
snakeDirs = [];
// Center start
var startX = GAME_W / 2;
var startY = GAME_H / 2;
var dir = -Math.PI / 2; // Up
for (var i = 0; i < SNAKE_INIT_LEN; ++i) {
var seg = new SnakeSegment();
seg.isHead = i === 0;
// Re-attach correct asset for head
if (seg.isHead) {
seg.removeChildren();
seg.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
}
seg.x = startX - Math.cos(dir) * i * 85;
seg.y = startY - Math.sin(dir) * i * 85;
game.addChild(seg);
snake.push(seg);
snakeDirs.push(dir);
}
snakeCurDir = dir;
snakeTargetDir = dir;
snakePendingGrowth = 0;
}
// Initialize orbs
function initOrbs() {
for (var i = 0; i < orbs.length; ++i) {
orbs[i].destroy();
}
orbs = [];
for (var i = 0; i < ORB_COUNT; ++i) {
var orb = new Orb();
placeOrb(orb);
game.addChild(orb);
orbs.push(orb);
}
}
// Reset game state
function resetGame() {
score = 0;
scoreTxt.setText(score);
gameOver = false;
initSnake();
initOrbs();
window._orbsEaten = 0;
}
// Start game
resetGame();
// Touch/drag controls
game.down = function (x, y, obj) {
// Don't allow drag in top left 100x100
if (x < 100 && y < 100) return;
dragging = true;
dragTarget.x = x;
dragTarget.y = y;
// Set target direction immediately
var head = snake[0];
snakeTargetDir = angleTo(head.x, head.y, x, y);
};
game.move = function (x, y, obj) {
if (!dragging) return;
dragTarget.x = x;
dragTarget.y = y;
var head = snake[0];
snakeTargetDir = angleTo(head.x, head.y, x, y);
};
game.up = function (x, y, obj) {
dragging = false;
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Move snake head
var head = snake[0];
var angleDiff = wrapAngle(snakeTargetDir - snakeCurDir);
// Limit turn speed
if (Math.abs(angleDiff) > SNAKE_TURN_SPEED) {
snakeCurDir += SNAKE_TURN_SPEED * (angleDiff > 0 ? 1 : -1);
} else {
snakeCurDir = snakeTargetDir;
}
// Move head forward
var newHeadX = head.x + Math.cos(snakeCurDir) * SNAKE_SPEED;
var newHeadY = head.y + Math.sin(snakeCurDir) * SNAKE_SPEED;
// Check wall collision
if (newHeadX < ARENA_MARGIN || newHeadX > GAME_W - ARENA_MARGIN || newHeadY < ARENA_MARGIN || newHeadY > GAME_H - ARENA_MARGIN) {
// Game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
gameOver = true;
return;
}
// Move body: each segment follows the previous
var prevX = head.x,
prevY = head.y;
var prevDir = snakeCurDir;
// Move head
head.x = newHeadX;
head.y = newHeadY;
snakeDirs[0] = snakeCurDir;
// Rotate head to face direction
if (head.children && head.children.length > 0) {
head.children[0].rotation = snakeCurDir;
}
for (var i = 1; i < snake.length; ++i) {
var seg = snake[i];
var dx = prevX - seg.x;
var dy = prevY - seg.y;
var d = Math.sqrt(dx * dx + dy * dy);
var desiredDist = 85;
if (d > 1) {
var moveDist = clamp(d - desiredDist, 0, SNAKE_SPEED * 1.2);
if (moveDist > 0) {
var moveX = seg.x + dx / d * moveDist;
var moveY = seg.y + dy / d * moveDist;
seg.x = moveX;
seg.y = moveY;
}
}
// Face the direction of movement with smooth turning
var targetDir = Math.atan2(seg.y - prevY, seg.x - prevX);
var curDir = snakeDirs[i];
var diff = wrapAngle(targetDir - curDir);
if (Math.abs(diff) > SNAKE_TURN_SPEED) {
curDir += SNAKE_TURN_SPEED * (diff > 0 ? 1 : -1);
} else {
curDir = targetDir;
}
snakeDirs[i] = curDir;
// Rotate segment to face movement direction
if (seg.children && seg.children.length > 0) {
seg.children[0].rotation = curDir;
}
prevX = seg.x;
prevY = seg.y;
prevDir = snakeDirs[i];
}
// Check self-collision (head with body, skip first 3 segments for leniency)
for (var i = 3; i < snake.length; ++i) {
var seg = snake[i];
if (dist(head.x, head.y, seg.x, seg.y) < 60) {
// Game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
gameOver = true;
return;
}
}
// Check orb collision
for (var i = 0; i < orbs.length; ++i) {
var orb = orbs[i];
if (dist(head.x, head.y, orb.x, orb.y) < 80) {
// Eat orb
score += 1;
scoreTxt.setText(score);
// Grow much slower: require 5 orbs per new segment
if (!window._orbsEaten) window._orbsEaten = 0;
window._orbsEaten += 1;
if (window._orbsEaten >= 5) {
snakePendingGrowth += 1;
window._orbsEaten = 0;
}
// Animate orb
tween(orb, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 180,
easing: tween.easeOut,
onFinish: function (orb, i) {
return function () {
orb.scaleX = 1;
orb.scaleY = 1;
orb.alpha = 1;
placeOrb(orb);
};
}(orb, i)
});
}
}
// Grow snake if needed
while (snakePendingGrowth > 0) {
var last = snake[snake.length - 1];
var dir = snakeDirs[snake.length - 1];
var seg = new SnakeSegment();
seg.isHead = false;
seg.removeChildren();
seg.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Place at last segment's position, trailing behind
seg.x = last.x - Math.cos(dir) * 85;
seg.y = last.y - Math.sin(dir) * 85;
// Rotate new segment to match direction
if (seg.children && seg.children.length > 0) {
seg.children[0].rotation = dir;
}
game.addChild(seg);
snake.push(seg);
snakeDirs.push(dir);
snakePendingGrowth--;
}
};
// On game over, reset state for replay
LK.on('gameover', function () {
resetGame();
});
LK.on('youWin', function () {
resetGame();
});