/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BirdBoss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('birdBoss', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.moveTimer = 0;
self.moveInterval = 240; // 4 seconds at 60fps
self.lastTargetX = GAME_WIDTH / 2;
self.lastTargetY = GAME_HEIGHT / 2;
self.speed = 12;
self.isMoving = false;
self.targetX = GAME_WIDTH / 2;
self.targetY = GAME_HEIGHT / 2;
self.update = function () {
self.moveTimer++;
// Every 4 seconds, set new target to snake's current position
if (self.moveTimer >= self.moveInterval) {
self.moveTimer = 0;
if (snake.length > 0) {
var head = snake[0];
self.targetX = head.x;
self.targetY = head.y;
self.isMoving = true;
}
}
// Move towards target if moving
if (self.isMoving) {
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 10) {
var moveX = deltaX / distance * self.speed;
var moveY = deltaY / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
self.isMoving = false;
}
}
};
self.takeDamage = function () {
self.health--;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.setScore(LK.getScore() + 150);
scoreTxt.setText('Score: ' + LK.getScore());
birdBossActive = false;
birdBossDefeated = true;
self.destroy();
};
return self;
});
var BossRock = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossRock', {
anchorX: 0.5,
anchorY: 0.5
});
// Initialize velocity (will be set when rock is created)
self.velocityX = 0;
self.velocityY = 0;
self.bounceCount = 0;
self.maxBounces = 3;
self.lastX = 0;
self.lastY = 0;
self.lastIntersectingBoss = false;
self.setTarget = function (startX, startY, targetX, targetY) {
// Calculate velocity towards target
var angle = Math.atan2(targetY - startY, targetX - startX);
self.velocityX = Math.cos(angle) * 8;
self.velocityY = Math.sin(angle) * 8;
self.lastX = startX;
self.lastY = startY;
};
self.update = function () {
// Store last position
self.lastX = self.x;
self.lastY = self.y;
// Move rock
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off walls
if (self.x <= 50 || self.x >= GAME_WIDTH - 50) {
self.velocityX = -self.velocityX;
self.x = Math.max(50, Math.min(GAME_WIDTH - 50, self.x));
self.bounceCount++;
self.updateGrayness();
if (self.bounceCount >= self.maxBounces) {
self.destroy();
return;
}
}
if (self.y <= 50 || self.y >= GAME_HEIGHT - 50) {
self.velocityY = -self.velocityY;
self.y = Math.max(50, Math.min(GAME_HEIGHT - 50, self.y));
self.bounceCount++;
self.updateGrayness();
if (self.bounceCount >= self.maxBounces) {
self.destroy();
return;
}
}
};
self.updateGrayness = function () {
// Make rock grayer with each bounce
var grayness = 0.7 + self.bounceCount * 0.1;
var grayColor = Math.floor(0x69 * grayness) << 16 | Math.floor(0x69 * grayness) << 8 | Math.floor(0x69 * grayness);
graphics.tint = grayColor;
};
return self;
});
var CrownDrop = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('crownDrop', {
anchorX: 0.5,
anchorY: 0.5
});
// Add gentle pulsing animation
self.animationCounter = 0;
self.baseScale = 1.0;
self.update = function () {
// Gentle pulsing animation
self.animationCounter += 0.08;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.15;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
return self;
});
var EnemySnake = Container.expand(function () {
var self = Container.call(this);
// Create enemy snake head
var headGraphics = self.attachAsset('enemySnakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
// Enemy snake properties
self.segments = [];
self.direction = {
x: 1,
y: 0
};
self.speed = 6;
self.moveCounter = 0;
self.gridX = 0;
self.gridY = 0;
self.lastTargetTime = 0;
self.targetFruit = null;
self.targetGrowthPoint = null;
// Create initial body segments
for (var i = 0; i < 3; i++) {
var segment = new Container();
var segmentGraphics = segment.attachAsset('enemySnakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
segment.gridX = self.gridX - (i + 1);
segment.gridY = self.gridY;
segment.x = segment.gridX * GRID_SIZE + GRID_SIZE / 2;
segment.y = segment.gridY * GRID_SIZE + GRID_SIZE / 2;
self.segments.push(segment);
game.addChild(segment);
}
self.findNearestTarget = function () {
var targets = [];
// Add fruits as targets
if (currentFruit) {
targets.push({
type: 'fruit',
obj: currentFruit,
x: currentFruit.gridX,
y: currentFruit.gridY,
priority: 2
});
}
// Add growth points as targets
for (var i = 0; i < growthPoints.length; i++) {
targets.push({
type: 'growthPoint',
obj: growthPoints[i],
x: growthPoints[i].gridX,
y: growthPoints[i].gridY,
priority: 1
});
}
if (targets.length === 0) return null;
// Find closest target
var closest = targets[0];
var closestDist = Math.abs(closest.x - self.gridX) + Math.abs(closest.y - self.gridY);
for (var i = 1; i < targets.length; i++) {
var dist = Math.abs(targets[i].x - self.gridX) + Math.abs(targets[i].y - self.gridY);
if (dist < closestDist || dist === closestDist && targets[i].priority > closest.priority) {
closest = targets[i];
closestDist = dist;
}
}
return closest;
};
self.moveTowardsTarget = function () {
var target = self.findNearestTarget();
if (!target) {
// Random movement if no target
var directions = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
self.direction = directions[Math.floor(Math.random() * directions.length)];
return;
}
var deltaX = target.x - self.gridX;
var deltaY = target.y - self.gridY;
// Choose direction based on larger delta
if (Math.abs(deltaX) > Math.abs(deltaY)) {
self.direction = {
x: deltaX > 0 ? 1 : -1,
y: 0
};
} else {
self.direction = {
x: 0,
y: deltaY > 0 ? 1 : -1
};
}
};
self.update = function () {
self.moveCounter++;
var moveInterval = Math.max(3, 15 - self.speed);
if (self.moveCounter >= moveInterval) {
self.moveCounter = 0;
// Update movement direction towards targets
self.moveTowardsTarget();
// Calculate new position
var newGridX = self.gridX + self.direction.x;
var newGridY = self.gridY + self.direction.y;
// Bounce off walls
if (newGridX <= 0 || newGridX >= COLS - 1) {
self.direction.x = -self.direction.x;
newGridX = self.gridX + self.direction.x;
}
if (newGridY <= 0 || newGridY >= ROWS - 1) {
self.direction.y = -self.direction.y;
newGridY = self.gridY + self.direction.y;
}
// Move body segments
for (var i = self.segments.length - 1; i > 0; i--) {
self.segments[i].gridX = self.segments[i - 1].gridX;
self.segments[i].gridY = self.segments[i - 1].gridY;
self.segments[i].x = self.segments[i].gridX * GRID_SIZE + GRID_SIZE / 2;
self.segments[i].y = self.segments[i].gridY * GRID_SIZE + GRID_SIZE / 2;
}
// Move first body segment to old head position
if (self.segments.length > 0) {
self.segments[0].gridX = self.gridX;
self.segments[0].gridY = self.gridY;
self.segments[0].x = self.gridX * GRID_SIZE + GRID_SIZE / 2;
self.segments[0].y = self.gridY * GRID_SIZE + GRID_SIZE / 2;
}
// Move head
self.gridX = newGridX;
self.gridY = newGridY;
self.x = self.gridX * GRID_SIZE + GRID_SIZE / 2;
self.y = self.gridY * GRID_SIZE + GRID_SIZE / 2;
// Check for fruit collision
if (currentFruit && currentFruit.gridX === self.gridX && currentFruit.gridY === self.gridY) {
// Steal points (reduce player score)
var currentScore = LK.getScore();
LK.setScore(Math.max(0, currentScore - 10));
scoreTxt.setText('Score: ' + LK.getScore());
// Remove the fruit
currentFruit.destroy();
currentFruit = null;
spawnFruit();
// Flash screen purple to indicate theft
LK.effects.flashScreen(0x8b008b, 300);
}
// Check for growth point collision
for (var i = growthPoints.length - 1; i >= 0; i--) {
if (growthPoints[i].gridX === self.gridX && growthPoints[i].gridY === self.gridY) {
// Steal points (reduce player score)
var currentScore = LK.getScore();
LK.setScore(Math.max(0, currentScore - 5));
scoreTxt.setText('Score: ' + LK.getScore());
// Remove the growth point
growthPoints[i].destroy();
growthPoints.splice(i, 1);
// Flash screen purple to indicate theft
LK.effects.flashScreen(0x8b008b, 300);
break;
}
}
}
};
self.destroy = function () {
// Destroy all body segments
for (var i = 0; i < self.segments.length; i++) {
if (self.segments[i].parent) {
self.segments[i].destroy();
}
}
self.segments = [];
// Destroy head
if (self.parent) {
Container.prototype.destroy.call(self);
}
};
return self;
});
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
var assetName = 'apple';
if (type === 'pear') assetName = 'pear';else if (type === 'banana') assetName = 'banana';else if (type === 'multicolor') assetName = 'multicolor';else if (type === 'coconut') assetName = 'coconut';else if (type === 'orange') assetName = 'orange';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.fruitType = type || 'apple';
// Add gentle pulsing animation to all fruits
self.animationCounter = 0;
self.baseScale = 1.0;
// Add color animation for multicolor fruit
if (type === 'multicolor') {
self.colorIndex = 0;
self.colors = [0xff3333, 0x44dd44, 0x3366ff, 0xffdd44, 0xff44dd, 0x44ddff];
self.update = function () {
// Color cycling for multicolor fruit
if (LK.ticks % 15 === 0) {
self.colorIndex = (self.colorIndex + 1) % self.colors.length;
graphics.tint = self.colors[self.colorIndex];
}
// Gentle pulsing animation
self.animationCounter += 0.08;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.15;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
} else {
self.update = function () {
// Gentle pulsing animation for regular fruits
self.animationCounter += 0.06;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.1;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
}
return self;
});
var GrowthPoint = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('growthPoint', {
anchorX: 0.5,
anchorY: 0.5
});
// Add gentle pulsing animation
self.animationCounter = 0;
self.baseScale = 1.0;
self.update = function () {
// Gentle pulsing animation
self.animationCounter += 0.1;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.2;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
return self;
});
var LavaArea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('lavaArea', {
anchorX: 0,
anchorY: 0
});
self.isWarning = true;
self.warningTime = 5000; // 5 seconds warning
self.startTime = Date.now();
// Set initial warning appearance
graphics.alpha = 0.3;
graphics.tint = 0xffff00; // Yellow warning
self.update = function () {
var elapsed = Date.now() - self.startTime;
if (self.isWarning && elapsed < self.warningTime) {
// Slower, more intermittent blinking warning effect
var blinkSpeed = 2; // Much slower blinking
var blinkCycle = Math.sin(LK.ticks * blinkSpeed * 0.1);
// More intermittent pattern - only blink when sine wave is positive
if (blinkCycle > 0) {
graphics.alpha = 0.3 + 0.4 * blinkCycle;
} else {
graphics.alpha = 0.1; // Very dim when not blinking
}
} else if (self.isWarning && elapsed >= self.warningTime) {
// Convert to lava
self.isWarning = false;
graphics.alpha = 1.0;
graphics.tint = 0xff4400; // Red lava
}
};
return self;
});
var PotatoBoss = Container.expand(function () {
var self = Container.call(this);
// Create boss body
var body = self.attachAsset('bossBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Create angry eyebrows
var leftEyebrow = self.attachAsset('bossEyebrow', {
anchorX: 0.5,
anchorY: 0.5,
x: -40,
y: -50
});
var rightEyebrow = self.attachAsset('bossEyebrow', {
anchorX: 0.5,
anchorY: 0.5,
x: 40,
y: -50
});
// Angle the eyebrows for angry look
leftEyebrow.rotation = 0.3;
rightEyebrow.rotation = -0.3;
// Create crown
var crown = self.attachAsset('bossCrown', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -80
});
self.health = 5;
self.maxHealth = 5;
self.shootTimer = 0;
self.shootInterval = 120; // 2 seconds at 60fps
self.hitFlashTimer = 0;
self.update = function () {
// Shooting logic
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
self.shootTimer = 0;
self.shootRock();
}
// Hit flash effect
if (self.hitFlashTimer > 0) {
self.hitFlashTimer--;
var flashIntensity = self.hitFlashTimer / 30;
body.tint = 0xFFFFFF * flashIntensity + 0xD2B48C * (1 - flashIntensity);
} else {
body.tint = 0xFFFFFF;
}
};
self.shootRock = function () {
if (!gameRunning || snake.length === 0) return;
// Shoot towards snake head
var head = snake[0];
var rock = new BossRock();
rock.x = self.x;
rock.y = self.y;
rock.setTarget(self.x, self.y, head.x, head.y);
bossRocks.push(rock);
game.addChild(rock);
LK.getSound('boss_shoot').play();
};
self.takeDamage = function () {
self.health--;
self.hitFlashTimer = 30;
LK.getSound('boss_hit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('boss_defeat').play();
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore());
bossActive = false;
bossDefeated = true;
// Drop crown at boss position
crownDrop = new CrownDrop();
crownDrop.x = self.x;
crownDrop.y = self.y;
game.addChild(crownDrop);
// Update missions - boss killed
if (!missions.bossKilled.completed) {
missions.bossKilled.current++;
storage.bossKilled = missions.bossKilled.current;
if (missions.bossKilled.current >= missions.bossKilled.target) {
missions.bossKilled.completed = true;
storage.bossCompleted = true;
}
updateMissionsDisplay();
}
// Clear boss and rocks
for (var i = bossRocks.length - 1; i >= 0; i--) {
bossRocks[i].destroy();
}
bossRocks = [];
self.destroy();
};
return self;
});
var PurpleSnake = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('purpleSnake', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetX = GAME_WIDTH / 2;
self.targetY = GAME_HEIGHT / 2;
self.update = function () {
// Move towards center of screen
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 5) {
var moveX = deltaX / distance * self.speed;
var moveY = deltaY / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
};
return self;
});
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
// Create crown but make it invisible initially
var crown = self.attachAsset('snakeCrown', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -20
});
crown.visible = false; // Crown is hidden until collected
// Create shield but make it invisible initially
var shield = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -10
});
shield.visible = false; // Shield is hidden until orange is consumed
shield.alpha = 0.7; // Semi-transparent shield
self.thickness = 1;
self.animationCounter = 0;
self.baseThickness = 1;
self.hasCrown = false; // Track if snake has collected crown
self.hasShield = false; // Track if snake has shield protection
self.setThickness = function (newThickness) {
self.thickness = newThickness;
self.baseThickness = newThickness;
graphics.scaleX = newThickness;
graphics.scaleY = newThickness;
};
self.showCrown = function () {
self.hasCrown = true;
crown.visible = true;
};
self.activateShield = function () {
self.hasShield = true;
shield.visible = true;
shield.alpha = 0.7;
// Add shield pulsing animation
tween(shield, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (shield && shield.visible && self.hasShield) {
tween(shield, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeInOut,
loop: true,
yoyo: true
});
}
}
});
};
self.removeShield = function () {
self.hasShield = false;
shield.visible = false;
// Stop any active tweens on the shield
if (shield && shield.parent) {
tween.stop(shield);
shield.scaleX = 1;
shield.scaleY = 1;
}
};
self.update = function () {
// Subtle breathing animation for snake head
self.animationCounter += 0.05;
var breatheScale = self.baseThickness + Math.sin(self.animationCounter) * 0.05;
graphics.scaleX = breatheScale;
graphics.scaleY = breatheScale;
// Shield pulsing animation when active
if (self.hasShield && shield && shield.visible) {
shield.alpha = 0.5 + Math.sin(self.animationCounter * 3) * 0.2;
}
};
return self;
});
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
self.thickness = 1;
self.setThickness = function (newThickness) {
self.thickness = newThickness;
graphics.scaleX = newThickness;
graphics.scaleY = newThickness;
};
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 3;
// Set random direction
var angle = Math.random() * Math.PI * 2;
self.velocityX = Math.cos(angle) * self.speed;
self.velocityY = Math.sin(angle) * self.speed;
self.update = function () {
// Move spike
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off walls
if (self.x <= 30 || self.x >= GAME_WIDTH - 30) {
self.velocityX = -self.velocityX;
self.x = Math.max(30, Math.min(GAME_WIDTH - 30, self.x));
LK.getSound('spike_bounce').play();
}
if (self.y <= 30 || self.y >= GAME_HEIGHT - 30) {
self.velocityY = -self.velocityY;
self.y = Math.max(30, Math.min(GAME_HEIGHT - 30, self.y));
LK.getSound('spike_bounce').play();
}
};
return self;
});
var WarningLight = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('warningLight', {
anchorX: 0.5,
anchorY: 0.5
});
self.blinkTimer = 0;
self.update = function () {
self.blinkTimer += 0.15;
// Dark intermittent blinking
graphics.alpha = Math.abs(Math.sin(self.blinkTimer)) < 0.3 ? 0.8 : 0.1;
// Ensure circular shape by maintaining equal scale
graphics.scaleX = graphics.scaleY;
};
return self;
});
var X2Powerup = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('x2Powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifeTime = 10000; // 10 seconds
self.startTime = Date.now();
self.blinkStartTime = 7000; // Start blinking at 7 seconds (3 seconds before end)
// Add bouncing movement properties
self.velocityX = 0;
self.velocityY = 0;
self.speed = 1.5; // Slow movement speed
// Set random direction
var angle = Math.random() * Math.PI * 2;
self.velocityX = Math.cos(angle) * self.speed;
self.velocityY = Math.sin(angle) * self.speed;
self.update = function () {
var elapsed = Date.now() - self.startTime;
// Check if power-up should be destroyed
if (elapsed >= self.lifeTime) {
self.destroy();
return;
}
// Move X2 powerup
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off walls
if (self.x <= 60 || self.x >= GAME_WIDTH - 60) {
self.velocityX = -self.velocityX;
self.x = Math.max(60, Math.min(GAME_WIDTH - 60, self.x));
}
if (self.y <= 60 || self.y >= GAME_HEIGHT - 60) {
self.velocityY = -self.velocityY;
self.y = Math.max(60, Math.min(GAME_HEIGHT - 60, self.y));
}
// Blinking effect in last 3 seconds
if (elapsed >= self.blinkStartTime) {
var blinkSpeed = 8; // Fast blinking
graphics.alpha = Math.abs(Math.sin(LK.ticks * blinkSpeed * 0.1)) > 0.5 ? 1.0 : 0.3;
} else {
graphics.alpha = 1.0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a4a3a
});
/****
* Game Code
****/
var GRID_SIZE = 40;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var COLS = Math.floor(GAME_WIDTH / GRID_SIZE);
var ROWS = Math.floor(GAME_HEIGHT / GRID_SIZE);
// Snake variables
var snake = [];
var snakeDirection = {
x: 1,
y: 0
};
var nextDirection = {
x: 1,
y: 0
};
var snakeSpeed = 8;
var moveCounter = 0;
var snakeThickness = 1;
// Fruit variables
var currentFruit = null;
var fruits = [];
// Growth points variables
var growthPoints = [];
var coconutPowerActive = false;
var coconutPowerEndTime = 0;
var lastGrowthPointSpawn = 0;
// Game state
var gameRunning = true;
var touchStartX = 0;
var touchStartY = 0;
// Autopilot variables
var autoPilotActive = false;
var autoPilotEndTime = 0;
var countdownText = null;
// Event system variables
var lastEventTime = 0;
var eventInterval = 30000; // 30 seconds
var activeSpikes = [];
var activeLavaAreas = [];
var lastX2EventTime = 0;
var x2EventInterval = 30000; // 30 seconds
var activeX2Powerups = [];
var x2MultiplierActive = false;
var x2MultiplierEndTime = 0;
// Boss battle variables
var gameStartTime = Date.now();
var bossWarningActive = false;
var bossWarningStartTime = 0;
var bossActive = false;
var bossDefeated = false;
var currentBoss = null;
var bossRocks = [];
var warningLights = [];
var bossHealthBar = null;
var bossHealthBarBg = null;
// Reset missions storage at game start
storage.fruitsEaten = 0;
storage.fruitsCompleted = false;
storage.pointsScored = 0;
storage.pointsCompleted = false;
storage.bossKilled = 0;
storage.bossCompleted = false;
storage.eventsLived = 0;
storage.eventsCompleted = false;
// Missions system variables
var missions = {
fruitsEaten: {
target: 5,
current: 0,
completed: false
},
pointsScored: {
target: 300,
current: 0,
completed: false
},
bossKilled: {
target: 1,
current: 0,
completed: false
},
eventsLived: {
target: 4,
current: 0,
completed: false
}
};
var missionsTable = null;
var eventsCounter = 0;
// Crown drop variable
var crownDrop = null;
// Enemy snake variable
var activeEnemySnake = null;
// Bird boss variables
var birdBossActive = false;
var birdBossDefeated = false;
var currentBirdBoss = null;
var birdBossStartTime = 0;
var purpleSnakeActive = false;
var currentPurpleSnake = null;
var purpleSnakeSpawnTime = 0;
// Timer display
var timerTxt = new Text2('Time: 00:00', {
size: 50,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 0;
timerTxt.y = 20;
LK.gui.top.addChild(timerTxt);
// Next event display
var nextEventTxt = new Text2('Next Event: Spike/Lava in 30s', {
size: 40,
fill: 0xFFFF00
});
nextEventTxt.anchor.set(0.5, 0);
nextEventTxt.x = 0;
nextEventTxt.y = 80;
LK.gui.top.addChild(nextEventTxt);
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120;
scoreTxt.y = 20;
LK.gui.topLeft.addChild(scoreTxt);
// Power-up display
var powerTxt = new Text2('Speed: 1x | Thickness: 1x', {
size: 40,
fill: 0xFFFF00
});
powerTxt.anchor.set(1, 0);
powerTxt.x = -20;
powerTxt.y = 20;
LK.gui.topRight.addChild(powerTxt);
// Boss health bar (initially hidden)
bossHealthBarBg = new LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBarBg.x = GAME_WIDTH / 2;
bossHealthBarBg.y = 100;
bossHealthBarBg.visible = false;
game.addChild(bossHealthBarBg);
bossHealthBar = new LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 100;
bossHealthBar.visible = false;
game.addChild(bossHealthBar);
// Create missions table
missionsTable = new Text2('MISSIONS:\n' + '• Eat 5 fruits: 0/5\n' + '• Score 300 points: 0/300\n' + '• Kill the boss: 0/1\n' + '• Survive 4 events: 0/4', {
size: 35,
fill: 0xFFFFFF
});
missionsTable.anchor.set(0, 0);
missionsTable.x = 20;
missionsTable.y = 120;
LK.gui.topLeft.addChild(missionsTable);
// Create walls
var topWall = game.addChild(LK.getAsset('wall', {
x: 0,
y: 0,
width: GAME_WIDTH,
height: 20
}));
var bottomWall = game.addChild(LK.getAsset('wall', {
x: 0,
y: GAME_HEIGHT - 20,
width: GAME_WIDTH,
height: 20
}));
var leftWall = game.addChild(LK.getAsset('wall', {
x: 0,
y: 0,
width: 20,
height: GAME_HEIGHT
}));
var rightWall = game.addChild(LK.getAsset('wall', {
x: GAME_WIDTH - 20,
y: 0,
width: 20,
height: GAME_HEIGHT
}));
// Initialize snake
function initSnake() {
// Create initial snake with 3 segments
var startX = Math.floor(COLS / 4);
var startY = Math.floor(ROWS / 2);
for (var i = 0; i < 3; i++) {
var segment;
if (i === 0) {
segment = new SnakeHead();
} else {
segment = new SnakeSegment();
}
segment.gridX = startX - i;
segment.gridY = startY;
segment.x = segment.gridX * GRID_SIZE + GRID_SIZE / 2;
segment.y = segment.gridY * GRID_SIZE + GRID_SIZE / 2;
snake.push(segment);
game.addChild(segment);
}
}
// Spawn growth point
function spawnGrowthPoint() {
// Only spawn if no growth points exist OR coconut power is active
if (growthPoints.length > 0 && !coconutPowerActive) {
return;
}
var validPosition = false;
var gridX, gridY;
while (!validPosition) {
gridX = Math.floor(Math.random() * (COLS - 4)) + 2;
gridY = Math.floor(Math.random() * (ROWS - 4)) + 2;
validPosition = true;
// Check snake collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === gridX && snake[i].gridY === gridY) {
validPosition = false;
break;
}
}
// Check fruit collision
if (currentFruit && currentFruit.gridX === gridX && currentFruit.gridY === gridY) {
validPosition = false;
}
// Check other growth points collision
for (var i = 0; i < growthPoints.length; i++) {
if (growthPoints[i].gridX === gridX && growthPoints[i].gridY === gridY) {
validPosition = false;
break;
}
}
// Avoid lava areas
var worldX = gridX * GRID_SIZE + GRID_SIZE / 2;
var worldY = gridY * GRID_SIZE + GRID_SIZE / 2;
for (var l = 0; l < activeLavaAreas.length; l++) {
var lavaArea = activeLavaAreas[l];
if (worldX >= lavaArea.x && worldX <= lavaArea.x + 1024 && worldY >= lavaArea.y && worldY <= lavaArea.y + 1366) {
validPosition = false;
break;
}
}
}
var growthPoint = new GrowthPoint();
growthPoint.gridX = gridX;
growthPoint.gridY = gridY;
growthPoint.x = gridX * GRID_SIZE + GRID_SIZE / 2;
growthPoint.y = gridY * GRID_SIZE + GRID_SIZE / 2;
growthPoints.push(growthPoint);
game.addChild(growthPoint);
}
// Spawn fruit
function spawnFruit() {
if (currentFruit) {
currentFruit.destroy();
currentFruit = null;
}
var fruitTypes = ['apple', 'pear', 'banana', 'multicolor', 'coconut', 'orange'];
var randomType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
var validPosition = false;
var gridX, gridY;
while (!validPosition) {
gridX = Math.floor(Math.random() * (COLS - 4)) + 2;
gridY = Math.floor(Math.random() * (ROWS - 4)) + 2;
validPosition = true;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === gridX && snake[i].gridY === gridY) {
validPosition = false;
break;
}
}
// Avoid lava areas
var worldX = gridX * GRID_SIZE + GRID_SIZE / 2;
var worldY = gridY * GRID_SIZE + GRID_SIZE / 2;
for (var l = 0; l < activeLavaAreas.length; l++) {
var lavaArea = activeLavaAreas[l];
if (worldX >= lavaArea.x && worldX <= lavaArea.x + 1024 && worldY >= lavaArea.y && worldY <= lavaArea.y + 1366) {
validPosition = false;
break;
}
}
}
currentFruit = new Fruit(randomType);
currentFruit.gridX = gridX;
currentFruit.gridY = gridY;
currentFruit.x = gridX * GRID_SIZE + GRID_SIZE / 2;
currentFruit.y = gridY * GRID_SIZE + GRID_SIZE / 2;
game.addChild(currentFruit);
}
// Move snake
function moveSnake() {
if (!gameRunning) return;
var head = snake[0];
// Autopilot logic
if (autoPilotActive && (growthPoints.length > 0 || currentFruit)) {
// Function to check if a position is safe
var isSafePosition = function isSafePosition(gridX, gridY) {
// Check walls
if (gridX <= 0 || gridX >= COLS - 1 || gridY <= 0 || gridY >= ROWS - 1) {
return false;
}
// Check snake body collision
for (var i = 1; i < snake.length; i++) {
if (snake[i].gridX === gridX && snake[i].gridY === gridY) {
return false;
}
}
// In autopilot mode, avoid all deadly objects
if (autoPilotActive) {
var worldX = gridX * GRID_SIZE + GRID_SIZE / 2;
var worldY = gridY * GRID_SIZE + GRID_SIZE / 2;
// Check spike collisions
for (var s = 0; s < activeSpikes.length; s++) {
var spike = activeSpikes[s];
var spikeDistance = Math.sqrt(Math.pow(spike.x - worldX, 2) + Math.pow(spike.y - worldY, 2));
if (spikeDistance < 150) {
// Give extra buffer for autopilot
return false;
}
}
// Check lava area collisions
for (var l = 0; l < activeLavaAreas.length; l++) {
var lavaArea = activeLavaAreas[l];
if (!lavaArea.isWarning) {
// Only avoid active lava
if (worldX >= lavaArea.x - 80 && worldX <= lavaArea.x + 1024 + 80 && worldY >= lavaArea.y - 80 && worldY <= lavaArea.y + 1366 + 80) {
return false;
}
}
}
// Check boss rock collisions
for (var r = 0; r < bossRocks.length; r++) {
var rock = bossRocks[r];
var rockDistance = Math.sqrt(Math.pow(rock.x - worldX, 2) + Math.pow(rock.y - worldY, 2));
if (rockDistance < 120) {
return false;
}
}
// Check boss collision
if (bossActive && currentBoss) {
var bossDistance = Math.sqrt(Math.pow(currentBoss.x - worldX, 2) + Math.pow(currentBoss.y - worldY, 2));
if (bossDistance < 250) {
return false;
}
}
// Check bird boss collision
if (birdBossActive && currentBirdBoss && !purpleSnakeActive) {
var birdDistance = Math.sqrt(Math.pow(currentBirdBoss.x - worldX, 2) + Math.pow(currentBirdBoss.y - worldY, 2));
if (birdDistance < 200) {
return false;
}
}
// Check enemy snake collision
if (activeEnemySnake) {
// Check enemy head
var enemyHeadDistance = Math.sqrt(Math.pow(activeEnemySnake.x - worldX, 2) + Math.pow(activeEnemySnake.y - worldY, 2));
if (enemyHeadDistance < 80) {
return false;
}
// Check enemy body segments
for (var es = 0; es < activeEnemySnake.segments.length; es++) {
var segment = activeEnemySnake.segments[es];
var segmentDistance = Math.sqrt(Math.pow(segment.x - worldX, 2) + Math.pow(segment.y - worldY, 2));
if (segmentDistance < 80) {
return false;
}
}
}
}
return true;
};
// Priority 1: Target growth points if available
var targetX, targetY;
if (growthPoints.length > 0) {
// Find closest growth point
var closestGrowthPoint = growthPoints[0];
var closestDistance = Math.abs(closestGrowthPoint.gridX - head.gridX) + Math.abs(closestGrowthPoint.gridY - head.gridY);
for (var g = 1; g < growthPoints.length; g++) {
var distance = Math.abs(growthPoints[g].gridX - head.gridX) + Math.abs(growthPoints[g].gridY - head.gridY);
if (distance < closestDistance) {
closestGrowthPoint = growthPoints[g];
closestDistance = distance;
}
}
targetX = closestGrowthPoint.gridX;
targetY = closestGrowthPoint.gridY;
} else {
// Priority 2: Target fruit if no growth points available
targetX = currentFruit.gridX;
targetY = currentFruit.gridY;
}
var deltaX = targetX - head.gridX;
var deltaY = targetY - head.gridY;
var possibleDirections = [];
var currentDirection = snakeDirection;
// Calculate potential moves towards fruit
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Prioritize horizontal movement
if (deltaX > 0) {
possibleDirections.push({
x: 1,
y: 0
}); // Right
} else if (deltaX < 0) {
possibleDirections.push({
x: -1,
y: 0
}); // Left
}
// Add vertical as backup
if (deltaY > 0) {
possibleDirections.push({
x: 0,
y: 1
}); // Down
} else if (deltaY < 0) {
possibleDirections.push({
x: 0,
y: -1
}); // Up
}
} else {
// Prioritize vertical movement
if (deltaY > 0) {
possibleDirections.push({
x: 0,
y: 1
}); // Down
} else if (deltaY < 0) {
possibleDirections.push({
x: 0,
y: -1
}); // Up
}
// Add horizontal as backup
if (deltaX > 0) {
possibleDirections.push({
x: 1,
y: 0
}); // Right
} else if (deltaX < 0) {
possibleDirections.push({
x: -1,
y: 0
}); // Left
}
}
// Add remaining directions as last resort
var allDirections = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
for (var i = 0; i < allDirections.length; i++) {
var dir = allDirections[i];
var found = false;
for (var j = 0; j < possibleDirections.length; j++) {
if (possibleDirections[j].x === dir.x && possibleDirections[j].y === dir.y) {
found = true;
break;
}
}
if (!found) {
possibleDirections.push(dir);
}
}
// Find first safe direction with multi-step lookahead
var newDirection = currentDirection;
for (var i = 0; i < possibleDirections.length; i++) {
var testDir = possibleDirections[i];
// Don't reverse direction
if (testDir.x === -currentDirection.x && testDir.y === -currentDirection.y) {
continue;
}
var testX = head.gridX + testDir.x;
var testY = head.gridY + testDir.y;
if (isSafePosition(testX, testY)) {
// For autopilot, also check if the next move after this one is safe (lookahead)
if (autoPilotActive) {
var canContinue = false;
var nextDirections = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
for (var nd = 0; nd < nextDirections.length; nd++) {
var nextDir = nextDirections[nd];
// Don't check reverse of current test direction
if (nextDir.x === -testDir.x && nextDir.y === -testDir.y) {
continue;
}
var nextX = testX + nextDir.x;
var nextY = testY + nextDir.y;
if (isSafePosition(nextX, nextY)) {
canContinue = true;
break;
}
}
if (canContinue) {
newDirection = testDir;
break;
}
} else {
newDirection = testDir;
break;
}
}
}
snakeDirection = newDirection;
nextDirection = snakeDirection;
}
var newGridX = head.gridX + snakeDirection.x;
var newGridY = head.gridY + snakeDirection.y;
// Check wall collision
if (newGridX <= 0 || newGridX >= COLS - 1 || newGridY <= 0 || newGridY >= ROWS - 1) {
gameOver();
return;
}
// Check self collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === newGridX && snake[i].gridY === newGridY) {
gameOver();
return;
}
}
// Check fruit collision
var ateFood = false;
if (currentFruit && currentFruit.gridX === newGridX && currentFruit.gridY === newGridY) {
// Only apple makes snake grow, other fruits don't
ateFood = currentFruit.fruitType === 'apple';
handleFruitEaten(currentFruit.fruitType);
}
// Check growth point collision
for (var i = growthPoints.length - 1; i >= 0; i--) {
var growthPoint = growthPoints[i];
if (growthPoint.gridX === newGridX && growthPoint.gridY === newGridY) {
// Apply X2 multiplier for growth (ateFood effect)
if (x2MultiplierActive) {
ateFood = true; // X2 makes growth points also grow the snake
} else {
ateFood = true;
}
LK.getSound('eat').play();
// Apply X2 multiplier for score
var pointsToAdd = x2MultiplierActive ? 2 : 1;
LK.setScore(LK.getScore() + pointsToAdd);
scoreTxt.setText('Score: ' + LK.getScore());
growthPoint.destroy();
growthPoints.splice(i, 1);
break;
}
}
// Move snake
var newHead = new SnakeHead();
newHead.gridX = newGridX;
newHead.gridY = newGridY;
newHead.x = newGridX * GRID_SIZE + GRID_SIZE / 2;
newHead.y = newGridY * GRID_SIZE + GRID_SIZE / 2;
newHead.setThickness(snakeThickness);
// Convert old head to body
var oldHead = snake[0];
// Transfer crown status to new head
if (oldHead.hasCrown) {
newHead.showCrown();
}
// Transfer shield status to new head
if (oldHead.hasShield) {
newHead.activateShield(); // Add to new head first
oldHead.removeShield(); // Remove from old head after
}
oldHead.destroy();
var newBody = new SnakeSegment();
newBody.gridX = oldHead.gridX;
newBody.gridY = oldHead.gridY;
newBody.x = oldHead.x;
newBody.y = oldHead.y;
newBody.setThickness(snakeThickness);
game.addChild(newBody);
snake[0] = newBody;
// Add new head
snake.unshift(newHead);
game.addChild(newHead);
if (!ateFood) {
// Remove tail
var tail = snake.pop();
tail.destroy();
}
// Update thickness for all segments
for (var i = 0; i < snake.length; i++) {
snake[i].setThickness(snakeThickness);
}
}
// Handle fruit eaten
function handleFruitEaten(fruitType) {
LK.getSound('eat').play();
// All fruits give 5 points (apply X2 multiplier if active)
var pointsToAdd = x2MultiplierActive ? 10 : 5;
LK.setScore(LK.getScore() + pointsToAdd);
if (fruitType === 'apple') {
// Apple: makes snake grow (ateFood stays true)
LK.getSound('powerup').play();
} else if (fruitType === 'pear') {
snakeThickness += 0.2;
LK.getSound('powerup').play();
} else if (fruitType === 'banana') {
snakeSpeed += 1;
LK.getSound('powerup').play();
} else if (fruitType === 'multicolor') {
// Activate autopilot for 10 seconds
autoPilotActive = true;
autoPilotEndTime = Date.now() + 10000;
LK.getSound('powerup').play();
// Clean up any existing countdown
if (countdownText) {
countdownText.destroy();
countdownText = null;
}
// Show autopilot activation feedback
LK.effects.flashScreen(0x00ff00, 500);
} else if (fruitType === 'coconut') {
// Activate coconut power for 10 seconds - allows multiple growth points
coconutPowerActive = true;
coconutPowerEndTime = Date.now() + 10000;
lastGrowthPointSpawn = Date.now();
LK.getSound('powerup').play();
// Show coconut power activation feedback
LK.effects.flashScreen(0xffffff, 500);
} else if (fruitType === 'orange') {
// Activate shield for one-time protection
var head = snake[0];
head.activateShield();
LK.getSound('powerup').play();
// Show orange power activation feedback
LK.effects.flashScreen(0xff8800, 500);
}
scoreTxt.setText('Score: ' + LK.getScore());
powerTxt.setText('Speed: ' + snakeSpeed + 'x | Thickness: ' + Math.round(snakeThickness * 10) / 10 + 'x');
// Update missions - fruits eaten
if (!missions.fruitsEaten.completed) {
missions.fruitsEaten.current++;
storage.fruitsEaten = missions.fruitsEaten.current;
if (missions.fruitsEaten.current >= missions.fruitsEaten.target) {
missions.fruitsEaten.completed = true;
storage.fruitsCompleted = true;
}
}
// Update missions - points scored
if (!missions.pointsScored.completed && LK.getScore() >= missions.pointsScored.target) {
missions.pointsScored.current = LK.getScore();
missions.pointsScored.completed = true;
storage.pointsScored = missions.pointsScored.current;
storage.pointsCompleted = true;
}
updateMissionsDisplay();
currentFruit.destroy();
currentFruit = null;
spawnFruit();
}
// Update missions display
function updateMissionsDisplay() {
if (missionsTable) {
missionsTable.setText('MISSIONS:\n' + '• Eat 5 fruits: ' + missions.fruitsEaten.current + '/' + missions.fruitsEaten.target + (missions.fruitsEaten.completed ? ' ✓' : '') + '\n' + '• Score 300 points: ' + Math.min(missions.pointsScored.current, LK.getScore()) + '/' + missions.pointsScored.target + (missions.pointsScored.completed ? ' ✓' : '') + '\n' + '• Kill the boss: ' + missions.bossKilled.current + '/' + missions.bossKilled.target + (missions.bossKilled.completed ? ' ✓' : '') + '\n' + '• Survive 4 events: ' + missions.eventsLived.current + '/' + missions.eventsLived.target + (missions.eventsLived.completed ? ' ✓' : ''));
}
}
// Spawn X2 event
function spawnX2Event() {
// 50% probability check - only spawn X2 powerup half the time
if (Math.random() < 0.5) {
// Clear any existing X2 powerups
for (var i = activeX2Powerups.length - 1; i >= 0; i--) {
activeX2Powerups[i].destroy();
}
activeX2Powerups = [];
// Spawn X2 powerup at random location
var x2Powerup = new X2Powerup();
x2Powerup.x = Math.random() * (GAME_WIDTH - 200) + 100;
x2Powerup.y = Math.random() * (GAME_HEIGHT - 200) + 100;
activeX2Powerups.push(x2Powerup);
game.addChild(x2Powerup);
}
}
// Spawn random event
function spawnEvent() {
// Clear any existing events first to ensure only one event at a time
for (var i = activeSpikes.length - 1; i >= 0; i--) {
activeSpikes[i].destroy();
}
activeSpikes = [];
for (var i = activeLavaAreas.length - 1; i >= 0; i--) {
activeLavaAreas[i].destroy();
}
activeLavaAreas = [];
// Clear enemy snake if active
if (activeEnemySnake) {
activeEnemySnake.destroy();
activeEnemySnake = null;
}
// Update events survived mission
eventsCounter++;
if (!missions.eventsLived.completed) {
missions.eventsLived.current = eventsCounter;
storage.eventsLived = missions.eventsLived.current;
if (missions.eventsLived.current >= missions.eventsLived.target) {
missions.eventsLived.completed = true;
storage.eventsCompleted = true;
}
updateMissionsDisplay();
}
var eventType = Math.random();
if (eventType < 0.33) {
// Spawn bouncing spike
var spike = new Spike();
spike.x = Math.random() * (GAME_WIDTH - 100) + 50;
spike.y = Math.random() * (GAME_HEIGHT - 100) + 50;
activeSpikes.push(spike);
game.addChild(spike);
} else if (eventType < 0.66) {
// Spawn lava area in one quarter of the screen
var lavaArea = new LavaArea();
var quarter = Math.floor(Math.random() * 4);
switch (quarter) {
case 0:
// Top-left
lavaArea.x = 20;
lavaArea.y = 20;
break;
case 1:
// Top-right
lavaArea.x = GAME_WIDTH / 2;
lavaArea.y = 20;
break;
case 2:
// Bottom-left
lavaArea.x = 20;
lavaArea.y = GAME_HEIGHT / 2;
break;
case 3:
// Bottom-right
lavaArea.x = GAME_WIDTH / 2;
lavaArea.y = GAME_HEIGHT / 2;
break;
}
activeLavaAreas.push(lavaArea);
game.addChild(lavaArea);
LK.getSound('lava_warning').play();
} else {
// Spawn enemy snake
activeEnemySnake = new EnemySnake();
// Spawn on opposite side from player
var head = snake[0];
var spawnX, spawnY;
if (head.gridX < COLS / 2) {
spawnX = Math.floor(COLS * 0.75);
} else {
spawnX = Math.floor(COLS * 0.25);
}
if (head.gridY < ROWS / 2) {
spawnY = Math.floor(ROWS * 0.75);
} else {
spawnY = Math.floor(ROWS * 0.25);
}
activeEnemySnake.gridX = spawnX;
activeEnemySnake.gridY = spawnY;
activeEnemySnake.x = spawnX * GRID_SIZE + GRID_SIZE / 2;
activeEnemySnake.y = spawnY * GRID_SIZE + GRID_SIZE / 2;
game.addChild(activeEnemySnake);
}
}
// Game over
function gameOver() {
gameRunning = false;
LK.showGameOver();
}
// Touch controls
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
if (!gameRunning) return;
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
var minSwipeDistance = 50;
if (Math.abs(deltaX) > minSwipeDistance || Math.abs(deltaY) > minSwipeDistance) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0 && snakeDirection.x !== -1) {
nextDirection = {
x: 1,
y: 0
}; // Right
} else if (deltaX < 0 && snakeDirection.x !== 1) {
nextDirection = {
x: -1,
y: 0
}; // Left
}
} else {
// Vertical swipe
if (deltaY > 0 && snakeDirection.y !== -1) {
nextDirection = {
x: 0,
y: 1
}; // Down
} else if (deltaY < 0 && snakeDirection.y !== 1) {
nextDirection = {
x: 0,
y: -1
}; // Up
}
}
}
};
// Initialize game
initSnake();
spawnFruit();
// Spawn initial growth points
for (var i = 0; i < 3; i++) {
spawnGrowthPoint();
}
// Main game loop
game.update = function () {
if (!gameRunning) return;
moveCounter++;
// Update timer display
var gameElapsed = Date.now() - gameStartTime;
var minutes = Math.floor(gameElapsed / 60000);
var seconds = Math.floor(gameElapsed % 60000 / 1000);
var timeString = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText('Time: ' + timeString);
// Update next event display
var currentTime = Date.now();
if (birdBossActive && purpleSnakeActive) {
nextEventTxt.setText('Purple Snake is capturing the Bird!');
} else if (birdBossActive && !purpleSnakeActive) {
var timeUntilPurple = Math.max(0, 35000 - (currentTime - birdBossStartTime));
var purpleSecondsLeft = Math.ceil(timeUntilPurple / 1000);
nextEventTxt.setText('Purple Snake arrives in ' + purpleSecondsLeft + 's');
} else if (!bossActive && !bossDefeated) {
var timeUntilBoss = Math.max(0, 60000 - (currentTime - gameStartTime));
var bossSecondsLeft = Math.ceil(timeUntilBoss / 1000);
nextEventTxt.setText('Boss arrives in ' + bossSecondsLeft + 's');
} else if (!birdBossActive && !birdBossDefeated && bossDefeated) {
var timeUntilBird = Math.max(0, 180000 - (currentTime - gameStartTime));
var birdSecondsLeft = Math.ceil(timeUntilBird / 1000);
nextEventTxt.setText('Bird Boss arrives in ' + birdSecondsLeft + 's');
} else if (!bossActive && bossDefeated) {
// Calculate time until next regular event
var timeUntilEvent = Math.max(0, eventInterval - (currentTime - lastEventTime));
var eventSecondsLeft = Math.ceil(timeUntilEvent / 1000);
// Calculate time until next X2 event
var timeUntilX2 = Math.max(0, x2EventInterval - (currentTime - lastX2EventTime));
var x2SecondsLeft = Math.ceil(timeUntilX2 / 1000);
// Show the next event that will happen first
if (timeUntilEvent <= timeUntilX2) {
nextEventTxt.setText('Next Event: Spike/Lava/Enemy in ' + eventSecondsLeft + 's');
} else {
nextEventTxt.setText('Next Event: X2 Power in ' + x2SecondsLeft + 's');
}
} else if (bossWarningActive) {
nextEventTxt.setText('Next Event: BOSS INCOMING!');
} else if (bossActive) {
nextEventTxt.setText('Next Event: BOSS BATTLE!');
} else {
nextEventTxt.setText('Next Event: Boss defeated');
}
// Handle autopilot countdown
if (autoPilotActive) {
var timeLeft = autoPilotEndTime - Date.now();
if (timeLeft <= 0) {
autoPilotActive = false;
if (countdownText) {
countdownText.destroy();
countdownText = null;
}
} else if (timeLeft <= 3000 && !countdownText) {
// Show countdown when 3 seconds left
countdownText = new Text2('3', {
size: 120,
fill: 0xFF0000
});
countdownText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(countdownText);
}
// Update countdown text
if (countdownText) {
var secondsLeft = Math.ceil(timeLeft / 1000);
if (secondsLeft > 0) {
countdownText.setText(secondsLeft.toString());
// Animate countdown
tween(countdownText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (countdownText) {
tween(countdownText, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
}
});
} else {
countdownText.destroy();
countdownText = null;
}
}
}
// Update direction (only if not in autopilot)
if (!autoPilotActive) {
snakeDirection = nextDirection;
}
// Event spawning timer - only spawn events if boss is not active
var currentTime = Date.now();
if (!bossActive && currentTime - lastEventTime >= eventInterval) {
lastEventTime = currentTime;
spawnEvent();
}
// X2 event spawning timer - independent of regular events
if (!bossActive && currentTime - lastX2EventTime >= x2EventInterval) {
lastX2EventTime = currentTime;
spawnX2Event();
}
// Check spike collisions with snake
for (var i = activeSpikes.length - 1; i >= 0; i--) {
var spike = activeSpikes[i];
var head = snake[0];
// Check collision with snake head - adjust collision radius based on snake thickness
var distance = Math.sqrt(Math.pow(spike.x - head.x, 2) + Math.pow(spike.y - head.y, 2));
var collisionRadius = 60 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
// Push spike away or destroy it
spike.destroy();
activeSpikes.splice(i, 1);
continue;
}
// No shield - game over
gameOver();
return;
}
}
// Check lava area collisions with snake
for (var i = activeLavaAreas.length - 1; i >= 0; i--) {
var lavaArea = activeLavaAreas[i];
// Only check collision if lava is active (not warning)
if (!lavaArea.isWarning) {
var head = snake[0];
// Check if snake head is inside lava area (larger quarter-screen area)
if (head.x >= lavaArea.x && head.x <= lavaArea.x + 1024 && head.y >= lavaArea.y && head.y <= lavaArea.y + 1366) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
continue;
}
// No shield - game over
gameOver();
return;
}
}
}
// Handle coconut power and growth point spawning
var currentTime = Date.now();
if (coconutPowerActive) {
// Check if coconut power has expired
if (currentTime >= coconutPowerEndTime) {
coconutPowerActive = false;
}
// Spawn growth points every 2.5 seconds during coconut power
if (currentTime - lastGrowthPointSpawn >= 2500) {
spawnGrowthPoint();
lastGrowthPointSpawn = currentTime;
}
} else {
// Normal mode: spawn growth point only if none exist
if (growthPoints.length === 0) {
spawnGrowthPoint();
}
}
// Bird boss timing and logic
var timeSinceStart = Date.now() - gameStartTime;
// Start bird boss at 3 minutes
if (timeSinceStart >= 180000 && !birdBossActive && !birdBossDefeated) {
birdBossActive = true;
birdBossStartTime = Date.now();
// Spawn bird boss at top of screen
currentBirdBoss = new BirdBoss();
currentBirdBoss.x = GAME_WIDTH / 2;
currentBirdBoss.y = 100;
game.addChild(currentBirdBoss);
}
// Purple snake timing - 35 seconds after bird boss starts
if (birdBossActive && !purpleSnakeActive && Date.now() - birdBossStartTime >= 35000) {
purpleSnakeActive = true;
purpleSnakeSpawnTime = Date.now();
// Spawn purple snake at edge of screen
currentPurpleSnake = new PurpleSnake();
currentPurpleSnake.x = GAME_WIDTH - 100;
currentPurpleSnake.y = GAME_HEIGHT / 2;
game.addChild(currentPurpleSnake);
}
// Check if purple snake reaches bird boss (defeats bird boss)
if (birdBossActive && purpleSnakeActive && currentBirdBoss && currentPurpleSnake) {
var distance = Math.sqrt(Math.pow(currentPurpleSnake.x - currentBirdBoss.x, 2) + Math.pow(currentPurpleSnake.y - currentBirdBoss.y, 2));
if (distance < 150) {
// Purple snake captures bird boss
LK.effects.flashScreen(0x800080, 1000);
// Animate both flying away
tween(currentBirdBoss, {
x: currentBirdBoss.x,
y: -200
}, {
duration: 2000,
easing: tween.easeIn
});
tween(currentPurpleSnake, {
x: currentPurpleSnake.x,
y: -200
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (currentBirdBoss && currentBirdBoss.parent) {
currentBirdBoss.destroy();
}
if (currentPurpleSnake && currentPurpleSnake.parent) {
currentPurpleSnake.destroy();
}
currentBirdBoss = null;
currentPurpleSnake = null;
}
});
birdBossActive = false;
birdBossDefeated = true;
purpleSnakeActive = false;
LK.setScore(LK.getScore() + 200);
scoreTxt.setText('Score: ' + LK.getScore());
}
}
// Boss battle timing and logic
// Start boss warning at 60 seconds
if (timeSinceStart >= 60000 && !bossWarningActive && !bossActive && !bossDefeated) {
bossWarningActive = true;
bossWarningStartTime = Date.now();
// Create warning lights in a perfect circle around boss spawn location
var bossSpawnX = GAME_WIDTH / 2;
var bossSpawnY = GAME_HEIGHT / 2;
var circleRadius = 500; // Distance from boss spawn center
for (var i = 0; i < 12; i++) {
var light = new WarningLight();
var angle = i / 12 * Math.PI * 2;
light.x = bossSpawnX + Math.cos(angle) * circleRadius;
light.y = bossSpawnY + Math.sin(angle) * circleRadius;
warningLights.push(light);
game.addChild(light);
}
}
// End warning phase and spawn boss after 5 seconds
if (bossWarningActive && Date.now() - bossWarningStartTime >= 5000) {
bossWarningActive = false;
bossActive = true;
// Remove warning lights
for (var i = warningLights.length - 1; i >= 0; i--) {
warningLights[i].destroy();
}
warningLights = [];
// Clear all active events when boss spawns
for (var i = activeSpikes.length - 1; i >= 0; i--) {
activeSpikes[i].destroy();
}
activeSpikes = [];
for (var i = activeLavaAreas.length - 1; i >= 0; i--) {
activeLavaAreas[i].destroy();
}
activeLavaAreas = [];
// Spawn boss
currentBoss = new PotatoBoss();
currentBoss.x = GAME_WIDTH / 2;
currentBoss.y = GAME_HEIGHT / 2;
game.addChild(currentBoss);
// Show health bar
bossHealthBarBg.visible = true;
bossHealthBar.visible = true;
}
// Update boss health bar
if (bossActive && currentBoss) {
var healthPercentage = currentBoss.health / currentBoss.maxHealth;
bossHealthBar.scaleX = healthPercentage;
}
// Hide health bar when boss is defeated
if (bossDefeated) {
bossHealthBarBg.visible = false;
bossHealthBar.visible = false;
}
// Check boss rock collisions with snake
if (bossActive) {
for (var i = bossRocks.length - 1; i >= 0; i--) {
var rock = bossRocks[i];
// Remove rocks that have been destroyed due to max bounces
if (!rock.parent) {
bossRocks.splice(i, 1);
continue;
}
var head = snake[0];
var distance = Math.sqrt(Math.pow(rock.x - head.x, 2) + Math.pow(rock.y - head.y, 2));
var collisionRadius = 70 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
// Destroy the rock
rock.destroy();
bossRocks.splice(i, 1);
continue;
}
// No shield - game over
gameOver();
return;
}
}
}
// Check boss rock collisions with boss (damage boss) - only after bouncing
if (bossActive && currentBoss) {
for (var i = bossRocks.length - 1; i >= 0; i--) {
var rock = bossRocks[i];
// Comprehensive null check for currentBoss before any property access
if (!currentBoss || !currentBoss.parent || currentBoss.x === undefined || currentBoss.y === undefined) {
continue;
}
var currentIntersectingBoss = Math.sqrt(Math.pow(rock.x - currentBoss.x, 2) + Math.pow(rock.y - currentBoss.y, 2)) < 190;
// Only damage boss if rock has bounced at least once and just started intersecting
if (rock.bounceCount > 0 && !rock.lastIntersectingBoss && currentIntersectingBoss) {
// Deal 25% damage (1.25 damage out of 5 total health)
var damage = Math.ceil(currentBoss.maxHealth * 0.25);
for (var d = 0; d < damage; d++) {
if (currentBoss.health > 0) {
currentBoss.takeDamage();
}
}
// Remove the rock
rock.destroy();
bossRocks.splice(i, 1);
continue;
}
// Update last intersecting state
rock.lastIntersectingBoss = currentIntersectingBoss;
}
}
// Check snake collision with boss (damage boss)
if (bossActive && currentBoss) {
var head = snake[0];
var distance = Math.sqrt(Math.pow(currentBoss.x - head.x, 2) + Math.pow(currentBoss.y - head.y, 2));
var collisionRadius = 170 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
currentBoss.takeDamage();
// Push snake away to prevent multiple hits
var pushAngle = Math.atan2(head.y - currentBoss.y, head.x - currentBoss.x);
var pushDistance = 150 * snakeThickness; // Scale push distance with snake thickness
var newX = currentBoss.x + Math.cos(pushAngle) * pushDistance;
var newY = currentBoss.y + Math.sin(pushAngle) * pushDistance;
// Convert to grid position
var gridX = Math.round(newX / GRID_SIZE);
var gridY = Math.round(newY / GRID_SIZE);
// Ensure within bounds
gridX = Math.max(1, Math.min(COLS - 2, gridX));
gridY = Math.max(1, Math.min(ROWS - 2, gridY));
head.gridX = gridX;
head.gridY = gridY;
head.x = gridX * GRID_SIZE + GRID_SIZE / 2;
head.y = gridY * GRID_SIZE + GRID_SIZE / 2;
}
}
// Check snake collision with bird boss
if (birdBossActive && currentBirdBoss && !purpleSnakeActive) {
var head = snake[0];
var distance = Math.sqrt(Math.pow(currentBirdBoss.x - head.x, 2) + Math.pow(currentBirdBoss.y - head.y, 2));
var collisionRadius = 140 * snakeThickness;
if (distance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
} else {
gameOver();
return;
}
}
}
// Update points mission continuously
if (!missions.pointsScored.completed) {
missions.pointsScored.current = LK.getScore();
storage.pointsScored = missions.pointsScored.current;
if (missions.pointsScored.current >= missions.pointsScored.target) {
missions.pointsScored.completed = true;
storage.pointsCompleted = true;
updateMissionsDisplay();
}
}
// Check X2 powerup collision
if (activeX2Powerups.length > 0 && snake.length > 0) {
var head = snake[0];
for (var i = activeX2Powerups.length - 1; i >= 0; i--) {
var x2Powerup = activeX2Powerups[i];
var distance = Math.sqrt(Math.pow(x2Powerup.x - head.x, 2) + Math.pow(x2Powerup.y - head.y, 2));
var collisionRadius = 80 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// X2 powerup collected
x2MultiplierActive = true;
x2MultiplierEndTime = Date.now() + 10000; // 10 seconds
LK.getSound('powerup').play();
LK.effects.flashScreen(0xff0000, 500);
x2Powerup.destroy();
activeX2Powerups.splice(i, 1);
break;
}
}
}
// Handle X2 multiplier expiration
if (x2MultiplierActive && Date.now() >= x2MultiplierEndTime) {
x2MultiplierActive = false;
}
// Check enemy snake collision with player
if (activeEnemySnake && snake.length > 0) {
var head = snake[0];
var headDistance = Math.sqrt(Math.pow(activeEnemySnake.x - head.x, 2) + Math.pow(activeEnemySnake.y - head.y, 2));
var collisionRadius = 50 * snakeThickness;
// Check collision with enemy snake head
if (headDistance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
} else {
gameOver();
return;
}
}
// Check collision with enemy snake body segments
for (var i = 0; i < activeEnemySnake.segments.length; i++) {
var segment = activeEnemySnake.segments[i];
var segmentDistance = Math.sqrt(Math.pow(segment.x - head.x, 2) + Math.pow(segment.y - head.y, 2));
if (segmentDistance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
break;
} else {
gameOver();
return;
}
}
}
}
// Check crown drop collision
if (crownDrop && snake.length > 0) {
var head = snake[0];
var distance = Math.sqrt(Math.pow(crownDrop.x - head.x, 2) + Math.pow(crownDrop.y - head.y, 2));
var collisionRadius = 80 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// Crown collected
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore());
LK.getSound('powerup').play();
// Show crown on snake head
head.showCrown();
crownDrop.destroy();
crownDrop = null;
}
}
// Check if all missions are completed for win condition
if (missions.fruitsEaten.completed && missions.pointsScored.completed && missions.bossKilled.completed && missions.eventsLived.completed) {
LK.showYouWin();
return;
}
// Move snake based on speed
var moveInterval = Math.max(3, 15 - snakeSpeed);
if (moveCounter >= moveInterval) {
moveCounter = 0;
moveSnake();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BirdBoss = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('birdBoss', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.moveTimer = 0;
self.moveInterval = 240; // 4 seconds at 60fps
self.lastTargetX = GAME_WIDTH / 2;
self.lastTargetY = GAME_HEIGHT / 2;
self.speed = 12;
self.isMoving = false;
self.targetX = GAME_WIDTH / 2;
self.targetY = GAME_HEIGHT / 2;
self.update = function () {
self.moveTimer++;
// Every 4 seconds, set new target to snake's current position
if (self.moveTimer >= self.moveInterval) {
self.moveTimer = 0;
if (snake.length > 0) {
var head = snake[0];
self.targetX = head.x;
self.targetY = head.y;
self.isMoving = true;
}
}
// Move towards target if moving
if (self.isMoving) {
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 10) {
var moveX = deltaX / distance * self.speed;
var moveY = deltaY / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
self.isMoving = false;
}
}
};
self.takeDamage = function () {
self.health--;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.setScore(LK.getScore() + 150);
scoreTxt.setText('Score: ' + LK.getScore());
birdBossActive = false;
birdBossDefeated = true;
self.destroy();
};
return self;
});
var BossRock = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bossRock', {
anchorX: 0.5,
anchorY: 0.5
});
// Initialize velocity (will be set when rock is created)
self.velocityX = 0;
self.velocityY = 0;
self.bounceCount = 0;
self.maxBounces = 3;
self.lastX = 0;
self.lastY = 0;
self.lastIntersectingBoss = false;
self.setTarget = function (startX, startY, targetX, targetY) {
// Calculate velocity towards target
var angle = Math.atan2(targetY - startY, targetX - startX);
self.velocityX = Math.cos(angle) * 8;
self.velocityY = Math.sin(angle) * 8;
self.lastX = startX;
self.lastY = startY;
};
self.update = function () {
// Store last position
self.lastX = self.x;
self.lastY = self.y;
// Move rock
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off walls
if (self.x <= 50 || self.x >= GAME_WIDTH - 50) {
self.velocityX = -self.velocityX;
self.x = Math.max(50, Math.min(GAME_WIDTH - 50, self.x));
self.bounceCount++;
self.updateGrayness();
if (self.bounceCount >= self.maxBounces) {
self.destroy();
return;
}
}
if (self.y <= 50 || self.y >= GAME_HEIGHT - 50) {
self.velocityY = -self.velocityY;
self.y = Math.max(50, Math.min(GAME_HEIGHT - 50, self.y));
self.bounceCount++;
self.updateGrayness();
if (self.bounceCount >= self.maxBounces) {
self.destroy();
return;
}
}
};
self.updateGrayness = function () {
// Make rock grayer with each bounce
var grayness = 0.7 + self.bounceCount * 0.1;
var grayColor = Math.floor(0x69 * grayness) << 16 | Math.floor(0x69 * grayness) << 8 | Math.floor(0x69 * grayness);
graphics.tint = grayColor;
};
return self;
});
var CrownDrop = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('crownDrop', {
anchorX: 0.5,
anchorY: 0.5
});
// Add gentle pulsing animation
self.animationCounter = 0;
self.baseScale = 1.0;
self.update = function () {
// Gentle pulsing animation
self.animationCounter += 0.08;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.15;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
return self;
});
var EnemySnake = Container.expand(function () {
var self = Container.call(this);
// Create enemy snake head
var headGraphics = self.attachAsset('enemySnakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
// Enemy snake properties
self.segments = [];
self.direction = {
x: 1,
y: 0
};
self.speed = 6;
self.moveCounter = 0;
self.gridX = 0;
self.gridY = 0;
self.lastTargetTime = 0;
self.targetFruit = null;
self.targetGrowthPoint = null;
// Create initial body segments
for (var i = 0; i < 3; i++) {
var segment = new Container();
var segmentGraphics = segment.attachAsset('enemySnakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
segment.gridX = self.gridX - (i + 1);
segment.gridY = self.gridY;
segment.x = segment.gridX * GRID_SIZE + GRID_SIZE / 2;
segment.y = segment.gridY * GRID_SIZE + GRID_SIZE / 2;
self.segments.push(segment);
game.addChild(segment);
}
self.findNearestTarget = function () {
var targets = [];
// Add fruits as targets
if (currentFruit) {
targets.push({
type: 'fruit',
obj: currentFruit,
x: currentFruit.gridX,
y: currentFruit.gridY,
priority: 2
});
}
// Add growth points as targets
for (var i = 0; i < growthPoints.length; i++) {
targets.push({
type: 'growthPoint',
obj: growthPoints[i],
x: growthPoints[i].gridX,
y: growthPoints[i].gridY,
priority: 1
});
}
if (targets.length === 0) return null;
// Find closest target
var closest = targets[0];
var closestDist = Math.abs(closest.x - self.gridX) + Math.abs(closest.y - self.gridY);
for (var i = 1; i < targets.length; i++) {
var dist = Math.abs(targets[i].x - self.gridX) + Math.abs(targets[i].y - self.gridY);
if (dist < closestDist || dist === closestDist && targets[i].priority > closest.priority) {
closest = targets[i];
closestDist = dist;
}
}
return closest;
};
self.moveTowardsTarget = function () {
var target = self.findNearestTarget();
if (!target) {
// Random movement if no target
var directions = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
self.direction = directions[Math.floor(Math.random() * directions.length)];
return;
}
var deltaX = target.x - self.gridX;
var deltaY = target.y - self.gridY;
// Choose direction based on larger delta
if (Math.abs(deltaX) > Math.abs(deltaY)) {
self.direction = {
x: deltaX > 0 ? 1 : -1,
y: 0
};
} else {
self.direction = {
x: 0,
y: deltaY > 0 ? 1 : -1
};
}
};
self.update = function () {
self.moveCounter++;
var moveInterval = Math.max(3, 15 - self.speed);
if (self.moveCounter >= moveInterval) {
self.moveCounter = 0;
// Update movement direction towards targets
self.moveTowardsTarget();
// Calculate new position
var newGridX = self.gridX + self.direction.x;
var newGridY = self.gridY + self.direction.y;
// Bounce off walls
if (newGridX <= 0 || newGridX >= COLS - 1) {
self.direction.x = -self.direction.x;
newGridX = self.gridX + self.direction.x;
}
if (newGridY <= 0 || newGridY >= ROWS - 1) {
self.direction.y = -self.direction.y;
newGridY = self.gridY + self.direction.y;
}
// Move body segments
for (var i = self.segments.length - 1; i > 0; i--) {
self.segments[i].gridX = self.segments[i - 1].gridX;
self.segments[i].gridY = self.segments[i - 1].gridY;
self.segments[i].x = self.segments[i].gridX * GRID_SIZE + GRID_SIZE / 2;
self.segments[i].y = self.segments[i].gridY * GRID_SIZE + GRID_SIZE / 2;
}
// Move first body segment to old head position
if (self.segments.length > 0) {
self.segments[0].gridX = self.gridX;
self.segments[0].gridY = self.gridY;
self.segments[0].x = self.gridX * GRID_SIZE + GRID_SIZE / 2;
self.segments[0].y = self.gridY * GRID_SIZE + GRID_SIZE / 2;
}
// Move head
self.gridX = newGridX;
self.gridY = newGridY;
self.x = self.gridX * GRID_SIZE + GRID_SIZE / 2;
self.y = self.gridY * GRID_SIZE + GRID_SIZE / 2;
// Check for fruit collision
if (currentFruit && currentFruit.gridX === self.gridX && currentFruit.gridY === self.gridY) {
// Steal points (reduce player score)
var currentScore = LK.getScore();
LK.setScore(Math.max(0, currentScore - 10));
scoreTxt.setText('Score: ' + LK.getScore());
// Remove the fruit
currentFruit.destroy();
currentFruit = null;
spawnFruit();
// Flash screen purple to indicate theft
LK.effects.flashScreen(0x8b008b, 300);
}
// Check for growth point collision
for (var i = growthPoints.length - 1; i >= 0; i--) {
if (growthPoints[i].gridX === self.gridX && growthPoints[i].gridY === self.gridY) {
// Steal points (reduce player score)
var currentScore = LK.getScore();
LK.setScore(Math.max(0, currentScore - 5));
scoreTxt.setText('Score: ' + LK.getScore());
// Remove the growth point
growthPoints[i].destroy();
growthPoints.splice(i, 1);
// Flash screen purple to indicate theft
LK.effects.flashScreen(0x8b008b, 300);
break;
}
}
}
};
self.destroy = function () {
// Destroy all body segments
for (var i = 0; i < self.segments.length; i++) {
if (self.segments[i].parent) {
self.segments[i].destroy();
}
}
self.segments = [];
// Destroy head
if (self.parent) {
Container.prototype.destroy.call(self);
}
};
return self;
});
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
var assetName = 'apple';
if (type === 'pear') assetName = 'pear';else if (type === 'banana') assetName = 'banana';else if (type === 'multicolor') assetName = 'multicolor';else if (type === 'coconut') assetName = 'coconut';else if (type === 'orange') assetName = 'orange';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.fruitType = type || 'apple';
// Add gentle pulsing animation to all fruits
self.animationCounter = 0;
self.baseScale = 1.0;
// Add color animation for multicolor fruit
if (type === 'multicolor') {
self.colorIndex = 0;
self.colors = [0xff3333, 0x44dd44, 0x3366ff, 0xffdd44, 0xff44dd, 0x44ddff];
self.update = function () {
// Color cycling for multicolor fruit
if (LK.ticks % 15 === 0) {
self.colorIndex = (self.colorIndex + 1) % self.colors.length;
graphics.tint = self.colors[self.colorIndex];
}
// Gentle pulsing animation
self.animationCounter += 0.08;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.15;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
} else {
self.update = function () {
// Gentle pulsing animation for regular fruits
self.animationCounter += 0.06;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.1;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
}
return self;
});
var GrowthPoint = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('growthPoint', {
anchorX: 0.5,
anchorY: 0.5
});
// Add gentle pulsing animation
self.animationCounter = 0;
self.baseScale = 1.0;
self.update = function () {
// Gentle pulsing animation
self.animationCounter += 0.1;
var pulseScale = self.baseScale + Math.sin(self.animationCounter) * 0.2;
graphics.scaleX = pulseScale;
graphics.scaleY = pulseScale;
};
return self;
});
var LavaArea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('lavaArea', {
anchorX: 0,
anchorY: 0
});
self.isWarning = true;
self.warningTime = 5000; // 5 seconds warning
self.startTime = Date.now();
// Set initial warning appearance
graphics.alpha = 0.3;
graphics.tint = 0xffff00; // Yellow warning
self.update = function () {
var elapsed = Date.now() - self.startTime;
if (self.isWarning && elapsed < self.warningTime) {
// Slower, more intermittent blinking warning effect
var blinkSpeed = 2; // Much slower blinking
var blinkCycle = Math.sin(LK.ticks * blinkSpeed * 0.1);
// More intermittent pattern - only blink when sine wave is positive
if (blinkCycle > 0) {
graphics.alpha = 0.3 + 0.4 * blinkCycle;
} else {
graphics.alpha = 0.1; // Very dim when not blinking
}
} else if (self.isWarning && elapsed >= self.warningTime) {
// Convert to lava
self.isWarning = false;
graphics.alpha = 1.0;
graphics.tint = 0xff4400; // Red lava
}
};
return self;
});
var PotatoBoss = Container.expand(function () {
var self = Container.call(this);
// Create boss body
var body = self.attachAsset('bossBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Create angry eyebrows
var leftEyebrow = self.attachAsset('bossEyebrow', {
anchorX: 0.5,
anchorY: 0.5,
x: -40,
y: -50
});
var rightEyebrow = self.attachAsset('bossEyebrow', {
anchorX: 0.5,
anchorY: 0.5,
x: 40,
y: -50
});
// Angle the eyebrows for angry look
leftEyebrow.rotation = 0.3;
rightEyebrow.rotation = -0.3;
// Create crown
var crown = self.attachAsset('bossCrown', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -80
});
self.health = 5;
self.maxHealth = 5;
self.shootTimer = 0;
self.shootInterval = 120; // 2 seconds at 60fps
self.hitFlashTimer = 0;
self.update = function () {
// Shooting logic
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
self.shootTimer = 0;
self.shootRock();
}
// Hit flash effect
if (self.hitFlashTimer > 0) {
self.hitFlashTimer--;
var flashIntensity = self.hitFlashTimer / 30;
body.tint = 0xFFFFFF * flashIntensity + 0xD2B48C * (1 - flashIntensity);
} else {
body.tint = 0xFFFFFF;
}
};
self.shootRock = function () {
if (!gameRunning || snake.length === 0) return;
// Shoot towards snake head
var head = snake[0];
var rock = new BossRock();
rock.x = self.x;
rock.y = self.y;
rock.setTarget(self.x, self.y, head.x, head.y);
bossRocks.push(rock);
game.addChild(rock);
LK.getSound('boss_shoot').play();
};
self.takeDamage = function () {
self.health--;
self.hitFlashTimer = 30;
LK.getSound('boss_hit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.getSound('boss_defeat').play();
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore());
bossActive = false;
bossDefeated = true;
// Drop crown at boss position
crownDrop = new CrownDrop();
crownDrop.x = self.x;
crownDrop.y = self.y;
game.addChild(crownDrop);
// Update missions - boss killed
if (!missions.bossKilled.completed) {
missions.bossKilled.current++;
storage.bossKilled = missions.bossKilled.current;
if (missions.bossKilled.current >= missions.bossKilled.target) {
missions.bossKilled.completed = true;
storage.bossCompleted = true;
}
updateMissionsDisplay();
}
// Clear boss and rocks
for (var i = bossRocks.length - 1; i >= 0; i--) {
bossRocks[i].destroy();
}
bossRocks = [];
self.destroy();
};
return self;
});
var PurpleSnake = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('purpleSnake', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetX = GAME_WIDTH / 2;
self.targetY = GAME_HEIGHT / 2;
self.update = function () {
// Move towards center of screen
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 5) {
var moveX = deltaX / distance * self.speed;
var moveY = deltaY / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
};
return self;
});
var SnakeHead = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('snakeHead', {
anchorX: 0.5,
anchorY: 0.5
});
// Create crown but make it invisible initially
var crown = self.attachAsset('snakeCrown', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -20
});
crown.visible = false; // Crown is hidden until collected
// Create shield but make it invisible initially
var shield = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -10
});
shield.visible = false; // Shield is hidden until orange is consumed
shield.alpha = 0.7; // Semi-transparent shield
self.thickness = 1;
self.animationCounter = 0;
self.baseThickness = 1;
self.hasCrown = false; // Track if snake has collected crown
self.hasShield = false; // Track if snake has shield protection
self.setThickness = function (newThickness) {
self.thickness = newThickness;
self.baseThickness = newThickness;
graphics.scaleX = newThickness;
graphics.scaleY = newThickness;
};
self.showCrown = function () {
self.hasCrown = true;
crown.visible = true;
};
self.activateShield = function () {
self.hasShield = true;
shield.visible = true;
shield.alpha = 0.7;
// Add shield pulsing animation
tween(shield, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (shield && shield.visible && self.hasShield) {
tween(shield, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeInOut,
loop: true,
yoyo: true
});
}
}
});
};
self.removeShield = function () {
self.hasShield = false;
shield.visible = false;
// Stop any active tweens on the shield
if (shield && shield.parent) {
tween.stop(shield);
shield.scaleX = 1;
shield.scaleY = 1;
}
};
self.update = function () {
// Subtle breathing animation for snake head
self.animationCounter += 0.05;
var breatheScale = self.baseThickness + Math.sin(self.animationCounter) * 0.05;
graphics.scaleX = breatheScale;
graphics.scaleY = breatheScale;
// Shield pulsing animation when active
if (self.hasShield && shield && shield.visible) {
shield.alpha = 0.5 + Math.sin(self.animationCounter * 3) * 0.2;
}
};
return self;
});
var SnakeSegment = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
self.thickness = 1;
self.setThickness = function (newThickness) {
self.thickness = newThickness;
graphics.scaleX = newThickness;
graphics.scaleY = newThickness;
};
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 3;
// Set random direction
var angle = Math.random() * Math.PI * 2;
self.velocityX = Math.cos(angle) * self.speed;
self.velocityY = Math.sin(angle) * self.speed;
self.update = function () {
// Move spike
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off walls
if (self.x <= 30 || self.x >= GAME_WIDTH - 30) {
self.velocityX = -self.velocityX;
self.x = Math.max(30, Math.min(GAME_WIDTH - 30, self.x));
LK.getSound('spike_bounce').play();
}
if (self.y <= 30 || self.y >= GAME_HEIGHT - 30) {
self.velocityY = -self.velocityY;
self.y = Math.max(30, Math.min(GAME_HEIGHT - 30, self.y));
LK.getSound('spike_bounce').play();
}
};
return self;
});
var WarningLight = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('warningLight', {
anchorX: 0.5,
anchorY: 0.5
});
self.blinkTimer = 0;
self.update = function () {
self.blinkTimer += 0.15;
// Dark intermittent blinking
graphics.alpha = Math.abs(Math.sin(self.blinkTimer)) < 0.3 ? 0.8 : 0.1;
// Ensure circular shape by maintaining equal scale
graphics.scaleX = graphics.scaleY;
};
return self;
});
var X2Powerup = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('x2Powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifeTime = 10000; // 10 seconds
self.startTime = Date.now();
self.blinkStartTime = 7000; // Start blinking at 7 seconds (3 seconds before end)
// Add bouncing movement properties
self.velocityX = 0;
self.velocityY = 0;
self.speed = 1.5; // Slow movement speed
// Set random direction
var angle = Math.random() * Math.PI * 2;
self.velocityX = Math.cos(angle) * self.speed;
self.velocityY = Math.sin(angle) * self.speed;
self.update = function () {
var elapsed = Date.now() - self.startTime;
// Check if power-up should be destroyed
if (elapsed >= self.lifeTime) {
self.destroy();
return;
}
// Move X2 powerup
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off walls
if (self.x <= 60 || self.x >= GAME_WIDTH - 60) {
self.velocityX = -self.velocityX;
self.x = Math.max(60, Math.min(GAME_WIDTH - 60, self.x));
}
if (self.y <= 60 || self.y >= GAME_HEIGHT - 60) {
self.velocityY = -self.velocityY;
self.y = Math.max(60, Math.min(GAME_HEIGHT - 60, self.y));
}
// Blinking effect in last 3 seconds
if (elapsed >= self.blinkStartTime) {
var blinkSpeed = 8; // Fast blinking
graphics.alpha = Math.abs(Math.sin(LK.ticks * blinkSpeed * 0.1)) > 0.5 ? 1.0 : 0.3;
} else {
graphics.alpha = 1.0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a4a3a
});
/****
* Game Code
****/
var GRID_SIZE = 40;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var COLS = Math.floor(GAME_WIDTH / GRID_SIZE);
var ROWS = Math.floor(GAME_HEIGHT / GRID_SIZE);
// Snake variables
var snake = [];
var snakeDirection = {
x: 1,
y: 0
};
var nextDirection = {
x: 1,
y: 0
};
var snakeSpeed = 8;
var moveCounter = 0;
var snakeThickness = 1;
// Fruit variables
var currentFruit = null;
var fruits = [];
// Growth points variables
var growthPoints = [];
var coconutPowerActive = false;
var coconutPowerEndTime = 0;
var lastGrowthPointSpawn = 0;
// Game state
var gameRunning = true;
var touchStartX = 0;
var touchStartY = 0;
// Autopilot variables
var autoPilotActive = false;
var autoPilotEndTime = 0;
var countdownText = null;
// Event system variables
var lastEventTime = 0;
var eventInterval = 30000; // 30 seconds
var activeSpikes = [];
var activeLavaAreas = [];
var lastX2EventTime = 0;
var x2EventInterval = 30000; // 30 seconds
var activeX2Powerups = [];
var x2MultiplierActive = false;
var x2MultiplierEndTime = 0;
// Boss battle variables
var gameStartTime = Date.now();
var bossWarningActive = false;
var bossWarningStartTime = 0;
var bossActive = false;
var bossDefeated = false;
var currentBoss = null;
var bossRocks = [];
var warningLights = [];
var bossHealthBar = null;
var bossHealthBarBg = null;
// Reset missions storage at game start
storage.fruitsEaten = 0;
storage.fruitsCompleted = false;
storage.pointsScored = 0;
storage.pointsCompleted = false;
storage.bossKilled = 0;
storage.bossCompleted = false;
storage.eventsLived = 0;
storage.eventsCompleted = false;
// Missions system variables
var missions = {
fruitsEaten: {
target: 5,
current: 0,
completed: false
},
pointsScored: {
target: 300,
current: 0,
completed: false
},
bossKilled: {
target: 1,
current: 0,
completed: false
},
eventsLived: {
target: 4,
current: 0,
completed: false
}
};
var missionsTable = null;
var eventsCounter = 0;
// Crown drop variable
var crownDrop = null;
// Enemy snake variable
var activeEnemySnake = null;
// Bird boss variables
var birdBossActive = false;
var birdBossDefeated = false;
var currentBirdBoss = null;
var birdBossStartTime = 0;
var purpleSnakeActive = false;
var currentPurpleSnake = null;
var purpleSnakeSpawnTime = 0;
// Timer display
var timerTxt = new Text2('Time: 00:00', {
size: 50,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 0;
timerTxt.y = 20;
LK.gui.top.addChild(timerTxt);
// Next event display
var nextEventTxt = new Text2('Next Event: Spike/Lava in 30s', {
size: 40,
fill: 0xFFFF00
});
nextEventTxt.anchor.set(0.5, 0);
nextEventTxt.x = 0;
nextEventTxt.y = 80;
LK.gui.top.addChild(nextEventTxt);
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120;
scoreTxt.y = 20;
LK.gui.topLeft.addChild(scoreTxt);
// Power-up display
var powerTxt = new Text2('Speed: 1x | Thickness: 1x', {
size: 40,
fill: 0xFFFF00
});
powerTxt.anchor.set(1, 0);
powerTxt.x = -20;
powerTxt.y = 20;
LK.gui.topRight.addChild(powerTxt);
// Boss health bar (initially hidden)
bossHealthBarBg = new LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBarBg.x = GAME_WIDTH / 2;
bossHealthBarBg.y = 100;
bossHealthBarBg.visible = false;
game.addChild(bossHealthBarBg);
bossHealthBar = new LK.getAsset('healthBar', {
anchorX: 0.5,
anchorY: 0
});
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 100;
bossHealthBar.visible = false;
game.addChild(bossHealthBar);
// Create missions table
missionsTable = new Text2('MISSIONS:\n' + '• Eat 5 fruits: 0/5\n' + '• Score 300 points: 0/300\n' + '• Kill the boss: 0/1\n' + '• Survive 4 events: 0/4', {
size: 35,
fill: 0xFFFFFF
});
missionsTable.anchor.set(0, 0);
missionsTable.x = 20;
missionsTable.y = 120;
LK.gui.topLeft.addChild(missionsTable);
// Create walls
var topWall = game.addChild(LK.getAsset('wall', {
x: 0,
y: 0,
width: GAME_WIDTH,
height: 20
}));
var bottomWall = game.addChild(LK.getAsset('wall', {
x: 0,
y: GAME_HEIGHT - 20,
width: GAME_WIDTH,
height: 20
}));
var leftWall = game.addChild(LK.getAsset('wall', {
x: 0,
y: 0,
width: 20,
height: GAME_HEIGHT
}));
var rightWall = game.addChild(LK.getAsset('wall', {
x: GAME_WIDTH - 20,
y: 0,
width: 20,
height: GAME_HEIGHT
}));
// Initialize snake
function initSnake() {
// Create initial snake with 3 segments
var startX = Math.floor(COLS / 4);
var startY = Math.floor(ROWS / 2);
for (var i = 0; i < 3; i++) {
var segment;
if (i === 0) {
segment = new SnakeHead();
} else {
segment = new SnakeSegment();
}
segment.gridX = startX - i;
segment.gridY = startY;
segment.x = segment.gridX * GRID_SIZE + GRID_SIZE / 2;
segment.y = segment.gridY * GRID_SIZE + GRID_SIZE / 2;
snake.push(segment);
game.addChild(segment);
}
}
// Spawn growth point
function spawnGrowthPoint() {
// Only spawn if no growth points exist OR coconut power is active
if (growthPoints.length > 0 && !coconutPowerActive) {
return;
}
var validPosition = false;
var gridX, gridY;
while (!validPosition) {
gridX = Math.floor(Math.random() * (COLS - 4)) + 2;
gridY = Math.floor(Math.random() * (ROWS - 4)) + 2;
validPosition = true;
// Check snake collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === gridX && snake[i].gridY === gridY) {
validPosition = false;
break;
}
}
// Check fruit collision
if (currentFruit && currentFruit.gridX === gridX && currentFruit.gridY === gridY) {
validPosition = false;
}
// Check other growth points collision
for (var i = 0; i < growthPoints.length; i++) {
if (growthPoints[i].gridX === gridX && growthPoints[i].gridY === gridY) {
validPosition = false;
break;
}
}
// Avoid lava areas
var worldX = gridX * GRID_SIZE + GRID_SIZE / 2;
var worldY = gridY * GRID_SIZE + GRID_SIZE / 2;
for (var l = 0; l < activeLavaAreas.length; l++) {
var lavaArea = activeLavaAreas[l];
if (worldX >= lavaArea.x && worldX <= lavaArea.x + 1024 && worldY >= lavaArea.y && worldY <= lavaArea.y + 1366) {
validPosition = false;
break;
}
}
}
var growthPoint = new GrowthPoint();
growthPoint.gridX = gridX;
growthPoint.gridY = gridY;
growthPoint.x = gridX * GRID_SIZE + GRID_SIZE / 2;
growthPoint.y = gridY * GRID_SIZE + GRID_SIZE / 2;
growthPoints.push(growthPoint);
game.addChild(growthPoint);
}
// Spawn fruit
function spawnFruit() {
if (currentFruit) {
currentFruit.destroy();
currentFruit = null;
}
var fruitTypes = ['apple', 'pear', 'banana', 'multicolor', 'coconut', 'orange'];
var randomType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
var validPosition = false;
var gridX, gridY;
while (!validPosition) {
gridX = Math.floor(Math.random() * (COLS - 4)) + 2;
gridY = Math.floor(Math.random() * (ROWS - 4)) + 2;
validPosition = true;
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === gridX && snake[i].gridY === gridY) {
validPosition = false;
break;
}
}
// Avoid lava areas
var worldX = gridX * GRID_SIZE + GRID_SIZE / 2;
var worldY = gridY * GRID_SIZE + GRID_SIZE / 2;
for (var l = 0; l < activeLavaAreas.length; l++) {
var lavaArea = activeLavaAreas[l];
if (worldX >= lavaArea.x && worldX <= lavaArea.x + 1024 && worldY >= lavaArea.y && worldY <= lavaArea.y + 1366) {
validPosition = false;
break;
}
}
}
currentFruit = new Fruit(randomType);
currentFruit.gridX = gridX;
currentFruit.gridY = gridY;
currentFruit.x = gridX * GRID_SIZE + GRID_SIZE / 2;
currentFruit.y = gridY * GRID_SIZE + GRID_SIZE / 2;
game.addChild(currentFruit);
}
// Move snake
function moveSnake() {
if (!gameRunning) return;
var head = snake[0];
// Autopilot logic
if (autoPilotActive && (growthPoints.length > 0 || currentFruit)) {
// Function to check if a position is safe
var isSafePosition = function isSafePosition(gridX, gridY) {
// Check walls
if (gridX <= 0 || gridX >= COLS - 1 || gridY <= 0 || gridY >= ROWS - 1) {
return false;
}
// Check snake body collision
for (var i = 1; i < snake.length; i++) {
if (snake[i].gridX === gridX && snake[i].gridY === gridY) {
return false;
}
}
// In autopilot mode, avoid all deadly objects
if (autoPilotActive) {
var worldX = gridX * GRID_SIZE + GRID_SIZE / 2;
var worldY = gridY * GRID_SIZE + GRID_SIZE / 2;
// Check spike collisions
for (var s = 0; s < activeSpikes.length; s++) {
var spike = activeSpikes[s];
var spikeDistance = Math.sqrt(Math.pow(spike.x - worldX, 2) + Math.pow(spike.y - worldY, 2));
if (spikeDistance < 150) {
// Give extra buffer for autopilot
return false;
}
}
// Check lava area collisions
for (var l = 0; l < activeLavaAreas.length; l++) {
var lavaArea = activeLavaAreas[l];
if (!lavaArea.isWarning) {
// Only avoid active lava
if (worldX >= lavaArea.x - 80 && worldX <= lavaArea.x + 1024 + 80 && worldY >= lavaArea.y - 80 && worldY <= lavaArea.y + 1366 + 80) {
return false;
}
}
}
// Check boss rock collisions
for (var r = 0; r < bossRocks.length; r++) {
var rock = bossRocks[r];
var rockDistance = Math.sqrt(Math.pow(rock.x - worldX, 2) + Math.pow(rock.y - worldY, 2));
if (rockDistance < 120) {
return false;
}
}
// Check boss collision
if (bossActive && currentBoss) {
var bossDistance = Math.sqrt(Math.pow(currentBoss.x - worldX, 2) + Math.pow(currentBoss.y - worldY, 2));
if (bossDistance < 250) {
return false;
}
}
// Check bird boss collision
if (birdBossActive && currentBirdBoss && !purpleSnakeActive) {
var birdDistance = Math.sqrt(Math.pow(currentBirdBoss.x - worldX, 2) + Math.pow(currentBirdBoss.y - worldY, 2));
if (birdDistance < 200) {
return false;
}
}
// Check enemy snake collision
if (activeEnemySnake) {
// Check enemy head
var enemyHeadDistance = Math.sqrt(Math.pow(activeEnemySnake.x - worldX, 2) + Math.pow(activeEnemySnake.y - worldY, 2));
if (enemyHeadDistance < 80) {
return false;
}
// Check enemy body segments
for (var es = 0; es < activeEnemySnake.segments.length; es++) {
var segment = activeEnemySnake.segments[es];
var segmentDistance = Math.sqrt(Math.pow(segment.x - worldX, 2) + Math.pow(segment.y - worldY, 2));
if (segmentDistance < 80) {
return false;
}
}
}
}
return true;
};
// Priority 1: Target growth points if available
var targetX, targetY;
if (growthPoints.length > 0) {
// Find closest growth point
var closestGrowthPoint = growthPoints[0];
var closestDistance = Math.abs(closestGrowthPoint.gridX - head.gridX) + Math.abs(closestGrowthPoint.gridY - head.gridY);
for (var g = 1; g < growthPoints.length; g++) {
var distance = Math.abs(growthPoints[g].gridX - head.gridX) + Math.abs(growthPoints[g].gridY - head.gridY);
if (distance < closestDistance) {
closestGrowthPoint = growthPoints[g];
closestDistance = distance;
}
}
targetX = closestGrowthPoint.gridX;
targetY = closestGrowthPoint.gridY;
} else {
// Priority 2: Target fruit if no growth points available
targetX = currentFruit.gridX;
targetY = currentFruit.gridY;
}
var deltaX = targetX - head.gridX;
var deltaY = targetY - head.gridY;
var possibleDirections = [];
var currentDirection = snakeDirection;
// Calculate potential moves towards fruit
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Prioritize horizontal movement
if (deltaX > 0) {
possibleDirections.push({
x: 1,
y: 0
}); // Right
} else if (deltaX < 0) {
possibleDirections.push({
x: -1,
y: 0
}); // Left
}
// Add vertical as backup
if (deltaY > 0) {
possibleDirections.push({
x: 0,
y: 1
}); // Down
} else if (deltaY < 0) {
possibleDirections.push({
x: 0,
y: -1
}); // Up
}
} else {
// Prioritize vertical movement
if (deltaY > 0) {
possibleDirections.push({
x: 0,
y: 1
}); // Down
} else if (deltaY < 0) {
possibleDirections.push({
x: 0,
y: -1
}); // Up
}
// Add horizontal as backup
if (deltaX > 0) {
possibleDirections.push({
x: 1,
y: 0
}); // Right
} else if (deltaX < 0) {
possibleDirections.push({
x: -1,
y: 0
}); // Left
}
}
// Add remaining directions as last resort
var allDirections = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
for (var i = 0; i < allDirections.length; i++) {
var dir = allDirections[i];
var found = false;
for (var j = 0; j < possibleDirections.length; j++) {
if (possibleDirections[j].x === dir.x && possibleDirections[j].y === dir.y) {
found = true;
break;
}
}
if (!found) {
possibleDirections.push(dir);
}
}
// Find first safe direction with multi-step lookahead
var newDirection = currentDirection;
for (var i = 0; i < possibleDirections.length; i++) {
var testDir = possibleDirections[i];
// Don't reverse direction
if (testDir.x === -currentDirection.x && testDir.y === -currentDirection.y) {
continue;
}
var testX = head.gridX + testDir.x;
var testY = head.gridY + testDir.y;
if (isSafePosition(testX, testY)) {
// For autopilot, also check if the next move after this one is safe (lookahead)
if (autoPilotActive) {
var canContinue = false;
var nextDirections = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
for (var nd = 0; nd < nextDirections.length; nd++) {
var nextDir = nextDirections[nd];
// Don't check reverse of current test direction
if (nextDir.x === -testDir.x && nextDir.y === -testDir.y) {
continue;
}
var nextX = testX + nextDir.x;
var nextY = testY + nextDir.y;
if (isSafePosition(nextX, nextY)) {
canContinue = true;
break;
}
}
if (canContinue) {
newDirection = testDir;
break;
}
} else {
newDirection = testDir;
break;
}
}
}
snakeDirection = newDirection;
nextDirection = snakeDirection;
}
var newGridX = head.gridX + snakeDirection.x;
var newGridY = head.gridY + snakeDirection.y;
// Check wall collision
if (newGridX <= 0 || newGridX >= COLS - 1 || newGridY <= 0 || newGridY >= ROWS - 1) {
gameOver();
return;
}
// Check self collision
for (var i = 0; i < snake.length; i++) {
if (snake[i].gridX === newGridX && snake[i].gridY === newGridY) {
gameOver();
return;
}
}
// Check fruit collision
var ateFood = false;
if (currentFruit && currentFruit.gridX === newGridX && currentFruit.gridY === newGridY) {
// Only apple makes snake grow, other fruits don't
ateFood = currentFruit.fruitType === 'apple';
handleFruitEaten(currentFruit.fruitType);
}
// Check growth point collision
for (var i = growthPoints.length - 1; i >= 0; i--) {
var growthPoint = growthPoints[i];
if (growthPoint.gridX === newGridX && growthPoint.gridY === newGridY) {
// Apply X2 multiplier for growth (ateFood effect)
if (x2MultiplierActive) {
ateFood = true; // X2 makes growth points also grow the snake
} else {
ateFood = true;
}
LK.getSound('eat').play();
// Apply X2 multiplier for score
var pointsToAdd = x2MultiplierActive ? 2 : 1;
LK.setScore(LK.getScore() + pointsToAdd);
scoreTxt.setText('Score: ' + LK.getScore());
growthPoint.destroy();
growthPoints.splice(i, 1);
break;
}
}
// Move snake
var newHead = new SnakeHead();
newHead.gridX = newGridX;
newHead.gridY = newGridY;
newHead.x = newGridX * GRID_SIZE + GRID_SIZE / 2;
newHead.y = newGridY * GRID_SIZE + GRID_SIZE / 2;
newHead.setThickness(snakeThickness);
// Convert old head to body
var oldHead = snake[0];
// Transfer crown status to new head
if (oldHead.hasCrown) {
newHead.showCrown();
}
// Transfer shield status to new head
if (oldHead.hasShield) {
newHead.activateShield(); // Add to new head first
oldHead.removeShield(); // Remove from old head after
}
oldHead.destroy();
var newBody = new SnakeSegment();
newBody.gridX = oldHead.gridX;
newBody.gridY = oldHead.gridY;
newBody.x = oldHead.x;
newBody.y = oldHead.y;
newBody.setThickness(snakeThickness);
game.addChild(newBody);
snake[0] = newBody;
// Add new head
snake.unshift(newHead);
game.addChild(newHead);
if (!ateFood) {
// Remove tail
var tail = snake.pop();
tail.destroy();
}
// Update thickness for all segments
for (var i = 0; i < snake.length; i++) {
snake[i].setThickness(snakeThickness);
}
}
// Handle fruit eaten
function handleFruitEaten(fruitType) {
LK.getSound('eat').play();
// All fruits give 5 points (apply X2 multiplier if active)
var pointsToAdd = x2MultiplierActive ? 10 : 5;
LK.setScore(LK.getScore() + pointsToAdd);
if (fruitType === 'apple') {
// Apple: makes snake grow (ateFood stays true)
LK.getSound('powerup').play();
} else if (fruitType === 'pear') {
snakeThickness += 0.2;
LK.getSound('powerup').play();
} else if (fruitType === 'banana') {
snakeSpeed += 1;
LK.getSound('powerup').play();
} else if (fruitType === 'multicolor') {
// Activate autopilot for 10 seconds
autoPilotActive = true;
autoPilotEndTime = Date.now() + 10000;
LK.getSound('powerup').play();
// Clean up any existing countdown
if (countdownText) {
countdownText.destroy();
countdownText = null;
}
// Show autopilot activation feedback
LK.effects.flashScreen(0x00ff00, 500);
} else if (fruitType === 'coconut') {
// Activate coconut power for 10 seconds - allows multiple growth points
coconutPowerActive = true;
coconutPowerEndTime = Date.now() + 10000;
lastGrowthPointSpawn = Date.now();
LK.getSound('powerup').play();
// Show coconut power activation feedback
LK.effects.flashScreen(0xffffff, 500);
} else if (fruitType === 'orange') {
// Activate shield for one-time protection
var head = snake[0];
head.activateShield();
LK.getSound('powerup').play();
// Show orange power activation feedback
LK.effects.flashScreen(0xff8800, 500);
}
scoreTxt.setText('Score: ' + LK.getScore());
powerTxt.setText('Speed: ' + snakeSpeed + 'x | Thickness: ' + Math.round(snakeThickness * 10) / 10 + 'x');
// Update missions - fruits eaten
if (!missions.fruitsEaten.completed) {
missions.fruitsEaten.current++;
storage.fruitsEaten = missions.fruitsEaten.current;
if (missions.fruitsEaten.current >= missions.fruitsEaten.target) {
missions.fruitsEaten.completed = true;
storage.fruitsCompleted = true;
}
}
// Update missions - points scored
if (!missions.pointsScored.completed && LK.getScore() >= missions.pointsScored.target) {
missions.pointsScored.current = LK.getScore();
missions.pointsScored.completed = true;
storage.pointsScored = missions.pointsScored.current;
storage.pointsCompleted = true;
}
updateMissionsDisplay();
currentFruit.destroy();
currentFruit = null;
spawnFruit();
}
// Update missions display
function updateMissionsDisplay() {
if (missionsTable) {
missionsTable.setText('MISSIONS:\n' + '• Eat 5 fruits: ' + missions.fruitsEaten.current + '/' + missions.fruitsEaten.target + (missions.fruitsEaten.completed ? ' ✓' : '') + '\n' + '• Score 300 points: ' + Math.min(missions.pointsScored.current, LK.getScore()) + '/' + missions.pointsScored.target + (missions.pointsScored.completed ? ' ✓' : '') + '\n' + '• Kill the boss: ' + missions.bossKilled.current + '/' + missions.bossKilled.target + (missions.bossKilled.completed ? ' ✓' : '') + '\n' + '• Survive 4 events: ' + missions.eventsLived.current + '/' + missions.eventsLived.target + (missions.eventsLived.completed ? ' ✓' : ''));
}
}
// Spawn X2 event
function spawnX2Event() {
// 50% probability check - only spawn X2 powerup half the time
if (Math.random() < 0.5) {
// Clear any existing X2 powerups
for (var i = activeX2Powerups.length - 1; i >= 0; i--) {
activeX2Powerups[i].destroy();
}
activeX2Powerups = [];
// Spawn X2 powerup at random location
var x2Powerup = new X2Powerup();
x2Powerup.x = Math.random() * (GAME_WIDTH - 200) + 100;
x2Powerup.y = Math.random() * (GAME_HEIGHT - 200) + 100;
activeX2Powerups.push(x2Powerup);
game.addChild(x2Powerup);
}
}
// Spawn random event
function spawnEvent() {
// Clear any existing events first to ensure only one event at a time
for (var i = activeSpikes.length - 1; i >= 0; i--) {
activeSpikes[i].destroy();
}
activeSpikes = [];
for (var i = activeLavaAreas.length - 1; i >= 0; i--) {
activeLavaAreas[i].destroy();
}
activeLavaAreas = [];
// Clear enemy snake if active
if (activeEnemySnake) {
activeEnemySnake.destroy();
activeEnemySnake = null;
}
// Update events survived mission
eventsCounter++;
if (!missions.eventsLived.completed) {
missions.eventsLived.current = eventsCounter;
storage.eventsLived = missions.eventsLived.current;
if (missions.eventsLived.current >= missions.eventsLived.target) {
missions.eventsLived.completed = true;
storage.eventsCompleted = true;
}
updateMissionsDisplay();
}
var eventType = Math.random();
if (eventType < 0.33) {
// Spawn bouncing spike
var spike = new Spike();
spike.x = Math.random() * (GAME_WIDTH - 100) + 50;
spike.y = Math.random() * (GAME_HEIGHT - 100) + 50;
activeSpikes.push(spike);
game.addChild(spike);
} else if (eventType < 0.66) {
// Spawn lava area in one quarter of the screen
var lavaArea = new LavaArea();
var quarter = Math.floor(Math.random() * 4);
switch (quarter) {
case 0:
// Top-left
lavaArea.x = 20;
lavaArea.y = 20;
break;
case 1:
// Top-right
lavaArea.x = GAME_WIDTH / 2;
lavaArea.y = 20;
break;
case 2:
// Bottom-left
lavaArea.x = 20;
lavaArea.y = GAME_HEIGHT / 2;
break;
case 3:
// Bottom-right
lavaArea.x = GAME_WIDTH / 2;
lavaArea.y = GAME_HEIGHT / 2;
break;
}
activeLavaAreas.push(lavaArea);
game.addChild(lavaArea);
LK.getSound('lava_warning').play();
} else {
// Spawn enemy snake
activeEnemySnake = new EnemySnake();
// Spawn on opposite side from player
var head = snake[0];
var spawnX, spawnY;
if (head.gridX < COLS / 2) {
spawnX = Math.floor(COLS * 0.75);
} else {
spawnX = Math.floor(COLS * 0.25);
}
if (head.gridY < ROWS / 2) {
spawnY = Math.floor(ROWS * 0.75);
} else {
spawnY = Math.floor(ROWS * 0.25);
}
activeEnemySnake.gridX = spawnX;
activeEnemySnake.gridY = spawnY;
activeEnemySnake.x = spawnX * GRID_SIZE + GRID_SIZE / 2;
activeEnemySnake.y = spawnY * GRID_SIZE + GRID_SIZE / 2;
game.addChild(activeEnemySnake);
}
}
// Game over
function gameOver() {
gameRunning = false;
LK.showGameOver();
}
// Touch controls
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
if (!gameRunning) return;
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
var minSwipeDistance = 50;
if (Math.abs(deltaX) > minSwipeDistance || Math.abs(deltaY) > minSwipeDistance) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0 && snakeDirection.x !== -1) {
nextDirection = {
x: 1,
y: 0
}; // Right
} else if (deltaX < 0 && snakeDirection.x !== 1) {
nextDirection = {
x: -1,
y: 0
}; // Left
}
} else {
// Vertical swipe
if (deltaY > 0 && snakeDirection.y !== -1) {
nextDirection = {
x: 0,
y: 1
}; // Down
} else if (deltaY < 0 && snakeDirection.y !== 1) {
nextDirection = {
x: 0,
y: -1
}; // Up
}
}
}
};
// Initialize game
initSnake();
spawnFruit();
// Spawn initial growth points
for (var i = 0; i < 3; i++) {
spawnGrowthPoint();
}
// Main game loop
game.update = function () {
if (!gameRunning) return;
moveCounter++;
// Update timer display
var gameElapsed = Date.now() - gameStartTime;
var minutes = Math.floor(gameElapsed / 60000);
var seconds = Math.floor(gameElapsed % 60000 / 1000);
var timeString = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText('Time: ' + timeString);
// Update next event display
var currentTime = Date.now();
if (birdBossActive && purpleSnakeActive) {
nextEventTxt.setText('Purple Snake is capturing the Bird!');
} else if (birdBossActive && !purpleSnakeActive) {
var timeUntilPurple = Math.max(0, 35000 - (currentTime - birdBossStartTime));
var purpleSecondsLeft = Math.ceil(timeUntilPurple / 1000);
nextEventTxt.setText('Purple Snake arrives in ' + purpleSecondsLeft + 's');
} else if (!bossActive && !bossDefeated) {
var timeUntilBoss = Math.max(0, 60000 - (currentTime - gameStartTime));
var bossSecondsLeft = Math.ceil(timeUntilBoss / 1000);
nextEventTxt.setText('Boss arrives in ' + bossSecondsLeft + 's');
} else if (!birdBossActive && !birdBossDefeated && bossDefeated) {
var timeUntilBird = Math.max(0, 180000 - (currentTime - gameStartTime));
var birdSecondsLeft = Math.ceil(timeUntilBird / 1000);
nextEventTxt.setText('Bird Boss arrives in ' + birdSecondsLeft + 's');
} else if (!bossActive && bossDefeated) {
// Calculate time until next regular event
var timeUntilEvent = Math.max(0, eventInterval - (currentTime - lastEventTime));
var eventSecondsLeft = Math.ceil(timeUntilEvent / 1000);
// Calculate time until next X2 event
var timeUntilX2 = Math.max(0, x2EventInterval - (currentTime - lastX2EventTime));
var x2SecondsLeft = Math.ceil(timeUntilX2 / 1000);
// Show the next event that will happen first
if (timeUntilEvent <= timeUntilX2) {
nextEventTxt.setText('Next Event: Spike/Lava/Enemy in ' + eventSecondsLeft + 's');
} else {
nextEventTxt.setText('Next Event: X2 Power in ' + x2SecondsLeft + 's');
}
} else if (bossWarningActive) {
nextEventTxt.setText('Next Event: BOSS INCOMING!');
} else if (bossActive) {
nextEventTxt.setText('Next Event: BOSS BATTLE!');
} else {
nextEventTxt.setText('Next Event: Boss defeated');
}
// Handle autopilot countdown
if (autoPilotActive) {
var timeLeft = autoPilotEndTime - Date.now();
if (timeLeft <= 0) {
autoPilotActive = false;
if (countdownText) {
countdownText.destroy();
countdownText = null;
}
} else if (timeLeft <= 3000 && !countdownText) {
// Show countdown when 3 seconds left
countdownText = new Text2('3', {
size: 120,
fill: 0xFF0000
});
countdownText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(countdownText);
}
// Update countdown text
if (countdownText) {
var secondsLeft = Math.ceil(timeLeft / 1000);
if (secondsLeft > 0) {
countdownText.setText(secondsLeft.toString());
// Animate countdown
tween(countdownText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (countdownText) {
tween(countdownText, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
}
});
} else {
countdownText.destroy();
countdownText = null;
}
}
}
// Update direction (only if not in autopilot)
if (!autoPilotActive) {
snakeDirection = nextDirection;
}
// Event spawning timer - only spawn events if boss is not active
var currentTime = Date.now();
if (!bossActive && currentTime - lastEventTime >= eventInterval) {
lastEventTime = currentTime;
spawnEvent();
}
// X2 event spawning timer - independent of regular events
if (!bossActive && currentTime - lastX2EventTime >= x2EventInterval) {
lastX2EventTime = currentTime;
spawnX2Event();
}
// Check spike collisions with snake
for (var i = activeSpikes.length - 1; i >= 0; i--) {
var spike = activeSpikes[i];
var head = snake[0];
// Check collision with snake head - adjust collision radius based on snake thickness
var distance = Math.sqrt(Math.pow(spike.x - head.x, 2) + Math.pow(spike.y - head.y, 2));
var collisionRadius = 60 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
// Push spike away or destroy it
spike.destroy();
activeSpikes.splice(i, 1);
continue;
}
// No shield - game over
gameOver();
return;
}
}
// Check lava area collisions with snake
for (var i = activeLavaAreas.length - 1; i >= 0; i--) {
var lavaArea = activeLavaAreas[i];
// Only check collision if lava is active (not warning)
if (!lavaArea.isWarning) {
var head = snake[0];
// Check if snake head is inside lava area (larger quarter-screen area)
if (head.x >= lavaArea.x && head.x <= lavaArea.x + 1024 && head.y >= lavaArea.y && head.y <= lavaArea.y + 1366) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
continue;
}
// No shield - game over
gameOver();
return;
}
}
}
// Handle coconut power and growth point spawning
var currentTime = Date.now();
if (coconutPowerActive) {
// Check if coconut power has expired
if (currentTime >= coconutPowerEndTime) {
coconutPowerActive = false;
}
// Spawn growth points every 2.5 seconds during coconut power
if (currentTime - lastGrowthPointSpawn >= 2500) {
spawnGrowthPoint();
lastGrowthPointSpawn = currentTime;
}
} else {
// Normal mode: spawn growth point only if none exist
if (growthPoints.length === 0) {
spawnGrowthPoint();
}
}
// Bird boss timing and logic
var timeSinceStart = Date.now() - gameStartTime;
// Start bird boss at 3 minutes
if (timeSinceStart >= 180000 && !birdBossActive && !birdBossDefeated) {
birdBossActive = true;
birdBossStartTime = Date.now();
// Spawn bird boss at top of screen
currentBirdBoss = new BirdBoss();
currentBirdBoss.x = GAME_WIDTH / 2;
currentBirdBoss.y = 100;
game.addChild(currentBirdBoss);
}
// Purple snake timing - 35 seconds after bird boss starts
if (birdBossActive && !purpleSnakeActive && Date.now() - birdBossStartTime >= 35000) {
purpleSnakeActive = true;
purpleSnakeSpawnTime = Date.now();
// Spawn purple snake at edge of screen
currentPurpleSnake = new PurpleSnake();
currentPurpleSnake.x = GAME_WIDTH - 100;
currentPurpleSnake.y = GAME_HEIGHT / 2;
game.addChild(currentPurpleSnake);
}
// Check if purple snake reaches bird boss (defeats bird boss)
if (birdBossActive && purpleSnakeActive && currentBirdBoss && currentPurpleSnake) {
var distance = Math.sqrt(Math.pow(currentPurpleSnake.x - currentBirdBoss.x, 2) + Math.pow(currentPurpleSnake.y - currentBirdBoss.y, 2));
if (distance < 150) {
// Purple snake captures bird boss
LK.effects.flashScreen(0x800080, 1000);
// Animate both flying away
tween(currentBirdBoss, {
x: currentBirdBoss.x,
y: -200
}, {
duration: 2000,
easing: tween.easeIn
});
tween(currentPurpleSnake, {
x: currentPurpleSnake.x,
y: -200
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (currentBirdBoss && currentBirdBoss.parent) {
currentBirdBoss.destroy();
}
if (currentPurpleSnake && currentPurpleSnake.parent) {
currentPurpleSnake.destroy();
}
currentBirdBoss = null;
currentPurpleSnake = null;
}
});
birdBossActive = false;
birdBossDefeated = true;
purpleSnakeActive = false;
LK.setScore(LK.getScore() + 200);
scoreTxt.setText('Score: ' + LK.getScore());
}
}
// Boss battle timing and logic
// Start boss warning at 60 seconds
if (timeSinceStart >= 60000 && !bossWarningActive && !bossActive && !bossDefeated) {
bossWarningActive = true;
bossWarningStartTime = Date.now();
// Create warning lights in a perfect circle around boss spawn location
var bossSpawnX = GAME_WIDTH / 2;
var bossSpawnY = GAME_HEIGHT / 2;
var circleRadius = 500; // Distance from boss spawn center
for (var i = 0; i < 12; i++) {
var light = new WarningLight();
var angle = i / 12 * Math.PI * 2;
light.x = bossSpawnX + Math.cos(angle) * circleRadius;
light.y = bossSpawnY + Math.sin(angle) * circleRadius;
warningLights.push(light);
game.addChild(light);
}
}
// End warning phase and spawn boss after 5 seconds
if (bossWarningActive && Date.now() - bossWarningStartTime >= 5000) {
bossWarningActive = false;
bossActive = true;
// Remove warning lights
for (var i = warningLights.length - 1; i >= 0; i--) {
warningLights[i].destroy();
}
warningLights = [];
// Clear all active events when boss spawns
for (var i = activeSpikes.length - 1; i >= 0; i--) {
activeSpikes[i].destroy();
}
activeSpikes = [];
for (var i = activeLavaAreas.length - 1; i >= 0; i--) {
activeLavaAreas[i].destroy();
}
activeLavaAreas = [];
// Spawn boss
currentBoss = new PotatoBoss();
currentBoss.x = GAME_WIDTH / 2;
currentBoss.y = GAME_HEIGHT / 2;
game.addChild(currentBoss);
// Show health bar
bossHealthBarBg.visible = true;
bossHealthBar.visible = true;
}
// Update boss health bar
if (bossActive && currentBoss) {
var healthPercentage = currentBoss.health / currentBoss.maxHealth;
bossHealthBar.scaleX = healthPercentage;
}
// Hide health bar when boss is defeated
if (bossDefeated) {
bossHealthBarBg.visible = false;
bossHealthBar.visible = false;
}
// Check boss rock collisions with snake
if (bossActive) {
for (var i = bossRocks.length - 1; i >= 0; i--) {
var rock = bossRocks[i];
// Remove rocks that have been destroyed due to max bounces
if (!rock.parent) {
bossRocks.splice(i, 1);
continue;
}
var head = snake[0];
var distance = Math.sqrt(Math.pow(rock.x - head.x, 2) + Math.pow(rock.y - head.y, 2));
var collisionRadius = 70 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
// Destroy the rock
rock.destroy();
bossRocks.splice(i, 1);
continue;
}
// No shield - game over
gameOver();
return;
}
}
}
// Check boss rock collisions with boss (damage boss) - only after bouncing
if (bossActive && currentBoss) {
for (var i = bossRocks.length - 1; i >= 0; i--) {
var rock = bossRocks[i];
// Comprehensive null check for currentBoss before any property access
if (!currentBoss || !currentBoss.parent || currentBoss.x === undefined || currentBoss.y === undefined) {
continue;
}
var currentIntersectingBoss = Math.sqrt(Math.pow(rock.x - currentBoss.x, 2) + Math.pow(rock.y - currentBoss.y, 2)) < 190;
// Only damage boss if rock has bounced at least once and just started intersecting
if (rock.bounceCount > 0 && !rock.lastIntersectingBoss && currentIntersectingBoss) {
// Deal 25% damage (1.25 damage out of 5 total health)
var damage = Math.ceil(currentBoss.maxHealth * 0.25);
for (var d = 0; d < damage; d++) {
if (currentBoss.health > 0) {
currentBoss.takeDamage();
}
}
// Remove the rock
rock.destroy();
bossRocks.splice(i, 1);
continue;
}
// Update last intersecting state
rock.lastIntersectingBoss = currentIntersectingBoss;
}
}
// Check snake collision with boss (damage boss)
if (bossActive && currentBoss) {
var head = snake[0];
var distance = Math.sqrt(Math.pow(currentBoss.x - head.x, 2) + Math.pow(currentBoss.y - head.y, 2));
var collisionRadius = 170 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
currentBoss.takeDamage();
// Push snake away to prevent multiple hits
var pushAngle = Math.atan2(head.y - currentBoss.y, head.x - currentBoss.x);
var pushDistance = 150 * snakeThickness; // Scale push distance with snake thickness
var newX = currentBoss.x + Math.cos(pushAngle) * pushDistance;
var newY = currentBoss.y + Math.sin(pushAngle) * pushDistance;
// Convert to grid position
var gridX = Math.round(newX / GRID_SIZE);
var gridY = Math.round(newY / GRID_SIZE);
// Ensure within bounds
gridX = Math.max(1, Math.min(COLS - 2, gridX));
gridY = Math.max(1, Math.min(ROWS - 2, gridY));
head.gridX = gridX;
head.gridY = gridY;
head.x = gridX * GRID_SIZE + GRID_SIZE / 2;
head.y = gridY * GRID_SIZE + GRID_SIZE / 2;
}
}
// Check snake collision with bird boss
if (birdBossActive && currentBirdBoss && !purpleSnakeActive) {
var head = snake[0];
var distance = Math.sqrt(Math.pow(currentBirdBoss.x - head.x, 2) + Math.pow(currentBirdBoss.y - head.y, 2));
var collisionRadius = 140 * snakeThickness;
if (distance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
} else {
gameOver();
return;
}
}
}
// Update points mission continuously
if (!missions.pointsScored.completed) {
missions.pointsScored.current = LK.getScore();
storage.pointsScored = missions.pointsScored.current;
if (missions.pointsScored.current >= missions.pointsScored.target) {
missions.pointsScored.completed = true;
storage.pointsCompleted = true;
updateMissionsDisplay();
}
}
// Check X2 powerup collision
if (activeX2Powerups.length > 0 && snake.length > 0) {
var head = snake[0];
for (var i = activeX2Powerups.length - 1; i >= 0; i--) {
var x2Powerup = activeX2Powerups[i];
var distance = Math.sqrt(Math.pow(x2Powerup.x - head.x, 2) + Math.pow(x2Powerup.y - head.y, 2));
var collisionRadius = 80 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// X2 powerup collected
x2MultiplierActive = true;
x2MultiplierEndTime = Date.now() + 10000; // 10 seconds
LK.getSound('powerup').play();
LK.effects.flashScreen(0xff0000, 500);
x2Powerup.destroy();
activeX2Powerups.splice(i, 1);
break;
}
}
}
// Handle X2 multiplier expiration
if (x2MultiplierActive && Date.now() >= x2MultiplierEndTime) {
x2MultiplierActive = false;
}
// Check enemy snake collision with player
if (activeEnemySnake && snake.length > 0) {
var head = snake[0];
var headDistance = Math.sqrt(Math.pow(activeEnemySnake.x - head.x, 2) + Math.pow(activeEnemySnake.y - head.y, 2));
var collisionRadius = 50 * snakeThickness;
// Check collision with enemy snake head
if (headDistance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
} else {
gameOver();
return;
}
}
// Check collision with enemy snake body segments
for (var i = 0; i < activeEnemySnake.segments.length; i++) {
var segment = activeEnemySnake.segments[i];
var segmentDistance = Math.sqrt(Math.pow(segment.x - head.x, 2) + Math.pow(segment.y - head.y, 2));
if (segmentDistance < collisionRadius) {
// Check if snake has shield protection
if (head.hasShield) {
// Remove shield and continue playing as extra life
head.removeShield();
LK.effects.flashScreen(0x87ceeb, 300);
break;
} else {
gameOver();
return;
}
}
}
}
// Check crown drop collision
if (crownDrop && snake.length > 0) {
var head = snake[0];
var distance = Math.sqrt(Math.pow(crownDrop.x - head.x, 2) + Math.pow(crownDrop.y - head.y, 2));
var collisionRadius = 80 * snakeThickness; // Scale collision radius with snake thickness
if (distance < collisionRadius) {
// Crown collected
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore());
LK.getSound('powerup').play();
// Show crown on snake head
head.showCrown();
crownDrop.destroy();
crownDrop = null;
}
}
// Check if all missions are completed for win condition
if (missions.fruitsEaten.completed && missions.pointsScored.completed && missions.bossKilled.completed && missions.eventsLived.completed) {
LK.showYouWin();
return;
}
// Move snake based on speed
var moveInterval = Math.max(3, 15 - snakeSpeed);
if (moveCounter >= moveInterval) {
moveCounter = 0;
moveSnake();
}
};
cabeza de serpiente morada con x en los ojos. In-Game asset. High contrast. No shadows. 2d draw, kid draw, simple, minimalistic
apple. In-Game asset. High contrast. minimalistic
banana. In-Game asset. 2d. High contrast. No shadows
angry blue bird. no shadow. cartoon. simple. minimalisitc. animated
crown. In-Game asset. 2d. No shadows. cartoon. animate. simple
snake head. In-Game asset. 2d. No shadows. cartoon. animated. simple. funny
spike block with an angry face. In-Game asset. 2d. High contrast. No shadows. cartoon
X2 signal. In-Game asset. 2d. No shadows. cartoon. simple
orange fruit. In-Game asset. 2d. High contrast. No shadows. cartoon. simple. minimalistic
coconout. In-Game asset. 2d. High contrast. No shadows. cartoon
circular green rough skin. In-Game asset. 2d. High contrast. No shadows. cartoon. comic. drawed
pear fruit. In-Game asset. 2d. High contrast. No shadows. animated. cartoon
eyebrow. In-Game asset. 2d. High contrast. No shadows. comic. cartoon
rock. In-Game asset. 2d. High contrast. No shadows. cartoon
orbe de luz. In-Game asset. 2d. High contrast. No shadows
potato. In-Game asset. 2d. High contrast. No shadows. cartoon
baby rat sprite. In-Game asset. 2d. High contrast. No shadows. cartoonm. animated
purple circle of rough skin. In-Game asset. 2d. High contrast. No shadows. animated. comic. cartoon
blue sfere, ball, slime, translucent,. In-Game asset. 2d. High contrast. No shadows. cartoon. simple. draw
rock line. In-Game asset. 2d. High contrast. No shadows. long. cartoon
fire block background. In-Game asset. 2d. High contrast. No shadows. cartoon. draw