/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Orb class
var Orb = Container.expand(function () {
var self = Container.call(this);
var orb = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
// 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();
}
// 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 slower: only add 1 segment per orb
snakePendingGrowth += 1;
// 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();
}); ===================================================================
--- original.js
+++ change.js
@@ -315,9 +315,10 @@
if (dist(head.x, head.y, orb.x, orb.y) < 80) {
// Eat orb
score += 1;
scoreTxt.setText(score);
- snakePendingGrowth += 2;
+ // Grow slower: only add 1 segment per orb
+ snakePendingGrowth += 1;
// Animate orb
tween(orb, {
scaleX: 1.7,
scaleY: 1.7,