/****
* 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();