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