/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Food = Container.expand(function (foodType) {
var self = Container.call(this);
self.foodType = foodType || 'apple';
var assetName = self.foodType;
var foodGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.timeLeft = 0;
self.maxTime = 0;
self.originalScale = 1.0;
self.update = function () {
if (self.timeLeft > 0) {
self.timeLeft--;
// Pulse effect when time is running low
if (self.timeLeft < 120) {
// Last 2 seconds at 60fps
var pulseScale = 0.8 + 0.4 * Math.sin(self.timeLeft * 0.3);
foodGraphics.scaleX = self.originalScale * pulseScale;
foodGraphics.scaleY = self.originalScale * pulseScale;
}
if (self.timeLeft <= 0) {
// Food expired, spawn new one
if (food === self) {
spawnFood();
}
}
}
};
return self;
});
var SnakeSegment = Container.expand(function (isHead) {
var self = Container.call(this);
var segmentGraphics = self.attachAsset(isHead ? 'snakeHead' : 'snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.isHead = isHead;
self.segmentGraphics = segmentGraphics;
self.setDirection = function (direction) {
if (self.isHead && self.segmentGraphics) {
switch (direction) {
case 'up':
self.segmentGraphics.rotation = -Math.PI / 2;
break;
case 'down':
self.segmentGraphics.rotation = Math.PI / 2;
break;
case 'left':
self.segmentGraphics.rotation = Math.PI;
break;
case 'right':
self.segmentGraphics.rotation = 0;
break;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E7D32
});
/****
* Game Code
****/
// Import tween plugin for smooth speed transitions
// Game constants
var GRID_SIZE = 90;
var GRID_WIDTH = Math.floor(2048 / GRID_SIZE);
var GRID_HEIGHT = Math.floor(2732 / GRID_SIZE);
var GAME_AREA_WIDTH = GRID_WIDTH * GRID_SIZE;
var GAME_AREA_HEIGHT = GRID_HEIGHT * GRID_SIZE;
var OFFSET_X = (2048 - GAME_AREA_WIDTH) / 2;
var OFFSET_Y = (2732 - GAME_AREA_HEIGHT) / 2 + 100;
// Speed constants
var INITIAL_MOVE_INTERVAL = 12; // Start slower for better control
var MIN_MOVE_INTERVAL = 4; // Maximum speed cap (fastest the snake can go)
var SPEED_INCREASE_FACTOR = 0.95; // Each food makes snake 5% faster (multiply interval by 0.95)
// Game variables
var snake = [];
var currentDirection = 'right';
var nextDirection = 'right';
var food = null;
var gameRunning = true;
var moveTimer = 0;
var MOVE_INTERVAL = INITIAL_MOVE_INTERVAL; // Current move interval (starts at initial speed)
var currentMoveInterval = {
value: INITIAL_MOVE_INTERVAL
}; // Object for tweening
var gameMode = 'classic'; // 'classic' or 'wrap'
var modeSelected = false;
// Food type definitions
var FOOD_TYPES = {
apple: {
points: 1,
probability: 0.7,
duration: 720
},
// 12 seconds at 60fps
grape: {
points: 2,
probability: 0.25,
duration: 720
},
// 12 seconds at 60fps
strawberry: {
points: 3,
probability: 0.05,
duration: 720
} // 12 seconds at 60fps
};
// Touch control variables
var touchStartX = 0;
var touchStartY = 0;
var MIN_SWIPE_DISTANCE = 50;
// Initialize score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize mode display
var modeTxt = new Text2('Mode: Classic', {
size: 50,
fill: 0xFFFFFF
});
modeTxt.anchor.set(1, 0);
modeTxt.x = -20; // Offset from right edge
modeTxt.y = 20; // Small offset from top
LK.gui.topRight.addChild(modeTxt);
// Convert grid coordinates to screen coordinates
function gridToScreen(gridX, gridY) {
return {
x: OFFSET_X + gridX * GRID_SIZE + GRID_SIZE / 2,
y: OFFSET_Y + gridY * GRID_SIZE + GRID_SIZE / 2
};
}
// Create mode selection UI
function createModeSelection() {
// Classic mode button
var classicButton = LK.getAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 2732 / 2 - 100,
scaleX: 3,
scaleY: 2
});
game.addChild(classicButton);
var classicText = new Text2('CLASSIC', {
size: 60,
fill: 0xFFFFFF
});
classicText.anchor.set(0.5, 0.5);
classicText.x = 2048 / 2 - 200;
classicText.y = 2732 / 2 - 100;
game.addChild(classicText);
// Wrap mode button
var wrapButton = LK.getAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 200,
y: 2732 / 2 - 100,
scaleX: 3,
scaleY: 2
});
game.addChild(wrapButton);
var wrapText = new Text2('WRAP', {
size: 60,
fill: 0xFFFFFF
});
wrapText.anchor.set(0.5, 0.5);
wrapText.x = 2048 / 2 + 200;
wrapText.y = 2732 / 2 - 100;
game.addChild(wrapText);
// Instructions
var instructionText = new Text2('Choose Wall Mode:', {
size: 80,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 - 300;
game.addChild(instructionText);
// Store references for removal
game.modeSelectionElements = [classicButton, classicText, wrapButton, wrapText, instructionText];
// Handle mode selection
classicButton.interactive = true;
classicText.interactive = true;
wrapButton.interactive = true;
wrapText.interactive = true;
function selectMode(mode) {
gameMode = mode;
modeSelected = true;
modeTxt.setText('Mode: ' + (mode === 'classic' ? 'Classic' : 'Wrap'));
// Remove mode selection UI
for (var i = 0; i < game.modeSelectionElements.length; i++) {
game.modeSelectionElements[i].destroy();
}
game.modeSelectionElements = [];
// Start the game immediately
gameRunning = true;
initSnake();
spawnFood();
}
classicButton.down = function () {
selectMode('classic');
};
classicText.down = function () {
selectMode('classic');
};
wrapButton.down = function () {
selectMode('wrap');
};
wrapText.down = function () {
selectMode('wrap');
};
}
// Initialize snake
function initSnake() {
// Reset speed to initial values
MOVE_INTERVAL = INITIAL_MOVE_INTERVAL;
currentMoveInterval.value = INITIAL_MOVE_INTERVAL;
moveTimer = 0;
snake = [];
var startX = Math.floor(GRID_WIDTH / 2);
var startY = Math.floor(GRID_HEIGHT / 2);
// Create initial snake with 3 segments
for (var i = 0; i < 3; i++) {
var segment = new SnakeSegment(i === 0);
segment.gridX = startX - i;
segment.gridY = startY;
var pos = gridToScreen(segment.gridX, segment.gridY);
segment.x = pos.x;
segment.y = pos.y;
// Set initial head direction
if (i === 0) {
segment.setDirection('right');
}
snake.push(segment);
game.addChild(segment);
}
}
// Spawn food at random location
function spawnFood() {
if (food) {
food.destroy();
}
var validPositions = [];
// Calculate safe zone boundaries (80% of screen area, centered)
var safeMarginX = Math.floor(GRID_WIDTH * 0.1); // 10% margin on each side
var safeMarginY = Math.floor(GRID_HEIGHT * 0.1); // 10% margin on top/bottom
var safeStartX = safeMarginX;
var safeEndX = GRID_WIDTH - safeMarginX;
var safeStartY = safeMarginY;
var safeEndY = GRID_HEIGHT - safeMarginY;
// Find all valid positions (not occupied by snake) within safe zone
for (var x = safeStartX; x < safeEndX; x++) {
for (var y = safeStartY; y < safeEndY; y++) {
var occupied = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === x && snake[i].gridY === y) {
occupied = true;
break;
}
}
if (!occupied) {
validPositions.push({
x: x,
y: y
});
}
}
}
if (validPositions.length > 0) {
// Select random food type based on probability
var rand = Math.random();
var selectedType = 'apple';
if (rand < FOOD_TYPES.strawberry.probability) {
selectedType = 'strawberry';
} else if (rand < FOOD_TYPES.strawberry.probability + FOOD_TYPES.grape.probability) {
selectedType = 'grape';
}
var randomPos = validPositions[Math.floor(Math.random() * validPositions.length)];
food = new Food(selectedType);
food.gridX = randomPos.x;
food.gridY = randomPos.y;
food.timeLeft = FOOD_TYPES[selectedType].duration;
food.maxTime = FOOD_TYPES[selectedType].duration;
var pos = gridToScreen(food.gridX, food.gridY);
food.x = pos.x;
food.y = pos.y;
game.addChild(food);
}
}
// Check if position is valid (within bounds and not hitting snake body)
function isValidPosition(x, y) {
// In wrap mode, adjust coordinates to wrap around
if (gameMode === 'wrap') {
if (x < 0) x = GRID_WIDTH - 1;
if (x >= GRID_WIDTH) x = 0;
if (y < 0) y = GRID_HEIGHT - 1;
if (y >= GRID_HEIGHT) y = 0;
} else {
// Classic mode - check bounds
if (x < 0 || x >= GRID_WIDTH || y < 0 || y >= GRID_HEIGHT) {
return false;
}
}
// Check collision with snake body (excluding head)
for (var i = 1; i < snake.length; i++) {
if (snake[i].gridX === x && snake[i].gridY === y) {
return false;
}
}
return true;
}
// Move snake
function moveSnake() {
if (!gameRunning) return;
var head = snake[0];
var newX = head.gridX;
var newY = head.gridY;
// Apply direction
currentDirection = nextDirection;
switch (currentDirection) {
case 'up':
newY--;
break;
case 'down':
newY++;
break;
case 'left':
newX--;
break;
case 'right':
newX++;
break;
}
// Handle wrapping in wrap mode
if (gameMode === 'wrap') {
if (newX < 0) newX = GRID_WIDTH - 1;
if (newX >= GRID_WIDTH) newX = 0;
if (newY < 0) newY = GRID_HEIGHT - 1;
if (newY >= GRID_HEIGHT) newY = 0;
}
// Check if new position is valid
if (!isValidPosition(newX, newY)) {
// Game over
gameRunning = false;
LK.getSound('gameOver').play();
LK.showGameOver();
return;
}
// Check if food is eaten
var ateFood = false;
if (food && newX === food.gridX && newY === food.gridY) {
ateFood = true;
var points = FOOD_TYPES[food.foodType].points;
LK.setScore(LK.getScore() + points);
scoreTxt.setText(LK.getScore());
LK.getSound('eat').play();
spawnFood();
// Increase snake speed with each food eaten
var newInterval = Math.max(MIN_MOVE_INTERVAL, MOVE_INTERVAL * SPEED_INCREASE_FACTOR);
if (newInterval !== MOVE_INTERVAL) {
// Smooth speed transition using tween
tween(currentMoveInterval, {
value: newInterval
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
MOVE_INTERVAL = Math.round(currentMoveInterval.value);
}
});
}
}
// Create new head
var newHead = new SnakeSegment(true);
newHead.gridX = newX;
newHead.gridY = newY;
var pos = gridToScreen(newHead.gridX, newHead.gridY);
newHead.x = pos.x;
newHead.y = pos.y;
// Set head rotation based on movement direction
newHead.setDirection(currentDirection);
// Add new head to front
snake.unshift(newHead);
game.addChild(newHead);
// Convert old head to body
if (snake.length > 1) {
var oldHead = snake[1];
oldHead.removeChildren();
var bodyGraphics = oldHead.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Remove tail if no food was eaten
if (!ateFood) {
var tail = snake.pop();
tail.destroy();
}
}
// Handle swipe input
function handleSwipe(deltaX, deltaY) {
if (!gameRunning) return;
var absX = Math.abs(deltaX);
var absY = Math.abs(deltaY);
// Determine swipe direction
if (absX > absY) {
// Horizontal swipe
if (deltaX > 0 && currentDirection !== 'left') {
nextDirection = 'right';
} else if (deltaX < 0 && currentDirection !== 'right') {
nextDirection = 'left';
}
} else {
// Vertical swipe
if (deltaY > 0 && currentDirection !== 'up') {
nextDirection = 'down';
} else if (deltaY < 0 && currentDirection !== 'down') {
nextDirection = 'up';
}
}
}
// Touch event handlers
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance >= MIN_SWIPE_DISTANCE) {
handleSwipe(deltaX, deltaY);
}
};
// Initialize game with mode selection
createModeSelection();
// Main game loop
game.update = function () {
if (!modeSelected || !gameRunning) return;
moveTimer++;
if (moveTimer >= MOVE_INTERVAL) {
moveTimer = 0;
moveSnake();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Food = Container.expand(function (foodType) {
var self = Container.call(this);
self.foodType = foodType || 'apple';
var assetName = self.foodType;
var foodGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.timeLeft = 0;
self.maxTime = 0;
self.originalScale = 1.0;
self.update = function () {
if (self.timeLeft > 0) {
self.timeLeft--;
// Pulse effect when time is running low
if (self.timeLeft < 120) {
// Last 2 seconds at 60fps
var pulseScale = 0.8 + 0.4 * Math.sin(self.timeLeft * 0.3);
foodGraphics.scaleX = self.originalScale * pulseScale;
foodGraphics.scaleY = self.originalScale * pulseScale;
}
if (self.timeLeft <= 0) {
// Food expired, spawn new one
if (food === self) {
spawnFood();
}
}
}
};
return self;
});
var SnakeSegment = Container.expand(function (isHead) {
var self = Container.call(this);
var segmentGraphics = self.attachAsset(isHead ? 'snakeHead' : 'snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.isHead = isHead;
self.segmentGraphics = segmentGraphics;
self.setDirection = function (direction) {
if (self.isHead && self.segmentGraphics) {
switch (direction) {
case 'up':
self.segmentGraphics.rotation = -Math.PI / 2;
break;
case 'down':
self.segmentGraphics.rotation = Math.PI / 2;
break;
case 'left':
self.segmentGraphics.rotation = Math.PI;
break;
case 'right':
self.segmentGraphics.rotation = 0;
break;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E7D32
});
/****
* Game Code
****/
// Import tween plugin for smooth speed transitions
// Game constants
var GRID_SIZE = 90;
var GRID_WIDTH = Math.floor(2048 / GRID_SIZE);
var GRID_HEIGHT = Math.floor(2732 / GRID_SIZE);
var GAME_AREA_WIDTH = GRID_WIDTH * GRID_SIZE;
var GAME_AREA_HEIGHT = GRID_HEIGHT * GRID_SIZE;
var OFFSET_X = (2048 - GAME_AREA_WIDTH) / 2;
var OFFSET_Y = (2732 - GAME_AREA_HEIGHT) / 2 + 100;
// Speed constants
var INITIAL_MOVE_INTERVAL = 12; // Start slower for better control
var MIN_MOVE_INTERVAL = 4; // Maximum speed cap (fastest the snake can go)
var SPEED_INCREASE_FACTOR = 0.95; // Each food makes snake 5% faster (multiply interval by 0.95)
// Game variables
var snake = [];
var currentDirection = 'right';
var nextDirection = 'right';
var food = null;
var gameRunning = true;
var moveTimer = 0;
var MOVE_INTERVAL = INITIAL_MOVE_INTERVAL; // Current move interval (starts at initial speed)
var currentMoveInterval = {
value: INITIAL_MOVE_INTERVAL
}; // Object for tweening
var gameMode = 'classic'; // 'classic' or 'wrap'
var modeSelected = false;
// Food type definitions
var FOOD_TYPES = {
apple: {
points: 1,
probability: 0.7,
duration: 720
},
// 12 seconds at 60fps
grape: {
points: 2,
probability: 0.25,
duration: 720
},
// 12 seconds at 60fps
strawberry: {
points: 3,
probability: 0.05,
duration: 720
} // 12 seconds at 60fps
};
// Touch control variables
var touchStartX = 0;
var touchStartY = 0;
var MIN_SWIPE_DISTANCE = 50;
// Initialize score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize mode display
var modeTxt = new Text2('Mode: Classic', {
size: 50,
fill: 0xFFFFFF
});
modeTxt.anchor.set(1, 0);
modeTxt.x = -20; // Offset from right edge
modeTxt.y = 20; // Small offset from top
LK.gui.topRight.addChild(modeTxt);
// Convert grid coordinates to screen coordinates
function gridToScreen(gridX, gridY) {
return {
x: OFFSET_X + gridX * GRID_SIZE + GRID_SIZE / 2,
y: OFFSET_Y + gridY * GRID_SIZE + GRID_SIZE / 2
};
}
// Create mode selection UI
function createModeSelection() {
// Classic mode button
var classicButton = LK.getAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 200,
y: 2732 / 2 - 100,
scaleX: 3,
scaleY: 2
});
game.addChild(classicButton);
var classicText = new Text2('CLASSIC', {
size: 60,
fill: 0xFFFFFF
});
classicText.anchor.set(0.5, 0.5);
classicText.x = 2048 / 2 - 200;
classicText.y = 2732 / 2 - 100;
game.addChild(classicText);
// Wrap mode button
var wrapButton = LK.getAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 200,
y: 2732 / 2 - 100,
scaleX: 3,
scaleY: 2
});
game.addChild(wrapButton);
var wrapText = new Text2('WRAP', {
size: 60,
fill: 0xFFFFFF
});
wrapText.anchor.set(0.5, 0.5);
wrapText.x = 2048 / 2 + 200;
wrapText.y = 2732 / 2 - 100;
game.addChild(wrapText);
// Instructions
var instructionText = new Text2('Choose Wall Mode:', {
size: 80,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 - 300;
game.addChild(instructionText);
// Store references for removal
game.modeSelectionElements = [classicButton, classicText, wrapButton, wrapText, instructionText];
// Handle mode selection
classicButton.interactive = true;
classicText.interactive = true;
wrapButton.interactive = true;
wrapText.interactive = true;
function selectMode(mode) {
gameMode = mode;
modeSelected = true;
modeTxt.setText('Mode: ' + (mode === 'classic' ? 'Classic' : 'Wrap'));
// Remove mode selection UI
for (var i = 0; i < game.modeSelectionElements.length; i++) {
game.modeSelectionElements[i].destroy();
}
game.modeSelectionElements = [];
// Start the game immediately
gameRunning = true;
initSnake();
spawnFood();
}
classicButton.down = function () {
selectMode('classic');
};
classicText.down = function () {
selectMode('classic');
};
wrapButton.down = function () {
selectMode('wrap');
};
wrapText.down = function () {
selectMode('wrap');
};
}
// Initialize snake
function initSnake() {
// Reset speed to initial values
MOVE_INTERVAL = INITIAL_MOVE_INTERVAL;
currentMoveInterval.value = INITIAL_MOVE_INTERVAL;
moveTimer = 0;
snake = [];
var startX = Math.floor(GRID_WIDTH / 2);
var startY = Math.floor(GRID_HEIGHT / 2);
// Create initial snake with 3 segments
for (var i = 0; i < 3; i++) {
var segment = new SnakeSegment(i === 0);
segment.gridX = startX - i;
segment.gridY = startY;
var pos = gridToScreen(segment.gridX, segment.gridY);
segment.x = pos.x;
segment.y = pos.y;
// Set initial head direction
if (i === 0) {
segment.setDirection('right');
}
snake.push(segment);
game.addChild(segment);
}
}
// Spawn food at random location
function spawnFood() {
if (food) {
food.destroy();
}
var validPositions = [];
// Calculate safe zone boundaries (80% of screen area, centered)
var safeMarginX = Math.floor(GRID_WIDTH * 0.1); // 10% margin on each side
var safeMarginY = Math.floor(GRID_HEIGHT * 0.1); // 10% margin on top/bottom
var safeStartX = safeMarginX;
var safeEndX = GRID_WIDTH - safeMarginX;
var safeStartY = safeMarginY;
var safeEndY = GRID_HEIGHT - safeMarginY;
// Find all valid positions (not occupied by snake) within safe zone
for (var x = safeStartX; x < safeEndX; x++) {
for (var y = safeStartY; y < safeEndY; y++) {
var occupied = false;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === x && snake[i].gridY === y) {
occupied = true;
break;
}
}
if (!occupied) {
validPositions.push({
x: x,
y: y
});
}
}
}
if (validPositions.length > 0) {
// Select random food type based on probability
var rand = Math.random();
var selectedType = 'apple';
if (rand < FOOD_TYPES.strawberry.probability) {
selectedType = 'strawberry';
} else if (rand < FOOD_TYPES.strawberry.probability + FOOD_TYPES.grape.probability) {
selectedType = 'grape';
}
var randomPos = validPositions[Math.floor(Math.random() * validPositions.length)];
food = new Food(selectedType);
food.gridX = randomPos.x;
food.gridY = randomPos.y;
food.timeLeft = FOOD_TYPES[selectedType].duration;
food.maxTime = FOOD_TYPES[selectedType].duration;
var pos = gridToScreen(food.gridX, food.gridY);
food.x = pos.x;
food.y = pos.y;
game.addChild(food);
}
}
// Check if position is valid (within bounds and not hitting snake body)
function isValidPosition(x, y) {
// In wrap mode, adjust coordinates to wrap around
if (gameMode === 'wrap') {
if (x < 0) x = GRID_WIDTH - 1;
if (x >= GRID_WIDTH) x = 0;
if (y < 0) y = GRID_HEIGHT - 1;
if (y >= GRID_HEIGHT) y = 0;
} else {
// Classic mode - check bounds
if (x < 0 || x >= GRID_WIDTH || y < 0 || y >= GRID_HEIGHT) {
return false;
}
}
// Check collision with snake body (excluding head)
for (var i = 1; i < snake.length; i++) {
if (snake[i].gridX === x && snake[i].gridY === y) {
return false;
}
}
return true;
}
// Move snake
function moveSnake() {
if (!gameRunning) return;
var head = snake[0];
var newX = head.gridX;
var newY = head.gridY;
// Apply direction
currentDirection = nextDirection;
switch (currentDirection) {
case 'up':
newY--;
break;
case 'down':
newY++;
break;
case 'left':
newX--;
break;
case 'right':
newX++;
break;
}
// Handle wrapping in wrap mode
if (gameMode === 'wrap') {
if (newX < 0) newX = GRID_WIDTH - 1;
if (newX >= GRID_WIDTH) newX = 0;
if (newY < 0) newY = GRID_HEIGHT - 1;
if (newY >= GRID_HEIGHT) newY = 0;
}
// Check if new position is valid
if (!isValidPosition(newX, newY)) {
// Game over
gameRunning = false;
LK.getSound('gameOver').play();
LK.showGameOver();
return;
}
// Check if food is eaten
var ateFood = false;
if (food && newX === food.gridX && newY === food.gridY) {
ateFood = true;
var points = FOOD_TYPES[food.foodType].points;
LK.setScore(LK.getScore() + points);
scoreTxt.setText(LK.getScore());
LK.getSound('eat').play();
spawnFood();
// Increase snake speed with each food eaten
var newInterval = Math.max(MIN_MOVE_INTERVAL, MOVE_INTERVAL * SPEED_INCREASE_FACTOR);
if (newInterval !== MOVE_INTERVAL) {
// Smooth speed transition using tween
tween(currentMoveInterval, {
value: newInterval
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
MOVE_INTERVAL = Math.round(currentMoveInterval.value);
}
});
}
}
// Create new head
var newHead = new SnakeSegment(true);
newHead.gridX = newX;
newHead.gridY = newY;
var pos = gridToScreen(newHead.gridX, newHead.gridY);
newHead.x = pos.x;
newHead.y = pos.y;
// Set head rotation based on movement direction
newHead.setDirection(currentDirection);
// Add new head to front
snake.unshift(newHead);
game.addChild(newHead);
// Convert old head to body
if (snake.length > 1) {
var oldHead = snake[1];
oldHead.removeChildren();
var bodyGraphics = oldHead.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Remove tail if no food was eaten
if (!ateFood) {
var tail = snake.pop();
tail.destroy();
}
}
// Handle swipe input
function handleSwipe(deltaX, deltaY) {
if (!gameRunning) return;
var absX = Math.abs(deltaX);
var absY = Math.abs(deltaY);
// Determine swipe direction
if (absX > absY) {
// Horizontal swipe
if (deltaX > 0 && currentDirection !== 'left') {
nextDirection = 'right';
} else if (deltaX < 0 && currentDirection !== 'right') {
nextDirection = 'left';
}
} else {
// Vertical swipe
if (deltaY > 0 && currentDirection !== 'up') {
nextDirection = 'down';
} else if (deltaY < 0 && currentDirection !== 'down') {
nextDirection = 'up';
}
}
}
// Touch event handlers
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance >= MIN_SWIPE_DISTANCE) {
handleSwipe(deltaX, deltaY);
}
};
// Initialize game with mode selection
createModeSelection();
// Main game loop
game.update = function () {
if (!modeSelected || !gameRunning) return;
moveTimer++;
if (moveTimer >= MOVE_INTERVAL) {
moveTimer = 0;
moveSnake();
}
};