/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x404040 }); /**** * Game Code ****/ // Game configuration var segmentLength = 40, startingSegments = 10, spawn = { x: 1024, y: 1366 }, snakeSpeed = 500, baseSnakeSpeed = 4, speedIncrement = 500, maxApples = 5, appleLife = 300, segmentsPerApple = 2, snakeWidth = 30, appleWidth = 50, cursorSize = 20, snake, target, apples, score, highScore, gameState, deathMeans, smoothMovement = true, movementQueue = [], playerNickname = '', rankings = [], showRankings = false, rankingButton = null, rankingPanel = null, nicknameInput = false, gameStarted = false; // Initialize high score and rankings from storage highScore = storage.highScore || 0; var storedRankings = storage.rankings || []; rankings = []; // Handle fallback storage format if main array failed if (storedRankings.length === 0 && storage.rankingLength) { storedRankings = []; for (var k = 0; k < storage.rankingLength; k++) { storedRankings.push(storage['ranking_' + k]); } } // Convert from simple array storage format back to objects for (var i = 0; i < storedRankings.length; i += 2) { var nickname = storedRankings[i]; var score = storedRankings[i + 1]; if (nickname && score !== undefined) { rankings.push({ nickname: String(nickname), score: Number(score) }); } } function distance(p1, p2) { var dx = p2.x - p1.x; var dy = p2.y - p1.y; return Math.sqrt(dx * dx + dy * dy); } function lineIntersect(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) { var s1_x = p1_x - p0_x, s1_y = p1_y - p0_y, s2_x = p3_x - p2_x, s2_y = p3_y - p2_y, s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y), t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y); if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { return true; } return false; } function SGM(angle, x, y) { this.x = x || 0; this.y = y || 0; this.angle = angle || 0; this.parent = null; } ; SGM.prototype.endX = function () { return this.x + Math.cos(this.angle) * segmentLength; }; SGM.prototype.endY = function () { return this.y + Math.sin(this.angle) * segmentLength; }; SGM.prototype.pointAt = function (x, y) { var dx = x - this.x, dy = y - this.y; this.angle = Math.atan2(dy, dx); }; SGM.prototype.target = function (x, y) { this.targetX = x; this.targetY = y; this.arrived = false; this.totalDist = distance({ x: this.endX(), y: this.endY() }, { x: this.targetX, y: this.targetY }); this.currentDist = parseInt(this.totalDist); }; SGM.prototype.gotoTarget = function () { if (!this.arrived) { if (this.targetX > this.x + segmentLength || this.targetX < this.x - segmentLength || this.targetY > this.y + segmentLength || this.targetY < this.y - segmentLength) { this.pointAt(this.targetX, this.targetY); } else { this.arrived = true; } this.currentDist = distance({ x: this.endX(), y: this.endY() }, { x: this.targetX, y: this.targetY }); } this.x += (this.endX() - this.x) / (snakeSpeed * 0.5); this.y += (this.endY() - this.y) / (snakeSpeed * 0.5); if (this.parent) { this.parent.drag(this.x, this.y); } }; SGM.prototype.drag = function (x, y) { this.pointAt(x, y); var newX = x - Math.cos(this.angle) * segmentLength; var newY = y - Math.sin(this.angle) * segmentLength; this.x = newX; this.y = newY; if (this.parent) { this.parent.drag(this.x, this.y); } }; SGM.prototype.render = function (context) { context.lineTo(this.endX(), this.endY()); }; function IKR(x, y) { this.ix = x || 0; this.iy = y || 0; this.sgms = []; this.lastArm = null; } ; IKR.prototype.addSeg = function (angle) { var arm = new SGM(angle); if (this.lastArm !== null) { arm.x = this.lastArm.endX(); arm.y = this.lastArm.endY(); arm.parent = this.lastArm; } else { arm.x = this.ix; arm.y = this.iy; } this.sgms.push(arm); this.lastArm = arm; }; IKR.prototype.grow = function () { var tail = this.sgms[0], arm = new SGM(tail.angle); arm.x = tail.x - Math.cos(tail.angle) * segmentLength; arm.y = tail.y - Math.sin(tail.angle) * segmentLength; tail.parent = arm; this.sgms.unshift(arm); }; IKR.prototype.drag = function (x, y) { this.lastArm.drag(x, y); }; function CUR(x, y) { this.x = x; this.y = y; this.rotation = 0; } ; CUR.prototype.render = function (context) { context.save(); context.translate(this.x, this.y); context.rotate(this.rotation); context.beginPath(); context.moveTo(0, -cursorSize); context.lineTo(0, -cursorSize / 2); context.moveTo(0, cursorSize / 2); context.lineTo(0, cursorSize); context.moveTo(-cursorSize, 0); context.lineTo(-cursorSize / 2, 0); context.moveTo(cursorSize / 2, 0); context.lineTo(cursorSize, 0); context.stroke(); context.restore(); this.rotation = (this.rotation + cursorSpin) % 360; }; function Apple(x, y) { this.x = x; this.y = y; this.life = appleLife; this.rotation = 0; } Apple.prototype.update = function () { this.life--; }; Apple.prototype.render = function (context) { context.beginPath(); context.arc(this.x, this.y, appleWidth, 0, Math.PI * 2); context.fill(); if (gameState !== 'dead') { context.save(); context.fillStyle = 'white'; context.font = '8px sans-serif'; context.fillText(this.life, this.x + 10, this.y + 10); context.restore(); CUR.prototype.render.call(this, context); } }; function promptNickname() { if (!playerNickname) { // Set default nickname automatically playerNickname = 'Player' + Math.floor(Math.random() * 1000); } } function showRankingPanel() { if (rankingPanel) { rankingPanel.destroy(); } showRankings = !showRankings; if (showRankings) { // Create ranking panel rankingPanel = new Container(); var panelBg = LK.getAsset('snakeSegment', { width: 600, height: 800, color: 0x222222, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); panelBg.x = 1024; panelBg.y = 1366; rankingPanel.addChild(panelBg); var titleText = new Text2('Rankings', { size: 60, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 1000; rankingPanel.addChild(titleText); // Sort rankings by score var sortedRankings = rankings.slice().sort(function (a, b) { return b.score - a.score; }); for (var i = 0; i < Math.min(sortedRankings.length, 10); i++) { var rankText = new Text2(i + 1 + '. ' + sortedRankings[i].nickname + ': ' + sortedRankings[i].score, { size: 35, fill: sortedRankings[i].nickname === playerNickname ? 0xFFFF00 : 0xFFFFFF }); rankText.anchor.set(0.5, 0.5); rankText.x = 1024; rankText.y = 1100 + i * 50; rankingPanel.addChild(rankText); } var closeButton = LK.getAsset('snakeSegment', { width: 150, height: 50, color: 0xff4444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); closeButton.x = 1024; closeButton.y = 1700; rankingPanel.addChild(closeButton); var closeText = new Text2('Close', { size: 30, fill: 0xFFFFFF }); closeText.anchor.set(0.5, 0.5); closeText.x = 1024; closeText.y = 1700; rankingPanel.addChild(closeText); game.addChild(rankingPanel); } } function updateRankings(nickname, score) { // Find existing player or add new one var found = false; for (var i = 0; i < rankings.length; i++) { if (rankings[i].nickname === nickname) { if (score > rankings[i].score) { rankings[i].score = score; } found = true; break; } } if (!found) { rankings.push({ nickname: nickname, score: score }); } // Keep only top 50 scores rankings.sort(function (a, b) { return b.score - a.score; }); if (rankings.length > 50) { rankings = rankings.slice(0, 50); } // Convert rankings to storage-compatible format (simple array) var storageRankings = []; for (var i = 0; i < rankings.length; i++) { storageRankings.push(String(rankings[i].nickname)); storageRankings.push(Number(rankings[i].score)); } try { storage.rankings = storageRankings; } catch (e) { console.log('Storage error:', e); // Fallback: store each value separately if array fails for (var j = 0; j < storageRankings.length; j++) { storage['ranking_' + j] = storageRankings[j]; } storage.rankingLength = storageRankings.length; } } function init() { snake = new IKR(spawn.x, spawn.y); cursor = new CUR(-20, -20); target = new CUR(spawn.x + segmentLength * (startingSegments + 5), spawn.y); apples = []; score = 0; snakeSpeed = baseSnakeSpeed; // Reset speed to base value for (var i = 0; i < startingSegments; i++) { snake.addSeg(); } snake.lastArm.target(target.x, target.y); promptNickname(); // Set default nickname gameStarted = true; gameState = 'play'; } init(); // Touch events handled by game object game.down = function (x, y, obj) { // Check if ranking button was clicked if (rankingButton && x >= 1890 && x <= 2010 && y >= 70 && y <= 130) { showRankingPanel(); return; } // Check if ranking panel close button was clicked if (showRankings && x >= 949 && x <= 1099 && y >= 1675 && y <= 1725) { showRankings = false; if (rankingPanel) { rankingPanel.destroy(); rankingPanel = null; } return; } switch (gameState) { case 'play': if (!showRankings) { target.x = x; target.y = y; snake.lastArm.target(target.x, target.y); } break; case 'dead': if (!showRankings) { init(); } break; } }; function badPlacement(apple) { for (var s = 0; s < snake.sgms.length; s++) { var seg = snake.sgms[s]; if (Math.min(distance(apple, { x: seg.endX(), y: seg.endY() }), distance(apple, { x: seg.x, y: seg.y })) < appleWidth * 2) { return true; } } return false; } function addScoreSegments() { for (var i = 0; i < segmentsPerApple; i++) { snake.grow(); } // Keep snake speed constant - reset to base speed snakeSpeed = baseSnakeSpeed; } function update() { if (gameState !== 'dead') { snake.lastArm.gotoTarget(); if (snake.lastArm.endX() > 2048 - 50 || snake.lastArm.endX() < 50 || snake.lastArm.endY() > 2732 - 50 || snake.lastArm.endY() < 50) { gameState = 'dead'; deathMeans = 'Hit the wall!'; // Update high score if current score is higher if (score > highScore) { highScore = score; storage.highScore = highScore; } // Update rankings updateRankings(playerNickname, score); LK.showGameOver(); return; } for (var s = 0; s < snake.sgms.length - 2; s++) { var seg = snake.sgms[s]; if (lineIntersect(snake.lastArm.x, snake.lastArm.y, snake.lastArm.endX(), snake.lastArm.endY(), seg.x, seg.y, seg.endX(), seg.endY())) { gameState = 'dead'; deathMeans = 'You bit yourself'; // Update high score if current score is higher if (score > highScore) { highScore = score; storage.highScore = highScore; } // Update rankings updateRankings(playerNickname, score); LK.showGameOver(); return; } for (var a in apples) { var apple = apples[a]; if (Math.min(distance(apple, { x: seg.endX(), y: seg.endY() }), distance(apple, { x: seg.x, y: seg.y })) < appleWidth * 2) { score += Math.round(apple.life / 2); // half score if absorbed by the tail apples.splice(a, 1); addScoreSegments(); } } } for (var a in apples) { var apple = apples[a]; apple.update(); if (apple.life <= 0) { apples.splice(a, 1); continue; } if (distance(apple, { x: snake.lastArm.endX(), y: snake.lastArm.endY() }) < appleWidth * 2) { score += apple.life; apples.splice(a, 1); addScoreSegments(); } } if (apples.length < maxApples && Math.random() < 0.02) { var offset = appleWidth * 4, apple = new Apple(offset + Math.floor(Math.random() * (2048 - offset * 2)), offset + Math.floor(Math.random() * (2732 - offset * 2))); while (badPlacement(apple)) { apple.x = offset + Math.floor(Math.random() * (2048 - offset * 2)); apple.y = offset + Math.floor(Math.random() * (2732 - offset * 2)); } apples.push(apple); // Create visual apple var appleVisual = LK.getAsset('apple', { width: appleWidth * 2, height: appleWidth * 2, color: 0x00ff00, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); appleVisual.x = apple.x; appleVisual.y = apple.y; game.addChild(appleVisual); // Create life text under apple var lifeText = new Text2(apple.life.toString(), { size: 40, fill: 0xFFFFFF }); lifeText.anchor.set(0.5, 0); lifeText.x = apple.x; lifeText.y = apple.y + appleWidth; game.addChild(lifeText); appleVisual.lifeText = lifeText; appleObjects.push(appleVisual); } } } // LK rendering system - visual elements handled by game objects var snakeSegments = []; var appleObjects = []; var targetObject = null; var scoreText = null; // Initialize visual elements function initVisuals() { // Clear existing visuals for (var i = 0; i < snakeSegments.length; i++) { snakeSegments[i].destroy(); } for (var i = 0; i < appleObjects.length; i++) { appleObjects[i].destroy(); } if (targetObject) { targetObject.destroy(); } if (scoreText) { scoreText.destroy(); } if (rankingButton) { rankingButton.destroy(); } if (rankingPanel) { rankingPanel.destroy(); } snakeSegments = []; appleObjects = []; // Create score text scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.topLeft.addChild(scoreText); scoreText.x = 120; scoreText.y = 20; // Create high score text var highScoreText = new Text2('High Score: ' + highScore, { size: 40, fill: 0xFFFFFF }); highScoreText.anchor.set(0.5, 0); LK.gui.top.addChild(highScoreText); highScoreText.x = 0; highScoreText.y = 20; // Create ranking button on right side rankingButton = LK.getAsset('snakeSegment', { width: 120, height: 60, color: 0x4444ff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); rankingButton.x = 1950; rankingButton.y = 100; LK.gui.addChild(rankingButton); var rankingButtonText = new Text2('Rankings', { size: 30, fill: 0xFFFFFF }); rankingButtonText.anchor.set(0.5, 0.5); rankingButtonText.x = 1950; rankingButtonText.y = 100; LK.gui.addChild(rankingButtonText); // Create target indicator targetObject = LK.getAsset('target', { width: 30, height: 30, color: 0x0000ff, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); game.addChild(targetObject); } // Initialize the game init(); initVisuals(); // LK game update loop game.update = function () { if (gameState === 'play') { update(); // Update score display if (scoreText) { scoreText.setText('Score: ' + score); } // Update high score display var highScoreDisplay = LK.gui.top.children[0]; if (highScoreDisplay) { highScoreDisplay.setText('High Score: ' + highScore); } // Update target position if (targetObject && !snake.lastArm.arrived) { targetObject.x = target.x; targetObject.y = target.y; targetObject.visible = true; } else if (targetObject) { targetObject.visible = false; } // Update snake visual segments updateSnakeVisuals(); // Update apple visuals updateAppleVisuals(); } }; function updateSnakeVisuals() { // Only update positions if we have existing segments, otherwise recreate if (snakeSegments.length !== snake.sgms.length + 1) { // Clear existing snake segments for (var i = 0; i < snakeSegments.length; i++) { snakeSegments[i].destroy(); } snakeSegments = []; // Create new snake segments for (var s = 0; s < snake.sgms.length; s++) { var seg = snake.sgms[s]; var segmentVisual = LK.getAsset('snakeSegment', { width: snakeWidth, height: segmentLength, color: 0x64ff64, shape: 'box', anchorX: 0.5, anchorY: 0 }); segmentVisual.x = seg.x; segmentVisual.y = seg.y; segmentVisual.rotation = seg.angle + Math.PI / 2; game.addChild(segmentVisual); snakeSegments.push(segmentVisual); } // Create snake head with proper sizing var head = LK.getAsset('snakeHead', { width: 60, height: 60, anchorX: 0.5, anchorY: 0.5 }); head.x = snake.lastArm.endX(); head.y = snake.lastArm.endY(); game.addChild(head); snakeSegments.push(head); } else { // Update all segments as one unified body with synchronized movement var targetPositions = []; var targetRotations = []; // Calculate all target positions first for (var s = 0; s < snake.sgms.length; s++) { var seg = snake.sgms[s]; targetPositions[s] = { x: seg.x, y: seg.y }; targetRotations[s] = seg.angle + Math.PI / 2; } targetPositions[snake.sgms.length] = { x: snake.lastArm.endX(), y: snake.lastArm.endY() }; // Apply unified movement to all segments simultaneously with improved smoothness for (var s = 0; s < snakeSegments.length; s++) { var segmentVisual = snakeSegments[s]; var targetPos = targetPositions[s]; var targetRot = targetRotations[s] || segmentVisual.rotation; // Stop any existing tweens to prevent conflicts tween.stop(segmentVisual); // Unified smooth movement for entire snake body with optimized parameters tween(segmentVisual, { x: targetPos.x, y: targetPos.y, rotation: targetRot }, { duration: 100, easing: tween.easeOut }); } } } function updateAppleVisuals() { // Remove apples that no longer exist for (var i = appleObjects.length - 1; i >= 0; i--) { var found = false; for (var a = 0; a < apples.length; a++) { if (Math.abs(appleObjects[i].x - apples[a].x) < 5 && Math.abs(appleObjects[i].y - apples[a].y) < 5) { found = true; break; } } if (!found) { appleObjects[i].destroy(); if (appleObjects[i].lifeText) { appleObjects[i].lifeText.destroy(); } appleObjects.splice(i, 1); } } // Update life text for existing apples for (var i = 0; i < appleObjects.length; i++) { for (var a = 0; a < apples.length; a++) { if (Math.abs(appleObjects[i].x - apples[a].x) < 5 && Math.abs(appleObjects[i].y - apples[a].y) < 5) { if (appleObjects[i].lifeText) { appleObjects[i].lifeText.setText(apples[a].life.toString()); } break; } } } } ; ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x404040
});
/****
* Game Code
****/
// Game configuration
var segmentLength = 40,
startingSegments = 10,
spawn = {
x: 1024,
y: 1366
},
snakeSpeed = 500,
baseSnakeSpeed = 4,
speedIncrement = 500,
maxApples = 5,
appleLife = 300,
segmentsPerApple = 2,
snakeWidth = 30,
appleWidth = 50,
cursorSize = 20,
snake,
target,
apples,
score,
highScore,
gameState,
deathMeans,
smoothMovement = true,
movementQueue = [],
playerNickname = '',
rankings = [],
showRankings = false,
rankingButton = null,
rankingPanel = null,
nicknameInput = false,
gameStarted = false;
// Initialize high score and rankings from storage
highScore = storage.highScore || 0;
var storedRankings = storage.rankings || [];
rankings = [];
// Handle fallback storage format if main array failed
if (storedRankings.length === 0 && storage.rankingLength) {
storedRankings = [];
for (var k = 0; k < storage.rankingLength; k++) {
storedRankings.push(storage['ranking_' + k]);
}
}
// Convert from simple array storage format back to objects
for (var i = 0; i < storedRankings.length; i += 2) {
var nickname = storedRankings[i];
var score = storedRankings[i + 1];
if (nickname && score !== undefined) {
rankings.push({
nickname: String(nickname),
score: Number(score)
});
}
}
function distance(p1, p2) {
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
function lineIntersect(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) {
var s1_x = p1_x - p0_x,
s1_y = p1_y - p0_y,
s2_x = p3_x - p2_x,
s2_y = p3_y - p2_y,
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y),
t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
return true;
}
return false;
}
function SGM(angle, x, y) {
this.x = x || 0;
this.y = y || 0;
this.angle = angle || 0;
this.parent = null;
}
;
SGM.prototype.endX = function () {
return this.x + Math.cos(this.angle) * segmentLength;
};
SGM.prototype.endY = function () {
return this.y + Math.sin(this.angle) * segmentLength;
};
SGM.prototype.pointAt = function (x, y) {
var dx = x - this.x,
dy = y - this.y;
this.angle = Math.atan2(dy, dx);
};
SGM.prototype.target = function (x, y) {
this.targetX = x;
this.targetY = y;
this.arrived = false;
this.totalDist = distance({
x: this.endX(),
y: this.endY()
}, {
x: this.targetX,
y: this.targetY
});
this.currentDist = parseInt(this.totalDist);
};
SGM.prototype.gotoTarget = function () {
if (!this.arrived) {
if (this.targetX > this.x + segmentLength || this.targetX < this.x - segmentLength || this.targetY > this.y + segmentLength || this.targetY < this.y - segmentLength) {
this.pointAt(this.targetX, this.targetY);
} else {
this.arrived = true;
}
this.currentDist = distance({
x: this.endX(),
y: this.endY()
}, {
x: this.targetX,
y: this.targetY
});
}
this.x += (this.endX() - this.x) / (snakeSpeed * 0.5);
this.y += (this.endY() - this.y) / (snakeSpeed * 0.5);
if (this.parent) {
this.parent.drag(this.x, this.y);
}
};
SGM.prototype.drag = function (x, y) {
this.pointAt(x, y);
var newX = x - Math.cos(this.angle) * segmentLength;
var newY = y - Math.sin(this.angle) * segmentLength;
this.x = newX;
this.y = newY;
if (this.parent) {
this.parent.drag(this.x, this.y);
}
};
SGM.prototype.render = function (context) {
context.lineTo(this.endX(), this.endY());
};
function IKR(x, y) {
this.ix = x || 0;
this.iy = y || 0;
this.sgms = [];
this.lastArm = null;
}
;
IKR.prototype.addSeg = function (angle) {
var arm = new SGM(angle);
if (this.lastArm !== null) {
arm.x = this.lastArm.endX();
arm.y = this.lastArm.endY();
arm.parent = this.lastArm;
} else {
arm.x = this.ix;
arm.y = this.iy;
}
this.sgms.push(arm);
this.lastArm = arm;
};
IKR.prototype.grow = function () {
var tail = this.sgms[0],
arm = new SGM(tail.angle);
arm.x = tail.x - Math.cos(tail.angle) * segmentLength;
arm.y = tail.y - Math.sin(tail.angle) * segmentLength;
tail.parent = arm;
this.sgms.unshift(arm);
};
IKR.prototype.drag = function (x, y) {
this.lastArm.drag(x, y);
};
function CUR(x, y) {
this.x = x;
this.y = y;
this.rotation = 0;
}
;
CUR.prototype.render = function (context) {
context.save();
context.translate(this.x, this.y);
context.rotate(this.rotation);
context.beginPath();
context.moveTo(0, -cursorSize);
context.lineTo(0, -cursorSize / 2);
context.moveTo(0, cursorSize / 2);
context.lineTo(0, cursorSize);
context.moveTo(-cursorSize, 0);
context.lineTo(-cursorSize / 2, 0);
context.moveTo(cursorSize / 2, 0);
context.lineTo(cursorSize, 0);
context.stroke();
context.restore();
this.rotation = (this.rotation + cursorSpin) % 360;
};
function Apple(x, y) {
this.x = x;
this.y = y;
this.life = appleLife;
this.rotation = 0;
}
Apple.prototype.update = function () {
this.life--;
};
Apple.prototype.render = function (context) {
context.beginPath();
context.arc(this.x, this.y, appleWidth, 0, Math.PI * 2);
context.fill();
if (gameState !== 'dead') {
context.save();
context.fillStyle = 'white';
context.font = '8px sans-serif';
context.fillText(this.life, this.x + 10, this.y + 10);
context.restore();
CUR.prototype.render.call(this, context);
}
};
function promptNickname() {
if (!playerNickname) {
// Set default nickname automatically
playerNickname = 'Player' + Math.floor(Math.random() * 1000);
}
}
function showRankingPanel() {
if (rankingPanel) {
rankingPanel.destroy();
}
showRankings = !showRankings;
if (showRankings) {
// Create ranking panel
rankingPanel = new Container();
var panelBg = LK.getAsset('snakeSegment', {
width: 600,
height: 800,
color: 0x222222,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
panelBg.x = 1024;
panelBg.y = 1366;
rankingPanel.addChild(panelBg);
var titleText = new Text2('Rankings', {
size: 60,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 1000;
rankingPanel.addChild(titleText);
// Sort rankings by score
var sortedRankings = rankings.slice().sort(function (a, b) {
return b.score - a.score;
});
for (var i = 0; i < Math.min(sortedRankings.length, 10); i++) {
var rankText = new Text2(i + 1 + '. ' + sortedRankings[i].nickname + ': ' + sortedRankings[i].score, {
size: 35,
fill: sortedRankings[i].nickname === playerNickname ? 0xFFFF00 : 0xFFFFFF
});
rankText.anchor.set(0.5, 0.5);
rankText.x = 1024;
rankText.y = 1100 + i * 50;
rankingPanel.addChild(rankText);
}
var closeButton = LK.getAsset('snakeSegment', {
width: 150,
height: 50,
color: 0xff4444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
closeButton.x = 1024;
closeButton.y = 1700;
rankingPanel.addChild(closeButton);
var closeText = new Text2('Close', {
size: 30,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeText.x = 1024;
closeText.y = 1700;
rankingPanel.addChild(closeText);
game.addChild(rankingPanel);
}
}
function updateRankings(nickname, score) {
// Find existing player or add new one
var found = false;
for (var i = 0; i < rankings.length; i++) {
if (rankings[i].nickname === nickname) {
if (score > rankings[i].score) {
rankings[i].score = score;
}
found = true;
break;
}
}
if (!found) {
rankings.push({
nickname: nickname,
score: score
});
}
// Keep only top 50 scores
rankings.sort(function (a, b) {
return b.score - a.score;
});
if (rankings.length > 50) {
rankings = rankings.slice(0, 50);
}
// Convert rankings to storage-compatible format (simple array)
var storageRankings = [];
for (var i = 0; i < rankings.length; i++) {
storageRankings.push(String(rankings[i].nickname));
storageRankings.push(Number(rankings[i].score));
}
try {
storage.rankings = storageRankings;
} catch (e) {
console.log('Storage error:', e);
// Fallback: store each value separately if array fails
for (var j = 0; j < storageRankings.length; j++) {
storage['ranking_' + j] = storageRankings[j];
}
storage.rankingLength = storageRankings.length;
}
}
function init() {
snake = new IKR(spawn.x, spawn.y);
cursor = new CUR(-20, -20);
target = new CUR(spawn.x + segmentLength * (startingSegments + 5), spawn.y);
apples = [];
score = 0;
snakeSpeed = baseSnakeSpeed; // Reset speed to base value
for (var i = 0; i < startingSegments; i++) {
snake.addSeg();
}
snake.lastArm.target(target.x, target.y);
promptNickname(); // Set default nickname
gameStarted = true;
gameState = 'play';
}
init();
// Touch events handled by game object
game.down = function (x, y, obj) {
// Check if ranking button was clicked
if (rankingButton && x >= 1890 && x <= 2010 && y >= 70 && y <= 130) {
showRankingPanel();
return;
}
// Check if ranking panel close button was clicked
if (showRankings && x >= 949 && x <= 1099 && y >= 1675 && y <= 1725) {
showRankings = false;
if (rankingPanel) {
rankingPanel.destroy();
rankingPanel = null;
}
return;
}
switch (gameState) {
case 'play':
if (!showRankings) {
target.x = x;
target.y = y;
snake.lastArm.target(target.x, target.y);
}
break;
case 'dead':
if (!showRankings) {
init();
}
break;
}
};
function badPlacement(apple) {
for (var s = 0; s < snake.sgms.length; s++) {
var seg = snake.sgms[s];
if (Math.min(distance(apple, {
x: seg.endX(),
y: seg.endY()
}), distance(apple, {
x: seg.x,
y: seg.y
})) < appleWidth * 2) {
return true;
}
}
return false;
}
function addScoreSegments() {
for (var i = 0; i < segmentsPerApple; i++) {
snake.grow();
}
// Keep snake speed constant - reset to base speed
snakeSpeed = baseSnakeSpeed;
}
function update() {
if (gameState !== 'dead') {
snake.lastArm.gotoTarget();
if (snake.lastArm.endX() > 2048 - 50 || snake.lastArm.endX() < 50 || snake.lastArm.endY() > 2732 - 50 || snake.lastArm.endY() < 50) {
gameState = 'dead';
deathMeans = 'Hit the wall!';
// Update high score if current score is higher
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
// Update rankings
updateRankings(playerNickname, score);
LK.showGameOver();
return;
}
for (var s = 0; s < snake.sgms.length - 2; s++) {
var seg = snake.sgms[s];
if (lineIntersect(snake.lastArm.x, snake.lastArm.y, snake.lastArm.endX(), snake.lastArm.endY(), seg.x, seg.y, seg.endX(), seg.endY())) {
gameState = 'dead';
deathMeans = 'You bit yourself';
// Update high score if current score is higher
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
// Update rankings
updateRankings(playerNickname, score);
LK.showGameOver();
return;
}
for (var a in apples) {
var apple = apples[a];
if (Math.min(distance(apple, {
x: seg.endX(),
y: seg.endY()
}), distance(apple, {
x: seg.x,
y: seg.y
})) < appleWidth * 2) {
score += Math.round(apple.life / 2); // half score if absorbed by the tail
apples.splice(a, 1);
addScoreSegments();
}
}
}
for (var a in apples) {
var apple = apples[a];
apple.update();
if (apple.life <= 0) {
apples.splice(a, 1);
continue;
}
if (distance(apple, {
x: snake.lastArm.endX(),
y: snake.lastArm.endY()
}) < appleWidth * 2) {
score += apple.life;
apples.splice(a, 1);
addScoreSegments();
}
}
if (apples.length < maxApples && Math.random() < 0.02) {
var offset = appleWidth * 4,
apple = new Apple(offset + Math.floor(Math.random() * (2048 - offset * 2)), offset + Math.floor(Math.random() * (2732 - offset * 2)));
while (badPlacement(apple)) {
apple.x = offset + Math.floor(Math.random() * (2048 - offset * 2));
apple.y = offset + Math.floor(Math.random() * (2732 - offset * 2));
}
apples.push(apple);
// Create visual apple
var appleVisual = LK.getAsset('apple', {
width: appleWidth * 2,
height: appleWidth * 2,
color: 0x00ff00,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
appleVisual.x = apple.x;
appleVisual.y = apple.y;
game.addChild(appleVisual);
// Create life text under apple
var lifeText = new Text2(apple.life.toString(), {
size: 40,
fill: 0xFFFFFF
});
lifeText.anchor.set(0.5, 0);
lifeText.x = apple.x;
lifeText.y = apple.y + appleWidth;
game.addChild(lifeText);
appleVisual.lifeText = lifeText;
appleObjects.push(appleVisual);
}
}
}
// LK rendering system - visual elements handled by game objects
var snakeSegments = [];
var appleObjects = [];
var targetObject = null;
var scoreText = null;
// Initialize visual elements
function initVisuals() {
// Clear existing visuals
for (var i = 0; i < snakeSegments.length; i++) {
snakeSegments[i].destroy();
}
for (var i = 0; i < appleObjects.length; i++) {
appleObjects[i].destroy();
}
if (targetObject) {
targetObject.destroy();
}
if (scoreText) {
scoreText.destroy();
}
if (rankingButton) {
rankingButton.destroy();
}
if (rankingPanel) {
rankingPanel.destroy();
}
snakeSegments = [];
appleObjects = [];
// Create score text
scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topLeft.addChild(scoreText);
scoreText.x = 120;
scoreText.y = 20;
// Create high score text
var highScoreText = new Text2('High Score: ' + highScore, {
size: 40,
fill: 0xFFFFFF
});
highScoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(highScoreText);
highScoreText.x = 0;
highScoreText.y = 20;
// Create ranking button on right side
rankingButton = LK.getAsset('snakeSegment', {
width: 120,
height: 60,
color: 0x4444ff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
rankingButton.x = 1950;
rankingButton.y = 100;
LK.gui.addChild(rankingButton);
var rankingButtonText = new Text2('Rankings', {
size: 30,
fill: 0xFFFFFF
});
rankingButtonText.anchor.set(0.5, 0.5);
rankingButtonText.x = 1950;
rankingButtonText.y = 100;
LK.gui.addChild(rankingButtonText);
// Create target indicator
targetObject = LK.getAsset('target', {
width: 30,
height: 30,
color: 0x0000ff,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(targetObject);
}
// Initialize the game
init();
initVisuals();
// LK game update loop
game.update = function () {
if (gameState === 'play') {
update();
// Update score display
if (scoreText) {
scoreText.setText('Score: ' + score);
}
// Update high score display
var highScoreDisplay = LK.gui.top.children[0];
if (highScoreDisplay) {
highScoreDisplay.setText('High Score: ' + highScore);
}
// Update target position
if (targetObject && !snake.lastArm.arrived) {
targetObject.x = target.x;
targetObject.y = target.y;
targetObject.visible = true;
} else if (targetObject) {
targetObject.visible = false;
}
// Update snake visual segments
updateSnakeVisuals();
// Update apple visuals
updateAppleVisuals();
}
};
function updateSnakeVisuals() {
// Only update positions if we have existing segments, otherwise recreate
if (snakeSegments.length !== snake.sgms.length + 1) {
// Clear existing snake segments
for (var i = 0; i < snakeSegments.length; i++) {
snakeSegments[i].destroy();
}
snakeSegments = [];
// Create new snake segments
for (var s = 0; s < snake.sgms.length; s++) {
var seg = snake.sgms[s];
var segmentVisual = LK.getAsset('snakeSegment', {
width: snakeWidth,
height: segmentLength,
color: 0x64ff64,
shape: 'box',
anchorX: 0.5,
anchorY: 0
});
segmentVisual.x = seg.x;
segmentVisual.y = seg.y;
segmentVisual.rotation = seg.angle + Math.PI / 2;
game.addChild(segmentVisual);
snakeSegments.push(segmentVisual);
}
// Create snake head with proper sizing
var head = LK.getAsset('snakeHead', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
head.x = snake.lastArm.endX();
head.y = snake.lastArm.endY();
game.addChild(head);
snakeSegments.push(head);
} else {
// Update all segments as one unified body with synchronized movement
var targetPositions = [];
var targetRotations = [];
// Calculate all target positions first
for (var s = 0; s < snake.sgms.length; s++) {
var seg = snake.sgms[s];
targetPositions[s] = {
x: seg.x,
y: seg.y
};
targetRotations[s] = seg.angle + Math.PI / 2;
}
targetPositions[snake.sgms.length] = {
x: snake.lastArm.endX(),
y: snake.lastArm.endY()
};
// Apply unified movement to all segments simultaneously with improved smoothness
for (var s = 0; s < snakeSegments.length; s++) {
var segmentVisual = snakeSegments[s];
var targetPos = targetPositions[s];
var targetRot = targetRotations[s] || segmentVisual.rotation;
// Stop any existing tweens to prevent conflicts
tween.stop(segmentVisual);
// Unified smooth movement for entire snake body with optimized parameters
tween(segmentVisual, {
x: targetPos.x,
y: targetPos.y,
rotation: targetRot
}, {
duration: 100,
easing: tween.easeOut
});
}
}
}
function updateAppleVisuals() {
// Remove apples that no longer exist
for (var i = appleObjects.length - 1; i >= 0; i--) {
var found = false;
for (var a = 0; a < apples.length; a++) {
if (Math.abs(appleObjects[i].x - apples[a].x) < 5 && Math.abs(appleObjects[i].y - apples[a].y) < 5) {
found = true;
break;
}
}
if (!found) {
appleObjects[i].destroy();
if (appleObjects[i].lifeText) {
appleObjects[i].lifeText.destroy();
}
appleObjects.splice(i, 1);
}
}
// Update life text for existing apples
for (var i = 0; i < appleObjects.length; i++) {
for (var a = 0; a < apples.length; a++) {
if (Math.abs(appleObjects[i].x - apples[a].x) < 5 && Math.abs(appleObjects[i].y - apples[a].y) < 5) {
if (appleObjects[i].lifeText) {
appleObjects[i].lifeText.setText(apples[a].life.toString());
}
break;
}
}
}
}
;
;