/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Food class var Food = Container.expand(function () { var self = Container.call(this); // Food is a red ellipse var foodAsset = self.attachAsset('food', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // Snake Segment class var SnakeSegment = Container.expand(function () { var self = Container.call(this); // Each segment is a green box var segmentAsset = self.attachAsset('snakeSegment', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Snake Head class // --- Asset Initialization --- // We'll use tween for smooth snake movement and food spawn animation // --- Game Constants --- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var GRID_SIZE = 80; // Each cell is 80x80 px var GRID_COLS = Math.floor(2048 / GRID_SIZE); var GRID_ROWS = Math.floor(2732 / GRID_SIZE); var START_LENGTH = 4; var START_SPEED = 10; // Lower is faster (ticks per move) var MIN_SPEED = 3; // Fastest speed var SPEEDUP_EVERY = 5; // Speed up every 5 food eaten var SAFE_MARGIN = 2; // Margin from wall for food spawn // --- Game State --- var snake = []; var snakeDir = { x: 1, y: 0 }; // Start moving right var nextDir = { x: 1, y: 0 }; var food = null; var moveTick = 0; var moveInterval = START_SPEED; var score = 0; var alive = true; var pendingGrowth = 0; // --- UI --- var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Helper Functions --- function gridToPos(col, row) { return { x: col * GRID_SIZE + GRID_SIZE / 2, y: row * GRID_SIZE + GRID_SIZE / 2 }; } function posToGrid(x, y) { return { col: Math.floor(x / GRID_SIZE), row: Math.floor(y / GRID_SIZE) }; } function randomFreeCell() { // Avoid spawning food on the snake var occupied = {}; for (var i = 0; i < snake.length; i++) { var seg = snake[i]; var g = posToGrid(seg.x, seg.y); occupied[g.col + ',' + g.row] = true; } var tries = 0; while (tries < 1000) { var col = SAFE_MARGIN + Math.floor(Math.random() * (GRID_COLS - SAFE_MARGIN * 2)); var row = SAFE_MARGIN + Math.floor(Math.random() * (GRID_ROWS - SAFE_MARGIN * 2)); if (!occupied[col + ',' + row]) { return { col: col, row: row }; } tries++; } // Fallback: just pick center return { col: Math.floor(GRID_COLS / 2), row: Math.floor(GRID_ROWS / 2) }; } function resetGame() { // Remove old snake for (var i = 0; i < snake.length; i++) { snake[i].destroy(); } snake = []; // Remove food if (food) { food.destroy(); food = null; } // Reset state snakeDir = { x: 1, y: 0 }; nextDir = { x: 1, y: 0 }; moveTick = 0; moveInterval = START_SPEED; score = 0; LK.setScore(0); scoreTxt.setText('0'); alive = true; pendingGrowth = 0; // Place snake in center, horizontal var startCol = Math.floor(GRID_COLS / 2) - Math.floor(START_LENGTH / 2); var startRow = Math.floor(GRID_ROWS / 2); for (var i = 0; i < START_LENGTH; i++) { var seg = new SnakeSegment(); var pos = gridToPos(startCol + i, startRow); seg.x = pos.x; seg.y = pos.y; snake.push(seg); game.addChild(seg); } spawnFood(); } function spawnFood() { if (food) { food.destroy(); } var cell = randomFreeCell(); food = new Food(); var pos = gridToPos(cell.col, cell.row); food.x = pos.x; food.y = pos.y; food.scale.set(0.1, 0.1); game.addChild(food); // Animate food pop-in tween(food.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.elasticOut }); } function isCollision(x, y, skipTail) { // Check wall if (x < GRID_SIZE / 2 || x > 2048 - GRID_SIZE / 2 || y < GRID_SIZE / 2 || y > 2732 - GRID_SIZE / 2) { return true; } // Check self for (var i = 0; i < snake.length - (skipTail ? 1 : 0); i++) { if (Math.abs(snake[i].x - x) < 1 && Math.abs(snake[i].y - y) < 1) { return true; } } return false; } function tryChangeDir(dx, dy) { // Prevent reversing if (snakeDir.x === -dx && snakeDir.y === -dy) return; nextDir = { x: dx, y: dy }; } // --- Touch Controls --- // Track last pointer position for snake to follow var lastPointerPos = null; game.down = function (x, y, obj) { lastPointerPos = { x: x, y: y }; }; game.up = function (x, y, obj) { lastPointerPos = { x: x, y: y }; }; game.move = function (x, y, obj) { lastPointerPos = { x: x, y: y }; }; // --- Main Game Loop --- game.update = function () { if (!alive) return; moveTick++; if (moveTick < moveInterval) return; moveTick = 0; // Move snake // --- AUTO MOVE TOWARDS MOUSE/TAP POSITION --- var head = snake[snake.length - 1]; var headGrid = posToGrid(head.x, head.y); var targetPos = null; // Use last known mouse/touch position, or default to food if not available if (_typeof(lastPointerPos) === "object" && lastPointerPos !== null) { // Clamp pointer to grid var pointerGrid = posToGrid(lastPointerPos.x, lastPointerPos.y); targetPos = { col: pointerGrid.col, row: pointerGrid.row }; } else if (food) { var foodGrid = posToGrid(food.x, food.y); targetPos = { col: foodGrid.col, row: foodGrid.row }; } else { targetPos = { col: headGrid.col, row: headGrid.row }; } var dx = targetPos.col - headGrid.col; var dy = targetPos.row - headGrid.row; // Prefer horizontal movement if not aligned, and prevent reversing if (Math.abs(dx) > Math.abs(dy) && dx !== 0 && !(snakeDir.x === -Math.sign(dx) && snakeDir.y === 0)) { snakeDir = { x: Math.sign(dx), y: 0 }; nextDir = { x: Math.sign(dx), y: 0 }; } else if (dy !== 0 && !(snakeDir.x === 0 && snakeDir.y === -Math.sign(dy))) { snakeDir = { x: 0, y: Math.sign(dy) }; nextDir = { x: 0, y: Math.sign(dy) }; } var head = snake[snake.length - 1]; var headGrid = posToGrid(head.x, head.y); var newCol = headGrid.col + snakeDir.x; var newRow = headGrid.row + snakeDir.y; var newPos = gridToPos(newCol, newRow); // Check collision (skip tail if growing, as tail will move away) var skipTail = pendingGrowth > 0; if (isCollision(newPos.x, newPos.y, skipTail)) { alive = false; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } // Move segments var prevX = head.x; var prevY = head.y; for (var i = 0; i < snake.length - 1; i++) { var seg = snake[i]; var nextSeg = snake[i + 1]; seg.x = nextSeg.x; seg.y = nextSeg.y; } head.x = newPos.x; head.y = newPos.y; // Grow if needed if (pendingGrowth > 0) { var newSeg = new SnakeSegment(); newSeg.x = prevX; newSeg.y = prevY; snake.unshift(newSeg); game.addChild(newSeg); pendingGrowth--; } // Check food collision if (food && Math.abs(head.x - food.x) < 1 && Math.abs(head.y - food.y) < 1) { pendingGrowth++; score++; LK.setScore(score); scoreTxt.setText(score + ''); spawnFood(); // Speed up if (score % SPEEDUP_EVERY === 0 && moveInterval > MIN_SPEED) { moveInterval--; LK.effects.flashObject(head, 0xffff00, 200); } // Win condition (optional, e.g. 100 points) if (score >= 100) { LK.showYouWin(); alive = false; return; } } }; // --- Start Game --- resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Food class
var Food = Container.expand(function () {
var self = Container.call(this);
// Food is a red ellipse
var foodAsset = self.attachAsset('food', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Snake Segment class
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
// Each segment is a green box
var segmentAsset = self.attachAsset('snakeSegment', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Snake Head class
// --- Asset Initialization ---
// We'll use tween for smooth snake movement and food spawn animation
// --- Game Constants ---
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var GRID_SIZE = 80; // Each cell is 80x80 px
var GRID_COLS = Math.floor(2048 / GRID_SIZE);
var GRID_ROWS = Math.floor(2732 / GRID_SIZE);
var START_LENGTH = 4;
var START_SPEED = 10; // Lower is faster (ticks per move)
var MIN_SPEED = 3; // Fastest speed
var SPEEDUP_EVERY = 5; // Speed up every 5 food eaten
var SAFE_MARGIN = 2; // Margin from wall for food spawn
// --- Game State ---
var snake = [];
var snakeDir = {
x: 1,
y: 0
}; // Start moving right
var nextDir = {
x: 1,
y: 0
};
var food = null;
var moveTick = 0;
var moveInterval = START_SPEED;
var score = 0;
var alive = true;
var pendingGrowth = 0;
// --- UI ---
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper Functions ---
function gridToPos(col, row) {
return {
x: col * GRID_SIZE + GRID_SIZE / 2,
y: row * GRID_SIZE + GRID_SIZE / 2
};
}
function posToGrid(x, y) {
return {
col: Math.floor(x / GRID_SIZE),
row: Math.floor(y / GRID_SIZE)
};
}
function randomFreeCell() {
// Avoid spawning food on the snake
var occupied = {};
for (var i = 0; i < snake.length; i++) {
var seg = snake[i];
var g = posToGrid(seg.x, seg.y);
occupied[g.col + ',' + g.row] = true;
}
var tries = 0;
while (tries < 1000) {
var col = SAFE_MARGIN + Math.floor(Math.random() * (GRID_COLS - SAFE_MARGIN * 2));
var row = SAFE_MARGIN + Math.floor(Math.random() * (GRID_ROWS - SAFE_MARGIN * 2));
if (!occupied[col + ',' + row]) {
return {
col: col,
row: row
};
}
tries++;
}
// Fallback: just pick center
return {
col: Math.floor(GRID_COLS / 2),
row: Math.floor(GRID_ROWS / 2)
};
}
function resetGame() {
// Remove old snake
for (var i = 0; i < snake.length; i++) {
snake[i].destroy();
}
snake = [];
// Remove food
if (food) {
food.destroy();
food = null;
}
// Reset state
snakeDir = {
x: 1,
y: 0
};
nextDir = {
x: 1,
y: 0
};
moveTick = 0;
moveInterval = START_SPEED;
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
alive = true;
pendingGrowth = 0;
// Place snake in center, horizontal
var startCol = Math.floor(GRID_COLS / 2) - Math.floor(START_LENGTH / 2);
var startRow = Math.floor(GRID_ROWS / 2);
for (var i = 0; i < START_LENGTH; i++) {
var seg = new SnakeSegment();
var pos = gridToPos(startCol + i, startRow);
seg.x = pos.x;
seg.y = pos.y;
snake.push(seg);
game.addChild(seg);
}
spawnFood();
}
function spawnFood() {
if (food) {
food.destroy();
}
var cell = randomFreeCell();
food = new Food();
var pos = gridToPos(cell.col, cell.row);
food.x = pos.x;
food.y = pos.y;
food.scale.set(0.1, 0.1);
game.addChild(food);
// Animate food pop-in
tween(food.scale, {
x: 1,
y: 1
}, {
duration: 200,
easing: tween.elasticOut
});
}
function isCollision(x, y, skipTail) {
// Check wall
if (x < GRID_SIZE / 2 || x > 2048 - GRID_SIZE / 2 || y < GRID_SIZE / 2 || y > 2732 - GRID_SIZE / 2) {
return true;
}
// Check self
for (var i = 0; i < snake.length - (skipTail ? 1 : 0); i++) {
if (Math.abs(snake[i].x - x) < 1 && Math.abs(snake[i].y - y) < 1) {
return true;
}
}
return false;
}
function tryChangeDir(dx, dy) {
// Prevent reversing
if (snakeDir.x === -dx && snakeDir.y === -dy) return;
nextDir = {
x: dx,
y: dy
};
}
// --- Touch Controls ---
// Track last pointer position for snake to follow
var lastPointerPos = null;
game.down = function (x, y, obj) {
lastPointerPos = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
lastPointerPos = {
x: x,
y: y
};
};
game.move = function (x, y, obj) {
lastPointerPos = {
x: x,
y: y
};
};
// --- Main Game Loop ---
game.update = function () {
if (!alive) return;
moveTick++;
if (moveTick < moveInterval) return;
moveTick = 0;
// Move snake
// --- AUTO MOVE TOWARDS MOUSE/TAP POSITION ---
var head = snake[snake.length - 1];
var headGrid = posToGrid(head.x, head.y);
var targetPos = null;
// Use last known mouse/touch position, or default to food if not available
if (_typeof(lastPointerPos) === "object" && lastPointerPos !== null) {
// Clamp pointer to grid
var pointerGrid = posToGrid(lastPointerPos.x, lastPointerPos.y);
targetPos = {
col: pointerGrid.col,
row: pointerGrid.row
};
} else if (food) {
var foodGrid = posToGrid(food.x, food.y);
targetPos = {
col: foodGrid.col,
row: foodGrid.row
};
} else {
targetPos = {
col: headGrid.col,
row: headGrid.row
};
}
var dx = targetPos.col - headGrid.col;
var dy = targetPos.row - headGrid.row;
// Prefer horizontal movement if not aligned, and prevent reversing
if (Math.abs(dx) > Math.abs(dy) && dx !== 0 && !(snakeDir.x === -Math.sign(dx) && snakeDir.y === 0)) {
snakeDir = {
x: Math.sign(dx),
y: 0
};
nextDir = {
x: Math.sign(dx),
y: 0
};
} else if (dy !== 0 && !(snakeDir.x === 0 && snakeDir.y === -Math.sign(dy))) {
snakeDir = {
x: 0,
y: Math.sign(dy)
};
nextDir = {
x: 0,
y: Math.sign(dy)
};
}
var head = snake[snake.length - 1];
var headGrid = posToGrid(head.x, head.y);
var newCol = headGrid.col + snakeDir.x;
var newRow = headGrid.row + snakeDir.y;
var newPos = gridToPos(newCol, newRow);
// Check collision (skip tail if growing, as tail will move away)
var skipTail = pendingGrowth > 0;
if (isCollision(newPos.x, newPos.y, skipTail)) {
alive = false;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
// Move segments
var prevX = head.x;
var prevY = head.y;
for (var i = 0; i < snake.length - 1; i++) {
var seg = snake[i];
var nextSeg = snake[i + 1];
seg.x = nextSeg.x;
seg.y = nextSeg.y;
}
head.x = newPos.x;
head.y = newPos.y;
// Grow if needed
if (pendingGrowth > 0) {
var newSeg = new SnakeSegment();
newSeg.x = prevX;
newSeg.y = prevY;
snake.unshift(newSeg);
game.addChild(newSeg);
pendingGrowth--;
}
// Check food collision
if (food && Math.abs(head.x - food.x) < 1 && Math.abs(head.y - food.y) < 1) {
pendingGrowth++;
score++;
LK.setScore(score);
scoreTxt.setText(score + '');
spawnFood();
// Speed up
if (score % SPEEDUP_EVERY === 0 && moveInterval > MIN_SPEED) {
moveInterval--;
LK.effects.flashObject(head, 0xffff00, 200);
}
// Win condition (optional, e.g. 100 points)
if (score >= 100) {
LK.showYouWin();
alive = false;
return;
}
}
};
// --- Start Game ---
resetGame();