/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var Animal = Container.expand(function () { var self = Container.call(this); self.animalGraphic = self.attachAsset('animal', { anchorX: 0.5, anchorY: 0.5 }); self.direction = Math.random() > 0.5 ? 1 : -1; self.speed = Math.random() * 2 + 1; self.health = 100; self.hunger = 0; self.thirsty = 0; self.state = 'wandering'; // wandering, eating, drinking self.targetX = null; self.targetY = null; self.targetPlant = null; self.wanderTimer = 0; if (self.direction === -1) { self.animalGraphic.scaleX = -1; } self.update = function () { // Natural state changes self.hunger += 0.1; self.thirsty += 0.15; // Cap values if (self.hunger > 100) { self.hunger = 100; } if (self.thirsty > 100) { self.thirsty = 100; } // Health decreases if hungry or thirsty if (self.hunger > 80 || self.thirsty > 80) { self.health -= 0.2; } else { // Recover health when not hungry or thirsty self.health += 0.05; if (self.health > 100) { self.health = 100; } } // Decision making if (self.state === 'wandering') { // Find food or water if needed if (self.thirsty > 70 && waterPools.length > 0) { self.state = 'drinking'; // Find nearest water var closestDist = Infinity; var closestWater = null; for (var i = 0; i < waterPools.length; i++) { var water = waterPools[i]; var dist = Math.sqrt(Math.pow(self.x - water.x, 2) + Math.pow(self.y - water.y, 2)); if (dist < closestDist) { closestDist = dist; closestWater = water; } } if (closestWater) { self.targetX = closestWater.x + Math.random() * 100 - 50; self.targetY = closestWater.y - 30; } } else if (self.hunger > 60 && plants.length > 0) { self.state = 'eating'; // Find nearest plant that's mature enough var closestDist = Infinity; var closestPlant = null; for (var j = 0; j < plants.length; j++) { var plant = plants[j]; if (plant.growthStage > 2) { // Only eat mature plants var dist = Math.sqrt(Math.pow(self.x - plant.x, 2) + Math.pow(self.y - plant.y, 2)); if (dist < closestDist) { closestDist = dist; closestPlant = plant; } } } if (closestPlant) { self.targetPlant = closestPlant; self.targetX = closestPlant.x; self.targetY = closestPlant.y; } else { // No suitable plants, continue wandering self.wanderTimer++; if (self.wanderTimer > 120) { self.wanderTimer = 0; self.targetX = Math.random() * 1800 + 100; } } } else { // Regular wandering self.wanderTimer++; if (self.wanderTimer > 180 || self.targetX === null) { self.wanderTimer = 0; self.targetX = Math.random() * 1800 + 100; } } // Move towards target if there is one if (self.targetX !== null) { var dx = self.targetX - self.x; if (Math.abs(dx) < 5) { // Reached target self.targetX = null; } else { // Move towards target self.direction = dx > 0 ? 1 : -1; self.animalGraphic.scaleX = self.direction; self.x += self.direction * self.speed; } } } else if (self.state === 'drinking') { // Move towards water if (self.targetX !== null) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 20) { // Reached water, drink self.thirsty -= 1; // Make drinking animation if (Math.random() < 0.1) { var splash = new Particle(); splash.x = self.x; splash.y = self.y + 20; splash.vx = Math.random() * 2 - 1; splash.vy = -Math.random() * 2 - 1; splash.lifespan = 20; game.addChild(splash); particles.push(splash); } // Done drinking if (self.thirsty <= 10) { self.state = 'wandering'; self.targetX = null; LK.getSound('animalSound').play(); } } else { // Move towards water self.direction = dx > 0 ? 1 : -1; self.animalGraphic.scaleX = self.direction; self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.state = 'wandering'; } } else if (self.state === 'eating') { // Move towards plant if (self.targetPlant && self.targetPlant.parent) { var dx = self.targetPlant.x - self.x; var dy = self.targetPlant.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 30) { // Reached plant, eat self.hunger -= 0.5; self.targetPlant.growthStage -= 0.01; // Done eating if (self.hunger <= 10 || self.targetPlant.growthStage <= 1) { self.state = 'wandering'; self.targetPlant = null; self.targetX = null; LK.getSound('animalSound').play(); } } else { // Move towards plant self.direction = dx > 0 ? 1 : -1; self.animalGraphic.scaleX = self.direction; self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } } else { self.state = 'wandering'; self.targetPlant = null; } } // Health visual indicator if (self.health < 50) { self.animalGraphic.alpha = 0.5 + self.health / 100 * 0.5; } else { self.animalGraphic.alpha = 1.0; } // Die if health reaches 0 if (self.health <= 0 && self.parent) { tween(self, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { self.destroy(); } }); } }; return self; }); var Particle = Container.expand(function () { var self = Container.call(this); var particleGraphic = self.attachAsset('particleSmall', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); self.vx = 0; self.vy = 0; self.lifespan = 100; self.age = 0; self.update = function () { self.x += self.vx; self.y += self.vy; self.age++; if (self.age > self.lifespan) { if (self.parent) { self.destroy(); } return; } particleGraphic.alpha = 1 - self.age / self.lifespan; }; return self; }); var Plant = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'grass'; self.growthStage = 0; self.maxGrowth = 5; self.growthSpeed = 0; self.health = 100; self.waterLevel = 50; self.sunLevel = 50; self.buildGraphics = function () { self.removeChildren(); if (self.type === 'grass') { var scale = 0.4 + self.growthStage / self.maxGrowth * 0.6; self.mainGraphic = self.attachAsset('grass', { anchorX: 0.5, anchorY: 1.0, scaleX: scale, scaleY: scale }); } else if (self.type === 'tree') { var scale = 0.3 + self.growthStage / self.maxGrowth * 0.7; // Tree trunk self.trunkGraphic = self.attachAsset('tree', { anchorX: 0.5, anchorY: 1.0, scaleX: scale, scaleY: scale }); // Tree top self.topGraphic = self.attachAsset('treeTop', { anchorX: 0.5, anchorY: 1.0, y: -self.trunkGraphic.height * scale, scaleX: scale * 1.2, scaleY: scale * 1.2 }); } else if (self.type === 'flower') { var scale = 0.3 + self.growthStage / self.maxGrowth * 0.7; // Stem self.stemGraphic = self.attachAsset('grass', { anchorX: 0.5, anchorY: 1.0, scaleX: scale * 0.5, scaleY: scale }); // Flower self.flowerGraphic = self.attachAsset('flower', { anchorX: 0.5, anchorY: 0.5, y: -self.stemGraphic.height * scale, scaleX: scale, scaleY: scale }); } }; // Set initial graphics self.buildGraphics(); self.grow = function () { if (self.growthStage < self.maxGrowth) { self.growthStage += 0.1; self.buildGraphics(); } }; self.updateHealth = function () { // Ideal conditions: water 50-70, sun 50-80 if (self.waterLevel < 20 || self.waterLevel > 90) { self.health -= 0.5; } else if (self.waterLevel > 40 && self.waterLevel < 80) { self.health += 0.2; } if (self.sunLevel < 30 || self.sunLevel > 90) { self.health -= 0.5; } else if (self.sunLevel > 40 && self.sunLevel < 80) { self.health += 0.2; } // Cap health if (self.health > 100) { self.health = 100; } if (self.health < 0) { self.health = 0; } // Update visual health var healthColor = 0x33D17A; // Healthy green if (self.health < 70) { healthColor = 0xF6D32D; } // Yellow-ish if (self.health < 40) { healthColor = 0xE01B24; } // Red-ish // Change tint based on health if (self.type === 'grass') { self.mainGraphic.tint = healthColor; } else if (self.type === 'tree') { self.topGraphic.tint = healthColor; } else if (self.type === 'flower') { self.flowerGraphic.tint = healthColor; } // If plant is dead, make it wither if (self.health <= 0 && self.parent) { tween(self, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { self.destroy(); } }); } }; self.update = function () { // Natural water loss self.waterLevel -= 0.1; // Natural sun effects based on weather if (currentWeather === 'sunny') { self.sunLevel += 0.2; self.waterLevel -= 0.05; // Extra evaporation when sunny } else if (currentWeather === 'rainy') { self.sunLevel -= 0.1; self.waterLevel += 0.3; } else if (currentWeather === 'windy') { self.waterLevel -= 0.15; // Wind causes more evaporation } else if (currentWeather === 'stormy') { self.sunLevel -= 0.2; self.waterLevel += 0.5; } // Cap water and sun levels if (self.waterLevel > 100) { self.waterLevel = 100; } if (self.waterLevel < 0) { self.waterLevel = 0; } if (self.sunLevel > 100) { self.sunLevel = 100; } if (self.sunLevel < 0) { self.sunLevel = 0; } // Update health based on conditions self.updateHealth(); // Calculate growth speed self.growthSpeed = 0; // Perfect conditions for growth if (self.waterLevel > 40 && self.waterLevel < 80 && self.sunLevel > 40 && self.sunLevel < 80) { self.growthSpeed = 0.01; } // Apply growth if (self.growthSpeed > 0) { self.grow(); } }; return self; }); var Raindrop = Container.expand(function () { var self = Container.call(this); var dropGraphic = self.attachAsset('rain', { anchorX: 0.5, anchorY: 0.5 }); self.speed = Math.random() * 5 + 10; self.alpha = Math.random() * 0.5 + 0.5; dropGraphic.alpha = self.alpha; self.update = function () { self.y += self.speed; // If raindrop is below ground level if (self.y > groundLevel) { // Create splash effect for (var i = 0; i < 3; i++) { var splash = new Particle(); splash.x = self.x; splash.y = groundLevel; splash.vx = Math.random() * 4 - 2; splash.vy = -Math.random() * 3 - 2; splash.lifespan = 20; game.addChild(splash); particles.push(splash); } if (self.parent) { self.destroy(); } } }; return self; }); var StormCloud = Container.expand(function () { var self = Container.call(this); var cloudBase = self.attachAsset('storm', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 2, alpha: 0.7 }); // Add some smaller clouds around it for texture for (var i = 0; i < 5; i++) { var cloudPuff = self.attachAsset('storm', { anchorX: 0.5, anchorY: 0.5, x: Math.random() * 160 - 80, y: Math.random() * 60 - 30, scaleX: Math.random() * 1.5 + 0.5, scaleY: Math.random() * 1.5 + 0.5, alpha: 0.8 }); } self.vx = Math.random() * 2 - 1; self.vy = Math.random() * 0.5 - 0.25; self.thunderTimer = 0; self.thunderFrequency = Math.floor(Math.random() * 120) + 180; // 3-5 seconds between thunder self.lifespan = 600; // 10 seconds life self.age = 0; self.update = function () { self.x += self.vx; self.y += self.vy; self.age++; // Wrap around edges if (self.x < -100) { self.x = 2148; } if (self.x > 2148) { self.x = -100; } self.thunderTimer++; if (self.thunderTimer >= self.thunderFrequency) { self.thunderTimer = 0; // Flash the storm cloud tween(self, { alpha: 0.9 }, { duration: 100, onFinish: function onFinish() { tween(self, { alpha: 0.7 }, { duration: 300 }); } }); // Play thunder sound LK.getSound('stormSound').play(); // Create rain from the storm for (var i = 0; i < 8; i++) { var drop = new Raindrop(); drop.x = self.x + Math.random() * 160 - 80; drop.y = self.y + 40; game.addChild(drop); rain.push(drop); } } if (self.age > self.lifespan) { if (self.parent) { tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.destroy(); } }); } } }; return self; }); var Sun = Container.expand(function () { var self = Container.call(this); var sunGraphic = self.attachAsset('sun', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.intensity = 0; self.targetIntensity = 0; self.update = function () { // Smooth transition to target intensity if (self.intensity < self.targetIntensity) { self.intensity += 0.01; } else if (self.intensity > self.targetIntensity) { self.intensity -= 0.01; } // Visual representation of intensity sunGraphic.alpha = self.intensity; // Sun rays effect if (self.intensity > 0.5 && Math.random() < 0.1) { var ray = new Particle(); ray.x = self.x; ray.y = self.y; var angle = Math.random() * Math.PI * 2; var distance = sunGraphic.width * 0.7; ray.vx = Math.cos(angle) * 2; ray.vy = Math.sin(angle) * 2; ray.x += Math.cos(angle) * distance; ray.y += Math.sin(angle) * distance; ray.lifespan = 30; game.addChild(ray); particles.push(ray); } }; return self; }); var WaterPool = Container.expand(function () { var self = Container.call(this); self.width = 400; self.height = 200; var waterGraphic = self.attachAsset('water', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); self.level = 100; // Water level percentage self.update = function () { // Water level changes based on weather if (currentWeather === 'sunny') { self.level -= 0.1; } else if (currentWeather === 'rainy') { self.level += 0.2; } else if (currentWeather === 'windy') { self.level -= 0.05; } else if (currentWeather === 'stormy') { self.level += 0.3; } // Cap water level if (self.level > 100) { self.level = 100; } if (self.level < 0) { self.level = 0; } // Visual representation of water level waterGraphic.scaleY = self.level / 100; // Create ripples occasionally if (Math.random() < 0.05 && self.level > 10) { var ripple = new Particle(); ripple.x = self.x + Math.random() * self.width - self.width / 2; ripple.y = self.y + Math.random() * self.height / 2 - self.height / 4; ripple.vx = Math.random() * 0.5 - 0.25; ripple.vy = Math.random() * 0.5 - 0.25; ripple.lifespan = 30; game.addChild(ripple); particles.push(ripple); } }; return self; }); var WindGust = Container.expand(function () { var self = Container.call(this); var gustGraphic = self.attachAsset('wind', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); self.speed = Math.random() * 8 + 5; self.lifespan = 120; self.age = 0; self.update = function () { self.x += self.speed; self.age++; if (self.age > self.lifespan || self.x > 2048 + gustGraphic.width) { if (self.parent) { self.destroy(); } return; } gustGraphic.alpha = 0.3 * (1 - self.age / self.lifespan); // Create particles along the wind path if (self.age % 5 === 0) { var particle = new Particle(); particle.x = self.x; particle.y = self.y + (Math.random() * gustGraphic.height - gustGraphic.height / 2); particle.vx = self.speed * 0.8; particle.vy = Math.random() * 2 - 1; particle.lifespan = 40; game.addChild(particle); particles.push(particle); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB // Sky blue background }); /**** * Game Code ****/ // Global variables var groundLevel = 2200; var currentWeather = 'neutral'; // neutral, sunny, rainy, windy, stormy var weatherIntensity = 0; var weatherChangeTimer = 0; var faceDetected = false; var lastExpression = 'neutral'; // Arrays to keep track of game objects var plants = []; var animals = []; var rain = []; var wind = []; var storms = []; var particles = []; var waterPools = []; // Create ground var ground = game.addChild(LK.getAsset('ground', { anchorX: 0.5, anchorY: 0.0, x: 2048 / 2, y: groundLevel })); // Create sun var sun = new Sun(); sun.x = 200; sun.y = 200; game.addChild(sun); // Create water pool var pool = new WaterPool(); pool.x = 1700; pool.y = groundLevel - 60; game.addChild(pool); waterPools.push(pool); // UI Elements var ecosystemTxt = new Text2('Emotion Ecosystem', { size: 70, fill: 0xFFFFFF }); ecosystemTxt.anchor.set(0.5, 0); LK.gui.top.addChild(ecosystemTxt); var detectionTxt = new Text2('Looking for your face...', { size: 40, fill: 0xFFFFFF }); detectionTxt.anchor.set(0.5, 0); detectionTxt.y = 100; LK.gui.top.addChild(detectionTxt); var weatherTxt = new Text2('Weather: Neutral', { size: 40, fill: 0xFFFFFF }); weatherTxt.anchor.set(0.5, 0); weatherTxt.y = 150; LK.gui.top.addChild(weatherTxt); var instructionsTxt = new Text2('Smile = Sun\n' + 'Frown = Rain\n' + 'Surprised = Wind\n' + 'Angry = Storm', { size: 30, fill: 0xFFFFFF }); instructionsTxt.anchor.set(0.5, 1); LK.gui.bottom.addChild(instructionsTxt); // Helper functions function generatePlant(type, x) { var plant = new Plant(type); plant.x = x || Math.random() * 1800 + 100; plant.y = groundLevel; game.addChild(plant); plants.push(plant); return plant; } function generateAnimal(x) { var animal = new Animal(); animal.x = x || Math.random() * 1800 + 100; animal.y = groundLevel - 20; game.addChild(animal); animals.push(animal); return animal; } function createRain() { var drop = new Raindrop(); drop.x = Math.random() * 2048; drop.y = -50; game.addChild(drop); rain.push(drop); // Play rain sound occasionally if (Math.random() < 0.05) { LK.getSound('rainSound').play(); } } function createWind() { var gust = new WindGust(); gust.x = -100; gust.y = Math.random() * 1500; game.addChild(gust); wind.push(gust); // Play wind sound occasionally if (Math.random() < 0.1) { LK.getSound('windSound').play(); } } function createStorm() { var storm = new StormCloud(); storm.x = Math.random() * 2048; storm.y = Math.random() * 300 + 100; game.addChild(storm); storms.push(storm); } function detectFacialExpression() { // Check if face is detected if (facekit.mouthCenter.x > 0 || facekit.mouthCenter.y > 0) { faceDetected = true; // Check facial expressions if (facekit.mouthOpen) { // Calculate how open the mouth is based on distance between upper and lower lip var mouthOpenness = Math.abs(facekit.upperLip.y - facekit.lowerLip.y) / 100; if (mouthOpenness > 0.5) { // Very open mouth - surprised expression return 'surprised'; } } // Check smile (happy expression) // A smile is detected by curvature of the mouth // This is a simple approximation - in a real implementation we'd use more robust detection var mouthWidth = Math.abs(facekit.upperLip.x - facekit.lowerLip.x); var mouthHeight = Math.abs(facekit.upperLip.y - facekit.lowerLip.y); if (mouthWidth > 30 && mouthHeight < 20) { return 'happy'; } // Check frown (sad expression) // Simple approximation - in real implementation we'd use more robust detection if (mouthWidth < 20 && facekit.upperLip.y > facekit.lowerLip.y) { return 'sad'; } // Check angry expression // Simple approximation using eyebrows and mouth // For this demo, we'll use volume as a proxy for anger if (facekit.volume > 0.7) { return 'angry'; } return 'neutral'; } else { faceDetected = false; return 'not detected'; } } function updateWeather() { // Blend from current weather to target weather based on expression if (lastExpression === 'happy') { currentWeather = 'sunny'; sun.targetIntensity = 1.0; } else if (lastExpression === 'sad') { currentWeather = 'rainy'; sun.targetIntensity = 0.3; } else if (lastExpression === 'surprised') { currentWeather = 'windy'; sun.targetIntensity = 0.6; } else if (lastExpression === 'angry') { currentWeather = 'stormy'; sun.targetIntensity = 0.2; } else { currentWeather = 'neutral'; sun.targetIntensity = 0.5; } // Update weather text weatherTxt.setText('Weather: ' + currentWeather.charAt(0).toUpperCase() + currentWeather.slice(1)); // Spawn weather effects based on current weather if (currentWeather === 'rainy' && Math.random() < 0.2) { createRain(); } else if (currentWeather === 'windy' && Math.random() < 0.1) { createWind(); } else if (currentWeather === 'stormy') { if (Math.random() < 0.02 && storms.length < 3) { createStorm(); } if (Math.random() < 0.1) { createRain(); } } } // Setup initial ecosystem function setupInitialEcosystem() { // Create initial plants for (var i = 0; i < 5; i++) { generatePlant('grass', 200 + i * 300); } for (var i = 0; i < 3; i++) { generatePlant('tree', 400 + i * 400); } for (var i = 0; i < 4; i++) { generatePlant('flower', 350 + i * 350); } // Create initial animals for (var i = 0; i < 3; i++) { generateAnimal(300 + i * 500); } // Start background music LK.playMusic('bgMusic'); } // Call setup setupInitialEcosystem(); // Main game loop game.update = function () { // Update face detection status var expression = detectFacialExpression(); if (expression !== 'not detected') { detectionTxt.setText('Expression: ' + expression.charAt(0).toUpperCase() + expression.slice(1)); lastExpression = expression; } else { detectionTxt.setText('Looking for your face...'); } // Update weather based on expression updateWeather(); // Update weather timer weatherChangeTimer++; if (weatherChangeTimer > 300) { // Every 5 seconds weatherChangeTimer = 0; // Occasionally spawn new plants or animals if (plants.length < 20 && Math.random() < 0.3) { var types = ['grass', 'tree', 'flower']; generatePlant(types[Math.floor(Math.random() * types.length)]); } if (animals.length < 8 && Math.random() < 0.2) { generateAnimal(); } } // Update all game objects sun.update(); // Update plants for (var i = plants.length - 1; i >= 0; i--) { if (plants[i].parent) { plants[i].update(); } else { plants.splice(i, 1); } } // Update animals for (var i = animals.length - 1; i >= 0; i--) { if (animals[i].parent) { animals[i].update(); } else { animals.splice(i, 1); } } // Update rain for (var i = rain.length - 1; i >= 0; i--) { if (rain[i].parent) { rain[i].update(); } else { rain.splice(i, 1); } } // Update wind for (var i = wind.length - 1; i >= 0; i--) { if (wind[i].parent) { wind[i].update(); } else { wind.splice(i, 1); } } // Update storms for (var i = storms.length - 1; i >= 0; i--) { if (storms[i].parent) { storms[i].update(); } else { storms.splice(i, 1); } } // Update particles for (var i = particles.length - 1; i >= 0; i--) { if (particles[i].parent) { particles[i].update(); } else { particles.splice(i, 1); } } // Update water pools for (var i = waterPools.length - 1; i >= 0; i--) { if (waterPools[i].parent) { waterPools[i].update(); } else { waterPools.splice(i, 1); } } // Game over condition - all plants and animals died if (plants.length === 0 && animals.length === 0) { LK.showGameOver(); } // Win condition - flourishing ecosystem (many plants and animals) if (plants.length > 15 && animals.length > 5) { // Update score based on ecosystem health LK.setScore(plants.length + animals.length * 2); // Check if score is high enough to win if (LK.getScore() > 30) { LK.showYouWin(); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var Animal = Container.expand(function () {
var self = Container.call(this);
self.animalGraphic = self.attachAsset('animal', {
anchorX: 0.5,
anchorY: 0.5
});
self.direction = Math.random() > 0.5 ? 1 : -1;
self.speed = Math.random() * 2 + 1;
self.health = 100;
self.hunger = 0;
self.thirsty = 0;
self.state = 'wandering'; // wandering, eating, drinking
self.targetX = null;
self.targetY = null;
self.targetPlant = null;
self.wanderTimer = 0;
if (self.direction === -1) {
self.animalGraphic.scaleX = -1;
}
self.update = function () {
// Natural state changes
self.hunger += 0.1;
self.thirsty += 0.15;
// Cap values
if (self.hunger > 100) {
self.hunger = 100;
}
if (self.thirsty > 100) {
self.thirsty = 100;
}
// Health decreases if hungry or thirsty
if (self.hunger > 80 || self.thirsty > 80) {
self.health -= 0.2;
} else {
// Recover health when not hungry or thirsty
self.health += 0.05;
if (self.health > 100) {
self.health = 100;
}
}
// Decision making
if (self.state === 'wandering') {
// Find food or water if needed
if (self.thirsty > 70 && waterPools.length > 0) {
self.state = 'drinking';
// Find nearest water
var closestDist = Infinity;
var closestWater = null;
for (var i = 0; i < waterPools.length; i++) {
var water = waterPools[i];
var dist = Math.sqrt(Math.pow(self.x - water.x, 2) + Math.pow(self.y - water.y, 2));
if (dist < closestDist) {
closestDist = dist;
closestWater = water;
}
}
if (closestWater) {
self.targetX = closestWater.x + Math.random() * 100 - 50;
self.targetY = closestWater.y - 30;
}
} else if (self.hunger > 60 && plants.length > 0) {
self.state = 'eating';
// Find nearest plant that's mature enough
var closestDist = Infinity;
var closestPlant = null;
for (var j = 0; j < plants.length; j++) {
var plant = plants[j];
if (plant.growthStage > 2) {
// Only eat mature plants
var dist = Math.sqrt(Math.pow(self.x - plant.x, 2) + Math.pow(self.y - plant.y, 2));
if (dist < closestDist) {
closestDist = dist;
closestPlant = plant;
}
}
}
if (closestPlant) {
self.targetPlant = closestPlant;
self.targetX = closestPlant.x;
self.targetY = closestPlant.y;
} else {
// No suitable plants, continue wandering
self.wanderTimer++;
if (self.wanderTimer > 120) {
self.wanderTimer = 0;
self.targetX = Math.random() * 1800 + 100;
}
}
} else {
// Regular wandering
self.wanderTimer++;
if (self.wanderTimer > 180 || self.targetX === null) {
self.wanderTimer = 0;
self.targetX = Math.random() * 1800 + 100;
}
}
// Move towards target if there is one
if (self.targetX !== null) {
var dx = self.targetX - self.x;
if (Math.abs(dx) < 5) {
// Reached target
self.targetX = null;
} else {
// Move towards target
self.direction = dx > 0 ? 1 : -1;
self.animalGraphic.scaleX = self.direction;
self.x += self.direction * self.speed;
}
}
} else if (self.state === 'drinking') {
// Move towards water
if (self.targetX !== null) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 20) {
// Reached water, drink
self.thirsty -= 1;
// Make drinking animation
if (Math.random() < 0.1) {
var splash = new Particle();
splash.x = self.x;
splash.y = self.y + 20;
splash.vx = Math.random() * 2 - 1;
splash.vy = -Math.random() * 2 - 1;
splash.lifespan = 20;
game.addChild(splash);
particles.push(splash);
}
// Done drinking
if (self.thirsty <= 10) {
self.state = 'wandering';
self.targetX = null;
LK.getSound('animalSound').play();
}
} else {
// Move towards water
self.direction = dx > 0 ? 1 : -1;
self.animalGraphic.scaleX = self.direction;
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.state = 'wandering';
}
} else if (self.state === 'eating') {
// Move towards plant
if (self.targetPlant && self.targetPlant.parent) {
var dx = self.targetPlant.x - self.x;
var dy = self.targetPlant.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 30) {
// Reached plant, eat
self.hunger -= 0.5;
self.targetPlant.growthStage -= 0.01;
// Done eating
if (self.hunger <= 10 || self.targetPlant.growthStage <= 1) {
self.state = 'wandering';
self.targetPlant = null;
self.targetX = null;
LK.getSound('animalSound').play();
}
} else {
// Move towards plant
self.direction = dx > 0 ? 1 : -1;
self.animalGraphic.scaleX = self.direction;
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
} else {
self.state = 'wandering';
self.targetPlant = null;
}
}
// Health visual indicator
if (self.health < 50) {
self.animalGraphic.alpha = 0.5 + self.health / 100 * 0.5;
} else {
self.animalGraphic.alpha = 1.0;
}
// Die if health reaches 0
if (self.health <= 0 && self.parent) {
tween(self, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
var particleGraphic = self.attachAsset('particleSmall', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.vx = 0;
self.vy = 0;
self.lifespan = 100;
self.age = 0;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.age++;
if (self.age > self.lifespan) {
if (self.parent) {
self.destroy();
}
return;
}
particleGraphic.alpha = 1 - self.age / self.lifespan;
};
return self;
});
var Plant = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'grass';
self.growthStage = 0;
self.maxGrowth = 5;
self.growthSpeed = 0;
self.health = 100;
self.waterLevel = 50;
self.sunLevel = 50;
self.buildGraphics = function () {
self.removeChildren();
if (self.type === 'grass') {
var scale = 0.4 + self.growthStage / self.maxGrowth * 0.6;
self.mainGraphic = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 1.0,
scaleX: scale,
scaleY: scale
});
} else if (self.type === 'tree') {
var scale = 0.3 + self.growthStage / self.maxGrowth * 0.7;
// Tree trunk
self.trunkGraphic = self.attachAsset('tree', {
anchorX: 0.5,
anchorY: 1.0,
scaleX: scale,
scaleY: scale
});
// Tree top
self.topGraphic = self.attachAsset('treeTop', {
anchorX: 0.5,
anchorY: 1.0,
y: -self.trunkGraphic.height * scale,
scaleX: scale * 1.2,
scaleY: scale * 1.2
});
} else if (self.type === 'flower') {
var scale = 0.3 + self.growthStage / self.maxGrowth * 0.7;
// Stem
self.stemGraphic = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 1.0,
scaleX: scale * 0.5,
scaleY: scale
});
// Flower
self.flowerGraphic = self.attachAsset('flower', {
anchorX: 0.5,
anchorY: 0.5,
y: -self.stemGraphic.height * scale,
scaleX: scale,
scaleY: scale
});
}
};
// Set initial graphics
self.buildGraphics();
self.grow = function () {
if (self.growthStage < self.maxGrowth) {
self.growthStage += 0.1;
self.buildGraphics();
}
};
self.updateHealth = function () {
// Ideal conditions: water 50-70, sun 50-80
if (self.waterLevel < 20 || self.waterLevel > 90) {
self.health -= 0.5;
} else if (self.waterLevel > 40 && self.waterLevel < 80) {
self.health += 0.2;
}
if (self.sunLevel < 30 || self.sunLevel > 90) {
self.health -= 0.5;
} else if (self.sunLevel > 40 && self.sunLevel < 80) {
self.health += 0.2;
}
// Cap health
if (self.health > 100) {
self.health = 100;
}
if (self.health < 0) {
self.health = 0;
}
// Update visual health
var healthColor = 0x33D17A; // Healthy green
if (self.health < 70) {
healthColor = 0xF6D32D;
} // Yellow-ish
if (self.health < 40) {
healthColor = 0xE01B24;
} // Red-ish
// Change tint based on health
if (self.type === 'grass') {
self.mainGraphic.tint = healthColor;
} else if (self.type === 'tree') {
self.topGraphic.tint = healthColor;
} else if (self.type === 'flower') {
self.flowerGraphic.tint = healthColor;
}
// If plant is dead, make it wither
if (self.health <= 0 && self.parent) {
tween(self, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
self.update = function () {
// Natural water loss
self.waterLevel -= 0.1;
// Natural sun effects based on weather
if (currentWeather === 'sunny') {
self.sunLevel += 0.2;
self.waterLevel -= 0.05; // Extra evaporation when sunny
} else if (currentWeather === 'rainy') {
self.sunLevel -= 0.1;
self.waterLevel += 0.3;
} else if (currentWeather === 'windy') {
self.waterLevel -= 0.15; // Wind causes more evaporation
} else if (currentWeather === 'stormy') {
self.sunLevel -= 0.2;
self.waterLevel += 0.5;
}
// Cap water and sun levels
if (self.waterLevel > 100) {
self.waterLevel = 100;
}
if (self.waterLevel < 0) {
self.waterLevel = 0;
}
if (self.sunLevel > 100) {
self.sunLevel = 100;
}
if (self.sunLevel < 0) {
self.sunLevel = 0;
}
// Update health based on conditions
self.updateHealth();
// Calculate growth speed
self.growthSpeed = 0;
// Perfect conditions for growth
if (self.waterLevel > 40 && self.waterLevel < 80 && self.sunLevel > 40 && self.sunLevel < 80) {
self.growthSpeed = 0.01;
}
// Apply growth
if (self.growthSpeed > 0) {
self.grow();
}
};
return self;
});
var Raindrop = Container.expand(function () {
var self = Container.call(this);
var dropGraphic = self.attachAsset('rain', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Math.random() * 5 + 10;
self.alpha = Math.random() * 0.5 + 0.5;
dropGraphic.alpha = self.alpha;
self.update = function () {
self.y += self.speed;
// If raindrop is below ground level
if (self.y > groundLevel) {
// Create splash effect
for (var i = 0; i < 3; i++) {
var splash = new Particle();
splash.x = self.x;
splash.y = groundLevel;
splash.vx = Math.random() * 4 - 2;
splash.vy = -Math.random() * 3 - 2;
splash.lifespan = 20;
game.addChild(splash);
particles.push(splash);
}
if (self.parent) {
self.destroy();
}
}
};
return self;
});
var StormCloud = Container.expand(function () {
var self = Container.call(this);
var cloudBase = self.attachAsset('storm', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2,
alpha: 0.7
});
// Add some smaller clouds around it for texture
for (var i = 0; i < 5; i++) {
var cloudPuff = self.attachAsset('storm', {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 160 - 80,
y: Math.random() * 60 - 30,
scaleX: Math.random() * 1.5 + 0.5,
scaleY: Math.random() * 1.5 + 0.5,
alpha: 0.8
});
}
self.vx = Math.random() * 2 - 1;
self.vy = Math.random() * 0.5 - 0.25;
self.thunderTimer = 0;
self.thunderFrequency = Math.floor(Math.random() * 120) + 180; // 3-5 seconds between thunder
self.lifespan = 600; // 10 seconds life
self.age = 0;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.age++;
// Wrap around edges
if (self.x < -100) {
self.x = 2148;
}
if (self.x > 2148) {
self.x = -100;
}
self.thunderTimer++;
if (self.thunderTimer >= self.thunderFrequency) {
self.thunderTimer = 0;
// Flash the storm cloud
tween(self, {
alpha: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(self, {
alpha: 0.7
}, {
duration: 300
});
}
});
// Play thunder sound
LK.getSound('stormSound').play();
// Create rain from the storm
for (var i = 0; i < 8; i++) {
var drop = new Raindrop();
drop.x = self.x + Math.random() * 160 - 80;
drop.y = self.y + 40;
game.addChild(drop);
rain.push(drop);
}
}
if (self.age > self.lifespan) {
if (self.parent) {
tween(self, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.destroy();
}
});
}
}
};
return self;
});
var Sun = Container.expand(function () {
var self = Container.call(this);
var sunGraphic = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.intensity = 0;
self.targetIntensity = 0;
self.update = function () {
// Smooth transition to target intensity
if (self.intensity < self.targetIntensity) {
self.intensity += 0.01;
} else if (self.intensity > self.targetIntensity) {
self.intensity -= 0.01;
}
// Visual representation of intensity
sunGraphic.alpha = self.intensity;
// Sun rays effect
if (self.intensity > 0.5 && Math.random() < 0.1) {
var ray = new Particle();
ray.x = self.x;
ray.y = self.y;
var angle = Math.random() * Math.PI * 2;
var distance = sunGraphic.width * 0.7;
ray.vx = Math.cos(angle) * 2;
ray.vy = Math.sin(angle) * 2;
ray.x += Math.cos(angle) * distance;
ray.y += Math.sin(angle) * distance;
ray.lifespan = 30;
game.addChild(ray);
particles.push(ray);
}
};
return self;
});
var WaterPool = Container.expand(function () {
var self = Container.call(this);
self.width = 400;
self.height = 200;
var waterGraphic = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.level = 100; // Water level percentage
self.update = function () {
// Water level changes based on weather
if (currentWeather === 'sunny') {
self.level -= 0.1;
} else if (currentWeather === 'rainy') {
self.level += 0.2;
} else if (currentWeather === 'windy') {
self.level -= 0.05;
} else if (currentWeather === 'stormy') {
self.level += 0.3;
}
// Cap water level
if (self.level > 100) {
self.level = 100;
}
if (self.level < 0) {
self.level = 0;
}
// Visual representation of water level
waterGraphic.scaleY = self.level / 100;
// Create ripples occasionally
if (Math.random() < 0.05 && self.level > 10) {
var ripple = new Particle();
ripple.x = self.x + Math.random() * self.width - self.width / 2;
ripple.y = self.y + Math.random() * self.height / 2 - self.height / 4;
ripple.vx = Math.random() * 0.5 - 0.25;
ripple.vy = Math.random() * 0.5 - 0.25;
ripple.lifespan = 30;
game.addChild(ripple);
particles.push(ripple);
}
};
return self;
});
var WindGust = Container.expand(function () {
var self = Container.call(this);
var gustGraphic = self.attachAsset('wind', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
self.speed = Math.random() * 8 + 5;
self.lifespan = 120;
self.age = 0;
self.update = function () {
self.x += self.speed;
self.age++;
if (self.age > self.lifespan || self.x > 2048 + gustGraphic.width) {
if (self.parent) {
self.destroy();
}
return;
}
gustGraphic.alpha = 0.3 * (1 - self.age / self.lifespan);
// Create particles along the wind path
if (self.age % 5 === 0) {
var particle = new Particle();
particle.x = self.x;
particle.y = self.y + (Math.random() * gustGraphic.height - gustGraphic.height / 2);
particle.vx = self.speed * 0.8;
particle.vy = Math.random() * 2 - 1;
particle.lifespan = 40;
game.addChild(particle);
particles.push(particle);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Sky blue background
});
/****
* Game Code
****/
// Global variables
var groundLevel = 2200;
var currentWeather = 'neutral'; // neutral, sunny, rainy, windy, stormy
var weatherIntensity = 0;
var weatherChangeTimer = 0;
var faceDetected = false;
var lastExpression = 'neutral';
// Arrays to keep track of game objects
var plants = [];
var animals = [];
var rain = [];
var wind = [];
var storms = [];
var particles = [];
var waterPools = [];
// Create ground
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0.0,
x: 2048 / 2,
y: groundLevel
}));
// Create sun
var sun = new Sun();
sun.x = 200;
sun.y = 200;
game.addChild(sun);
// Create water pool
var pool = new WaterPool();
pool.x = 1700;
pool.y = groundLevel - 60;
game.addChild(pool);
waterPools.push(pool);
// UI Elements
var ecosystemTxt = new Text2('Emotion Ecosystem', {
size: 70,
fill: 0xFFFFFF
});
ecosystemTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(ecosystemTxt);
var detectionTxt = new Text2('Looking for your face...', {
size: 40,
fill: 0xFFFFFF
});
detectionTxt.anchor.set(0.5, 0);
detectionTxt.y = 100;
LK.gui.top.addChild(detectionTxt);
var weatherTxt = new Text2('Weather: Neutral', {
size: 40,
fill: 0xFFFFFF
});
weatherTxt.anchor.set(0.5, 0);
weatherTxt.y = 150;
LK.gui.top.addChild(weatherTxt);
var instructionsTxt = new Text2('Smile = Sun\n' + 'Frown = Rain\n' + 'Surprised = Wind\n' + 'Angry = Storm', {
size: 30,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 1);
LK.gui.bottom.addChild(instructionsTxt);
// Helper functions
function generatePlant(type, x) {
var plant = new Plant(type);
plant.x = x || Math.random() * 1800 + 100;
plant.y = groundLevel;
game.addChild(plant);
plants.push(plant);
return plant;
}
function generateAnimal(x) {
var animal = new Animal();
animal.x = x || Math.random() * 1800 + 100;
animal.y = groundLevel - 20;
game.addChild(animal);
animals.push(animal);
return animal;
}
function createRain() {
var drop = new Raindrop();
drop.x = Math.random() * 2048;
drop.y = -50;
game.addChild(drop);
rain.push(drop);
// Play rain sound occasionally
if (Math.random() < 0.05) {
LK.getSound('rainSound').play();
}
}
function createWind() {
var gust = new WindGust();
gust.x = -100;
gust.y = Math.random() * 1500;
game.addChild(gust);
wind.push(gust);
// Play wind sound occasionally
if (Math.random() < 0.1) {
LK.getSound('windSound').play();
}
}
function createStorm() {
var storm = new StormCloud();
storm.x = Math.random() * 2048;
storm.y = Math.random() * 300 + 100;
game.addChild(storm);
storms.push(storm);
}
function detectFacialExpression() {
// Check if face is detected
if (facekit.mouthCenter.x > 0 || facekit.mouthCenter.y > 0) {
faceDetected = true;
// Check facial expressions
if (facekit.mouthOpen) {
// Calculate how open the mouth is based on distance between upper and lower lip
var mouthOpenness = Math.abs(facekit.upperLip.y - facekit.lowerLip.y) / 100;
if (mouthOpenness > 0.5) {
// Very open mouth - surprised expression
return 'surprised';
}
}
// Check smile (happy expression)
// A smile is detected by curvature of the mouth
// This is a simple approximation - in a real implementation we'd use more robust detection
var mouthWidth = Math.abs(facekit.upperLip.x - facekit.lowerLip.x);
var mouthHeight = Math.abs(facekit.upperLip.y - facekit.lowerLip.y);
if (mouthWidth > 30 && mouthHeight < 20) {
return 'happy';
}
// Check frown (sad expression)
// Simple approximation - in real implementation we'd use more robust detection
if (mouthWidth < 20 && facekit.upperLip.y > facekit.lowerLip.y) {
return 'sad';
}
// Check angry expression
// Simple approximation using eyebrows and mouth
// For this demo, we'll use volume as a proxy for anger
if (facekit.volume > 0.7) {
return 'angry';
}
return 'neutral';
} else {
faceDetected = false;
return 'not detected';
}
}
function updateWeather() {
// Blend from current weather to target weather based on expression
if (lastExpression === 'happy') {
currentWeather = 'sunny';
sun.targetIntensity = 1.0;
} else if (lastExpression === 'sad') {
currentWeather = 'rainy';
sun.targetIntensity = 0.3;
} else if (lastExpression === 'surprised') {
currentWeather = 'windy';
sun.targetIntensity = 0.6;
} else if (lastExpression === 'angry') {
currentWeather = 'stormy';
sun.targetIntensity = 0.2;
} else {
currentWeather = 'neutral';
sun.targetIntensity = 0.5;
}
// Update weather text
weatherTxt.setText('Weather: ' + currentWeather.charAt(0).toUpperCase() + currentWeather.slice(1));
// Spawn weather effects based on current weather
if (currentWeather === 'rainy' && Math.random() < 0.2) {
createRain();
} else if (currentWeather === 'windy' && Math.random() < 0.1) {
createWind();
} else if (currentWeather === 'stormy') {
if (Math.random() < 0.02 && storms.length < 3) {
createStorm();
}
if (Math.random() < 0.1) {
createRain();
}
}
}
// Setup initial ecosystem
function setupInitialEcosystem() {
// Create initial plants
for (var i = 0; i < 5; i++) {
generatePlant('grass', 200 + i * 300);
}
for (var i = 0; i < 3; i++) {
generatePlant('tree', 400 + i * 400);
}
for (var i = 0; i < 4; i++) {
generatePlant('flower', 350 + i * 350);
}
// Create initial animals
for (var i = 0; i < 3; i++) {
generateAnimal(300 + i * 500);
}
// Start background music
LK.playMusic('bgMusic');
}
// Call setup
setupInitialEcosystem();
// Main game loop
game.update = function () {
// Update face detection status
var expression = detectFacialExpression();
if (expression !== 'not detected') {
detectionTxt.setText('Expression: ' + expression.charAt(0).toUpperCase() + expression.slice(1));
lastExpression = expression;
} else {
detectionTxt.setText('Looking for your face...');
}
// Update weather based on expression
updateWeather();
// Update weather timer
weatherChangeTimer++;
if (weatherChangeTimer > 300) {
// Every 5 seconds
weatherChangeTimer = 0;
// Occasionally spawn new plants or animals
if (plants.length < 20 && Math.random() < 0.3) {
var types = ['grass', 'tree', 'flower'];
generatePlant(types[Math.floor(Math.random() * types.length)]);
}
if (animals.length < 8 && Math.random() < 0.2) {
generateAnimal();
}
}
// Update all game objects
sun.update();
// Update plants
for (var i = plants.length - 1; i >= 0; i--) {
if (plants[i].parent) {
plants[i].update();
} else {
plants.splice(i, 1);
}
}
// Update animals
for (var i = animals.length - 1; i >= 0; i--) {
if (animals[i].parent) {
animals[i].update();
} else {
animals.splice(i, 1);
}
}
// Update rain
for (var i = rain.length - 1; i >= 0; i--) {
if (rain[i].parent) {
rain[i].update();
} else {
rain.splice(i, 1);
}
}
// Update wind
for (var i = wind.length - 1; i >= 0; i--) {
if (wind[i].parent) {
wind[i].update();
} else {
wind.splice(i, 1);
}
}
// Update storms
for (var i = storms.length - 1; i >= 0; i--) {
if (storms[i].parent) {
storms[i].update();
} else {
storms.splice(i, 1);
}
}
// Update particles
for (var i = particles.length - 1; i >= 0; i--) {
if (particles[i].parent) {
particles[i].update();
} else {
particles.splice(i, 1);
}
}
// Update water pools
for (var i = waterPools.length - 1; i >= 0; i--) {
if (waterPools[i].parent) {
waterPools[i].update();
} else {
waterPools.splice(i, 1);
}
}
// Game over condition - all plants and animals died
if (plants.length === 0 && animals.length === 0) {
LK.showGameOver();
}
// Win condition - flourishing ecosystem (many plants and animals)
if (plants.length > 15 && animals.length > 5) {
// Update score based on ecosystem health
LK.setScore(plants.length + animals.length * 2);
// Check if score is high enough to win
if (LK.getScore() > 30) {
LK.showYouWin();
}
}
};