/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Animal = Container.expand(function () {
var self = Container.call(this);
var animalGraphics = self.attachAsset('animal', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 4;
self.speedY = (Math.random() - 0.5) * 4;
self.lifeTime = 300; // 5 seconds at 60fps
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 5;
self.speedY = (Math.random() - 0.5) * 5;
self.lifeTime = 350;
self.flapTimer = 0;
self.baseY = 0;
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Flying movement with up/down oscillation
if (self.baseY === 0) {
self.baseY = self.y;
}
self.x += self.speedX;
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.animOffset) * 15;
// Wing flapping animation
self.flapTimer++;
if (self.flapTimer >= 5) {
self.flapTimer = 0;
var flapScale = 0.8 + Math.sin(LK.ticks * 0.3 + self.animOffset) * 0.2;
birdGraphics.scaleX = flapScale;
}
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Deer = Container.expand(function () {
var self = Container.call(this);
var deerGraphics = self.attachAsset('deer', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 2; // Slower movement
self.speedY = (Math.random() - 0.5) * 2;
self.lifeTime = 500; // Lives longest
self.panicTimer = 0;
self.isPanicking = false;
self.update = function () {
// Deer occasionally panic and run faster
if (!self.isPanicking && Math.random() < 0.005) {
self.isPanicking = true;
self.panicTimer = 60; // Panic for 1 second
self.speedX *= 3;
self.speedY *= 3;
// Flash effect when panicking
tween(deerGraphics, {
tint: 0xFFAAAA
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(deerGraphics, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
if (self.isPanicking) {
self.panicTimer--;
if (self.panicTimer <= 0) {
self.isPanicking = false;
self.speedX /= 3;
self.speedY /= 3;
}
}
self.x += self.speedX;
self.y += self.speedY;
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Fire = Container.expand(function (size) {
var self = Container.call(this);
self.size = size || 1;
self.maxSize = 10; // Maximum fire size
self.waterNeeded = self.size;
self.growthTimer = 0;
// Fire grows every 20 seconds (1200 ticks at 60fps)
self.growthInterval = 1200; // 20 seconds fixed interval
var fireGraphics = self.attachAsset(self.size === 1 ? 'smallFire' : 'largeFire', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial scale based on fire size
var initialScale = 0.5 + (self.size - 1) * 0.2; // Start smaller, scale up with size
fireGraphics.scaleX = initialScale;
fireGraphics.scaleY = initialScale;
// Add flickering animation
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Growth logic - fire grows every 7 seconds
self.growthTimer++;
if (self.growthTimer >= self.growthInterval && self.size < self.maxSize) {
self.size++;
self.waterNeeded = self.size;
self.growthTimer = 0;
// Update size text
if (self.sizeText) {
self.sizeText.setText(self.size.toString());
}
// Update visual scale based on size - smaller start, bigger growth
var scale = 0.5 + (self.size - 1) * 0.25; // Start at 0.5, increase by 25% per size level
tween(fireGraphics, {
scaleX: scale,
scaleY: scale
}, {
duration: 500,
easing: tween.easeOut
});
// Change color intensity based on size
var intensity = Math.min(1, 0.5 + (self.size - 1) * 0.1);
var redComponent = 0xFF;
var greenBlueComponent = Math.floor((1 - intensity) * 0x44);
var redTint = redComponent << 16 | greenBlueComponent << 8 | greenBlueComponent;
tween(fireGraphics, {
tint: redTint
}, {
duration: 500,
easing: tween.easeOut
});
}
// Flickering animation
var flicker = 0.8 + Math.sin(LK.ticks * 0.1 + self.animOffset) * 0.2;
fireGraphics.alpha = flicker;
var baseScaleX = fireGraphics.scaleX || 1;
var baseScaleY = fireGraphics.scaleY || 1;
fireGraphics.scaleX = baseScaleX + Math.sin(LK.ticks * 0.08 + self.animOffset) * 0.1;
fireGraphics.scaleY = baseScaleY + Math.sin(LK.ticks * 0.08 + self.animOffset) * 0.1;
};
return self;
});
var HeartPowerUp = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.lifeTime = 600; // 10 seconds at 60fps
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Floating animation
heartGraphics.y = Math.sin(LK.ticks * 0.1 + self.animOffset) * 10;
// Pulsing animation
var pulse = 1.0 + Math.sin(LK.ticks * 0.15 + self.animOffset) * 0.3;
heartGraphics.scaleX = 1.5 * pulse;
heartGraphics.scaleY = 1.5 * pulse;
// Fade out in last 2 seconds
if (self.lifeTime <= 120) {
heartGraphics.alpha = self.lifeTime / 120;
}
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Rabbit = Container.expand(function () {
var self = Container.call(this);
var rabbitGraphics = self.attachAsset('rabbit', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 6; // Faster than regular animals
self.speedY = (Math.random() - 0.5) * 6;
self.lifeTime = 400; // Lives longer
self.hopTimer = 0;
self.hopInterval = 20; // Hop every 20 ticks
self.update = function () {
self.hopTimer++;
// Hopping movement - move in bursts
if (self.hopTimer >= self.hopInterval) {
self.x += self.speedX * 2; // Burst movement
self.y += self.speedY * 2;
self.hopTimer = 0;
// Add hop animation
tween(rabbitGraphics, {
scaleY: 1.3
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rabbitGraphics, {
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Rock = Container.expand(function () {
var self = Container.call(this);
var rockGraphics = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 0.5
});
// Add some visual variation to rocks
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Subtle rotation animation
rockGraphics.rotation = Math.sin(LK.ticks * 0.01 + self.animOffset) * 0.1;
};
return self;
});
var SnakeSegment = Container.expand(function (isHead, isTail) {
var self = Container.call(this);
var assetType = isHead ? 'snakeHead' : isTail ? 'snakeTail' : 'snakeBody';
var segmentGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var WaterDroplet = Container.expand(function () {
var self = Container.call(this);
var waterGraphics = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5
});
// Add floating animation
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
waterGraphics.y = Math.sin(LK.ticks * 0.05 + self.animOffset) * 5;
};
return self;
});
var WaterSpray = Container.expand(function (startX, startY, targetX, targetY) {
var self = Container.call(this);
// Create multiple water droplets for spray effect
self.droplets = [];
for (var i = 0; i < 8; i++) {
var droplet = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
// Position droplet at start position
droplet.x = 0;
droplet.y = 0;
// Calculate spray direction with some randomness
var angle = Math.atan2(targetY - startY, targetX - startX);
var spreadAngle = (Math.random() - 0.5) * 0.8; // Random spread
var finalAngle = angle + spreadAngle;
// Set movement properties
droplet.velocityX = Math.cos(finalAngle) * (3 + Math.random() * 2);
droplet.velocityY = Math.sin(finalAngle) * (3 + Math.random() * 2);
droplet.life = 30 + Math.random() * 20; // Random lifetime
droplet.maxLife = droplet.life;
self.droplets.push(droplet);
}
self.x = startX;
self.y = startY;
self.totalLife = 60;
self.update = function () {
self.totalLife--;
// Update each droplet
for (var i = self.droplets.length - 1; i >= 0; i--) {
var droplet = self.droplets[i];
// Move droplet
droplet.x += droplet.velocityX;
droplet.y += droplet.velocityY;
// Fade out droplet
droplet.life--;
droplet.alpha = droplet.life / droplet.maxLife;
// Remove dead droplets
if (droplet.life <= 0) {
self.removeChild(droplet);
self.droplets.splice(i, 1);
}
}
// Remove spray when all droplets are gone or time is up
if (self.droplets.length === 0 || self.totalLife <= 0) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Add forest background
var forestBackground = game.attachAsset('forest', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Background tracking variables
var currentBackgroundLevel = 0;
var backgrounds = ['forest', 'desert', 'snow', 'jungle', 'volcano'];
var backgroundAssets = [forestBackground];
// Game variables
var snake = [];
var snakeDirection = {
x: 1,
y: 0
};
var nextDirection = {
x: 1,
y: 0
};
var gridSize = 60;
var gameWidth = 2048;
var gameHeight = 2732;
var cols = Math.floor(gameWidth / gridSize);
var rows = Math.floor(gameHeight / gridSize);
var waterDroplets = [];
var fires = [];
var waterCollected = 0;
var moveTimer = 0;
var moveInterval = 15; // Snake moves every 15 ticks
var gameRunning = true;
var animals = [];
var rocks = [];
var heartPowerUps = [];
var maxFires = 1; // Start with 1 fire, increase with level
var lives = 3; // Player starts with 3 lives
var heartIcons = [];
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var waterTxt = new Text2('Water: 0', {
size: 70,
fill: 0x1E90FF
});
waterTxt.anchor.set(1, 0);
waterTxt.x = -20;
waterTxt.y = 80;
LK.gui.topRight.addChild(waterTxt);
// Create UI bar container positioned at the top
var uiBar = new Container();
uiBar.y = 20;
uiBar.x = 150;
LK.gui.topLeft.addChild(uiBar);
// Create heart icons for lives
for (var i = 0; i < 3; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.4,
scaleY: 2.4
});
heart.x = i * 50;
heart.y = 0;
heartIcons.push(heart);
uiBar.addChild(heart);
}
// Sound control button
var isMuted = false;
var soundBtn = LK.getAsset('soundOn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.4,
scaleY: 2.4
});
soundBtn.x = 200;
soundBtn.y = 0;
uiBar.addChild(soundBtn);
// Sound button click handler
soundBtn.down = function (x, y, obj) {
isMuted = !isMuted;
if (isMuted) {
LK.stopMusic();
soundBtn.removeChild(soundBtn.children[0]);
soundBtn.attachAsset('soundOff', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
LK.playMusic('bgmusic');
soundBtn.removeChild(soundBtn.children[0]);
soundBtn.attachAsset('soundOn', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Initialize snake
function initSnake() {
// Create initial snake with 3 segments
for (var i = 0; i < 3; i++) {
var isHead = i === 0;
var isTail = i === 2; // Last segment is tail
var segment = new SnakeSegment(isHead, isTail);
segment.x = (5 - i) * gridSize + gridSize / 2;
segment.y = 5 * gridSize + gridSize / 2;
snake.push(segment);
game.addChild(segment);
}
}
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: gridX * gridSize + gridSize / 2,
y: gridY * gridSize + gridSize / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
return {
x: Math.floor(worldX / gridSize),
y: Math.floor(worldY / gridSize)
};
}
// Get random empty grid position
function getRandomEmptyPosition() {
var attempts = 0;
while (attempts < 100) {
var gridX = Math.floor(Math.random() * cols);
var gridY = Math.floor(Math.random() * rows);
var worldPos = gridToWorld(gridX, gridY);
var occupied = false;
// Check if position is occupied by snake
for (var i = 0; i < snake.length; i++) {
var snakeGrid = worldToGrid(snake[i].x, snake[i].y);
if (snakeGrid.x === gridX && snakeGrid.y === gridY) {
occupied = true;
break;
}
}
// Check if position is occupied by water
if (!occupied) {
for (var i = 0; i < waterDroplets.length; i++) {
var waterGrid = worldToGrid(waterDroplets[i].x, waterDroplets[i].y);
if (waterGrid.x === gridX && waterGrid.y === gridY) {
occupied = true;
break;
}
}
}
// Check if position is occupied by fire
if (!occupied) {
for (var i = 0; i < fires.length; i++) {
var fireGrid = worldToGrid(fires[i].x, fires[i].y);
if (fireGrid.x === gridX && fireGrid.y === gridY) {
occupied = true;
break;
}
}
}
// Check if position is occupied by rock
if (!occupied) {
for (var i = 0; i < rocks.length; i++) {
var rockGrid = worldToGrid(rocks[i].x, rocks[i].y);
if (rockGrid.x === gridX && rockGrid.y === gridY) {
occupied = true;
break;
}
}
}
if (!occupied) {
return worldPos;
}
attempts++;
}
// Fallback to center if no empty position found
return gridToWorld(Math.floor(cols / 2), Math.floor(rows / 2));
}
// Spawn water droplet
function spawnWater() {
var pos = getRandomEmptyPosition();
var water = new WaterDroplet();
water.x = pos.x;
water.y = pos.y;
waterDroplets.push(water);
game.addChild(water);
}
// Spawn fire
function spawnFire() {
var pos = getRandomEmptyPosition();
var fireSize = Math.random() < 0.7 ? 1 : 2; // 70% chance for small fire
var fire = new Fire(fireSize);
fire.x = pos.x;
fire.y = pos.y;
// Add size display text
fire.sizeText = new Text2(fire.size.toString(), {
size: 50,
fill: 0xFFFFFF
});
fire.sizeText.anchor.set(0.5, 0.5);
fire.sizeText.x = 0;
fire.sizeText.y = -50;
fire.addChild(fire.sizeText);
fires.push(fire);
game.addChild(fire);
// Spawn multiple escaping animals near fire
var animalCount = Math.floor(Math.random() * 3) + 2; // 2-4 animals
for (var j = 0; j < animalCount; j++) {
var animal;
var animalType = Math.random();
if (animalType < 0.4) {
// 40% chance for rabbit
animal = new Rabbit();
} else if (animalType < 0.7) {
// 30% chance for deer
animal = new Deer();
} else if (animalType < 0.9) {
// 20% chance for bird
animal = new Bird();
} else {
// 10% chance for regular animal
animal = new Animal();
}
animal.x = pos.x + (Math.random() - 0.5) * 120;
animal.y = pos.y + (Math.random() - 0.5) * 120;
animals.push(animal);
game.addChild(animal);
}
}
// Spawn rock obstacle
function spawnRock() {
var pos = getRandomEmptyPosition();
var rock = new Rock();
rock.x = pos.x;
rock.y = pos.y;
// Scale rock based on score - start at 2x, increase with score
var baseScale = 2.0; // Start at 2x size
var scoreMultiplier = Math.floor(LK.getScore() / 1000) * 0.5; // +0.5x every 1000 points
var finalScale = baseScale + scoreMultiplier;
rock.children[0].scaleX = finalScale;
rock.children[0].scaleY = finalScale;
rocks.push(rock);
game.addChild(rock);
}
// Spawn heart power-up
function spawnHeartPowerUp() {
var pos = getRandomEmptyPosition();
var heartPowerUp = new HeartPowerUp();
heartPowerUp.x = pos.x;
heartPowerUp.y = pos.y;
heartPowerUps.push(heartPowerUp);
game.addChild(heartPowerUp);
// Auto-remove after 10 seconds using tween
tween(heartPowerUp, {}, {
duration: 10000,
onFinish: function onFinish() {
for (var i = heartPowerUps.length - 1; i >= 0; i--) {
if (heartPowerUps[i] === heartPowerUp) {
heartPowerUp.destroy();
heartPowerUps.splice(i, 1);
break;
}
}
}
});
}
// Move snake
function moveSnake() {
if (!gameRunning) {
return;
}
// Update direction
snakeDirection.x = nextDirection.x;
snakeDirection.y = nextDirection.y;
// Calculate new head position
var head = snake[0];
var headGrid = worldToGrid(head.x, head.y);
var newHeadGrid = {
x: headGrid.x + snakeDirection.x,
y: headGrid.y + snakeDirection.y
};
// Check boundaries
if (newHeadGrid.x < 0 || newHeadGrid.x >= cols || newHeadGrid.y < 0 || newHeadGrid.y >= rows) {
gameOver();
return;
}
// Check self collision
for (var i = 0; i < snake.length; i++) {
var segmentGrid = worldToGrid(snake[i].x, snake[i].y);
if (segmentGrid.x === newHeadGrid.x && segmentGrid.y === newHeadGrid.y) {
gameOver();
return;
}
}
// Check rock collision
for (var i = 0; i < rocks.length; i++) {
var rock = rocks[i];
var rockGrid = worldToGrid(rock.x, rock.y);
if (rockGrid.x === newHeadGrid.x && rockGrid.y === newHeadGrid.y) {
gameOver();
return;
}
}
// Move snake body and rotate segments
for (var i = snake.length - 1; i > 0; i--) {
// Store previous position for rotation calculation
var prevX = snake[i].x;
var prevY = snake[i].y;
// Move to new position
snake[i].x = snake[i - 1].x;
snake[i].y = snake[i - 1].y;
// Calculate direction for body segment rotation
var dirX = snake[i].x - prevX;
var dirY = snake[i].y - prevY;
// Rotate body segment based on movement direction
if (dirX > 0 && dirY === 0) {
// Moving right
snake[i].children[0].rotation = 0;
} else if (dirX < 0 && dirY === 0) {
// Moving left
snake[i].children[0].rotation = Math.PI;
} else if (dirX === 0 && dirY > 0) {
// Moving down
snake[i].children[0].rotation = Math.PI / 2;
} else if (dirX === 0 && dirY < 0) {
// Moving up
snake[i].children[0].rotation = -Math.PI / 2;
}
}
// Move head
var newHeadPos = gridToWorld(newHeadGrid.x, newHeadGrid.y);
head.x = newHeadPos.x;
head.y = newHeadPos.y;
// Rotate head based on direction
if (snakeDirection.x === 1 && snakeDirection.y === 0) {
// Moving right
head.children[0].rotation = 0;
} else if (snakeDirection.x === -1 && snakeDirection.y === 0) {
// Moving left
head.children[0].rotation = Math.PI;
} else if (snakeDirection.x === 0 && snakeDirection.y === 1) {
// Moving down
head.children[0].rotation = Math.PI / 2;
} else if (snakeDirection.x === 0 && snakeDirection.y === -1) {
// Moving up
head.children[0].rotation = -Math.PI / 2;
}
// Check water collection
for (var i = waterDroplets.length - 1; i >= 0; i--) {
var water = waterDroplets[i];
var waterGrid = worldToGrid(water.x, water.y);
if (waterGrid.x === newHeadGrid.x && waterGrid.y === newHeadGrid.y) {
// Collect water
waterCollected++;
waterTxt.setText('Water: ' + waterCollected);
LK.setScore(LK.getScore() + 10);
scoreTxt.setText('Score: ' + LK.getScore());
// Grow snake - insert new body segment before tail
var lastSegment = snake[snake.length - 1];
// Convert current tail to body segment
if (snake.length > 1) {
var currentTail = snake[snake.length - 1];
currentTail.removeChild(currentTail.children[0]);
var bodyGraphics = currentTail.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Create new tail segment
var newTail = new SnakeSegment(false, true);
newTail.x = lastSegment.x;
newTail.y = lastSegment.y;
snake.push(newTail);
game.addChild(newTail);
// Remove water
water.destroy();
waterDroplets.splice(i, 1);
// Play sound
if (!isMuted) {
LK.getSound('collect').play();
}
// Spawn new water
spawnWater();
break;
}
}
// Check heart power-up collection
for (var i = heartPowerUps.length - 1; i >= 0; i--) {
var heartPowerUp = heartPowerUps[i];
var heartGrid = worldToGrid(heartPowerUp.x, heartPowerUp.y);
if (heartGrid.x === newHeadGrid.x && heartGrid.y === newHeadGrid.y) {
// Collect heart power-up
if (lives < 3) {
lives++;
updateHearts();
// Flash effect
LK.effects.flashObject(head, 0xFF69B4, 300);
// Play sound
if (!isMuted) {
LK.getSound('heartPickup').play();
}
}
// Remove heart power-up
heartPowerUp.destroy();
heartPowerUps.splice(i, 1);
break;
}
}
// Check fire extinguishing - use area-based collision instead of grid-based
for (var i = fires.length - 1; i >= 0; i--) {
var fire = fires[i];
// Calculate fire's collision radius based on size (max 4 grid cells)
var extinguishCells = Math.min(fire.size, 4);
var fireRadius = extinguishCells * gridSize / 2;
// Calculate distance between snake head and fire center
var deltaX = head.x - fire.x;
var deltaY = head.y - fire.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Check if snake head is within fire's area
if (distance <= fireRadius) {
if (waterCollected >= fire.waterNeeded) {
// Create water spray effect
var spray = new WaterSpray(head.x, head.y, fire.x, fire.y);
game.addChild(spray);
// Extinguish fire
waterCollected -= fire.waterNeeded;
waterTxt.setText('Water: ' + waterCollected);
LK.setScore(LK.getScore() + (fire.size === 1 ? 50 : 100));
scoreTxt.setText('Score: ' + LK.getScore());
// Remove fire
fire.destroy();
fires.splice(i, 1);
// Play sound
if (!isMuted) {
LK.getSound('extinguish').play();
}
// Spawn extra water after extinguishing fire
spawnWater();
if (waterDroplets.length < 5) {
spawnWater();
}
// Spawn new fire if needed
if (fires.length < maxFires) {
spawnFire();
}
// Flash effect
LK.effects.flashObject(head, 0x00FF00, 300);
} else {
// Snake burns when touching fire without enough water
LK.effects.flashObject(head, 0xFF0000, 500);
// Add burning effect - make snake segments flash and shake
for (var j = 0; j < snake.length; j++) {
var segment = snake[j];
// Flash red with varying intensity
tween(segment.children[0], {
tint: 0xFF0000
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(segment.children[0], {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
// Shake effect
var originalX = segment.x;
var originalY = segment.y;
tween(segment, {
x: originalX + (Math.random() - 0.5) * 20,
y: originalY + (Math.random() - 0.5) * 20
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(segment, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
}
gameOver();
return;
}
break;
}
}
}
// Change background based on score
function changeBackground() {
var newLevel = Math.floor(LK.getScore() / 1000);
if (newLevel !== currentBackgroundLevel && newLevel < backgrounds.length) {
// Remove old background
if (backgroundAssets[currentBackgroundLevel]) {
backgroundAssets[currentBackgroundLevel].destroy();
}
// Add new background
var newBackground = game.attachAsset(backgrounds[newLevel], {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Move to back
game.addChildAt(newBackground, 0);
backgroundAssets[newLevel] = newBackground;
currentBackgroundLevel = newLevel;
}
}
// Update heart display
function updateHearts() {
for (var i = 0; i < heartIcons.length; i++) {
if (i < lives) {
heartIcons[i].alpha = 1.0;
} else {
heartIcons[i].alpha = 0.3;
}
}
}
// Game over
function gameOver() {
lives--;
updateHearts();
if (lives <= 0) {
gameRunning = false;
LK.showGameOver();
} else {
// Reset snake position and continue
LK.effects.flashScreen(0xFF0000, 500);
// Reset snake to starting position
var head = snake[0];
head.x = 5 * gridSize + gridSize / 2;
head.y = 5 * gridSize + gridSize / 2;
// Reset direction
snakeDirection = {
x: 1,
y: 0
};
nextDirection = {
x: 1,
y: 0
};
}
}
// Touch controls
game.down = function (x, y, obj) {
// No need to store touch start position for direct touch controls
};
game.up = function (x, y, obj) {
if (!gameRunning) {
return;
}
// Get snake head position
var head = snake[0];
var snakeX = head.x;
var snakeY = head.y;
// Calculate direction from snake head to touch position
var deltaX = x - snakeX;
var deltaY = y - snakeY;
// Turn based on snake's current direction and touch relative position
if (snakeDirection.x === 1) {
// Snake moving right
if (deltaY < 0) {
// Touch above snake - turn up
nextDirection = {
x: 0,
y: -1
};
} else if (deltaY > 0) {
// Touch below snake - turn down
nextDirection = {
x: 0,
y: 1
};
}
} else if (snakeDirection.x === -1) {
// Snake moving left
if (deltaY < 0) {
// Touch above snake - turn up
nextDirection = {
x: 0,
y: -1
};
} else if (deltaY > 0) {
// Touch below snake - turn down
nextDirection = {
x: 0,
y: 1
};
}
} else if (snakeDirection.y === 1) {
// Snake moving down
if (deltaX < 0) {
// Touch left of snake - turn left
nextDirection = {
x: -1,
y: 0
};
} else if (deltaX > 0) {
// Touch right of snake - turn right
nextDirection = {
x: 1,
y: 0
};
}
} else if (snakeDirection.y === -1) {
// Snake moving up
if (deltaX < 0) {
// Touch left of snake - turn left
nextDirection = {
x: -1,
y: 0
};
} else if (deltaX > 0) {
// Touch right of snake - turn right
nextDirection = {
x: 1,
y: 0
};
}
}
};
// Initialize game
initSnake();
updateHearts();
spawnWater();
spawnWater();
spawnWater();
spawnWater();
spawnFire();
// Start background music
LK.playMusic('bgmusic');
// Game update loop
game.update = function () {
if (!gameRunning) {
return;
}
moveTimer++;
if (moveTimer >= moveInterval) {
moveTimer = 0;
moveSnake();
}
// Spawn additional water periodically
if (LK.ticks % 80 === 0 && waterDroplets.length < 6) {
spawnWater();
}
// Increase difficulty - add more fires at higher scores
var currentLevel = Math.floor(LK.getScore() / 300) + 1;
maxFires = Math.min(currentLevel, 5); // Maximum 5 fires
if (fires.length < maxFires && LK.ticks % 900 === 0) {
spawnFire();
}
// Spawn heart power-up every 300 points
if (LK.getScore() > 0 && LK.getScore() % 300 === 0 && LK.ticks % 5 === 0) {
// Check if we should spawn a heart (only once per 300 point milestone)
var currentScoreMilestone = Math.floor(LK.getScore() / 300);
if (!game.lastHeartMilestone || game.lastHeartMilestone < currentScoreMilestone) {
spawnHeartPowerUp();
game.lastHeartMilestone = currentScoreMilestone;
}
}
// Clean up dead animals
for (var i = animals.length - 1; i >= 0; i--) {
if (animals[i].lifeTime <= 0) {
animals.splice(i, 1);
}
}
// Clean up expired heart power-ups
for (var i = heartPowerUps.length - 1; i >= 0; i--) {
if (heartPowerUps[i].lifeTime <= 0) {
heartPowerUps.splice(i, 1);
}
}
// Make game slightly faster as score increases - slower acceleration
if (LK.getScore() > 0 && LK.getScore() % 500 === 0) {
moveInterval = Math.max(10, moveInterval - 1);
}
// Spawn rock obstacles periodically
if (LK.ticks % 1800 === 0 && rocks.length < 3) {
spawnRock();
}
// Check for background changes every 1000 points
changeBackground();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Animal = Container.expand(function () {
var self = Container.call(this);
var animalGraphics = self.attachAsset('animal', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 4;
self.speedY = (Math.random() - 0.5) * 4;
self.lifeTime = 300; // 5 seconds at 60fps
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 5;
self.speedY = (Math.random() - 0.5) * 5;
self.lifeTime = 350;
self.flapTimer = 0;
self.baseY = 0;
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Flying movement with up/down oscillation
if (self.baseY === 0) {
self.baseY = self.y;
}
self.x += self.speedX;
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.animOffset) * 15;
// Wing flapping animation
self.flapTimer++;
if (self.flapTimer >= 5) {
self.flapTimer = 0;
var flapScale = 0.8 + Math.sin(LK.ticks * 0.3 + self.animOffset) * 0.2;
birdGraphics.scaleX = flapScale;
}
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Deer = Container.expand(function () {
var self = Container.call(this);
var deerGraphics = self.attachAsset('deer', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 2; // Slower movement
self.speedY = (Math.random() - 0.5) * 2;
self.lifeTime = 500; // Lives longest
self.panicTimer = 0;
self.isPanicking = false;
self.update = function () {
// Deer occasionally panic and run faster
if (!self.isPanicking && Math.random() < 0.005) {
self.isPanicking = true;
self.panicTimer = 60; // Panic for 1 second
self.speedX *= 3;
self.speedY *= 3;
// Flash effect when panicking
tween(deerGraphics, {
tint: 0xFFAAAA
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(deerGraphics, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
if (self.isPanicking) {
self.panicTimer--;
if (self.panicTimer <= 0) {
self.isPanicking = false;
self.speedX /= 3;
self.speedY /= 3;
}
}
self.x += self.speedX;
self.y += self.speedY;
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Fire = Container.expand(function (size) {
var self = Container.call(this);
self.size = size || 1;
self.maxSize = 10; // Maximum fire size
self.waterNeeded = self.size;
self.growthTimer = 0;
// Fire grows every 20 seconds (1200 ticks at 60fps)
self.growthInterval = 1200; // 20 seconds fixed interval
var fireGraphics = self.attachAsset(self.size === 1 ? 'smallFire' : 'largeFire', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial scale based on fire size
var initialScale = 0.5 + (self.size - 1) * 0.2; // Start smaller, scale up with size
fireGraphics.scaleX = initialScale;
fireGraphics.scaleY = initialScale;
// Add flickering animation
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Growth logic - fire grows every 7 seconds
self.growthTimer++;
if (self.growthTimer >= self.growthInterval && self.size < self.maxSize) {
self.size++;
self.waterNeeded = self.size;
self.growthTimer = 0;
// Update size text
if (self.sizeText) {
self.sizeText.setText(self.size.toString());
}
// Update visual scale based on size - smaller start, bigger growth
var scale = 0.5 + (self.size - 1) * 0.25; // Start at 0.5, increase by 25% per size level
tween(fireGraphics, {
scaleX: scale,
scaleY: scale
}, {
duration: 500,
easing: tween.easeOut
});
// Change color intensity based on size
var intensity = Math.min(1, 0.5 + (self.size - 1) * 0.1);
var redComponent = 0xFF;
var greenBlueComponent = Math.floor((1 - intensity) * 0x44);
var redTint = redComponent << 16 | greenBlueComponent << 8 | greenBlueComponent;
tween(fireGraphics, {
tint: redTint
}, {
duration: 500,
easing: tween.easeOut
});
}
// Flickering animation
var flicker = 0.8 + Math.sin(LK.ticks * 0.1 + self.animOffset) * 0.2;
fireGraphics.alpha = flicker;
var baseScaleX = fireGraphics.scaleX || 1;
var baseScaleY = fireGraphics.scaleY || 1;
fireGraphics.scaleX = baseScaleX + Math.sin(LK.ticks * 0.08 + self.animOffset) * 0.1;
fireGraphics.scaleY = baseScaleY + Math.sin(LK.ticks * 0.08 + self.animOffset) * 0.1;
};
return self;
});
var HeartPowerUp = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.lifeTime = 600; // 10 seconds at 60fps
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Floating animation
heartGraphics.y = Math.sin(LK.ticks * 0.1 + self.animOffset) * 10;
// Pulsing animation
var pulse = 1.0 + Math.sin(LK.ticks * 0.15 + self.animOffset) * 0.3;
heartGraphics.scaleX = 1.5 * pulse;
heartGraphics.scaleY = 1.5 * pulse;
// Fade out in last 2 seconds
if (self.lifeTime <= 120) {
heartGraphics.alpha = self.lifeTime / 120;
}
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Rabbit = Container.expand(function () {
var self = Container.call(this);
var rabbitGraphics = self.attachAsset('rabbit', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 6; // Faster than regular animals
self.speedY = (Math.random() - 0.5) * 6;
self.lifeTime = 400; // Lives longer
self.hopTimer = 0;
self.hopInterval = 20; // Hop every 20 ticks
self.update = function () {
self.hopTimer++;
// Hopping movement - move in bursts
if (self.hopTimer >= self.hopInterval) {
self.x += self.speedX * 2; // Burst movement
self.y += self.speedY * 2;
self.hopTimer = 0;
// Add hop animation
tween(rabbitGraphics, {
scaleY: 1.3
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rabbitGraphics, {
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
self.lifeTime--;
if (self.lifeTime <= 0) {
self.destroy();
}
};
return self;
});
var Rock = Container.expand(function () {
var self = Container.call(this);
var rockGraphics = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 0.5
});
// Add some visual variation to rocks
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
// Subtle rotation animation
rockGraphics.rotation = Math.sin(LK.ticks * 0.01 + self.animOffset) * 0.1;
};
return self;
});
var SnakeSegment = Container.expand(function (isHead, isTail) {
var self = Container.call(this);
var assetType = isHead ? 'snakeHead' : isTail ? 'snakeTail' : 'snakeBody';
var segmentGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var WaterDroplet = Container.expand(function () {
var self = Container.call(this);
var waterGraphics = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5
});
// Add floating animation
self.animOffset = Math.random() * Math.PI * 2;
self.update = function () {
waterGraphics.y = Math.sin(LK.ticks * 0.05 + self.animOffset) * 5;
};
return self;
});
var WaterSpray = Container.expand(function (startX, startY, targetX, targetY) {
var self = Container.call(this);
// Create multiple water droplets for spray effect
self.droplets = [];
for (var i = 0; i < 8; i++) {
var droplet = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
// Position droplet at start position
droplet.x = 0;
droplet.y = 0;
// Calculate spray direction with some randomness
var angle = Math.atan2(targetY - startY, targetX - startX);
var spreadAngle = (Math.random() - 0.5) * 0.8; // Random spread
var finalAngle = angle + spreadAngle;
// Set movement properties
droplet.velocityX = Math.cos(finalAngle) * (3 + Math.random() * 2);
droplet.velocityY = Math.sin(finalAngle) * (3 + Math.random() * 2);
droplet.life = 30 + Math.random() * 20; // Random lifetime
droplet.maxLife = droplet.life;
self.droplets.push(droplet);
}
self.x = startX;
self.y = startY;
self.totalLife = 60;
self.update = function () {
self.totalLife--;
// Update each droplet
for (var i = self.droplets.length - 1; i >= 0; i--) {
var droplet = self.droplets[i];
// Move droplet
droplet.x += droplet.velocityX;
droplet.y += droplet.velocityY;
// Fade out droplet
droplet.life--;
droplet.alpha = droplet.life / droplet.maxLife;
// Remove dead droplets
if (droplet.life <= 0) {
self.removeChild(droplet);
self.droplets.splice(i, 1);
}
}
// Remove spray when all droplets are gone or time is up
if (self.droplets.length === 0 || self.totalLife <= 0) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Add forest background
var forestBackground = game.attachAsset('forest', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Background tracking variables
var currentBackgroundLevel = 0;
var backgrounds = ['forest', 'desert', 'snow', 'jungle', 'volcano'];
var backgroundAssets = [forestBackground];
// Game variables
var snake = [];
var snakeDirection = {
x: 1,
y: 0
};
var nextDirection = {
x: 1,
y: 0
};
var gridSize = 60;
var gameWidth = 2048;
var gameHeight = 2732;
var cols = Math.floor(gameWidth / gridSize);
var rows = Math.floor(gameHeight / gridSize);
var waterDroplets = [];
var fires = [];
var waterCollected = 0;
var moveTimer = 0;
var moveInterval = 15; // Snake moves every 15 ticks
var gameRunning = true;
var animals = [];
var rocks = [];
var heartPowerUps = [];
var maxFires = 1; // Start with 1 fire, increase with level
var lives = 3; // Player starts with 3 lives
var heartIcons = [];
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var waterTxt = new Text2('Water: 0', {
size: 70,
fill: 0x1E90FF
});
waterTxt.anchor.set(1, 0);
waterTxt.x = -20;
waterTxt.y = 80;
LK.gui.topRight.addChild(waterTxt);
// Create UI bar container positioned at the top
var uiBar = new Container();
uiBar.y = 20;
uiBar.x = 150;
LK.gui.topLeft.addChild(uiBar);
// Create heart icons for lives
for (var i = 0; i < 3; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.4,
scaleY: 2.4
});
heart.x = i * 50;
heart.y = 0;
heartIcons.push(heart);
uiBar.addChild(heart);
}
// Sound control button
var isMuted = false;
var soundBtn = LK.getAsset('soundOn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.4,
scaleY: 2.4
});
soundBtn.x = 200;
soundBtn.y = 0;
uiBar.addChild(soundBtn);
// Sound button click handler
soundBtn.down = function (x, y, obj) {
isMuted = !isMuted;
if (isMuted) {
LK.stopMusic();
soundBtn.removeChild(soundBtn.children[0]);
soundBtn.attachAsset('soundOff', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
LK.playMusic('bgmusic');
soundBtn.removeChild(soundBtn.children[0]);
soundBtn.attachAsset('soundOn', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Initialize snake
function initSnake() {
// Create initial snake with 3 segments
for (var i = 0; i < 3; i++) {
var isHead = i === 0;
var isTail = i === 2; // Last segment is tail
var segment = new SnakeSegment(isHead, isTail);
segment.x = (5 - i) * gridSize + gridSize / 2;
segment.y = 5 * gridSize + gridSize / 2;
snake.push(segment);
game.addChild(segment);
}
}
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: gridX * gridSize + gridSize / 2,
y: gridY * gridSize + gridSize / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
return {
x: Math.floor(worldX / gridSize),
y: Math.floor(worldY / gridSize)
};
}
// Get random empty grid position
function getRandomEmptyPosition() {
var attempts = 0;
while (attempts < 100) {
var gridX = Math.floor(Math.random() * cols);
var gridY = Math.floor(Math.random() * rows);
var worldPos = gridToWorld(gridX, gridY);
var occupied = false;
// Check if position is occupied by snake
for (var i = 0; i < snake.length; i++) {
var snakeGrid = worldToGrid(snake[i].x, snake[i].y);
if (snakeGrid.x === gridX && snakeGrid.y === gridY) {
occupied = true;
break;
}
}
// Check if position is occupied by water
if (!occupied) {
for (var i = 0; i < waterDroplets.length; i++) {
var waterGrid = worldToGrid(waterDroplets[i].x, waterDroplets[i].y);
if (waterGrid.x === gridX && waterGrid.y === gridY) {
occupied = true;
break;
}
}
}
// Check if position is occupied by fire
if (!occupied) {
for (var i = 0; i < fires.length; i++) {
var fireGrid = worldToGrid(fires[i].x, fires[i].y);
if (fireGrid.x === gridX && fireGrid.y === gridY) {
occupied = true;
break;
}
}
}
// Check if position is occupied by rock
if (!occupied) {
for (var i = 0; i < rocks.length; i++) {
var rockGrid = worldToGrid(rocks[i].x, rocks[i].y);
if (rockGrid.x === gridX && rockGrid.y === gridY) {
occupied = true;
break;
}
}
}
if (!occupied) {
return worldPos;
}
attempts++;
}
// Fallback to center if no empty position found
return gridToWorld(Math.floor(cols / 2), Math.floor(rows / 2));
}
// Spawn water droplet
function spawnWater() {
var pos = getRandomEmptyPosition();
var water = new WaterDroplet();
water.x = pos.x;
water.y = pos.y;
waterDroplets.push(water);
game.addChild(water);
}
// Spawn fire
function spawnFire() {
var pos = getRandomEmptyPosition();
var fireSize = Math.random() < 0.7 ? 1 : 2; // 70% chance for small fire
var fire = new Fire(fireSize);
fire.x = pos.x;
fire.y = pos.y;
// Add size display text
fire.sizeText = new Text2(fire.size.toString(), {
size: 50,
fill: 0xFFFFFF
});
fire.sizeText.anchor.set(0.5, 0.5);
fire.sizeText.x = 0;
fire.sizeText.y = -50;
fire.addChild(fire.sizeText);
fires.push(fire);
game.addChild(fire);
// Spawn multiple escaping animals near fire
var animalCount = Math.floor(Math.random() * 3) + 2; // 2-4 animals
for (var j = 0; j < animalCount; j++) {
var animal;
var animalType = Math.random();
if (animalType < 0.4) {
// 40% chance for rabbit
animal = new Rabbit();
} else if (animalType < 0.7) {
// 30% chance for deer
animal = new Deer();
} else if (animalType < 0.9) {
// 20% chance for bird
animal = new Bird();
} else {
// 10% chance for regular animal
animal = new Animal();
}
animal.x = pos.x + (Math.random() - 0.5) * 120;
animal.y = pos.y + (Math.random() - 0.5) * 120;
animals.push(animal);
game.addChild(animal);
}
}
// Spawn rock obstacle
function spawnRock() {
var pos = getRandomEmptyPosition();
var rock = new Rock();
rock.x = pos.x;
rock.y = pos.y;
// Scale rock based on score - start at 2x, increase with score
var baseScale = 2.0; // Start at 2x size
var scoreMultiplier = Math.floor(LK.getScore() / 1000) * 0.5; // +0.5x every 1000 points
var finalScale = baseScale + scoreMultiplier;
rock.children[0].scaleX = finalScale;
rock.children[0].scaleY = finalScale;
rocks.push(rock);
game.addChild(rock);
}
// Spawn heart power-up
function spawnHeartPowerUp() {
var pos = getRandomEmptyPosition();
var heartPowerUp = new HeartPowerUp();
heartPowerUp.x = pos.x;
heartPowerUp.y = pos.y;
heartPowerUps.push(heartPowerUp);
game.addChild(heartPowerUp);
// Auto-remove after 10 seconds using tween
tween(heartPowerUp, {}, {
duration: 10000,
onFinish: function onFinish() {
for (var i = heartPowerUps.length - 1; i >= 0; i--) {
if (heartPowerUps[i] === heartPowerUp) {
heartPowerUp.destroy();
heartPowerUps.splice(i, 1);
break;
}
}
}
});
}
// Move snake
function moveSnake() {
if (!gameRunning) {
return;
}
// Update direction
snakeDirection.x = nextDirection.x;
snakeDirection.y = nextDirection.y;
// Calculate new head position
var head = snake[0];
var headGrid = worldToGrid(head.x, head.y);
var newHeadGrid = {
x: headGrid.x + snakeDirection.x,
y: headGrid.y + snakeDirection.y
};
// Check boundaries
if (newHeadGrid.x < 0 || newHeadGrid.x >= cols || newHeadGrid.y < 0 || newHeadGrid.y >= rows) {
gameOver();
return;
}
// Check self collision
for (var i = 0; i < snake.length; i++) {
var segmentGrid = worldToGrid(snake[i].x, snake[i].y);
if (segmentGrid.x === newHeadGrid.x && segmentGrid.y === newHeadGrid.y) {
gameOver();
return;
}
}
// Check rock collision
for (var i = 0; i < rocks.length; i++) {
var rock = rocks[i];
var rockGrid = worldToGrid(rock.x, rock.y);
if (rockGrid.x === newHeadGrid.x && rockGrid.y === newHeadGrid.y) {
gameOver();
return;
}
}
// Move snake body and rotate segments
for (var i = snake.length - 1; i > 0; i--) {
// Store previous position for rotation calculation
var prevX = snake[i].x;
var prevY = snake[i].y;
// Move to new position
snake[i].x = snake[i - 1].x;
snake[i].y = snake[i - 1].y;
// Calculate direction for body segment rotation
var dirX = snake[i].x - prevX;
var dirY = snake[i].y - prevY;
// Rotate body segment based on movement direction
if (dirX > 0 && dirY === 0) {
// Moving right
snake[i].children[0].rotation = 0;
} else if (dirX < 0 && dirY === 0) {
// Moving left
snake[i].children[0].rotation = Math.PI;
} else if (dirX === 0 && dirY > 0) {
// Moving down
snake[i].children[0].rotation = Math.PI / 2;
} else if (dirX === 0 && dirY < 0) {
// Moving up
snake[i].children[0].rotation = -Math.PI / 2;
}
}
// Move head
var newHeadPos = gridToWorld(newHeadGrid.x, newHeadGrid.y);
head.x = newHeadPos.x;
head.y = newHeadPos.y;
// Rotate head based on direction
if (snakeDirection.x === 1 && snakeDirection.y === 0) {
// Moving right
head.children[0].rotation = 0;
} else if (snakeDirection.x === -1 && snakeDirection.y === 0) {
// Moving left
head.children[0].rotation = Math.PI;
} else if (snakeDirection.x === 0 && snakeDirection.y === 1) {
// Moving down
head.children[0].rotation = Math.PI / 2;
} else if (snakeDirection.x === 0 && snakeDirection.y === -1) {
// Moving up
head.children[0].rotation = -Math.PI / 2;
}
// Check water collection
for (var i = waterDroplets.length - 1; i >= 0; i--) {
var water = waterDroplets[i];
var waterGrid = worldToGrid(water.x, water.y);
if (waterGrid.x === newHeadGrid.x && waterGrid.y === newHeadGrid.y) {
// Collect water
waterCollected++;
waterTxt.setText('Water: ' + waterCollected);
LK.setScore(LK.getScore() + 10);
scoreTxt.setText('Score: ' + LK.getScore());
// Grow snake - insert new body segment before tail
var lastSegment = snake[snake.length - 1];
// Convert current tail to body segment
if (snake.length > 1) {
var currentTail = snake[snake.length - 1];
currentTail.removeChild(currentTail.children[0]);
var bodyGraphics = currentTail.attachAsset('snakeBody', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Create new tail segment
var newTail = new SnakeSegment(false, true);
newTail.x = lastSegment.x;
newTail.y = lastSegment.y;
snake.push(newTail);
game.addChild(newTail);
// Remove water
water.destroy();
waterDroplets.splice(i, 1);
// Play sound
if (!isMuted) {
LK.getSound('collect').play();
}
// Spawn new water
spawnWater();
break;
}
}
// Check heart power-up collection
for (var i = heartPowerUps.length - 1; i >= 0; i--) {
var heartPowerUp = heartPowerUps[i];
var heartGrid = worldToGrid(heartPowerUp.x, heartPowerUp.y);
if (heartGrid.x === newHeadGrid.x && heartGrid.y === newHeadGrid.y) {
// Collect heart power-up
if (lives < 3) {
lives++;
updateHearts();
// Flash effect
LK.effects.flashObject(head, 0xFF69B4, 300);
// Play sound
if (!isMuted) {
LK.getSound('heartPickup').play();
}
}
// Remove heart power-up
heartPowerUp.destroy();
heartPowerUps.splice(i, 1);
break;
}
}
// Check fire extinguishing - use area-based collision instead of grid-based
for (var i = fires.length - 1; i >= 0; i--) {
var fire = fires[i];
// Calculate fire's collision radius based on size (max 4 grid cells)
var extinguishCells = Math.min(fire.size, 4);
var fireRadius = extinguishCells * gridSize / 2;
// Calculate distance between snake head and fire center
var deltaX = head.x - fire.x;
var deltaY = head.y - fire.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Check if snake head is within fire's area
if (distance <= fireRadius) {
if (waterCollected >= fire.waterNeeded) {
// Create water spray effect
var spray = new WaterSpray(head.x, head.y, fire.x, fire.y);
game.addChild(spray);
// Extinguish fire
waterCollected -= fire.waterNeeded;
waterTxt.setText('Water: ' + waterCollected);
LK.setScore(LK.getScore() + (fire.size === 1 ? 50 : 100));
scoreTxt.setText('Score: ' + LK.getScore());
// Remove fire
fire.destroy();
fires.splice(i, 1);
// Play sound
if (!isMuted) {
LK.getSound('extinguish').play();
}
// Spawn extra water after extinguishing fire
spawnWater();
if (waterDroplets.length < 5) {
spawnWater();
}
// Spawn new fire if needed
if (fires.length < maxFires) {
spawnFire();
}
// Flash effect
LK.effects.flashObject(head, 0x00FF00, 300);
} else {
// Snake burns when touching fire without enough water
LK.effects.flashObject(head, 0xFF0000, 500);
// Add burning effect - make snake segments flash and shake
for (var j = 0; j < snake.length; j++) {
var segment = snake[j];
// Flash red with varying intensity
tween(segment.children[0], {
tint: 0xFF0000
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(segment.children[0], {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
// Shake effect
var originalX = segment.x;
var originalY = segment.y;
tween(segment, {
x: originalX + (Math.random() - 0.5) * 20,
y: originalY + (Math.random() - 0.5) * 20
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(segment, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
}
gameOver();
return;
}
break;
}
}
}
// Change background based on score
function changeBackground() {
var newLevel = Math.floor(LK.getScore() / 1000);
if (newLevel !== currentBackgroundLevel && newLevel < backgrounds.length) {
// Remove old background
if (backgroundAssets[currentBackgroundLevel]) {
backgroundAssets[currentBackgroundLevel].destroy();
}
// Add new background
var newBackground = game.attachAsset(backgrounds[newLevel], {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Move to back
game.addChildAt(newBackground, 0);
backgroundAssets[newLevel] = newBackground;
currentBackgroundLevel = newLevel;
}
}
// Update heart display
function updateHearts() {
for (var i = 0; i < heartIcons.length; i++) {
if (i < lives) {
heartIcons[i].alpha = 1.0;
} else {
heartIcons[i].alpha = 0.3;
}
}
}
// Game over
function gameOver() {
lives--;
updateHearts();
if (lives <= 0) {
gameRunning = false;
LK.showGameOver();
} else {
// Reset snake position and continue
LK.effects.flashScreen(0xFF0000, 500);
// Reset snake to starting position
var head = snake[0];
head.x = 5 * gridSize + gridSize / 2;
head.y = 5 * gridSize + gridSize / 2;
// Reset direction
snakeDirection = {
x: 1,
y: 0
};
nextDirection = {
x: 1,
y: 0
};
}
}
// Touch controls
game.down = function (x, y, obj) {
// No need to store touch start position for direct touch controls
};
game.up = function (x, y, obj) {
if (!gameRunning) {
return;
}
// Get snake head position
var head = snake[0];
var snakeX = head.x;
var snakeY = head.y;
// Calculate direction from snake head to touch position
var deltaX = x - snakeX;
var deltaY = y - snakeY;
// Turn based on snake's current direction and touch relative position
if (snakeDirection.x === 1) {
// Snake moving right
if (deltaY < 0) {
// Touch above snake - turn up
nextDirection = {
x: 0,
y: -1
};
} else if (deltaY > 0) {
// Touch below snake - turn down
nextDirection = {
x: 0,
y: 1
};
}
} else if (snakeDirection.x === -1) {
// Snake moving left
if (deltaY < 0) {
// Touch above snake - turn up
nextDirection = {
x: 0,
y: -1
};
} else if (deltaY > 0) {
// Touch below snake - turn down
nextDirection = {
x: 0,
y: 1
};
}
} else if (snakeDirection.y === 1) {
// Snake moving down
if (deltaX < 0) {
// Touch left of snake - turn left
nextDirection = {
x: -1,
y: 0
};
} else if (deltaX > 0) {
// Touch right of snake - turn right
nextDirection = {
x: 1,
y: 0
};
}
} else if (snakeDirection.y === -1) {
// Snake moving up
if (deltaX < 0) {
// Touch left of snake - turn left
nextDirection = {
x: -1,
y: 0
};
} else if (deltaX > 0) {
// Touch right of snake - turn right
nextDirection = {
x: 1,
y: 0
};
}
}
};
// Initialize game
initSnake();
updateHearts();
spawnWater();
spawnWater();
spawnWater();
spawnWater();
spawnFire();
// Start background music
LK.playMusic('bgmusic');
// Game update loop
game.update = function () {
if (!gameRunning) {
return;
}
moveTimer++;
if (moveTimer >= moveInterval) {
moveTimer = 0;
moveSnake();
}
// Spawn additional water periodically
if (LK.ticks % 80 === 0 && waterDroplets.length < 6) {
spawnWater();
}
// Increase difficulty - add more fires at higher scores
var currentLevel = Math.floor(LK.getScore() / 300) + 1;
maxFires = Math.min(currentLevel, 5); // Maximum 5 fires
if (fires.length < maxFires && LK.ticks % 900 === 0) {
spawnFire();
}
// Spawn heart power-up every 300 points
if (LK.getScore() > 0 && LK.getScore() % 300 === 0 && LK.ticks % 5 === 0) {
// Check if we should spawn a heart (only once per 300 point milestone)
var currentScoreMilestone = Math.floor(LK.getScore() / 300);
if (!game.lastHeartMilestone || game.lastHeartMilestone < currentScoreMilestone) {
spawnHeartPowerUp();
game.lastHeartMilestone = currentScoreMilestone;
}
}
// Clean up dead animals
for (var i = animals.length - 1; i >= 0; i--) {
if (animals[i].lifeTime <= 0) {
animals.splice(i, 1);
}
}
// Clean up expired heart power-ups
for (var i = heartPowerUps.length - 1; i >= 0; i--) {
if (heartPowerUps[i].lifeTime <= 0) {
heartPowerUps.splice(i, 1);
}
}
// Make game slightly faster as score increases - slower acceleration
if (LK.getScore() > 0 && LK.getScore() % 500 === 0) {
moveInterval = Math.max(10, moveInterval - 1);
}
// Spawn rock obstacles periodically
if (LK.ticks % 1800 === 0 && rocks.length < 3) {
spawnRock();
}
// Check for background changes every 1000 points
changeBackground();
};