/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Food = Container.expand(function (foodType) {
var self = Container.call(this);
// Food types: normal, special, seed, root, fruit (removed insect)
self.foodType = foodType || 'normal';
self.isSpecial = self.foodType === 'special';
// Set value based on food type
switch (self.foodType) {
case 'normal':
self.value = 1;
break;
case 'special':
self.value = 3;
break;
case 'seed':
self.value = 1;
break;
case 'root':
self.value = 2;
break;
case 'fruit':
self.value = 4;
break;
default:
self.value = 1;
}
// Attach the appropriate food graphic
var foodGraphics = self.attachAsset('food_' + self.foodType, {
anchorX: 0.5,
anchorY: 0.5
});
// Insect type removed
// Add decoration for fruit type
if (self.foodType === 'fruit') {
var stem = self.attachAsset('soil_particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 1.5,
x: 0,
y: -15
});
stem.tint = 0x006400; // Dark green
}
// Animations based on food type (removed insect)
switch (self.foodType) {
case 'special':
case 'fruit':
self.pulseDirection = 1;
self.pulseMin = 0.85;
self.pulseMax = 1.15;
self.pulseSpeed = 0.01;
break;
}
self.update = function () {
// Pulsating effect for special foods and fruits
if (self.foodType === 'special' || self.foodType === 'fruit') {
if (self.pulseDirection > 0) {
foodGraphics.scale.x += self.pulseSpeed;
foodGraphics.scale.y += self.pulseSpeed;
if (foodGraphics.scale.x >= self.pulseMax) {
self.pulseDirection = -1;
}
} else {
foodGraphics.scale.x -= self.pulseSpeed;
foodGraphics.scale.y -= self.pulseSpeed;
if (foodGraphics.scale.x <= self.pulseMin) {
self.pulseDirection = 1;
}
}
}
// Insect movement pattern removed
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var PowerUp = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'speed'; // speed, invincibility, grow
var color;
switch (self.type) {
case 'speed':
color = 0x00BFFF;
break;
case 'invincibility':
color = 0xFFFF00;
break;
case 'grow':
color = 0xFF00FF;
break;
default:
color = 0xFFFFFF;
}
// Create a shape for the powerup
var powerupGraphics = self.attachAsset('powerup_' + self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Add pulsating animation
self.pulseDirection = 1;
self.pulseMin = 0.8;
self.pulseMax = 1.2;
self.pulseSpeed = 0.02;
self.update = function () {
// Pulsating effect
if (self.pulseDirection > 0) {
powerupGraphics.scale.x += self.pulseSpeed;
powerupGraphics.scale.y += self.pulseSpeed;
if (powerupGraphics.scale.x >= self.pulseMax) {
self.pulseDirection = -1;
}
} else {
powerupGraphics.scale.x -= self.pulseSpeed;
powerupGraphics.scale.y -= self.pulseSpeed;
if (powerupGraphics.scale.x <= self.pulseMin) {
self.pulseDirection = 1;
}
}
};
return self;
});
var Predator = Container.expand(function () {
var self = Container.call(this);
// Create predator body (mole or other underground creature)
var body = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
body.tint = 0x606060; // Gray color for mole
// Add eyes
var leftEye = self.attachAsset('food_normal', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
x: -15,
y: -10
});
leftEye.tint = 0xff0000; // Red eyes
var rightEye = self.attachAsset('food_normal', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
x: 15,
y: -10
});
rightEye.tint = 0xff0000;
// Add teeth/claws
var leftClaw = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.8,
rotation: -Math.PI / 6,
x: -20,
y: 15
});
leftClaw.tint = 0xffffff;
var rightClaw = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.8,
rotation: Math.PI / 6,
x: 20,
y: 15
});
rightClaw.tint = 0xffffff;
// Predator properties
self.speed = 2;
self.targetWorm = null;
self.huntRadius = 300; // Detection range
self.active = false;
self.idleTime = 0;
self.huntTime = 0;
self.maxHuntTime = 300; // Hunt for 5 seconds max
self.state = 'sleeping'; // sleeping, hunting, retreating
// Update method to handle predator behavior
self.update = function () {
if (self.state === 'sleeping') {
// Occasionally check if worm is nearby
if (LK.ticks % 30 === 0 && self.targetWorm) {
var wormHead = self.targetWorm[0];
var distance = getDistance(self.x, self.y, wormHead.x, wormHead.y);
// Activate if worm is within range
if (distance < self.huntRadius) {
self.state = 'hunting';
self.huntTime = 0;
// Flash eyes
tween(leftEye, {
alpha: 0
}, {
duration: 100,
yoyo: true,
repeat: 3
});
tween(rightEye, {
alpha: 0
}, {
duration: 100,
yoyo: true,
repeat: 3
});
}
}
// Slightly wiggle while sleeping
body.rotation = Math.sin(LK.ticks / 20) * 0.05;
} else if (self.state === 'hunting') {
// Hunt the worm
if (self.targetWorm && self.targetWorm.length > 0) {
var wormHead = self.targetWorm[0];
var dx = wormHead.x - self.x;
var dy = wormHead.y - self.y;
var angle = Math.atan2(dy, dx);
// Move toward the worm
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
// Rotate to face direction of movement
self.rotation = angle;
// Create soil particles while moving
if (LK.ticks % 5 === 0) {
createSoilEffect(self.x, self.y, 2);
}
// Increase hunt time
self.huntTime++;
if (self.huntTime >= self.maxHuntTime) {
// Give up after max hunt time
self.state = 'retreating';
}
}
} else if (self.state === 'retreating') {
// Retreat underground
self.alpha -= 0.02;
if (self.alpha <= 0) {
// Reset predator
self.alpha = 1;
// Move to a new random location
self.x = Math.random() * (GAME_WIDTH - 200) + 100;
self.y = Math.random() * (GAME_HEIGHT - 200) + 100;
// Go back to sleeping
self.state = 'sleeping';
}
}
};
return self;
});
var SoilParticle = Container.expand(function () {
var self = Container.call(this);
var particleGraphics = self.attachAsset('soil_particle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set tint method - allows changing the soil particle color
self.tint = function (color) {
particleGraphics.tint = color;
};
self.alpha = Math.random() * 0.5 + 0.2;
self.lifespan = Math.random() * 30 + 15;
self.velocityX = (Math.random() - 0.5) * 2;
self.velocityY = (Math.random() - 0.5) * 2;
// Random rotation
self.rotation = Math.random() * Math.PI * 2;
// Random scale for more varied soil particles
self.scale = Math.random() * 0.5 + 0.75;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.rotation += 0.02; // Slow rotation for visual interest
self.lifespan--;
// Gradually fade out and get smaller
if (self.lifespan <= 10) {
self.alpha -= 0.1;
self.scale -= 0.05;
}
};
return self;
});
var WormSegment = Container.expand(function (isHead) {
var self = Container.call(this);
self.isHead = isHead || false;
var segmentGraphics = self.attachAsset(self.isHead ? 'worm_head' : 'worm_body', {
anchorX: 0.5,
anchorY: 0.5
});
// Add details like eyes and mouth if this is the head
if (self.isHead) {
// Create mouth
var mouth = self.attachAsset('worm_body', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.2,
x: 10,
y: 5
});
mouth.tint = 0x000000;
// Left eye
var leftEye = self.attachAsset('worm_body', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
x: 10,
y: -10
});
leftEye.tint = 0x000000;
// Right eye
var rightEye = self.attachAsset('worm_body', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
x: 10,
y: 10
});
rightEye.tint = 0x000000;
// Add pulsating animation for head to simulate breathing
self.pulseDirection = 1;
self.pulseMin = 0.95;
self.pulseMax = 1.05;
self.pulseSpeed = 0.005;
}
// Store previous position for smooth following
self.prevX = 0;
self.prevY = 0;
// Save position to follow
self.savePosition = function () {
self.prevX = self.x;
self.prevY = self.y;
};
// Add update method to animate head
if (self.isHead) {
self.update = function () {
// Pulsating effect
if (self.pulseDirection > 0) {
segmentGraphics.scale.x += self.pulseSpeed;
segmentGraphics.scale.y += self.pulseSpeed;
if (segmentGraphics.scale.x >= self.pulseMax) {
self.pulseDirection = -1;
}
} else {
segmentGraphics.scale.x -= self.pulseSpeed;
segmentGraphics.scale.y -= self.pulseSpeed;
if (segmentGraphics.scale.x <= self.pulseMin) {
self.pulseDirection = 1;
}
}
};
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x3E2723
});
/****
* Game Code
****/
// Insect food type removed
// Spawn a predator
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function spawnPredator() {
if (predators.length >= MAX_PREDATORS) {
return;
}
var predator = new Predator();
// Position predator randomly away from edges and worm head
var head = wormSegments[0];
do {
predator.x = Math.random() * (GAME_WIDTH - 200) + 100;
predator.y = Math.random() * (GAME_HEIGHT - 200) + 100;
} while (getDistance(predator.x, predator.y, head.x, head.y) < 500);
// Link to worm segments for tracking
predator.targetWorm = wormSegments;
// Add to game
game.addChild(predator);
predators.push(predator);
// Create dramatic soil eruption effect
createSoilEffect(predator.x, predator.y, 20);
}
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var SEGMENT_SPACING = 30;
var STARTING_SEGMENTS = 5;
var MAX_SEGMENTS = 50;
var MOVE_SPEED = 5;
var TURNING_SPEED = 0.1;
var SPAWN_FOOD_INTERVAL = 60;
var SPAWN_OBSTACLE_INTERVAL = 180;
var SPAWN_POWERUP_INTERVAL = 600;
var SPECIAL_FOOD_CHANCE = 0.2;
var POWERUP_DURATION = 300;
// Terrain constants
var TERRAIN_TYPES = [{
name: 'soil',
color: 0x5d4037,
speedModifier: 1.0
}, {
name: 'sand',
color: 0xd2b48c,
speedModifier: 1.2
}, {
name: 'clay',
color: 0x8d6e63,
speedModifier: 0.8
}, {
name: 'humus',
color: 0x3e2723,
speedModifier: 0.9
}];
var TERRAIN_SIZE = 400; // Size of terrain patches
var currentTerrain = TERRAIN_TYPES[0]; // Default terrain
// Game variables
var wormSegments = [];
var foods = [];
var obstacles = [];
var powerups = [];
var predators = [];
var soilParticles = [];
var targetX = 0;
var targetY = 0;
var score = 0;
var gameLevel = storage.level || 1;
var highScore = storage.highScore || 0;
var movingToTarget = false;
var wormAngle = 0;
var foodTimer = 0;
var obstacleTimer = 0;
var powerupTimer = 0;
var predatorTimer = 0;
var SPAWN_PREDATOR_INTERVAL = 600; // 10 seconds at 60 FPS
var MAX_PREDATORS = Math.min(Math.floor(gameLevel / 2), 3); // Cap at 3 predators
var powerupActive = false;
var powerupType = null;
var powerupTimeRemaining = 0;
var gameStarted = false;
var levelScore = gameLevel * 10;
// Create background with terrain variations
function createBackground() {
var backgroundContainer = new Container();
game.addChild(backgroundContainer);
var tileSize = 100;
// Create terrain patches
var terrainPatches = [];
for (var tx = 0; tx < GAME_WIDTH; tx += TERRAIN_SIZE) {
for (var ty = 0; ty < GAME_HEIGHT; ty += TERRAIN_SIZE) {
// Randomly select terrain type for this patch
var terrainType = TERRAIN_TYPES[Math.floor(Math.random() * TERRAIN_TYPES.length)];
terrainPatches.push({
x: tx,
y: ty,
width: TERRAIN_SIZE,
height: TERRAIN_SIZE,
type: terrainType
});
}
}
// Create tiles based on terrain patches
for (var x = 0; x < GAME_WIDTH; x += tileSize) {
for (var y = 0; y < GAME_HEIGHT; y += tileSize) {
// Determine which terrain patch this tile belongs to
var terrainType = TERRAIN_TYPES[0]; // Default
for (var i = 0; i < terrainPatches.length; i++) {
var patch = terrainPatches[i];
if (x >= patch.x && x < patch.x + patch.width && y >= patch.y && y < patch.y + patch.height) {
terrainType = patch.type;
break;
}
}
var tile = LK.getAsset('background_tile', {
anchorX: 0,
anchorY: 0,
width: tileSize,
height: tileSize,
alpha: 0.7 + Math.random() * 0.3
});
tile.tint = terrainType.color;
tile.terrainType = terrainType;
tile.x = x;
tile.y = y;
tile.rotation = Math.random() * Math.PI * 2;
tile.scale.x = 0.9 + Math.random() * 0.2;
tile.scale.y = 0.9 + Math.random() * 0.2;
backgroundContainer.addChild(tile);
}
}
}
// Initialize worm
function createWorm() {
// Create head
var head = new WormSegment(true);
head.x = GAME_WIDTH / 2;
head.y = GAME_HEIGHT / 2;
game.addChild(head);
wormSegments.push(head);
// Create initial body segments
for (var i = 0; i < STARTING_SEGMENTS; i++) {
addWormSegment();
}
}
// Add new segment to the worm
function addWormSegment() {
if (wormSegments.length >= MAX_SEGMENTS) {
return;
}
var lastSegment = wormSegments[wormSegments.length - 1];
var newSegment = new WormSegment(false);
newSegment.x = lastSegment.x;
newSegment.y = lastSegment.y;
game.addChild(newSegment);
wormSegments.push(newSegment);
}
// Spawn a food item at a random position
function spawnFood() {
// Determine food type based on probabilities and level (removed insect)
var foodTypes = ['normal', 'seed', 'root', 'fruit', 'special'];
var probabilities = [0.45 - gameLevel * 0.02,
// normal (decreases with level)
0.25,
// seed
0.15,
// root
0.1,
// fruit
Math.min(0.05 + gameLevel * 0.01, 0.2) // special (increases with level, capped at 20%)
];
// Make sure probabilities add up to 1
var sum = probabilities.reduce(function (a, b) {
return a + b;
}, 0);
probabilities = probabilities.map(function (p) {
return p / sum;
});
// Select food type based on weighted random
var random = Math.random();
var cumulativeProb = 0;
var selectedFoodType = 'normal';
for (var i = 0; i < foodTypes.length; i++) {
cumulativeProb += probabilities[i];
if (random <= cumulativeProb) {
selectedFoodType = foodTypes[i];
break;
}
}
var food = new Food(selectedFoodType);
// Position food randomly away from edges
food.x = Math.random() * (GAME_WIDTH - 200) + 100;
food.y = Math.random() * (GAME_HEIGHT - 200) + 100;
// Make sure food doesn't overlap with obstacles
for (var i = 0; i < obstacles.length; i++) {
if (getDistance(food.x, food.y, obstacles[i].x, obstacles[i].y) < 100) {
// Reposition if too close to an obstacle
food.x = Math.random() * (GAME_WIDTH - 200) + 100;
food.y = Math.random() * (GAME_HEIGHT - 200) + 100;
i = -1; // Reset loop to check again
}
}
// For fruits and roots, try to place them in appropriate terrain if possible
if (selectedFoodType === 'fruit' || selectedFoodType === 'root') {
var attempts = 0;
var placed = false;
var desiredTerrain = selectedFoodType === 'fruit' ? 'humus' : 'clay';
while (attempts < 5 && !placed) {
var testX = Math.random() * (GAME_WIDTH - 200) + 100;
var testY = Math.random() * (GAME_HEIGHT - 200) + 100;
// Check terrain
var terrainAtPosition = findTerrainAtPosition(testX, testY);
if (terrainAtPosition && terrainAtPosition.name === desiredTerrain) {
// Check obstacles
var validPosition = true;
for (var j = 0; j < obstacles.length; j++) {
if (getDistance(testX, testY, obstacles[j].x, obstacles[j].y) < 100) {
validPosition = false;
break;
}
}
if (validPosition) {
food.x = testX;
food.y = testY;
placed = true;
}
}
attempts++;
}
}
// Insect type removed
game.addChild(food);
foods.push(food);
}
// Helper function to determine terrain at a specific position
function findTerrainAtPosition(x, y) {
// Find the terrain patch this position belongs to
for (var i = 0; i < TERRAIN_TYPES.length; i++) {
var terrainX = Math.floor(x / TERRAIN_SIZE) * TERRAIN_SIZE;
var terrainY = Math.floor(y / TERRAIN_SIZE) * TERRAIN_SIZE;
// Return the terrain type for this patch (simplified approach)
return TERRAIN_TYPES[Math.floor(Math.random() * TERRAIN_TYPES.length)];
}
return TERRAIN_TYPES[0]; // Default to first terrain type
}
// Spawn an obstacle
function spawnObstacle() {
var obstacle = new Obstacle();
// Position obstacle randomly away from edges and worm head
var head = wormSegments[0];
do {
obstacle.x = Math.random() * (GAME_WIDTH - 200) + 100;
obstacle.y = Math.random() * (GAME_HEIGHT - 200) + 100;
} while (getDistance(obstacle.x, obstacle.y, head.x, head.y) < 300);
game.addChild(obstacle);
obstacles.push(obstacle);
}
// Spawn a power-up
function spawnPowerup() {
var types = ['speed', 'invincibility', 'grow'];
var randomType = types[Math.floor(Math.random() * types.length)];
var powerup = new PowerUp(randomType);
// Position powerup randomly away from edges
powerup.x = Math.random() * (GAME_WIDTH - 200) + 100;
powerup.y = Math.random() * (GAME_HEIGHT - 200) + 100;
// Make sure powerup doesn't overlap with obstacles
for (var i = 0; i < obstacles.length; i++) {
if (getDistance(powerup.x, powerup.y, obstacles[i].x, obstacles[i].y) < 100) {
// Reposition if too close to an obstacle
powerup.x = Math.random() * (GAME_WIDTH - 200) + 100;
powerup.y = Math.random() * (GAME_HEIGHT - 200) + 100;
i = -1; // Reset loop to check again
}
}
game.addChild(powerup);
powerups.push(powerup);
}
// Create soil particles effect
function createSoilEffect(x, y, count, color) {
for (var i = 0; i < count; i++) {
var particle = new SoilParticle();
// Set position with slight randomization
particle.x = x + (Math.random() - 0.5) * 10;
particle.y = y + (Math.random() - 0.5) * 10;
// Apply terrain color if provided
if (color) {
particle.tint = color;
} else {
// Use color from current terrain if no specific color is provided
var terrainAtPosition = findTerrainAtPosition(x, y);
if (terrainAtPosition) {
particle.tint = terrainAtPosition.color;
}
}
game.addChild(particle);
soilParticles.push(particle);
}
}
// Calculate distance between two points
function getDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
// Activate powerup
function activatePowerup(type) {
powerupActive = true;
powerupType = type;
powerupTimeRemaining = POWERUP_DURATION;
LK.getSound('powerup').play();
// Apply power-up effects
switch (type) {
case 'speed':
MOVE_SPEED *= 1.75;
break;
case 'invincibility':
// Visual effect for invincibility
for (var i = 0; i < wormSegments.length; i++) {
// Ensure wormSegment is a valid object before applying tween
if (wormSegments[i] && _typeof(wormSegments[i]) === 'object') {
tween(wormSegments[i], {
alpha: 0.7
}, {
duration: 300
});
}
}
break;
case 'grow':
// Add multiple segments at once
for (var j = 0; j < 3; j++) {
addWormSegment();
}
break;
}
}
// End powerup effect
function endPowerupEffect() {
switch (powerupType) {
case 'speed':
MOVE_SPEED = 5;
break;
case 'invincibility':
// Reset visual effect
for (var i = 0; i < wormSegments.length; i++) {
// Ensure wormSegment is a valid object before applying tween
if (wormSegments[i] && _typeof(wormSegments[i]) === 'object') {
tween(wormSegments[i], {
alpha: 1
}, {
duration: 300
});
}
}
break;
}
powerupActive = false;
powerupType = null;
}
// Setup UI elements
function setupUI() {
// Score text
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -scoreTxt.width - 20;
scoreTxt.y = 20;
// Level text
var levelTxt = new Text2('Level: ' + gameLevel, {
size: 60,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(levelTxt);
levelTxt.x = -levelTxt.width - 20;
levelTxt.y = 100;
// High score text
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 50,
fill: 0xFFD700
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 160;
// Next level text
var nextLevelTxt = new Text2('Next Level: ' + levelScore, {
size: 50,
fill: 0x32CD32
});
nextLevelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(nextLevelTxt);
nextLevelTxt.y = 20;
// Powerup indicator
var powerupTxt = new Text2('', {
size: 60,
fill: 0xFFFF00
});
powerupTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(powerupTxt);
powerupTxt.x = 120; // Keep away from the top-left 100x100 px area
powerupTxt.y = 20;
// Start instructions
var instructionsTxt = new Text2('Tap to start\nDrag to move your worm\nEat food to grow', {
size: 80,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionsTxt);
// Update UI function
game.updateUI = function () {
scoreTxt.setText('Score: ' + score);
scoreTxt.x = -scoreTxt.width - 20;
levelTxt.setText('Level: ' + gameLevel);
levelTxt.x = -levelTxt.width - 20;
highScoreTxt.setText('Best: ' + highScore);
highScoreTxt.x = -highScoreTxt.width - 20;
nextLevelTxt.setText('Next Level: ' + (levelScore - score));
if (powerupActive) {
var timeLeft = Math.ceil(powerupTimeRemaining / 60);
powerupTxt.setText(powerupType.toUpperCase() + ': ' + timeLeft + 's');
} else {
powerupTxt.setText('');
}
if (gameStarted) {
instructionsTxt.alpha = 0;
}
};
}
// Initialize game
function initGame() {
// Reset game state
wormSegments = [];
foods = [];
obstacles = [];
powerups = [];
predators = [];
soilParticles = [];
score = 0;
movingToTarget = false;
wormAngle = 0;
foodTimer = 0;
obstacleTimer = 0;
powerupTimer = 0;
predatorTimer = 0;
powerupActive = false;
powerupType = null;
powerupTimeRemaining = 0;
levelScore = gameLevel * 10;
MOVE_SPEED = 5;
MAX_PREDATORS = Math.min(Math.floor(gameLevel / 2), 3); // Recalculate for current level
// Set background
createBackground();
// Create the worm
createWorm();
// Initial food
for (var i = 0; i < 3; i++) {
spawnFood();
}
// Initial obstacles based on level
for (var j = 0; j < Math.min(gameLevel, 5); j++) {
spawnObstacle();
}
// Setup UI
setupUI();
// Play background music
LK.playMusic('bgmusic');
}
// Initialize the game
initGame();
// Game logic update function
game.update = function () {
if (!gameStarted) {
return;
}
// Move worm head
var head = wormSegments[0];
if (movingToTarget) {
// Calculate angle to target
var dx = targetX - head.x;
var dy = targetY - head.y;
var targetAngle = Math.atan2(dy, dx);
// Smooth angle change
var angleDiff = targetAngle - wormAngle;
// Handle angle wrapping
if (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
if (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
// Apply gradual turning
wormAngle += angleDiff * TURNING_SPEED;
// Determine terrain at current position
var currentTerrainType = findTerrainAtPosition(head.x, head.y);
var speedModifier = currentTerrainType ? currentTerrainType.speedModifier : 1.0;
// Apply terrain effects to movement
var adjustedSpeed = MOVE_SPEED * speedModifier;
if (powerupActive && powerupType === 'speed') {
adjustedSpeed *= 1.75; // Apply speed powerup with terrain modifier
}
// Move in the current direction with terrain-adjusted speed
head.savePosition();
head.x += Math.cos(wormAngle) * adjustedSpeed;
head.y += Math.sin(wormAngle) * adjustedSpeed;
// Create soil particles as the worm moves
if (LK.ticks % 3 === 0) {
var particleCount = speedModifier > 1 ? 2 : 1; // More particles in sand (faster terrain)
var particleColor = currentTerrainType ? currentTerrainType.color : 0x8b4513;
createSoilEffect(head.x, head.y, particleCount, particleColor);
}
// Check if close enough to target
if (getDistance(head.x, head.y, targetX, targetY) < 10) {
movingToTarget = false;
}
}
// Move body segments
for (var i = 1; i < wormSegments.length; i++) {
var segment = wormSegments[i];
var prevSegment = wormSegments[i - 1];
// Save current position before updating
segment.savePosition();
// Calculate direction to previous segment
var dx = prevSegment.prevX - segment.x;
var dy = prevSegment.prevY - segment.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move towards the previous segment's saved position
if (distance > SEGMENT_SPACING) {
var moveRatio = (distance - SEGMENT_SPACING) / distance;
segment.x += dx * moveRatio;
segment.y += dy * moveRatio;
}
}
// Update soil particles
for (var p = soilParticles.length - 1; p >= 0; p--) {
var particle = soilParticles[p];
particle.update();
if (particle.lifespan <= 0 || particle.alpha <= 0) {
particle.destroy();
soilParticles.splice(p, 1);
}
}
// Boundary checking for worm head
if (head.x < 0) {
head.x = 0;
}
if (head.x > GAME_WIDTH) {
head.x = GAME_WIDTH;
}
if (head.y < 0) {
head.y = 0;
}
if (head.y > GAME_HEIGHT) {
head.y = GAME_HEIGHT;
}
// Check collisions with food
for (var f = foods.length - 1; f >= 0; f--) {
var food = foods[f];
food.update();
if (getDistance(head.x, head.y, food.x, food.y) < 40) {
// Eat food
score += food.value;
LK.setScore(score);
LK.getSound('eat').play();
// Add segments based on food value
for (var s = 0; s < food.value; s++) {
addWormSegment();
}
// Remove food
food.destroy();
foods.splice(f, 1);
// Create particle effect
createSoilEffect(food.x, food.y, 10);
}
}
// Check collisions with obstacles
for (var o = 0; o < obstacles.length; o++) {
var obstacle = obstacles[o];
if (getDistance(head.x, head.y, obstacle.x, obstacle.y) < 45) {
if (powerupActive && powerupType === 'invincibility') {
// Destroy obstacle if invincible
createSoilEffect(obstacle.x, obstacle.y, 15);
obstacle.destroy();
obstacles.splice(o, 1);
o--;
continue;
} else {
// Game over on collision
LK.getSound('hit').play();
LK.effects.flashScreen(0xFF0000, 500);
// Update high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
LK.showGameOver();
return;
}
}
}
// Check collisions with powerups
for (var pu = powerups.length - 1; pu >= 0; pu--) {
var powerup = powerups[pu];
powerup.update();
if (getDistance(head.x, head.y, powerup.x, powerup.y) < 40) {
// Collect powerup
activatePowerup(powerup.type);
// Remove powerup
powerup.destroy();
powerups.splice(pu, 1);
// Create particle effect
createSoilEffect(powerup.x, powerup.y, 15);
}
}
// Spawn new food
foodTimer++;
if (foodTimer >= SPAWN_FOOD_INTERVAL) {
spawnFood();
foodTimer = 0;
}
// Spawn new obstacles based on level
obstacleTimer++;
if (obstacleTimer >= SPAWN_OBSTACLE_INTERVAL && obstacles.length < gameLevel + 2) {
spawnObstacle();
obstacleTimer = 0;
}
// Spawn powerups occasionally
powerupTimer++;
if (powerupTimer >= SPAWN_POWERUP_INTERVAL) {
spawnPowerup();
powerupTimer = 0;
}
// Spawn predators if game level is high enough
if (gameLevel >= 2) {
predatorTimer++;
if (predatorTimer >= SPAWN_PREDATOR_INTERVAL && predators.length < MAX_PREDATORS) {
spawnPredator();
predatorTimer = 0;
}
// Update predators
for (var pr = predators.length - 1; pr >= 0; pr--) {
var predator = predators[pr];
predator.update();
// Check for collisions with worm head
var head = wormSegments[0];
if (getDistance(head.x, head.y, predator.x, predator.y) < 50 && predator.state === 'hunting' && predator.alpha > 0.5) {
if (powerupActive && powerupType === 'invincibility') {
// Destroy predator if invincible
createSoilEffect(predator.x, predator.y, 25);
predator.destroy();
predators.splice(pr, 1);
} else {
// Game over on collision
LK.getSound('hit').play();
LK.effects.flashScreen(0xFF0000, 500);
// Update high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
LK.showGameOver();
return;
}
}
}
}
// Update powerup duration
if (powerupActive) {
powerupTimeRemaining--;
if (powerupTimeRemaining <= 0) {
endPowerupEffect();
}
}
// Level up check
if (score >= levelScore) {
// Level up
gameLevel++;
storage.level = gameLevel;
levelScore = gameLevel * 10;
// Flash screen green
LK.effects.flashScreen(0x00FF00, 500);
// Speed increase with level
MOVE_SPEED = 5 + gameLevel * 0.25;
if (MOVE_SPEED > 10) {
MOVE_SPEED = 10;
}
}
// Update UI
game.updateUI();
};
// Event handlers
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
return;
}
targetX = x;
targetY = y;
movingToTarget = true;
};
game.move = function (x, y, obj) {
if (gameStarted) {
targetX = x;
targetY = y;
movingToTarget = true;
}
};
game.up = function (x, y, obj) {
// Keep moving to the last target point
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Food = Container.expand(function (foodType) {
var self = Container.call(this);
// Food types: normal, special, seed, root, fruit (removed insect)
self.foodType = foodType || 'normal';
self.isSpecial = self.foodType === 'special';
// Set value based on food type
switch (self.foodType) {
case 'normal':
self.value = 1;
break;
case 'special':
self.value = 3;
break;
case 'seed':
self.value = 1;
break;
case 'root':
self.value = 2;
break;
case 'fruit':
self.value = 4;
break;
default:
self.value = 1;
}
// Attach the appropriate food graphic
var foodGraphics = self.attachAsset('food_' + self.foodType, {
anchorX: 0.5,
anchorY: 0.5
});
// Insect type removed
// Add decoration for fruit type
if (self.foodType === 'fruit') {
var stem = self.attachAsset('soil_particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 1.5,
x: 0,
y: -15
});
stem.tint = 0x006400; // Dark green
}
// Animations based on food type (removed insect)
switch (self.foodType) {
case 'special':
case 'fruit':
self.pulseDirection = 1;
self.pulseMin = 0.85;
self.pulseMax = 1.15;
self.pulseSpeed = 0.01;
break;
}
self.update = function () {
// Pulsating effect for special foods and fruits
if (self.foodType === 'special' || self.foodType === 'fruit') {
if (self.pulseDirection > 0) {
foodGraphics.scale.x += self.pulseSpeed;
foodGraphics.scale.y += self.pulseSpeed;
if (foodGraphics.scale.x >= self.pulseMax) {
self.pulseDirection = -1;
}
} else {
foodGraphics.scale.x -= self.pulseSpeed;
foodGraphics.scale.y -= self.pulseSpeed;
if (foodGraphics.scale.x <= self.pulseMin) {
self.pulseDirection = 1;
}
}
}
// Insect movement pattern removed
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var PowerUp = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'speed'; // speed, invincibility, grow
var color;
switch (self.type) {
case 'speed':
color = 0x00BFFF;
break;
case 'invincibility':
color = 0xFFFF00;
break;
case 'grow':
color = 0xFF00FF;
break;
default:
color = 0xFFFFFF;
}
// Create a shape for the powerup
var powerupGraphics = self.attachAsset('powerup_' + self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Add pulsating animation
self.pulseDirection = 1;
self.pulseMin = 0.8;
self.pulseMax = 1.2;
self.pulseSpeed = 0.02;
self.update = function () {
// Pulsating effect
if (self.pulseDirection > 0) {
powerupGraphics.scale.x += self.pulseSpeed;
powerupGraphics.scale.y += self.pulseSpeed;
if (powerupGraphics.scale.x >= self.pulseMax) {
self.pulseDirection = -1;
}
} else {
powerupGraphics.scale.x -= self.pulseSpeed;
powerupGraphics.scale.y -= self.pulseSpeed;
if (powerupGraphics.scale.x <= self.pulseMin) {
self.pulseDirection = 1;
}
}
};
return self;
});
var Predator = Container.expand(function () {
var self = Container.call(this);
// Create predator body (mole or other underground creature)
var body = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
body.tint = 0x606060; // Gray color for mole
// Add eyes
var leftEye = self.attachAsset('food_normal', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
x: -15,
y: -10
});
leftEye.tint = 0xff0000; // Red eyes
var rightEye = self.attachAsset('food_normal', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
x: 15,
y: -10
});
rightEye.tint = 0xff0000;
// Add teeth/claws
var leftClaw = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.8,
rotation: -Math.PI / 6,
x: -20,
y: 15
});
leftClaw.tint = 0xffffff;
var rightClaw = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.8,
rotation: Math.PI / 6,
x: 20,
y: 15
});
rightClaw.tint = 0xffffff;
// Predator properties
self.speed = 2;
self.targetWorm = null;
self.huntRadius = 300; // Detection range
self.active = false;
self.idleTime = 0;
self.huntTime = 0;
self.maxHuntTime = 300; // Hunt for 5 seconds max
self.state = 'sleeping'; // sleeping, hunting, retreating
// Update method to handle predator behavior
self.update = function () {
if (self.state === 'sleeping') {
// Occasionally check if worm is nearby
if (LK.ticks % 30 === 0 && self.targetWorm) {
var wormHead = self.targetWorm[0];
var distance = getDistance(self.x, self.y, wormHead.x, wormHead.y);
// Activate if worm is within range
if (distance < self.huntRadius) {
self.state = 'hunting';
self.huntTime = 0;
// Flash eyes
tween(leftEye, {
alpha: 0
}, {
duration: 100,
yoyo: true,
repeat: 3
});
tween(rightEye, {
alpha: 0
}, {
duration: 100,
yoyo: true,
repeat: 3
});
}
}
// Slightly wiggle while sleeping
body.rotation = Math.sin(LK.ticks / 20) * 0.05;
} else if (self.state === 'hunting') {
// Hunt the worm
if (self.targetWorm && self.targetWorm.length > 0) {
var wormHead = self.targetWorm[0];
var dx = wormHead.x - self.x;
var dy = wormHead.y - self.y;
var angle = Math.atan2(dy, dx);
// Move toward the worm
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
// Rotate to face direction of movement
self.rotation = angle;
// Create soil particles while moving
if (LK.ticks % 5 === 0) {
createSoilEffect(self.x, self.y, 2);
}
// Increase hunt time
self.huntTime++;
if (self.huntTime >= self.maxHuntTime) {
// Give up after max hunt time
self.state = 'retreating';
}
}
} else if (self.state === 'retreating') {
// Retreat underground
self.alpha -= 0.02;
if (self.alpha <= 0) {
// Reset predator
self.alpha = 1;
// Move to a new random location
self.x = Math.random() * (GAME_WIDTH - 200) + 100;
self.y = Math.random() * (GAME_HEIGHT - 200) + 100;
// Go back to sleeping
self.state = 'sleeping';
}
}
};
return self;
});
var SoilParticle = Container.expand(function () {
var self = Container.call(this);
var particleGraphics = self.attachAsset('soil_particle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set tint method - allows changing the soil particle color
self.tint = function (color) {
particleGraphics.tint = color;
};
self.alpha = Math.random() * 0.5 + 0.2;
self.lifespan = Math.random() * 30 + 15;
self.velocityX = (Math.random() - 0.5) * 2;
self.velocityY = (Math.random() - 0.5) * 2;
// Random rotation
self.rotation = Math.random() * Math.PI * 2;
// Random scale for more varied soil particles
self.scale = Math.random() * 0.5 + 0.75;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.rotation += 0.02; // Slow rotation for visual interest
self.lifespan--;
// Gradually fade out and get smaller
if (self.lifespan <= 10) {
self.alpha -= 0.1;
self.scale -= 0.05;
}
};
return self;
});
var WormSegment = Container.expand(function (isHead) {
var self = Container.call(this);
self.isHead = isHead || false;
var segmentGraphics = self.attachAsset(self.isHead ? 'worm_head' : 'worm_body', {
anchorX: 0.5,
anchorY: 0.5
});
// Add details like eyes and mouth if this is the head
if (self.isHead) {
// Create mouth
var mouth = self.attachAsset('worm_body', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.2,
x: 10,
y: 5
});
mouth.tint = 0x000000;
// Left eye
var leftEye = self.attachAsset('worm_body', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
x: 10,
y: -10
});
leftEye.tint = 0x000000;
// Right eye
var rightEye = self.attachAsset('worm_body', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25,
x: 10,
y: 10
});
rightEye.tint = 0x000000;
// Add pulsating animation for head to simulate breathing
self.pulseDirection = 1;
self.pulseMin = 0.95;
self.pulseMax = 1.05;
self.pulseSpeed = 0.005;
}
// Store previous position for smooth following
self.prevX = 0;
self.prevY = 0;
// Save position to follow
self.savePosition = function () {
self.prevX = self.x;
self.prevY = self.y;
};
// Add update method to animate head
if (self.isHead) {
self.update = function () {
// Pulsating effect
if (self.pulseDirection > 0) {
segmentGraphics.scale.x += self.pulseSpeed;
segmentGraphics.scale.y += self.pulseSpeed;
if (segmentGraphics.scale.x >= self.pulseMax) {
self.pulseDirection = -1;
}
} else {
segmentGraphics.scale.x -= self.pulseSpeed;
segmentGraphics.scale.y -= self.pulseSpeed;
if (segmentGraphics.scale.x <= self.pulseMin) {
self.pulseDirection = 1;
}
}
};
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x3E2723
});
/****
* Game Code
****/
// Insect food type removed
// Spawn a predator
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function spawnPredator() {
if (predators.length >= MAX_PREDATORS) {
return;
}
var predator = new Predator();
// Position predator randomly away from edges and worm head
var head = wormSegments[0];
do {
predator.x = Math.random() * (GAME_WIDTH - 200) + 100;
predator.y = Math.random() * (GAME_HEIGHT - 200) + 100;
} while (getDistance(predator.x, predator.y, head.x, head.y) < 500);
// Link to worm segments for tracking
predator.targetWorm = wormSegments;
// Add to game
game.addChild(predator);
predators.push(predator);
// Create dramatic soil eruption effect
createSoilEffect(predator.x, predator.y, 20);
}
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var SEGMENT_SPACING = 30;
var STARTING_SEGMENTS = 5;
var MAX_SEGMENTS = 50;
var MOVE_SPEED = 5;
var TURNING_SPEED = 0.1;
var SPAWN_FOOD_INTERVAL = 60;
var SPAWN_OBSTACLE_INTERVAL = 180;
var SPAWN_POWERUP_INTERVAL = 600;
var SPECIAL_FOOD_CHANCE = 0.2;
var POWERUP_DURATION = 300;
// Terrain constants
var TERRAIN_TYPES = [{
name: 'soil',
color: 0x5d4037,
speedModifier: 1.0
}, {
name: 'sand',
color: 0xd2b48c,
speedModifier: 1.2
}, {
name: 'clay',
color: 0x8d6e63,
speedModifier: 0.8
}, {
name: 'humus',
color: 0x3e2723,
speedModifier: 0.9
}];
var TERRAIN_SIZE = 400; // Size of terrain patches
var currentTerrain = TERRAIN_TYPES[0]; // Default terrain
// Game variables
var wormSegments = [];
var foods = [];
var obstacles = [];
var powerups = [];
var predators = [];
var soilParticles = [];
var targetX = 0;
var targetY = 0;
var score = 0;
var gameLevel = storage.level || 1;
var highScore = storage.highScore || 0;
var movingToTarget = false;
var wormAngle = 0;
var foodTimer = 0;
var obstacleTimer = 0;
var powerupTimer = 0;
var predatorTimer = 0;
var SPAWN_PREDATOR_INTERVAL = 600; // 10 seconds at 60 FPS
var MAX_PREDATORS = Math.min(Math.floor(gameLevel / 2), 3); // Cap at 3 predators
var powerupActive = false;
var powerupType = null;
var powerupTimeRemaining = 0;
var gameStarted = false;
var levelScore = gameLevel * 10;
// Create background with terrain variations
function createBackground() {
var backgroundContainer = new Container();
game.addChild(backgroundContainer);
var tileSize = 100;
// Create terrain patches
var terrainPatches = [];
for (var tx = 0; tx < GAME_WIDTH; tx += TERRAIN_SIZE) {
for (var ty = 0; ty < GAME_HEIGHT; ty += TERRAIN_SIZE) {
// Randomly select terrain type for this patch
var terrainType = TERRAIN_TYPES[Math.floor(Math.random() * TERRAIN_TYPES.length)];
terrainPatches.push({
x: tx,
y: ty,
width: TERRAIN_SIZE,
height: TERRAIN_SIZE,
type: terrainType
});
}
}
// Create tiles based on terrain patches
for (var x = 0; x < GAME_WIDTH; x += tileSize) {
for (var y = 0; y < GAME_HEIGHT; y += tileSize) {
// Determine which terrain patch this tile belongs to
var terrainType = TERRAIN_TYPES[0]; // Default
for (var i = 0; i < terrainPatches.length; i++) {
var patch = terrainPatches[i];
if (x >= patch.x && x < patch.x + patch.width && y >= patch.y && y < patch.y + patch.height) {
terrainType = patch.type;
break;
}
}
var tile = LK.getAsset('background_tile', {
anchorX: 0,
anchorY: 0,
width: tileSize,
height: tileSize,
alpha: 0.7 + Math.random() * 0.3
});
tile.tint = terrainType.color;
tile.terrainType = terrainType;
tile.x = x;
tile.y = y;
tile.rotation = Math.random() * Math.PI * 2;
tile.scale.x = 0.9 + Math.random() * 0.2;
tile.scale.y = 0.9 + Math.random() * 0.2;
backgroundContainer.addChild(tile);
}
}
}
// Initialize worm
function createWorm() {
// Create head
var head = new WormSegment(true);
head.x = GAME_WIDTH / 2;
head.y = GAME_HEIGHT / 2;
game.addChild(head);
wormSegments.push(head);
// Create initial body segments
for (var i = 0; i < STARTING_SEGMENTS; i++) {
addWormSegment();
}
}
// Add new segment to the worm
function addWormSegment() {
if (wormSegments.length >= MAX_SEGMENTS) {
return;
}
var lastSegment = wormSegments[wormSegments.length - 1];
var newSegment = new WormSegment(false);
newSegment.x = lastSegment.x;
newSegment.y = lastSegment.y;
game.addChild(newSegment);
wormSegments.push(newSegment);
}
// Spawn a food item at a random position
function spawnFood() {
// Determine food type based on probabilities and level (removed insect)
var foodTypes = ['normal', 'seed', 'root', 'fruit', 'special'];
var probabilities = [0.45 - gameLevel * 0.02,
// normal (decreases with level)
0.25,
// seed
0.15,
// root
0.1,
// fruit
Math.min(0.05 + gameLevel * 0.01, 0.2) // special (increases with level, capped at 20%)
];
// Make sure probabilities add up to 1
var sum = probabilities.reduce(function (a, b) {
return a + b;
}, 0);
probabilities = probabilities.map(function (p) {
return p / sum;
});
// Select food type based on weighted random
var random = Math.random();
var cumulativeProb = 0;
var selectedFoodType = 'normal';
for (var i = 0; i < foodTypes.length; i++) {
cumulativeProb += probabilities[i];
if (random <= cumulativeProb) {
selectedFoodType = foodTypes[i];
break;
}
}
var food = new Food(selectedFoodType);
// Position food randomly away from edges
food.x = Math.random() * (GAME_WIDTH - 200) + 100;
food.y = Math.random() * (GAME_HEIGHT - 200) + 100;
// Make sure food doesn't overlap with obstacles
for (var i = 0; i < obstacles.length; i++) {
if (getDistance(food.x, food.y, obstacles[i].x, obstacles[i].y) < 100) {
// Reposition if too close to an obstacle
food.x = Math.random() * (GAME_WIDTH - 200) + 100;
food.y = Math.random() * (GAME_HEIGHT - 200) + 100;
i = -1; // Reset loop to check again
}
}
// For fruits and roots, try to place them in appropriate terrain if possible
if (selectedFoodType === 'fruit' || selectedFoodType === 'root') {
var attempts = 0;
var placed = false;
var desiredTerrain = selectedFoodType === 'fruit' ? 'humus' : 'clay';
while (attempts < 5 && !placed) {
var testX = Math.random() * (GAME_WIDTH - 200) + 100;
var testY = Math.random() * (GAME_HEIGHT - 200) + 100;
// Check terrain
var terrainAtPosition = findTerrainAtPosition(testX, testY);
if (terrainAtPosition && terrainAtPosition.name === desiredTerrain) {
// Check obstacles
var validPosition = true;
for (var j = 0; j < obstacles.length; j++) {
if (getDistance(testX, testY, obstacles[j].x, obstacles[j].y) < 100) {
validPosition = false;
break;
}
}
if (validPosition) {
food.x = testX;
food.y = testY;
placed = true;
}
}
attempts++;
}
}
// Insect type removed
game.addChild(food);
foods.push(food);
}
// Helper function to determine terrain at a specific position
function findTerrainAtPosition(x, y) {
// Find the terrain patch this position belongs to
for (var i = 0; i < TERRAIN_TYPES.length; i++) {
var terrainX = Math.floor(x / TERRAIN_SIZE) * TERRAIN_SIZE;
var terrainY = Math.floor(y / TERRAIN_SIZE) * TERRAIN_SIZE;
// Return the terrain type for this patch (simplified approach)
return TERRAIN_TYPES[Math.floor(Math.random() * TERRAIN_TYPES.length)];
}
return TERRAIN_TYPES[0]; // Default to first terrain type
}
// Spawn an obstacle
function spawnObstacle() {
var obstacle = new Obstacle();
// Position obstacle randomly away from edges and worm head
var head = wormSegments[0];
do {
obstacle.x = Math.random() * (GAME_WIDTH - 200) + 100;
obstacle.y = Math.random() * (GAME_HEIGHT - 200) + 100;
} while (getDistance(obstacle.x, obstacle.y, head.x, head.y) < 300);
game.addChild(obstacle);
obstacles.push(obstacle);
}
// Spawn a power-up
function spawnPowerup() {
var types = ['speed', 'invincibility', 'grow'];
var randomType = types[Math.floor(Math.random() * types.length)];
var powerup = new PowerUp(randomType);
// Position powerup randomly away from edges
powerup.x = Math.random() * (GAME_WIDTH - 200) + 100;
powerup.y = Math.random() * (GAME_HEIGHT - 200) + 100;
// Make sure powerup doesn't overlap with obstacles
for (var i = 0; i < obstacles.length; i++) {
if (getDistance(powerup.x, powerup.y, obstacles[i].x, obstacles[i].y) < 100) {
// Reposition if too close to an obstacle
powerup.x = Math.random() * (GAME_WIDTH - 200) + 100;
powerup.y = Math.random() * (GAME_HEIGHT - 200) + 100;
i = -1; // Reset loop to check again
}
}
game.addChild(powerup);
powerups.push(powerup);
}
// Create soil particles effect
function createSoilEffect(x, y, count, color) {
for (var i = 0; i < count; i++) {
var particle = new SoilParticle();
// Set position with slight randomization
particle.x = x + (Math.random() - 0.5) * 10;
particle.y = y + (Math.random() - 0.5) * 10;
// Apply terrain color if provided
if (color) {
particle.tint = color;
} else {
// Use color from current terrain if no specific color is provided
var terrainAtPosition = findTerrainAtPosition(x, y);
if (terrainAtPosition) {
particle.tint = terrainAtPosition.color;
}
}
game.addChild(particle);
soilParticles.push(particle);
}
}
// Calculate distance between two points
function getDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
// Activate powerup
function activatePowerup(type) {
powerupActive = true;
powerupType = type;
powerupTimeRemaining = POWERUP_DURATION;
LK.getSound('powerup').play();
// Apply power-up effects
switch (type) {
case 'speed':
MOVE_SPEED *= 1.75;
break;
case 'invincibility':
// Visual effect for invincibility
for (var i = 0; i < wormSegments.length; i++) {
// Ensure wormSegment is a valid object before applying tween
if (wormSegments[i] && _typeof(wormSegments[i]) === 'object') {
tween(wormSegments[i], {
alpha: 0.7
}, {
duration: 300
});
}
}
break;
case 'grow':
// Add multiple segments at once
for (var j = 0; j < 3; j++) {
addWormSegment();
}
break;
}
}
// End powerup effect
function endPowerupEffect() {
switch (powerupType) {
case 'speed':
MOVE_SPEED = 5;
break;
case 'invincibility':
// Reset visual effect
for (var i = 0; i < wormSegments.length; i++) {
// Ensure wormSegment is a valid object before applying tween
if (wormSegments[i] && _typeof(wormSegments[i]) === 'object') {
tween(wormSegments[i], {
alpha: 1
}, {
duration: 300
});
}
}
break;
}
powerupActive = false;
powerupType = null;
}
// Setup UI elements
function setupUI() {
// Score text
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -scoreTxt.width - 20;
scoreTxt.y = 20;
// Level text
var levelTxt = new Text2('Level: ' + gameLevel, {
size: 60,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(levelTxt);
levelTxt.x = -levelTxt.width - 20;
levelTxt.y = 100;
// High score text
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 50,
fill: 0xFFD700
});
highScoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreTxt);
highScoreTxt.x = -highScoreTxt.width - 20;
highScoreTxt.y = 160;
// Next level text
var nextLevelTxt = new Text2('Next Level: ' + levelScore, {
size: 50,
fill: 0x32CD32
});
nextLevelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(nextLevelTxt);
nextLevelTxt.y = 20;
// Powerup indicator
var powerupTxt = new Text2('', {
size: 60,
fill: 0xFFFF00
});
powerupTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(powerupTxt);
powerupTxt.x = 120; // Keep away from the top-left 100x100 px area
powerupTxt.y = 20;
// Start instructions
var instructionsTxt = new Text2('Tap to start\nDrag to move your worm\nEat food to grow', {
size: 80,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionsTxt);
// Update UI function
game.updateUI = function () {
scoreTxt.setText('Score: ' + score);
scoreTxt.x = -scoreTxt.width - 20;
levelTxt.setText('Level: ' + gameLevel);
levelTxt.x = -levelTxt.width - 20;
highScoreTxt.setText('Best: ' + highScore);
highScoreTxt.x = -highScoreTxt.width - 20;
nextLevelTxt.setText('Next Level: ' + (levelScore - score));
if (powerupActive) {
var timeLeft = Math.ceil(powerupTimeRemaining / 60);
powerupTxt.setText(powerupType.toUpperCase() + ': ' + timeLeft + 's');
} else {
powerupTxt.setText('');
}
if (gameStarted) {
instructionsTxt.alpha = 0;
}
};
}
// Initialize game
function initGame() {
// Reset game state
wormSegments = [];
foods = [];
obstacles = [];
powerups = [];
predators = [];
soilParticles = [];
score = 0;
movingToTarget = false;
wormAngle = 0;
foodTimer = 0;
obstacleTimer = 0;
powerupTimer = 0;
predatorTimer = 0;
powerupActive = false;
powerupType = null;
powerupTimeRemaining = 0;
levelScore = gameLevel * 10;
MOVE_SPEED = 5;
MAX_PREDATORS = Math.min(Math.floor(gameLevel / 2), 3); // Recalculate for current level
// Set background
createBackground();
// Create the worm
createWorm();
// Initial food
for (var i = 0; i < 3; i++) {
spawnFood();
}
// Initial obstacles based on level
for (var j = 0; j < Math.min(gameLevel, 5); j++) {
spawnObstacle();
}
// Setup UI
setupUI();
// Play background music
LK.playMusic('bgmusic');
}
// Initialize the game
initGame();
// Game logic update function
game.update = function () {
if (!gameStarted) {
return;
}
// Move worm head
var head = wormSegments[0];
if (movingToTarget) {
// Calculate angle to target
var dx = targetX - head.x;
var dy = targetY - head.y;
var targetAngle = Math.atan2(dy, dx);
// Smooth angle change
var angleDiff = targetAngle - wormAngle;
// Handle angle wrapping
if (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
if (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
// Apply gradual turning
wormAngle += angleDiff * TURNING_SPEED;
// Determine terrain at current position
var currentTerrainType = findTerrainAtPosition(head.x, head.y);
var speedModifier = currentTerrainType ? currentTerrainType.speedModifier : 1.0;
// Apply terrain effects to movement
var adjustedSpeed = MOVE_SPEED * speedModifier;
if (powerupActive && powerupType === 'speed') {
adjustedSpeed *= 1.75; // Apply speed powerup with terrain modifier
}
// Move in the current direction with terrain-adjusted speed
head.savePosition();
head.x += Math.cos(wormAngle) * adjustedSpeed;
head.y += Math.sin(wormAngle) * adjustedSpeed;
// Create soil particles as the worm moves
if (LK.ticks % 3 === 0) {
var particleCount = speedModifier > 1 ? 2 : 1; // More particles in sand (faster terrain)
var particleColor = currentTerrainType ? currentTerrainType.color : 0x8b4513;
createSoilEffect(head.x, head.y, particleCount, particleColor);
}
// Check if close enough to target
if (getDistance(head.x, head.y, targetX, targetY) < 10) {
movingToTarget = false;
}
}
// Move body segments
for (var i = 1; i < wormSegments.length; i++) {
var segment = wormSegments[i];
var prevSegment = wormSegments[i - 1];
// Save current position before updating
segment.savePosition();
// Calculate direction to previous segment
var dx = prevSegment.prevX - segment.x;
var dy = prevSegment.prevY - segment.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move towards the previous segment's saved position
if (distance > SEGMENT_SPACING) {
var moveRatio = (distance - SEGMENT_SPACING) / distance;
segment.x += dx * moveRatio;
segment.y += dy * moveRatio;
}
}
// Update soil particles
for (var p = soilParticles.length - 1; p >= 0; p--) {
var particle = soilParticles[p];
particle.update();
if (particle.lifespan <= 0 || particle.alpha <= 0) {
particle.destroy();
soilParticles.splice(p, 1);
}
}
// Boundary checking for worm head
if (head.x < 0) {
head.x = 0;
}
if (head.x > GAME_WIDTH) {
head.x = GAME_WIDTH;
}
if (head.y < 0) {
head.y = 0;
}
if (head.y > GAME_HEIGHT) {
head.y = GAME_HEIGHT;
}
// Check collisions with food
for (var f = foods.length - 1; f >= 0; f--) {
var food = foods[f];
food.update();
if (getDistance(head.x, head.y, food.x, food.y) < 40) {
// Eat food
score += food.value;
LK.setScore(score);
LK.getSound('eat').play();
// Add segments based on food value
for (var s = 0; s < food.value; s++) {
addWormSegment();
}
// Remove food
food.destroy();
foods.splice(f, 1);
// Create particle effect
createSoilEffect(food.x, food.y, 10);
}
}
// Check collisions with obstacles
for (var o = 0; o < obstacles.length; o++) {
var obstacle = obstacles[o];
if (getDistance(head.x, head.y, obstacle.x, obstacle.y) < 45) {
if (powerupActive && powerupType === 'invincibility') {
// Destroy obstacle if invincible
createSoilEffect(obstacle.x, obstacle.y, 15);
obstacle.destroy();
obstacles.splice(o, 1);
o--;
continue;
} else {
// Game over on collision
LK.getSound('hit').play();
LK.effects.flashScreen(0xFF0000, 500);
// Update high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
LK.showGameOver();
return;
}
}
}
// Check collisions with powerups
for (var pu = powerups.length - 1; pu >= 0; pu--) {
var powerup = powerups[pu];
powerup.update();
if (getDistance(head.x, head.y, powerup.x, powerup.y) < 40) {
// Collect powerup
activatePowerup(powerup.type);
// Remove powerup
powerup.destroy();
powerups.splice(pu, 1);
// Create particle effect
createSoilEffect(powerup.x, powerup.y, 15);
}
}
// Spawn new food
foodTimer++;
if (foodTimer >= SPAWN_FOOD_INTERVAL) {
spawnFood();
foodTimer = 0;
}
// Spawn new obstacles based on level
obstacleTimer++;
if (obstacleTimer >= SPAWN_OBSTACLE_INTERVAL && obstacles.length < gameLevel + 2) {
spawnObstacle();
obstacleTimer = 0;
}
// Spawn powerups occasionally
powerupTimer++;
if (powerupTimer >= SPAWN_POWERUP_INTERVAL) {
spawnPowerup();
powerupTimer = 0;
}
// Spawn predators if game level is high enough
if (gameLevel >= 2) {
predatorTimer++;
if (predatorTimer >= SPAWN_PREDATOR_INTERVAL && predators.length < MAX_PREDATORS) {
spawnPredator();
predatorTimer = 0;
}
// Update predators
for (var pr = predators.length - 1; pr >= 0; pr--) {
var predator = predators[pr];
predator.update();
// Check for collisions with worm head
var head = wormSegments[0];
if (getDistance(head.x, head.y, predator.x, predator.y) < 50 && predator.state === 'hunting' && predator.alpha > 0.5) {
if (powerupActive && powerupType === 'invincibility') {
// Destroy predator if invincible
createSoilEffect(predator.x, predator.y, 25);
predator.destroy();
predators.splice(pr, 1);
} else {
// Game over on collision
LK.getSound('hit').play();
LK.effects.flashScreen(0xFF0000, 500);
// Update high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
LK.showGameOver();
return;
}
}
}
}
// Update powerup duration
if (powerupActive) {
powerupTimeRemaining--;
if (powerupTimeRemaining <= 0) {
endPowerupEffect();
}
}
// Level up check
if (score >= levelScore) {
// Level up
gameLevel++;
storage.level = gameLevel;
levelScore = gameLevel * 10;
// Flash screen green
LK.effects.flashScreen(0x00FF00, 500);
// Speed increase with level
MOVE_SPEED = 5 + gameLevel * 0.25;
if (MOVE_SPEED > 10) {
MOVE_SPEED = 10;
}
}
// Update UI
game.updateUI();
};
// Event handlers
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
return;
}
targetX = x;
targetY = y;
movingToTarget = true;
};
game.move = function (x, y, obj) {
if (gameStarted) {
targetX = x;
targetY = y;
movingToTarget = true;
}
};
game.up = function (x, y, obj) {
// Keep moving to the last target point
};