/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var CactusSpike = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('cactusSpike', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 15; // Faster than regular peas // Calculate direction towards target var dx = target.x - startX; var dy = target.y - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; // Rotate spike based on direction self.graphics.rotation = Math.atan2(dy, dx); self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with any enemy for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.intersects(enemy)) { var destroyed = enemy.takeDamage(self.damage); if (destroyed) { // Remove enemy from array var index = enemies.indexOf(enemy); if (index > -1) { enemy.destroy(); enemies.splice(index, 1); resources += 10; updateUI(); } } // Remove bullet var bulletIndex = cactusSpikes.indexOf(self); if (bulletIndex > -1) { cactusSpikes.splice(bulletIndex, 1); self.destroy(); } LK.getSound('enemyHit').play(); return; // Exit after hitting first enemy } } // Remove bullet if it goes off screen if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) { var bulletIndex = cactusSpikes.indexOf(self); if (bulletIndex > -1) { cactusSpikes.splice(bulletIndex, 1); self.destroy(); } } }; return self; }); var CoconutBullet = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('coconutBullet', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 8; self.explosionRadius = 150; // Calculate direction towards target var dx = target.x - startX; var dy = target.y - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check if reached target area var distanceToTarget = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2)); if (distanceToTarget <= 50) { self.explode(); } // Remove bullet if it goes off screen if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) { var bulletIndex = coconutBullets.indexOf(self); if (bulletIndex > -1) { coconutBullets.splice(bulletIndex, 1); self.destroy(); } } }; self.explode = function () { // Damage all enemies in explosion radius for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= self.explosionRadius) { var destroyed = enemy.takeDamage(self.damage); if (destroyed) { enemy.destroy(); enemies.splice(i, 1); resources += 10; updateUI(); } } } // Remove bullet var bulletIndex = coconutBullets.indexOf(self); if (bulletIndex > -1) { coconutBullets.splice(bulletIndex, 1); self.destroy(); } LK.getSound('enemyHit').play(); }; return self; }); var Enemy = Container.expand(function (type, pathIndex) { var self = Container.call(this); self.type = type; self.speed = 0.5; self.health = 50; self.maxHealth = 50; self.damage = 10; self.attackCooldown = 0; // Use specified path index self.pathLane = pathIndex; self.targetRow = pathRows[pathIndex]; if (type === 'basic') { self.graphics = self.attachAsset('basicUndead', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); self.graphics.scale.x = -1; self.speed = 0.5; self.health = 50; self.maxHealth = 50; } else if (type === 'fast') { self.graphics = self.attachAsset('fastUndead', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1; self.health = 30; self.maxHealth = 30; } else if (type === 'tank') { self.graphics = self.attachAsset('tankUndead', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0.25; self.health = 150; self.maxHealth = 150; self.damage = 20; } else if (type === 'moon') { self.graphics = self.attachAsset('moonUndead', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.scale.x = -1; self.speed = 0.3; self.health = 200; self.maxHealth = 200; self.damage = 25; self.shootCooldown = 0; self.shootRate = 120; // 2 seconds between shots self.range = 600; // Shooting range } // Position will be set by spawnEnemy function self.x = 0; self.y = 0; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.health = 0; return true; // enemy destroyed } return false; }; self.update = function () { if (self.attackCooldown > 0) { self.attackCooldown--; } if (self.shootCooldown > 0) { self.shootCooldown--; } // Handle frozen state if (self.frozenTime > 0) { self.frozenTime--; if (self.frozenTime <= 0) { // Unfreeze enemy self.speed = self.originalSpeed; self.graphics.tint = 0xffffff; // Reset tint } } // Move straight horizontally from left to right self.x += self.speed; // Gradually grow zombie over time if (self.graphics.scaleX < 3) { tween(self.graphics, { scaleX: 3, scaleY: 3 }, { duration: 5000 }); } // Check if reached end of screen if (self.x > 2048 + 100) { // Reached end of screen, damage player playerHealth -= self.damage; updateUI(); // Remove enemy var index = enemies.indexOf(self); if (index > -1) { enemies.splice(index, 1); self.destroy(); } if (playerHealth <= 0) { LK.showGameOver(); } } // Moon zombie shooting behavior if (self.type === 'moon' && self.shootCooldown <= 0) { var target = self.findNearestPlant(); if (target) { self.shootMoon(target); self.shootCooldown = self.shootRate; } } // Check for plant collisions self.checkPlantCollisions(); }; self.findNearestPlant = function () { var nearest = null; var nearestDistance = self.range; for (var i = 0; i < plants.length; i++) { var plant = plants[i]; var distance = Math.sqrt(Math.pow(plant.x - self.x, 2) + Math.pow(plant.y - self.y, 2)); if (distance < nearestDistance) { nearest = plant; nearestDistance = distance; } } return nearest; }; self.shootMoon = function (target) { var moonBullet = new MoonBullet(self.x, self.y, target, self.damage); moonBullets.push(moonBullet); game.addChild(moonBullet); LK.getSound('plantShoot').play(); }; self.checkPlantCollisions = function () { for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (self.intersects(plant) && self.attackCooldown <= 0) { var destroyed = plant.takeDamage(self.damage); if (destroyed) { plant.destroy(); plants.splice(i, 1); // Remove from grid var gridX = Math.floor((plant.x - 24) / 100); var gridY = Math.floor((plant.y - 800) / 100); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { grid[gridY][gridX] = 0; } } self.attackCooldown = 60; // 1 second cooldown break; } } }; return self; }); var FireBullet = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('fireBullet', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 12; // Calculate direction towards target var dx = target.x - startX; var dy = target.y - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with any enemy for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.intersects(enemy)) { var destroyed = enemy.takeDamage(self.damage); if (destroyed) { // Remove enemy from array var index = enemies.indexOf(enemy); if (index > -1) { enemy.destroy(); enemies.splice(index, 1); resources += 10; updateUI(); } } // Remove bullet var bulletIndex = fireBullets.indexOf(self); if (bulletIndex > -1) { fireBullets.splice(bulletIndex, 1); self.destroy(); } LK.getSound('enemyHit').play(); return; // Exit after hitting first enemy } } }; return self; }); var FireWave = Container.expand(function (startX, startY, direction, damage) { var self = Container.call(this); self.graphics = self.attachAsset('fireWave', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.damage = damage; self.speed = 6; self.direction = direction; // 'left' or 'right' self.lifeTime = 120; // 2 seconds self.hitEnemies = []; // Track hit enemies to prevent multiple hits self.update = function () { self.lifeTime--; if (self.lifeTime <= 0) { var waveIndex = fireWaves.indexOf(self); if (waveIndex > -1) { fireWaves.splice(waveIndex, 1); self.destroy(); } return; } // Move in direction if (self.direction === 'left') { self.x -= self.speed; } else { self.x += self.speed; } // Check collision with enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.hitEnemies.indexOf(enemy) === -1 && self.intersects(enemy)) { self.hitEnemies.push(enemy); var destroyed = enemy.takeDamage(self.damage); if (destroyed) { var index = enemies.indexOf(enemy); if (index > -1) { enemy.destroy(); enemies.splice(index, 1); resources += 10; updateUI(); } } LK.getSound('enemyHit').play(); } } // Remove if off screen if (self.x < -100 || self.x > 2150) { var waveIndex = fireWaves.indexOf(self); if (waveIndex > -1) { fireWaves.splice(waveIndex, 1); self.destroy(); } } }; return self; }); var MoonBullet = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('moonBullet', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 8; // Calculate direction towards target var dx = target.x - startX; var dy = target.y - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with any plant for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (self.intersects(plant)) { var destroyed = plant.takeDamage(self.damage); if (destroyed) { // Remove plant from array var index = plants.indexOf(plant); if (index > -1) { plant.destroy(); plants.splice(index, 1); // Remove from grid var gridX = Math.floor((plant.x - 24) / 100); var gridY = Math.floor((plant.y - 800) / 100); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { grid[gridY][gridX] = 0; } } } // Remove bullet var bulletIndex = moonBullets.indexOf(self); if (bulletIndex > -1) { moonBullets.splice(bulletIndex, 1); self.destroy(); } LK.getSound('enemyHit').play(); return; // Exit after hitting first plant } } // Remove bullet if it goes off screen if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) { var bulletIndex = moonBullets.indexOf(self); if (bulletIndex > -1) { moonBullets.splice(bulletIndex, 1); self.destroy(); } } }; return self; }); var PeasFire = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('fireBullet', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 10; // Calculate direction towards target var dx = target.x - startX; var dy = target.y - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with any enemy for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.intersects(enemy)) { var destroyed = enemy.takeDamage(self.damage); if (destroyed) { // Remove enemy from array var index = enemies.indexOf(enemy); if (index > -1) { enemy.destroy(); enemies.splice(index, 1); resources += 10; updateUI(); } } // Remove bullet var bulletIndex = peasFireBullets.indexOf(self); if (bulletIndex > -1) { peasFireBullets.splice(bulletIndex, 1); self.destroy(); } LK.getSound('enemyHit').play(); return; // Exit after hitting first enemy } } // Remove bullet if it goes off screen if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) { var bulletIndex = peasFireBullets.indexOf(self); if (bulletIndex > -1) { peasFireBullets.splice(bulletIndex, 1); self.destroy(); } } }; return self; }); var Plant = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.shootCooldown = 0; self.range = 800; self.damage = 20; self.health = 100; self.maxHealth = 100; self.sunProductionTimer = 0; self.currentTarget = null; if (type === 'peashooter') { self.graphics = self.attachAsset('peashooter', { anchorX: 0.5, anchorY: 0.5 }); self.shootRate = 90; // frames between shots self.cost = 100; self.range = 800; self.damage = 20; } else if (type === 'firepeashooter') { self.graphics = self.attachAsset('firepeashooter', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0xff4500; // Red tint for fire peashooter self.shootRate = 90; // frames between shots self.cost = 125; self.range = 800; self.damage = 30; // Higher damage than regular peashooter } else if (type === 'wallnut') { self.graphics = self.attachAsset('wallnut', { anchorX: 0.5, anchorY: 0.5 }); self.health = 4000; self.maxHealth = 4000; self.cost = 50; } else if (type === 'sunflower') { self.graphics = self.attachAsset('sunflower', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 50; self.health = 300; self.maxHealth = 300; } else if (type === 'cherrybomb') { self.graphics = self.attachAsset('cherrybomb', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 150; self.health = 200; self.maxHealth = 200; self.explosionTimer = 180; // 3 seconds at 60fps self.hasExploded = false; } else if (type === 'peasfire') { self.graphics = self.attachAsset('peashooter', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0xff6347; // Tomato red tint for peasfire self.shootRate = 75; // frames between shots self.cost = 150; self.range = 800; self.damage = 35; // Higher damage than regular peashooter } else if (type === 'venusflytrap') { self.graphics = self.attachAsset('venusflytrap', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 175; self.health = 300; self.maxHealth = 300; self.damage = 200; // High damage for instant kill self.attackRange = 150; // Short range for close combat self.attackCooldown = 0; self.isEating = false; self.eatDuration = 0; } else if (type === 'bonkchoy') { self.graphics = self.attachAsset('bonkchoy', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x228B22; // Forest green tint self.cost = 150; self.health = 300; self.maxHealth = 300; self.damage = 75; self.attackRange = 100; self.attackCooldown = 0; self.attackRate = 60; // Attack every second } else if (type === 'bonkchoy') { self.graphics = self.attachAsset('bonkchoy', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x228B22; // Forest green tint self.cost = 150; self.health = 300; self.maxHealth = 300; self.damage = 75; self.attackRange = 100; self.attackCooldown = 0; self.attackRate = 60; // Attack every second } else if (type === 'coconutcannon') { self.graphics = self.attachAsset('coconutcannon', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x8B4513; // Brown tint self.cost = 400; self.health = 300; self.maxHealth = 300; self.damage = 90; self.range = 1000; self.shootRate = 240; // Shoot every 4 seconds } else if (type === 'tallnut') { self.graphics = self.attachAsset('tallnut', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x8B4513; // Brown tint self.cost = 125; self.health = 8000; // Higher health than wallnut self.maxHealth = 8000; } else if (type === 'spikeweed') { self.graphics = self.attachAsset('spikeweed', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x228B22; // Green tint self.cost = 100; self.health = 300; self.maxHealth = 300; self.damage = 20; self.spikeDamage = 20; } else if (type === 'spikerock') { self.graphics = self.attachAsset('spikerock', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x696969; // Gray tint self.cost = 200; self.health = 600; self.maxHealth = 600; self.damage = 40; self.spikeDamage = 40; } else if (type === 'cactus') { self.graphics = self.attachAsset('cactus', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x228B22; // Dark green tint self.shootRate = 80; // frames between shots self.cost = 125; self.range = 1200; // Long range self.damage = 25; self.health = 300; self.maxHealth = 300; } else if (type === 'magnetshroom') { self.graphics = self.attachAsset('magnetshroom', { anchorX: 0.5, anchorY: 0.5 }); self.graphics.tint = 0x800080; // Purple tint self.cost = 100; self.health = 300; self.maxHealth = 300; self.magnetRange = 200; self.magnetCooldown = 0; self.magnetRate = 120; // Every 2 seconds } self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.health = 0; return true; // plant destroyed } return false; }; self.update = function () { if (self.shootCooldown > 0) { self.shootCooldown--; } if (self.type === 'peashooter') { var target = self.findNearestEnemy(); // Shoot if cooldown is ready if (target && self.shootCooldown <= 0) { self.currentTarget = target; self.shoot(target); self.shootCooldown = self.shootRate; } else if (!target) { self.currentTarget = null; } } if (self.type === 'firepeashooter') { var target = self.findNearestEnemy(); // Shoot if cooldown is ready if (target && self.shootCooldown <= 0) { self.currentTarget = target; self.shootFire(target); self.shootCooldown = self.shootRate; } else if (!target) { self.currentTarget = null; } } if (self.type === 'peasfire') { var target = self.findNearestEnemy(); // Shoot if cooldown is ready if (target && self.shootCooldown <= 0) { self.currentTarget = target; self.shootPeasFire(target); self.shootCooldown = self.shootRate; } else if (!target) { self.currentTarget = null; } } if (self.type === 'cactus') { var target = self.findNearestEnemy(); // Shoot if cooldown is ready if (target && self.shootCooldown <= 0) { self.currentTarget = target; self.shootCactusSpike(target); self.shootCooldown = self.shootRate; } else if (!target) { self.currentTarget = null; } } // Sunflower produces sun every 24 seconds (1440 frames at 60fps) if (self.type === 'sunflower') { self.sunProductionTimer++; if (self.sunProductionTimer >= 1440) { self.produceSun(); self.sunProductionTimer = 0; } } // Cherry bomb explosion timer if (self.type === 'cherrybomb' && !self.hasExploded) { self.explosionTimer--; if (self.explosionTimer <= 0) { self.explode(); self.hasExploded = true; } } // Venus fly trap behavior if (self.type === 'venusflytrap') { if (self.attackCooldown > 0) { self.attackCooldown--; } if (self.isEating) { self.eatDuration--; if (self.eatDuration <= 0) { self.isEating = false; // Reset scale after eating tween(self.graphics, { scaleX: 1, scaleY: 1 }, { duration: 300 }); } } else if (self.attackCooldown <= 0) { // Look for enemies in attack range for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= self.attackRange) { // Instantly kill enemy enemy.destroy(); enemies.splice(i, 1); resources += 15; updateUI(); // Start eating animation self.isEating = true; self.eatDuration = 120; // 2 seconds self.attackCooldown = 420; // 7 seconds cooldown // Scale up during eating tween(self.graphics, { scaleX: 1.3, scaleY: 1.3 }, { duration: 300 }); LK.getSound('enemyHit').play(); break; } } } } // Bonk choy behavior - punches nearby enemies if (self.type === 'bonkchoy') { if (self.attackCooldown > 0) { self.attackCooldown--; } if (self.attackCooldown <= 0) { // Look for enemies in punch range for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= self.attackRange) { var destroyed = enemy.takeDamage(self.damage); if (destroyed) { enemy.destroy(); enemies.splice(i, 1); resources += 10; updateUI(); } self.attackCooldown = self.attackRate; // Punch animation tween(self.graphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, onFinish: function onFinish() { tween(self.graphics, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } }); LK.getSound('enemyHit').play(); break; } } } } // Coconut cannon behavior if (self.type === 'coconutcannon') { var target = self.findNearestEnemy(); if (target && self.shootCooldown <= 0) { self.currentTarget = target; self.shootCoconut(target); self.shootCooldown = self.shootRate; } else if (!target) { self.currentTarget = null; } } // Spikeweed behavior - damages enemies that walk over it if (self.type === 'spikeweed' || self.type === 'spikerock') { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.intersects(enemy)) { var destroyed = enemy.takeDamage(self.spikeDamage); if (destroyed) { enemy.destroy(); enemies.splice(i, 1); resources += 10; updateUI(); } // Take damage from contact var plantDestroyed = self.takeDamage(5); if (plantDestroyed) { var plantIndex = plants.indexOf(self); if (plantIndex > -1) { plants.splice(plantIndex, 1); var gridX = Math.floor((self.x - 24) / 100); var gridY = Math.floor((self.y - 800) / 100); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { grid[gridY][gridX] = 0; } self.destroy(); } } LK.getSound('enemyHit').play(); break; } } } // Magnet-shroom behavior - attracts metal objects if (self.type === 'magnetshroom') { if (self.magnetCooldown > 0) { self.magnetCooldown--; } if (self.magnetCooldown <= 0) { // Look for metal enemies or objects in range for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.type === 'tank') { // Tank enemies are metal var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= self.magnetRange) { // Store original speed if not already stored if (!enemy.originalSpeed) { enemy.originalSpeed = enemy.speed; } // Slow down metal enemy significantly enemy.speed = enemy.originalSpeed * 0.3; self.magnetCooldown = self.magnetRate; // Visual effect - purple tint to show magnetism tween(enemy.graphics, { tint: 0x800080 }, { duration: 1000, onFinish: function onFinish() { // Restore original speed after effect if (enemy.originalSpeed) { enemy.speed = enemy.originalSpeed; } enemy.graphics.tint = 0xffffff; } }); // Pull enemy slightly towards magnet var pullForce = 0.5; var dx = self.x - enemy.x; var dy = self.y - enemy.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { enemy.x += dx / dist * pullForce; enemy.y += dy / dist * pullForce; } break; } } } } } }; self.findNearestEnemy = function () { var nearest = null; var nearestDistance = self.range; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Plant can attack any enemy within range regardless of position var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance < nearestDistance) { nearest = enemy; nearestDistance = distance; } } return nearest; }; self.shoot = function (target) { var bullet = new PlantBullet(self.x, self.y, target, self.damage); bullets.push(bullet); game.addChild(bullet); LK.getSound('plantShoot').play(); }; self.shootFire = function (target) { var fireBullet = new FireBullet(self.x, self.y, target, self.damage); fireBullets.push(fireBullet); game.addChild(fireBullet); LK.getSound('plantShoot').play(); }; self.shootPeasFire = function (target) { var peasFire = new PeasFire(self.x, self.y, target, self.damage); peasFireBullets.push(peasFire); game.addChild(peasFire); LK.getSound('plantShoot').play(); }; self.produceSun = function () { var sun = new Sun(self.x, self.y); suns.push(sun); game.addChild(sun); }; self.shootFireWaves = function () { // Shoot fire waves in both directions var leftWave = new FireWave(self.x, self.y, 'left', self.damage); var rightWave = new FireWave(self.x, self.y, 'right', self.damage); fireWaves.push(leftWave); fireWaves.push(rightWave); game.addChild(leftWave); game.addChild(rightWave); LK.getSound('plantShoot').play(); }; self.shootCoconut = function (target) { var coconut = new CoconutBullet(self.x, self.y, target, self.damage); coconutBullets.push(coconut); game.addChild(coconut); LK.getSound('plantShoot').play(); }; self.shootCactusSpike = function (target) { var cactusSpike = new CactusSpike(self.x, self.y, target, self.damage); cactusSpikes.push(cactusSpike); game.addChild(cactusSpike); LK.getSound('plantShoot').play(); }; self.explode = function () { var explosionRadius = 200; // Damage all enemies in explosion radius for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= explosionRadius) { var destroyed = enemy.takeDamage(1800); // Massive damage to kill most enemies if (destroyed) { enemy.destroy(); enemies.splice(i, 1); resources += 10; updateUI(); } } } // Remove cherry bomb after explosion var plantIndex = plants.indexOf(self); if (plantIndex > -1) { plants.splice(plantIndex, 1); var gridX = Math.floor((self.x - 24) / 100); var gridY = Math.floor((self.y - 800) / 100); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { grid[gridY][gridX] = 0; } self.destroy(); } }; return self; }); var PlantBullet = Container.expand(function (startX, startY, target, damage) { var self = Container.call(this); self.graphics = self.attachAsset('pea', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.target = target; self.damage = damage; self.speed = 12; // Calculate direction towards target var dx = target.x - startX; var dy = target.y - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Check collision with any enemy for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (self.intersects(enemy)) { var destroyed = enemy.takeDamage(self.damage); if (destroyed) { // Remove enemy from array var index = enemies.indexOf(enemy); if (index > -1) { enemy.destroy(); enemies.splice(index, 1); resources += 10; updateUI(); } } // Remove bullet var bulletIndex = bullets.indexOf(self); if (bulletIndex > -1) { bullets.splice(bulletIndex, 1); self.destroy(); } LK.getSound('enemyHit').play(); return; // Exit after hitting first enemy } } }; return self; }); var Sun = Container.expand(function (startX, startY) { var self = Container.call(this); self.graphics = self.attachAsset('sun', { anchorX: 0.5, anchorY: 0.5 }); self.x = startX; self.y = startY; self.lifeTime = 600; // 10 seconds at 60fps self.collected = false; self.update = function () { self.lifeTime--; if (self.lifeTime <= 0 && !self.collected) { // Remove sun after timeout var index = suns.indexOf(self); if (index > -1) { suns.splice(index, 1); self.destroy(); } } }; self.down = function () { if (!self.collected) { self.collected = true; resources += 25; updateUI(); // Remove sun var index = suns.indexOf(self); if (index > -1) { suns.splice(index, 1); self.destroy(); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d5016 }); /**** * Game Code ****/ // Game variables // Grid and UI elements // Plants // Projectiles and effects // Enemies // UI buttons // Sound effects var gridWidth = 20; var gridHeight = 10; var grid = []; var plants = []; var enemies = []; var bullets = []; var moonBullets = []; var fireBullets = []; var peasFireBullets = []; var cactusSpikes = []; var coconutBullets = []; var fireWaves = []; var resources = 100; var playerHealth = 100; var waveNumber = 1; var enemiesInWave = 5; var enemySpawnTimer = 0; var enemiesSpawned = 0; var selectedPlantType = 'peashooter'; var suns = []; var selectedCell = null; // Multiple paths for enemies (horizontal paths from left to right) var enemyPaths = []; var pathRows = [2, 4, 6, 8]; // Multiple lane rows for paths // Create paths for each lane for (var p = 0; p < pathRows.length; p++) { var path = []; for (var i = 0; i <= gridWidth; i++) { path.push({ x: i * 100 + 50, y: 200 + pathRows[p] * 100 + 50 }); } enemyPaths.push(path); } // Initialize grid for (var y = 0; y < gridHeight; y++) { grid[y] = []; for (var x = 0; x < gridWidth; x++) { grid[y][x] = 0; // 0 = empty, 1 = plant, 2 = path } } // Mark path cells (horizontal lanes in multiple rows) for (var p = 0; p < pathRows.length; p++) { for (var i = 0; i < gridWidth; i++) { grid[pathRows[p]][i] = 2; // path in lane rows } } // Create grid visual var gridContainer = game.addChild(new Container()); gridContainer.x = 24; // Center the grid horizontally gridContainer.y = 800; // Move grid down to accommodate horizontal layout for (var y = 0; y < gridHeight; y++) { for (var x = 0; x < gridWidth; x++) { var cell; if (grid[y][x] === 2) { cell = LK.getAsset('pathCell', { anchorX: 0, anchorY: 0 }); } else { cell = LK.getAsset('gridCell', { anchorX: 0, anchorY: 0 }); } cell.x = x * 100; cell.y = y * 100; gridContainer.addChild(cell); } } // UI Elements var resourceText = new Text2('Resources: ' + resources, { size: 40, fill: '#ffffff' }); resourceText.anchor.set(0, 0); LK.gui.topLeft.addChild(resourceText); resourceText.x = 120; resourceText.y = 20; var healthText = new Text2('Health: ' + playerHealth, { size: 40, fill: '#ffffff' }); healthText.anchor.set(0, 0); LK.gui.topLeft.addChild(healthText); healthText.x = 120; healthText.y = 70; var waveText = new Text2('Wave: ' + waveNumber, { size: 40, fill: '#ffffff' }); waveText.anchor.set(0, 0); LK.gui.topLeft.addChild(waveText); waveText.x = 120; waveText.y = 120; // Plant selection buttons var buttonContainer = new Container(); LK.gui.bottom.addChild(buttonContainer); buttonContainer.y = -150; var shooterButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var shooterText = new Text2('Peashooter\n$100', { size: 24, fill: '#ffffff' }); shooterText.anchor.set(0.5, 0.5); shooterButton.addChild(shooterText); shooterButton.x = -200; buttonContainer.addChild(shooterButton); var blockerButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var blockerText = new Text2('Wall-nut\n$50', { size: 24, fill: '#ffffff' }); blockerText.anchor.set(0.5, 0.5); blockerButton.addChild(blockerText); blockerButton.x = 0; buttonContainer.addChild(blockerButton); var supportButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var supportText = new Text2('Sunflower\n$50', { size: 24, fill: '#ffffff' }); supportText.anchor.set(0.5, 0.5); supportButton.addChild(supportText); supportButton.x = 200; buttonContainer.addChild(supportButton); var bombButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var bombText = new Text2('Cherry Bomb\n$150', { size: 24, fill: '#ffffff' }); bombText.anchor.set(0.5, 0.5); bombButton.addChild(bombText); bombButton.x = 400; buttonContainer.addChild(bombButton); var venusButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var venusText = new Text2('Venus Fly Trap\n$175', { size: 20, fill: '#ffffff' }); venusText.anchor.set(0.5, 0.5); venusButton.addChild(venusText); venusButton.x = 600; buttonContainer.addChild(venusButton); var fireButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var fireText = new Text2('Fire Peashooter\n$125', { size: 18, fill: '#ffffff' }); fireText.anchor.set(0.5, 0.5); fireButton.addChild(fireText); fireButton.x = 800; buttonContainer.addChild(fireButton); var peasFireButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var peasFireText = new Text2('Peas Fire\n$150', { size: 18, fill: '#ffffff' }); peasFireText.anchor.set(0.5, 0.5); peasFireButton.addChild(peasFireText); peasFireButton.x = 1000; buttonContainer.addChild(peasFireButton); var bonkchoyButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var bonkchoyText = new Text2('Bonk Choy\n$150', { size: 18, fill: '#ffffff' }); bonkchoyText.anchor.set(0.5, 0.5); bonkchoyButton.addChild(bonkchoyText); bonkchoyButton.x = -400; buttonContainer.addChild(bonkchoyButton); var coconutButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var coconutText = new Text2('Coconut Cannon\n$400', { size: 16, fill: '#ffffff' }); coconutText.anchor.set(0.5, 0.5); coconutButton.addChild(coconutText); coconutButton.x = -1000; buttonContainer.addChild(coconutButton); var tallnutButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var tallnutText = new Text2('Tall-nut\n$125', { size: 18, fill: '#ffffff' }); tallnutText.anchor.set(0.5, 0.5); tallnutButton.addChild(tallnutText); tallnutButton.x = -1200; buttonContainer.addChild(tallnutButton); var spikeweedButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var spikeweedText = new Text2('Spikeweed\n$100', { size: 18, fill: '#ffffff' }); spikeweedText.anchor.set(0.5, 0.5); spikeweedButton.addChild(spikeweedText); spikeweedButton.x = -1400; buttonContainer.addChild(spikeweedButton); var spikerockButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var spikerockText = new Text2('Spikerock\n$200', { size: 18, fill: '#ffffff' }); spikerockText.anchor.set(0.5, 0.5); spikerockButton.addChild(spikerockText); spikerockButton.x = -1600; buttonContainer.addChild(spikerockButton); var cactusButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var cactusText = new Text2('Cactus\n$125', { size: 18, fill: '#ffffff' }); cactusText.anchor.set(0.5, 0.5); cactusButton.addChild(cactusText); cactusButton.x = -600; buttonContainer.addChild(cactusButton); var magnetButton = LK.getAsset('plantButton', { anchorX: 0.5, anchorY: 0.5 }); var magnetText = new Text2('Magnet-shroom\n$100', { size: 16, fill: '#ffffff' }); magnetText.anchor.set(0.5, 0.5); magnetButton.addChild(magnetText); magnetButton.x = -800; buttonContainer.addChild(magnetButton); // Button interactions shooterButton.down = function () { selectedPlantType = 'peashooter'; updateButtonSelection(); }; blockerButton.down = function () { selectedPlantType = 'wallnut'; updateButtonSelection(); }; supportButton.down = function () { selectedPlantType = 'sunflower'; updateButtonSelection(); }; bombButton.down = function () { selectedPlantType = 'cherrybomb'; updateButtonSelection(); }; venusButton.down = function () { selectedPlantType = 'venusflytrap'; updateButtonSelection(); }; fireButton.down = function () { selectedPlantType = 'firepeashooter'; updateButtonSelection(); }; peasFireButton.down = function () { selectedPlantType = 'peasfire'; updateButtonSelection(); }; bonkchoyButton.down = function () { selectedPlantType = 'bonkchoy'; updateButtonSelection(); }; coconutButton.down = function () { selectedPlantType = 'coconutcannon'; updateButtonSelection(); }; tallnutButton.down = function () { selectedPlantType = 'tallnut'; updateButtonSelection(); }; spikeweedButton.down = function () { selectedPlantType = 'spikeweed'; updateButtonSelection(); }; spikerockButton.down = function () { selectedPlantType = 'spikerock'; updateButtonSelection(); }; cactusButton.down = function () { selectedPlantType = 'cactus'; updateButtonSelection(); }; magnetButton.down = function () { selectedPlantType = 'magnetshroom'; updateButtonSelection(); }; function updateButtonSelection() { shooterButton.tint = selectedPlantType === 'peashooter' ? 0x90ee90 : 0xffffff; blockerButton.tint = selectedPlantType === 'wallnut' ? 0x90ee90 : 0xffffff; supportButton.tint = selectedPlantType === 'sunflower' ? 0x90ee90 : 0xffffff; bombButton.tint = selectedPlantType === 'cherrybomb' ? 0x90ee90 : 0xffffff; venusButton.tint = selectedPlantType === 'venusflytrap' ? 0x90ee90 : 0xffffff; fireButton.tint = selectedPlantType === 'firepeashooter' ? 0x90ee90 : 0xffffff; peasFireButton.tint = selectedPlantType === 'peasfire' ? 0x90ee90 : 0xffffff; bonkchoyButton.tint = selectedPlantType === 'bonkchoy' ? 0x90ee90 : 0xffffff; coconutButton.tint = selectedPlantType === 'coconutcannon' ? 0x90ee90 : 0xffffff; tallnutButton.tint = selectedPlantType === 'tallnut' ? 0x90ee90 : 0xffffff; spikeweedButton.tint = selectedPlantType === 'spikeweed' ? 0x90ee90 : 0xffffff; spikerockButton.tint = selectedPlantType === 'spikerock' ? 0x90ee90 : 0xffffff; cactusButton.tint = selectedPlantType === 'cactus' ? 0x90ee90 : 0xffffff; magnetButton.tint = selectedPlantType === 'magnetshroom' ? 0x90ee90 : 0xffffff; } updateButtonSelection(); function updateUI() { resourceText.setText('Resources: ' + resources); healthText.setText('Health: ' + playerHealth); waveText.setText('Wave: ' + waveNumber); } function getPlantCost(type) { switch (type) { case 'peashooter': return 100; case 'firepeashooter': return 125; case 'peasfire': return 150; case 'wallnut': return 50; case 'sunflower': return 50; case 'cherrybomb': return 150; case 'venusflytrap': return 175; case 'bonkchoy': return 150; case 'coconutcannon': return 400; case 'tallnut': return 125; case 'spikeweed': return 100; case 'spikerock': return 200; case 'cactus': return 125; case 'magnetshroom': return 100; default: return 50; } } function placePlant(gridX, gridY) { if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) return; if (grid[gridY][gridX] !== 0) return; // cell not empty var cost = getPlantCost(selectedPlantType); if (resources < cost) return; var plant = new Plant(selectedPlantType); plant.x = 24 + gridX * 100 + 50; plant.y = 800 + gridY * 100 + 50; plants.push(plant); game.addChild(plant); grid[gridY][gridX] = 1; resources -= cost; updateUI(); LK.getSound('plantPlace').play(); } function spawnEnemy() { var enemyType = 'basic'; if (waveNumber > 3) { var rand = Math.random(); if (rand < 0.25) enemyType = 'fast';else if (rand < 0.5) enemyType = 'tank';else if (rand < 0.7) enemyType = 'moon'; } // Spawn enemies in any available lane var randomLane = Math.floor(Math.random() * pathRows.length); var enemy = new Enemy(enemyType, randomLane); // Spawn enemy at left side of screen at random Y position enemy.x = -50; // Start from left edge enemy.y = 800 + Math.random() * (gridHeight * 100); // Random Y position within grid enemies.push(enemy); game.addChild(enemy); } function startNextWave() { waveNumber++; enemiesInWave = Math.min(5 + waveNumber * 2, 20); enemiesSpawned = 0; enemySpawnTimer = 0; updateUI(); } // Game input handling game.down = function (x, y, obj) { // Check if click is in grid area if (y >= 800 && y < 800 + gridHeight * 100 && x >= 24 && x < 24 + gridWidth * 100) { var gridX = Math.floor((x - 24) / 100); var gridY = Math.floor((y - 800) / 100); placePlant(gridX, gridY); } }; // Main game loop game.update = function () { // Update plants for (var i = plants.length - 1; i >= 0; i--) { plants[i].update(); } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i]) { bullets[i].update(); // Remove bullets that are off screen if (bullets[i] && (bullets[i].x < -50 || bullets[i].x > 2100 || bullets[i].y < -50 || bullets[i].y > 2800)) { bullets[i].destroy(); bullets.splice(i, 1); } } } // Update suns for (var i = suns.length - 1; i >= 0; i--) { suns[i].update(); } // Update moon bullets for (var i = moonBullets.length - 1; i >= 0; i--) { if (moonBullets[i]) { moonBullets[i].update(); } } // Update fire bullets for (var i = fireBullets.length - 1; i >= 0; i--) { if (fireBullets[i]) { fireBullets[i].update(); // Remove fire bullets that are off screen if (fireBullets[i] && (fireBullets[i].x < -50 || fireBullets[i].x > 2100 || fireBullets[i].y < -50 || fireBullets[i].y > 2800)) { fireBullets[i].destroy(); fireBullets.splice(i, 1); } } } // Update peas fire bullets for (var i = peasFireBullets.length - 1; i >= 0; i--) { if (peasFireBullets[i]) { peasFireBullets[i].update(); } } // Update coconut bullets for (var i = coconutBullets.length - 1; i >= 0; i--) { if (coconutBullets[i]) { coconutBullets[i].update(); } } // Update fire waves for (var i = fireWaves.length - 1; i >= 0; i--) { if (fireWaves[i]) { fireWaves[i].update(); } } // Update cactus spikes for (var i = cactusSpikes.length - 1; i >= 0; i--) { if (cactusSpikes[i]) { cactusSpikes[i].update(); } } // Spawn enemies if (enemiesSpawned < enemiesInWave) { enemySpawnTimer++; if (enemySpawnTimer >= 360) { // spawn every 6 seconds spawnEnemy(); enemiesSpawned++; enemySpawnTimer = 0; } } // Check wave completion if (enemiesSpawned >= enemiesInWave && enemies.length === 0) { if (waveNumber >= 10) { LK.showYouWin(); } else { // Bonus resources for completing wave resources += 25; updateUI(); // Start next wave after delay LK.setTimeout(function () { startNextWave(); }, 2000); } } }; // Start first wave enemySpawnTimer = 0; enemiesSpawned = 0;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CactusSpike = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('cactusSpike', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 15; // Faster than regular peas
// Calculate direction towards target
var dx = target.x - startX;
var dy = target.y - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
// Rotate spike based on direction
self.graphics.rotation = Math.atan2(dy, dx);
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with any enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
// Remove enemy from array
var index = enemies.indexOf(enemy);
if (index > -1) {
enemy.destroy();
enemies.splice(index, 1);
resources += 10;
updateUI();
}
}
// Remove bullet
var bulletIndex = cactusSpikes.indexOf(self);
if (bulletIndex > -1) {
cactusSpikes.splice(bulletIndex, 1);
self.destroy();
}
LK.getSound('enemyHit').play();
return; // Exit after hitting first enemy
}
}
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) {
var bulletIndex = cactusSpikes.indexOf(self);
if (bulletIndex > -1) {
cactusSpikes.splice(bulletIndex, 1);
self.destroy();
}
}
};
return self;
});
var CoconutBullet = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('coconutBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
self.explosionRadius = 150;
// Calculate direction towards target
var dx = target.x - startX;
var dy = target.y - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check if reached target area
var distanceToTarget = Math.sqrt(Math.pow(self.x - self.target.x, 2) + Math.pow(self.y - self.target.y, 2));
if (distanceToTarget <= 50) {
self.explode();
}
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) {
var bulletIndex = coconutBullets.indexOf(self);
if (bulletIndex > -1) {
coconutBullets.splice(bulletIndex, 1);
self.destroy();
}
}
};
self.explode = function () {
// Damage all enemies in explosion radius
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.explosionRadius) {
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
enemy.destroy();
enemies.splice(i, 1);
resources += 10;
updateUI();
}
}
}
// Remove bullet
var bulletIndex = coconutBullets.indexOf(self);
if (bulletIndex > -1) {
coconutBullets.splice(bulletIndex, 1);
self.destroy();
}
LK.getSound('enemyHit').play();
};
return self;
});
var Enemy = Container.expand(function (type, pathIndex) {
var self = Container.call(this);
self.type = type;
self.speed = 0.5;
self.health = 50;
self.maxHealth = 50;
self.damage = 10;
self.attackCooldown = 0;
// Use specified path index
self.pathLane = pathIndex;
self.targetRow = pathRows[pathIndex];
if (type === 'basic') {
self.graphics = self.attachAsset('basicUndead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
self.graphics.scale.x = -1;
self.speed = 0.5;
self.health = 50;
self.maxHealth = 50;
} else if (type === 'fast') {
self.graphics = self.attachAsset('fastUndead', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1;
self.health = 30;
self.maxHealth = 30;
} else if (type === 'tank') {
self.graphics = self.attachAsset('tankUndead', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.25;
self.health = 150;
self.maxHealth = 150;
self.damage = 20;
} else if (type === 'moon') {
self.graphics = self.attachAsset('moonUndead', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.scale.x = -1;
self.speed = 0.3;
self.health = 200;
self.maxHealth = 200;
self.damage = 25;
self.shootCooldown = 0;
self.shootRate = 120; // 2 seconds between shots
self.range = 600; // Shooting range
}
// Position will be set by spawnEnemy function
self.x = 0;
self.y = 0;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
return true; // enemy destroyed
}
return false;
};
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Handle frozen state
if (self.frozenTime > 0) {
self.frozenTime--;
if (self.frozenTime <= 0) {
// Unfreeze enemy
self.speed = self.originalSpeed;
self.graphics.tint = 0xffffff; // Reset tint
}
}
// Move straight horizontally from left to right
self.x += self.speed;
// Gradually grow zombie over time
if (self.graphics.scaleX < 3) {
tween(self.graphics, {
scaleX: 3,
scaleY: 3
}, {
duration: 5000
});
}
// Check if reached end of screen
if (self.x > 2048 + 100) {
// Reached end of screen, damage player
playerHealth -= self.damage;
updateUI();
// Remove enemy
var index = enemies.indexOf(self);
if (index > -1) {
enemies.splice(index, 1);
self.destroy();
}
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Moon zombie shooting behavior
if (self.type === 'moon' && self.shootCooldown <= 0) {
var target = self.findNearestPlant();
if (target) {
self.shootMoon(target);
self.shootCooldown = self.shootRate;
}
}
// Check for plant collisions
self.checkPlantCollisions();
};
self.findNearestPlant = function () {
var nearest = null;
var nearestDistance = self.range;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
var distance = Math.sqrt(Math.pow(plant.x - self.x, 2) + Math.pow(plant.y - self.y, 2));
if (distance < nearestDistance) {
nearest = plant;
nearestDistance = distance;
}
}
return nearest;
};
self.shootMoon = function (target) {
var moonBullet = new MoonBullet(self.x, self.y, target, self.damage);
moonBullets.push(moonBullet);
game.addChild(moonBullet);
LK.getSound('plantShoot').play();
};
self.checkPlantCollisions = function () {
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (self.intersects(plant) && self.attackCooldown <= 0) {
var destroyed = plant.takeDamage(self.damage);
if (destroyed) {
plant.destroy();
plants.splice(i, 1);
// Remove from grid
var gridX = Math.floor((plant.x - 24) / 100);
var gridY = Math.floor((plant.y - 800) / 100);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridY][gridX] = 0;
}
}
self.attackCooldown = 60; // 1 second cooldown
break;
}
}
};
return self;
});
var FireBullet = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('fireBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 12;
// Calculate direction towards target
var dx = target.x - startX;
var dy = target.y - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with any enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
// Remove enemy from array
var index = enemies.indexOf(enemy);
if (index > -1) {
enemy.destroy();
enemies.splice(index, 1);
resources += 10;
updateUI();
}
}
// Remove bullet
var bulletIndex = fireBullets.indexOf(self);
if (bulletIndex > -1) {
fireBullets.splice(bulletIndex, 1);
self.destroy();
}
LK.getSound('enemyHit').play();
return; // Exit after hitting first enemy
}
}
};
return self;
});
var FireWave = Container.expand(function (startX, startY, direction, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('fireWave', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.damage = damage;
self.speed = 6;
self.direction = direction; // 'left' or 'right'
self.lifeTime = 120; // 2 seconds
self.hitEnemies = []; // Track hit enemies to prevent multiple hits
self.update = function () {
self.lifeTime--;
if (self.lifeTime <= 0) {
var waveIndex = fireWaves.indexOf(self);
if (waveIndex > -1) {
fireWaves.splice(waveIndex, 1);
self.destroy();
}
return;
}
// Move in direction
if (self.direction === 'left') {
self.x -= self.speed;
} else {
self.x += self.speed;
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.hitEnemies.indexOf(enemy) === -1 && self.intersects(enemy)) {
self.hitEnemies.push(enemy);
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
var index = enemies.indexOf(enemy);
if (index > -1) {
enemy.destroy();
enemies.splice(index, 1);
resources += 10;
updateUI();
}
}
LK.getSound('enemyHit').play();
}
}
// Remove if off screen
if (self.x < -100 || self.x > 2150) {
var waveIndex = fireWaves.indexOf(self);
if (waveIndex > -1) {
fireWaves.splice(waveIndex, 1);
self.destroy();
}
}
};
return self;
});
var MoonBullet = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('moonBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 8;
// Calculate direction towards target
var dx = target.x - startX;
var dy = target.y - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with any plant
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (self.intersects(plant)) {
var destroyed = plant.takeDamage(self.damage);
if (destroyed) {
// Remove plant from array
var index = plants.indexOf(plant);
if (index > -1) {
plant.destroy();
plants.splice(index, 1);
// Remove from grid
var gridX = Math.floor((plant.x - 24) / 100);
var gridY = Math.floor((plant.y - 800) / 100);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridY][gridX] = 0;
}
}
}
// Remove bullet
var bulletIndex = moonBullets.indexOf(self);
if (bulletIndex > -1) {
moonBullets.splice(bulletIndex, 1);
self.destroy();
}
LK.getSound('enemyHit').play();
return; // Exit after hitting first plant
}
}
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) {
var bulletIndex = moonBullets.indexOf(self);
if (bulletIndex > -1) {
moonBullets.splice(bulletIndex, 1);
self.destroy();
}
}
};
return self;
});
var PeasFire = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('fireBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 10;
// Calculate direction towards target
var dx = target.x - startX;
var dy = target.y - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with any enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
// Remove enemy from array
var index = enemies.indexOf(enemy);
if (index > -1) {
enemy.destroy();
enemies.splice(index, 1);
resources += 10;
updateUI();
}
}
// Remove bullet
var bulletIndex = peasFireBullets.indexOf(self);
if (bulletIndex > -1) {
peasFireBullets.splice(bulletIndex, 1);
self.destroy();
}
LK.getSound('enemyHit').play();
return; // Exit after hitting first enemy
}
}
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 2100 || self.y < -50 || self.y > 2800) {
var bulletIndex = peasFireBullets.indexOf(self);
if (bulletIndex > -1) {
peasFireBullets.splice(bulletIndex, 1);
self.destroy();
}
}
};
return self;
});
var Plant = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.shootCooldown = 0;
self.range = 800;
self.damage = 20;
self.health = 100;
self.maxHealth = 100;
self.sunProductionTimer = 0;
self.currentTarget = null;
if (type === 'peashooter') {
self.graphics = self.attachAsset('peashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.shootRate = 90; // frames between shots
self.cost = 100;
self.range = 800;
self.damage = 20;
} else if (type === 'firepeashooter') {
self.graphics = self.attachAsset('firepeashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0xff4500; // Red tint for fire peashooter
self.shootRate = 90; // frames between shots
self.cost = 125;
self.range = 800;
self.damage = 30; // Higher damage than regular peashooter
} else if (type === 'wallnut') {
self.graphics = self.attachAsset('wallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 4000;
self.maxHealth = 4000;
self.cost = 50;
} else if (type === 'sunflower') {
self.graphics = self.attachAsset('sunflower', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 300;
self.maxHealth = 300;
} else if (type === 'cherrybomb') {
self.graphics = self.attachAsset('cherrybomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.health = 200;
self.maxHealth = 200;
self.explosionTimer = 180; // 3 seconds at 60fps
self.hasExploded = false;
} else if (type === 'peasfire') {
self.graphics = self.attachAsset('peashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0xff6347; // Tomato red tint for peasfire
self.shootRate = 75; // frames between shots
self.cost = 150;
self.range = 800;
self.damage = 35; // Higher damage than regular peashooter
} else if (type === 'venusflytrap') {
self.graphics = self.attachAsset('venusflytrap', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 175;
self.health = 300;
self.maxHealth = 300;
self.damage = 200; // High damage for instant kill
self.attackRange = 150; // Short range for close combat
self.attackCooldown = 0;
self.isEating = false;
self.eatDuration = 0;
} else if (type === 'bonkchoy') {
self.graphics = self.attachAsset('bonkchoy', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x228B22; // Forest green tint
self.cost = 150;
self.health = 300;
self.maxHealth = 300;
self.damage = 75;
self.attackRange = 100;
self.attackCooldown = 0;
self.attackRate = 60; // Attack every second
} else if (type === 'bonkchoy') {
self.graphics = self.attachAsset('bonkchoy', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x228B22; // Forest green tint
self.cost = 150;
self.health = 300;
self.maxHealth = 300;
self.damage = 75;
self.attackRange = 100;
self.attackCooldown = 0;
self.attackRate = 60; // Attack every second
} else if (type === 'coconutcannon') {
self.graphics = self.attachAsset('coconutcannon', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x8B4513; // Brown tint
self.cost = 400;
self.health = 300;
self.maxHealth = 300;
self.damage = 90;
self.range = 1000;
self.shootRate = 240; // Shoot every 4 seconds
} else if (type === 'tallnut') {
self.graphics = self.attachAsset('tallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x8B4513; // Brown tint
self.cost = 125;
self.health = 8000; // Higher health than wallnut
self.maxHealth = 8000;
} else if (type === 'spikeweed') {
self.graphics = self.attachAsset('spikeweed', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x228B22; // Green tint
self.cost = 100;
self.health = 300;
self.maxHealth = 300;
self.damage = 20;
self.spikeDamage = 20;
} else if (type === 'spikerock') {
self.graphics = self.attachAsset('spikerock', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x696969; // Gray tint
self.cost = 200;
self.health = 600;
self.maxHealth = 600;
self.damage = 40;
self.spikeDamage = 40;
} else if (type === 'cactus') {
self.graphics = self.attachAsset('cactus', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x228B22; // Dark green tint
self.shootRate = 80; // frames between shots
self.cost = 125;
self.range = 1200; // Long range
self.damage = 25;
self.health = 300;
self.maxHealth = 300;
} else if (type === 'magnetshroom') {
self.graphics = self.attachAsset('magnetshroom', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.tint = 0x800080; // Purple tint
self.cost = 100;
self.health = 300;
self.maxHealth = 300;
self.magnetRange = 200;
self.magnetCooldown = 0;
self.magnetRate = 120; // Every 2 seconds
}
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
return true; // plant destroyed
}
return false;
};
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.type === 'peashooter') {
var target = self.findNearestEnemy();
// Shoot if cooldown is ready
if (target && self.shootCooldown <= 0) {
self.currentTarget = target;
self.shoot(target);
self.shootCooldown = self.shootRate;
} else if (!target) {
self.currentTarget = null;
}
}
if (self.type === 'firepeashooter') {
var target = self.findNearestEnemy();
// Shoot if cooldown is ready
if (target && self.shootCooldown <= 0) {
self.currentTarget = target;
self.shootFire(target);
self.shootCooldown = self.shootRate;
} else if (!target) {
self.currentTarget = null;
}
}
if (self.type === 'peasfire') {
var target = self.findNearestEnemy();
// Shoot if cooldown is ready
if (target && self.shootCooldown <= 0) {
self.currentTarget = target;
self.shootPeasFire(target);
self.shootCooldown = self.shootRate;
} else if (!target) {
self.currentTarget = null;
}
}
if (self.type === 'cactus') {
var target = self.findNearestEnemy();
// Shoot if cooldown is ready
if (target && self.shootCooldown <= 0) {
self.currentTarget = target;
self.shootCactusSpike(target);
self.shootCooldown = self.shootRate;
} else if (!target) {
self.currentTarget = null;
}
}
// Sunflower produces sun every 24 seconds (1440 frames at 60fps)
if (self.type === 'sunflower') {
self.sunProductionTimer++;
if (self.sunProductionTimer >= 1440) {
self.produceSun();
self.sunProductionTimer = 0;
}
}
// Cherry bomb explosion timer
if (self.type === 'cherrybomb' && !self.hasExploded) {
self.explosionTimer--;
if (self.explosionTimer <= 0) {
self.explode();
self.hasExploded = true;
}
}
// Venus fly trap behavior
if (self.type === 'venusflytrap') {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.isEating) {
self.eatDuration--;
if (self.eatDuration <= 0) {
self.isEating = false;
// Reset scale after eating
tween(self.graphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
} else if (self.attackCooldown <= 0) {
// Look for enemies in attack range
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.attackRange) {
// Instantly kill enemy
enemy.destroy();
enemies.splice(i, 1);
resources += 15;
updateUI();
// Start eating animation
self.isEating = true;
self.eatDuration = 120; // 2 seconds
self.attackCooldown = 420; // 7 seconds cooldown
// Scale up during eating
tween(self.graphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300
});
LK.getSound('enemyHit').play();
break;
}
}
}
}
// Bonk choy behavior - punches nearby enemies
if (self.type === 'bonkchoy') {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (self.attackCooldown <= 0) {
// Look for enemies in punch range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.attackRange) {
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
enemy.destroy();
enemies.splice(i, 1);
resources += 10;
updateUI();
}
self.attackCooldown = self.attackRate;
// Punch animation
tween(self.graphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.graphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
LK.getSound('enemyHit').play();
break;
}
}
}
}
// Coconut cannon behavior
if (self.type === 'coconutcannon') {
var target = self.findNearestEnemy();
if (target && self.shootCooldown <= 0) {
self.currentTarget = target;
self.shootCoconut(target);
self.shootCooldown = self.shootRate;
} else if (!target) {
self.currentTarget = null;
}
}
// Spikeweed behavior - damages enemies that walk over it
if (self.type === 'spikeweed' || self.type === 'spikerock') {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
var destroyed = enemy.takeDamage(self.spikeDamage);
if (destroyed) {
enemy.destroy();
enemies.splice(i, 1);
resources += 10;
updateUI();
}
// Take damage from contact
var plantDestroyed = self.takeDamage(5);
if (plantDestroyed) {
var plantIndex = plants.indexOf(self);
if (plantIndex > -1) {
plants.splice(plantIndex, 1);
var gridX = Math.floor((self.x - 24) / 100);
var gridY = Math.floor((self.y - 800) / 100);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridY][gridX] = 0;
}
self.destroy();
}
}
LK.getSound('enemyHit').play();
break;
}
}
}
// Magnet-shroom behavior - attracts metal objects
if (self.type === 'magnetshroom') {
if (self.magnetCooldown > 0) {
self.magnetCooldown--;
}
if (self.magnetCooldown <= 0) {
// Look for metal enemies or objects in range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.type === 'tank') {
// Tank enemies are metal
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.magnetRange) {
// Store original speed if not already stored
if (!enemy.originalSpeed) {
enemy.originalSpeed = enemy.speed;
}
// Slow down metal enemy significantly
enemy.speed = enemy.originalSpeed * 0.3;
self.magnetCooldown = self.magnetRate;
// Visual effect - purple tint to show magnetism
tween(enemy.graphics, {
tint: 0x800080
}, {
duration: 1000,
onFinish: function onFinish() {
// Restore original speed after effect
if (enemy.originalSpeed) {
enemy.speed = enemy.originalSpeed;
}
enemy.graphics.tint = 0xffffff;
}
});
// Pull enemy slightly towards magnet
var pullForce = 0.5;
var dx = self.x - enemy.x;
var dy = self.y - enemy.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
enemy.x += dx / dist * pullForce;
enemy.y += dy / dist * pullForce;
}
break;
}
}
}
}
}
};
self.findNearestEnemy = function () {
var nearest = null;
var nearestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Plant can attack any enemy within range regardless of position
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance < nearestDistance) {
nearest = enemy;
nearestDistance = distance;
}
}
return nearest;
};
self.shoot = function (target) {
var bullet = new PlantBullet(self.x, self.y, target, self.damage);
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('plantShoot').play();
};
self.shootFire = function (target) {
var fireBullet = new FireBullet(self.x, self.y, target, self.damage);
fireBullets.push(fireBullet);
game.addChild(fireBullet);
LK.getSound('plantShoot').play();
};
self.shootPeasFire = function (target) {
var peasFire = new PeasFire(self.x, self.y, target, self.damage);
peasFireBullets.push(peasFire);
game.addChild(peasFire);
LK.getSound('plantShoot').play();
};
self.produceSun = function () {
var sun = new Sun(self.x, self.y);
suns.push(sun);
game.addChild(sun);
};
self.shootFireWaves = function () {
// Shoot fire waves in both directions
var leftWave = new FireWave(self.x, self.y, 'left', self.damage);
var rightWave = new FireWave(self.x, self.y, 'right', self.damage);
fireWaves.push(leftWave);
fireWaves.push(rightWave);
game.addChild(leftWave);
game.addChild(rightWave);
LK.getSound('plantShoot').play();
};
self.shootCoconut = function (target) {
var coconut = new CoconutBullet(self.x, self.y, target, self.damage);
coconutBullets.push(coconut);
game.addChild(coconut);
LK.getSound('plantShoot').play();
};
self.shootCactusSpike = function (target) {
var cactusSpike = new CactusSpike(self.x, self.y, target, self.damage);
cactusSpikes.push(cactusSpike);
game.addChild(cactusSpike);
LK.getSound('plantShoot').play();
};
self.explode = function () {
var explosionRadius = 200;
// Damage all enemies in explosion radius
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= explosionRadius) {
var destroyed = enemy.takeDamage(1800); // Massive damage to kill most enemies
if (destroyed) {
enemy.destroy();
enemies.splice(i, 1);
resources += 10;
updateUI();
}
}
}
// Remove cherry bomb after explosion
var plantIndex = plants.indexOf(self);
if (plantIndex > -1) {
plants.splice(plantIndex, 1);
var gridX = Math.floor((self.x - 24) / 100);
var gridY = Math.floor((self.y - 800) / 100);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridY][gridX] = 0;
}
self.destroy();
}
};
return self;
});
var PlantBullet = Container.expand(function (startX, startY, target, damage) {
var self = Container.call(this);
self.graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.target = target;
self.damage = damage;
self.speed = 12;
// Calculate direction towards target
var dx = target.x - startX;
var dy = target.y - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with any enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
var destroyed = enemy.takeDamage(self.damage);
if (destroyed) {
// Remove enemy from array
var index = enemies.indexOf(enemy);
if (index > -1) {
enemy.destroy();
enemies.splice(index, 1);
resources += 10;
updateUI();
}
}
// Remove bullet
var bulletIndex = bullets.indexOf(self);
if (bulletIndex > -1) {
bullets.splice(bulletIndex, 1);
self.destroy();
}
LK.getSound('enemyHit').play();
return; // Exit after hitting first enemy
}
}
};
return self;
});
var Sun = Container.expand(function (startX, startY) {
var self = Container.call(this);
self.graphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.lifeTime = 600; // 10 seconds at 60fps
self.collected = false;
self.update = function () {
self.lifeTime--;
if (self.lifeTime <= 0 && !self.collected) {
// Remove sun after timeout
var index = suns.indexOf(self);
if (index > -1) {
suns.splice(index, 1);
self.destroy();
}
}
};
self.down = function () {
if (!self.collected) {
self.collected = true;
resources += 25;
updateUI();
// Remove sun
var index = suns.indexOf(self);
if (index > -1) {
suns.splice(index, 1);
self.destroy();
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d5016
});
/****
* Game Code
****/
// Game variables
// Grid and UI elements
// Plants
// Projectiles and effects
// Enemies
// UI buttons
// Sound effects
var gridWidth = 20;
var gridHeight = 10;
var grid = [];
var plants = [];
var enemies = [];
var bullets = [];
var moonBullets = [];
var fireBullets = [];
var peasFireBullets = [];
var cactusSpikes = [];
var coconutBullets = [];
var fireWaves = [];
var resources = 100;
var playerHealth = 100;
var waveNumber = 1;
var enemiesInWave = 5;
var enemySpawnTimer = 0;
var enemiesSpawned = 0;
var selectedPlantType = 'peashooter';
var suns = [];
var selectedCell = null;
// Multiple paths for enemies (horizontal paths from left to right)
var enemyPaths = [];
var pathRows = [2, 4, 6, 8]; // Multiple lane rows for paths
// Create paths for each lane
for (var p = 0; p < pathRows.length; p++) {
var path = [];
for (var i = 0; i <= gridWidth; i++) {
path.push({
x: i * 100 + 50,
y: 200 + pathRows[p] * 100 + 50
});
}
enemyPaths.push(path);
}
// Initialize grid
for (var y = 0; y < gridHeight; y++) {
grid[y] = [];
for (var x = 0; x < gridWidth; x++) {
grid[y][x] = 0; // 0 = empty, 1 = plant, 2 = path
}
}
// Mark path cells (horizontal lanes in multiple rows)
for (var p = 0; p < pathRows.length; p++) {
for (var i = 0; i < gridWidth; i++) {
grid[pathRows[p]][i] = 2; // path in lane rows
}
}
// Create grid visual
var gridContainer = game.addChild(new Container());
gridContainer.x = 24; // Center the grid horizontally
gridContainer.y = 800; // Move grid down to accommodate horizontal layout
for (var y = 0; y < gridHeight; y++) {
for (var x = 0; x < gridWidth; x++) {
var cell;
if (grid[y][x] === 2) {
cell = LK.getAsset('pathCell', {
anchorX: 0,
anchorY: 0
});
} else {
cell = LK.getAsset('gridCell', {
anchorX: 0,
anchorY: 0
});
}
cell.x = x * 100;
cell.y = y * 100;
gridContainer.addChild(cell);
}
}
// UI Elements
var resourceText = new Text2('Resources: ' + resources, {
size: 40,
fill: '#ffffff'
});
resourceText.anchor.set(0, 0);
LK.gui.topLeft.addChild(resourceText);
resourceText.x = 120;
resourceText.y = 20;
var healthText = new Text2('Health: ' + playerHealth, {
size: 40,
fill: '#ffffff'
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120;
healthText.y = 70;
var waveText = new Text2('Wave: ' + waveNumber, {
size: 40,
fill: '#ffffff'
});
waveText.anchor.set(0, 0);
LK.gui.topLeft.addChild(waveText);
waveText.x = 120;
waveText.y = 120;
// Plant selection buttons
var buttonContainer = new Container();
LK.gui.bottom.addChild(buttonContainer);
buttonContainer.y = -150;
var shooterButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var shooterText = new Text2('Peashooter\n$100', {
size: 24,
fill: '#ffffff'
});
shooterText.anchor.set(0.5, 0.5);
shooterButton.addChild(shooterText);
shooterButton.x = -200;
buttonContainer.addChild(shooterButton);
var blockerButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var blockerText = new Text2('Wall-nut\n$50', {
size: 24,
fill: '#ffffff'
});
blockerText.anchor.set(0.5, 0.5);
blockerButton.addChild(blockerText);
blockerButton.x = 0;
buttonContainer.addChild(blockerButton);
var supportButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var supportText = new Text2('Sunflower\n$50', {
size: 24,
fill: '#ffffff'
});
supportText.anchor.set(0.5, 0.5);
supportButton.addChild(supportText);
supportButton.x = 200;
buttonContainer.addChild(supportButton);
var bombButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var bombText = new Text2('Cherry Bomb\n$150', {
size: 24,
fill: '#ffffff'
});
bombText.anchor.set(0.5, 0.5);
bombButton.addChild(bombText);
bombButton.x = 400;
buttonContainer.addChild(bombButton);
var venusButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var venusText = new Text2('Venus Fly Trap\n$175', {
size: 20,
fill: '#ffffff'
});
venusText.anchor.set(0.5, 0.5);
venusButton.addChild(venusText);
venusButton.x = 600;
buttonContainer.addChild(venusButton);
var fireButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var fireText = new Text2('Fire Peashooter\n$125', {
size: 18,
fill: '#ffffff'
});
fireText.anchor.set(0.5, 0.5);
fireButton.addChild(fireText);
fireButton.x = 800;
buttonContainer.addChild(fireButton);
var peasFireButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var peasFireText = new Text2('Peas Fire\n$150', {
size: 18,
fill: '#ffffff'
});
peasFireText.anchor.set(0.5, 0.5);
peasFireButton.addChild(peasFireText);
peasFireButton.x = 1000;
buttonContainer.addChild(peasFireButton);
var bonkchoyButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var bonkchoyText = new Text2('Bonk Choy\n$150', {
size: 18,
fill: '#ffffff'
});
bonkchoyText.anchor.set(0.5, 0.5);
bonkchoyButton.addChild(bonkchoyText);
bonkchoyButton.x = -400;
buttonContainer.addChild(bonkchoyButton);
var coconutButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var coconutText = new Text2('Coconut Cannon\n$400', {
size: 16,
fill: '#ffffff'
});
coconutText.anchor.set(0.5, 0.5);
coconutButton.addChild(coconutText);
coconutButton.x = -1000;
buttonContainer.addChild(coconutButton);
var tallnutButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var tallnutText = new Text2('Tall-nut\n$125', {
size: 18,
fill: '#ffffff'
});
tallnutText.anchor.set(0.5, 0.5);
tallnutButton.addChild(tallnutText);
tallnutButton.x = -1200;
buttonContainer.addChild(tallnutButton);
var spikeweedButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var spikeweedText = new Text2('Spikeweed\n$100', {
size: 18,
fill: '#ffffff'
});
spikeweedText.anchor.set(0.5, 0.5);
spikeweedButton.addChild(spikeweedText);
spikeweedButton.x = -1400;
buttonContainer.addChild(spikeweedButton);
var spikerockButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var spikerockText = new Text2('Spikerock\n$200', {
size: 18,
fill: '#ffffff'
});
spikerockText.anchor.set(0.5, 0.5);
spikerockButton.addChild(spikerockText);
spikerockButton.x = -1600;
buttonContainer.addChild(spikerockButton);
var cactusButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var cactusText = new Text2('Cactus\n$125', {
size: 18,
fill: '#ffffff'
});
cactusText.anchor.set(0.5, 0.5);
cactusButton.addChild(cactusText);
cactusButton.x = -600;
buttonContainer.addChild(cactusButton);
var magnetButton = LK.getAsset('plantButton', {
anchorX: 0.5,
anchorY: 0.5
});
var magnetText = new Text2('Magnet-shroom\n$100', {
size: 16,
fill: '#ffffff'
});
magnetText.anchor.set(0.5, 0.5);
magnetButton.addChild(magnetText);
magnetButton.x = -800;
buttonContainer.addChild(magnetButton);
// Button interactions
shooterButton.down = function () {
selectedPlantType = 'peashooter';
updateButtonSelection();
};
blockerButton.down = function () {
selectedPlantType = 'wallnut';
updateButtonSelection();
};
supportButton.down = function () {
selectedPlantType = 'sunflower';
updateButtonSelection();
};
bombButton.down = function () {
selectedPlantType = 'cherrybomb';
updateButtonSelection();
};
venusButton.down = function () {
selectedPlantType = 'venusflytrap';
updateButtonSelection();
};
fireButton.down = function () {
selectedPlantType = 'firepeashooter';
updateButtonSelection();
};
peasFireButton.down = function () {
selectedPlantType = 'peasfire';
updateButtonSelection();
};
bonkchoyButton.down = function () {
selectedPlantType = 'bonkchoy';
updateButtonSelection();
};
coconutButton.down = function () {
selectedPlantType = 'coconutcannon';
updateButtonSelection();
};
tallnutButton.down = function () {
selectedPlantType = 'tallnut';
updateButtonSelection();
};
spikeweedButton.down = function () {
selectedPlantType = 'spikeweed';
updateButtonSelection();
};
spikerockButton.down = function () {
selectedPlantType = 'spikerock';
updateButtonSelection();
};
cactusButton.down = function () {
selectedPlantType = 'cactus';
updateButtonSelection();
};
magnetButton.down = function () {
selectedPlantType = 'magnetshroom';
updateButtonSelection();
};
function updateButtonSelection() {
shooterButton.tint = selectedPlantType === 'peashooter' ? 0x90ee90 : 0xffffff;
blockerButton.tint = selectedPlantType === 'wallnut' ? 0x90ee90 : 0xffffff;
supportButton.tint = selectedPlantType === 'sunflower' ? 0x90ee90 : 0xffffff;
bombButton.tint = selectedPlantType === 'cherrybomb' ? 0x90ee90 : 0xffffff;
venusButton.tint = selectedPlantType === 'venusflytrap' ? 0x90ee90 : 0xffffff;
fireButton.tint = selectedPlantType === 'firepeashooter' ? 0x90ee90 : 0xffffff;
peasFireButton.tint = selectedPlantType === 'peasfire' ? 0x90ee90 : 0xffffff;
bonkchoyButton.tint = selectedPlantType === 'bonkchoy' ? 0x90ee90 : 0xffffff;
coconutButton.tint = selectedPlantType === 'coconutcannon' ? 0x90ee90 : 0xffffff;
tallnutButton.tint = selectedPlantType === 'tallnut' ? 0x90ee90 : 0xffffff;
spikeweedButton.tint = selectedPlantType === 'spikeweed' ? 0x90ee90 : 0xffffff;
spikerockButton.tint = selectedPlantType === 'spikerock' ? 0x90ee90 : 0xffffff;
cactusButton.tint = selectedPlantType === 'cactus' ? 0x90ee90 : 0xffffff;
magnetButton.tint = selectedPlantType === 'magnetshroom' ? 0x90ee90 : 0xffffff;
}
updateButtonSelection();
function updateUI() {
resourceText.setText('Resources: ' + resources);
healthText.setText('Health: ' + playerHealth);
waveText.setText('Wave: ' + waveNumber);
}
function getPlantCost(type) {
switch (type) {
case 'peashooter':
return 100;
case 'firepeashooter':
return 125;
case 'peasfire':
return 150;
case 'wallnut':
return 50;
case 'sunflower':
return 50;
case 'cherrybomb':
return 150;
case 'venusflytrap':
return 175;
case 'bonkchoy':
return 150;
case 'coconutcannon':
return 400;
case 'tallnut':
return 125;
case 'spikeweed':
return 100;
case 'spikerock':
return 200;
case 'cactus':
return 125;
case 'magnetshroom':
return 100;
default:
return 50;
}
}
function placePlant(gridX, gridY) {
if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) return;
if (grid[gridY][gridX] !== 0) return; // cell not empty
var cost = getPlantCost(selectedPlantType);
if (resources < cost) return;
var plant = new Plant(selectedPlantType);
plant.x = 24 + gridX * 100 + 50;
plant.y = 800 + gridY * 100 + 50;
plants.push(plant);
game.addChild(plant);
grid[gridY][gridX] = 1;
resources -= cost;
updateUI();
LK.getSound('plantPlace').play();
}
function spawnEnemy() {
var enemyType = 'basic';
if (waveNumber > 3) {
var rand = Math.random();
if (rand < 0.25) enemyType = 'fast';else if (rand < 0.5) enemyType = 'tank';else if (rand < 0.7) enemyType = 'moon';
}
// Spawn enemies in any available lane
var randomLane = Math.floor(Math.random() * pathRows.length);
var enemy = new Enemy(enemyType, randomLane);
// Spawn enemy at left side of screen at random Y position
enemy.x = -50; // Start from left edge
enemy.y = 800 + Math.random() * (gridHeight * 100); // Random Y position within grid
enemies.push(enemy);
game.addChild(enemy);
}
function startNextWave() {
waveNumber++;
enemiesInWave = Math.min(5 + waveNumber * 2, 20);
enemiesSpawned = 0;
enemySpawnTimer = 0;
updateUI();
}
// Game input handling
game.down = function (x, y, obj) {
// Check if click is in grid area
if (y >= 800 && y < 800 + gridHeight * 100 && x >= 24 && x < 24 + gridWidth * 100) {
var gridX = Math.floor((x - 24) / 100);
var gridY = Math.floor((y - 800) / 100);
placePlant(gridX, gridY);
}
};
// Main game loop
game.update = function () {
// Update plants
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].update();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i]) {
bullets[i].update();
// Remove bullets that are off screen
if (bullets[i] && (bullets[i].x < -50 || bullets[i].x > 2100 || bullets[i].y < -50 || bullets[i].y > 2800)) {
bullets[i].destroy();
bullets.splice(i, 1);
}
}
}
// Update suns
for (var i = suns.length - 1; i >= 0; i--) {
suns[i].update();
}
// Update moon bullets
for (var i = moonBullets.length - 1; i >= 0; i--) {
if (moonBullets[i]) {
moonBullets[i].update();
}
}
// Update fire bullets
for (var i = fireBullets.length - 1; i >= 0; i--) {
if (fireBullets[i]) {
fireBullets[i].update();
// Remove fire bullets that are off screen
if (fireBullets[i] && (fireBullets[i].x < -50 || fireBullets[i].x > 2100 || fireBullets[i].y < -50 || fireBullets[i].y > 2800)) {
fireBullets[i].destroy();
fireBullets.splice(i, 1);
}
}
}
// Update peas fire bullets
for (var i = peasFireBullets.length - 1; i >= 0; i--) {
if (peasFireBullets[i]) {
peasFireBullets[i].update();
}
}
// Update coconut bullets
for (var i = coconutBullets.length - 1; i >= 0; i--) {
if (coconutBullets[i]) {
coconutBullets[i].update();
}
}
// Update fire waves
for (var i = fireWaves.length - 1; i >= 0; i--) {
if (fireWaves[i]) {
fireWaves[i].update();
}
}
// Update cactus spikes
for (var i = cactusSpikes.length - 1; i >= 0; i--) {
if (cactusSpikes[i]) {
cactusSpikes[i].update();
}
}
// Spawn enemies
if (enemiesSpawned < enemiesInWave) {
enemySpawnTimer++;
if (enemySpawnTimer >= 360) {
// spawn every 6 seconds
spawnEnemy();
enemiesSpawned++;
enemySpawnTimer = 0;
}
}
// Check wave completion
if (enemiesSpawned >= enemiesInWave && enemies.length === 0) {
if (waveNumber >= 10) {
LK.showYouWin();
} else {
// Bonus resources for completing wave
resources += 25;
updateUI();
// Start next wave after delay
LK.setTimeout(function () {
startNextWave();
}, 2000);
}
}
};
// Start first wave
enemySpawnTimer = 0;
enemiesSpawned = 0;
sin l suelo debajo
a sun flower whit pvz 3 whit kawaii face. In-Game asset. 2d. High contrast. No shadows
a plant vs zombies zombie. In-Game asset. 2d. High contrast. No shadows
que sea morada y que no tenga ojos
a plant vs zombies zombie whit a flash costume. In-Game asset. 2d. High contrast. No shadows
a plant vs zombies zombie whit a robot costume and a laser gun arms. In-Game asset. 2d. High contrast. No shadows
fire bullet. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
pvz1 coconut cannon. In-Game asset. 2d. High contrast. No shadows
peashoter plant vs zombies 2 but fire. In-Game asset. 2d. High contrast. No shadows
Cactus inspirado en Plants vs. Zombies. Planta verde con cuerpo segmentado cubierto de espinas rojas intensas, aspecto heroico y desafiante. Tiene una flor en la parte superior de color amarillo brillante y ojos decididos mirando hacia el frente. Postura erguida sobre césped, sin atacar, con estilo caricaturesco, líneas limpias, colores vivos y sin sombras. Fondo transparente, ideal como asset 2D para videojuego.. In-Game asset. 2d. High contrast. No shadows
Bonk Choy inspirado en Plants vs. Zombies. Planta verde musculosa con dos enormes puños de hojas brillantes, postura de combate, mirada feroz y determinada. Tallos fuertes, cuerpo compacto, expresión desafiante mirando al frente. Estilo caricaturesco, colores vivos, sin sombras, fondo transparente. Ideal como asset visual 2D para videojuego. . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
magnetoseta de plant vs zombies . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat