/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var AllStarZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); // Tint to differentiate from normal zombie zombieGraphics.tint = 0x888888; self.health = 200; self.maxHealth = 200; self.speed = -1.2; // Faster than normal zombie self.damage = 40; self.attackTimer = 0; self.attackDelay = 45; // Faster attack self.row = 0; self.attacking = false; self.update = function () { if (!self.attacking) { self.x += self.speed; } else { self.attackTimer++; if (self.attackTimer >= self.attackDelay) { self.attack(); self.attackTimer = 0; } } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('zombie_hit').play(); if (self.health <= 0) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } // No resources awarded on death self.destroy(); } }; self.attack = function () { // Find plant to attack for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) { // Skip spikeweed - zombies ignore it completely if (plant instanceof Spikeweed) { continue; } plant.health -= self.damage; LK.effects.flashObject(plant, 0xff0000, 200); if (plant.health <= 0) { plants.splice(i, 1); plant.destroy(); grid[plant.row][plant.col].occupied = false; self.attacking = false; } break; } } }; return self; }); var CherryBomb = Container.expand(function () { var self = Container.call(this); var bombGraphics = self.attachAsset('cherryBomb', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 150; self.health = 50; self.maxHealth = 50; self.fuseTimer = 0; self.fuseDelay = 180; // 3 seconds self.update = function () { self.fuseTimer++; if (self.fuseTimer >= self.fuseDelay) { self.explode(); } }; self.explode = function () { var explosion = game.addChild(LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y })); // Damage all enemies in 3x3 grid area var explosionRow = self.row; var explosionCol = self.col; for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var enemyRow = enemy.row; var enemyCol = Math.floor((enemy.x - GRID_START_X) / CELL_SIZE); // Check if enemy is within 3x3 grid area (1 cell in each direction) if (enemyRow >= explosionRow - 1 && enemyRow <= explosionRow + 1 && enemyCol >= explosionCol - 1 && enemyCol <= explosionCol + 1) { enemy.takeDamage(100); } } LK.getSound('explode').play(); // Remove explosion after animation tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Remove self for (var j = 0; j < plants.length; j++) { if (plants[j] === self) { plants.splice(j, 1); // Clear grid occupied status grid[self.row][self.col].occupied = false; break; } } self.destroy(); }; return self; }); var Chomper = Container.expand(function () { var self = Container.call(this); var chomperGraphics = self.attachAsset('chomper', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 150; self.health = 200; self.maxHealth = 200; self.row = 0; self.col = 0; self.attackTimer = 0; self.attackDelay = 1200; // 20 seconds at 60fps self.canAttack = true; self.update = function () { if (self.attackTimer > 0) { self.attackTimer--; if (self.attackTimer === 0) { self.canAttack = true; // Change back to normal color when ready (no tint) tween(chomperGraphics, { tint: 0xFFFFFF }, { duration: 200 }); } } if (self.canAttack) { // Check for enemy one square ahead var targetCol = self.col + 1; if (targetCol < GRID_COLS) { for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var enemyCol = Math.floor((enemy.x - GRID_START_X) / CELL_SIZE); if (enemy.row === self.row && enemyCol === targetCol && Math.abs(enemy.x - (GRID_START_X + targetCol * CELL_SIZE + CELL_SIZE / 2)) <= 80) { self.chomp(enemy); break; } } } } }; self.chomp = function (enemy) { // Instantly kill the enemy enemy.takeDamage(10000); // Start cooldown self.canAttack = false; self.attackTimer = self.attackDelay; // Visual feedback - change to black during cooldown tween(chomperGraphics, { tint: 0x000000 }, { duration: 200 }); LK.getSound('zombie_hit').play(); }; return self; }); var Gargantuar = Container.expand(function () { var self = Container.call(this); var gargantuarGraphics = self.attachAsset('gargantuar', { anchorX: 0.5, anchorY: 0.5 }); // Tint to differentiate from other zombies gargantuarGraphics.tint = 0x8B4513; self.health = 500; // Very high health self.maxHealth = 500; self.speed = -0.3; // Slow movement self.damage = 1000; // One-hit kill for plants self.attackTimer = 0; self.attackDelay = 90; // Slower attack self.row = 0; self.attacking = false; self.update = function () { if (!self.attacking) { self.x += self.speed; } else { self.attackTimer++; if (self.attackTimer >= self.attackDelay) { self.attack(); self.attackTimer = 0; } } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('zombie_hit').play(); if (self.health <= 0) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } }; self.attack = function () { // Find plant to attack (but ignore spikeweed) for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) { // Skip spikeweed - zombies ignore it completely if (plant instanceof Spikeweed) { continue; } // Gargantuar destroys any plant with one hit plant.health = 0; LK.effects.flashObject(plant, 0xff0000, 200); plants.splice(i, 1); plant.destroy(); grid[plant.row][plant.col].occupied = false; self.attacking = false; break; } } }; return self; }); var JackInTheBoxZombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('jackInTheBox', { anchorX: 0.5, anchorY: 0.5 }); self.health = 80; self.maxHealth = 80; self.speed = -0.8; self.damage = 25; self.attackTimer = 0; self.attackDelay = 60; self.row = 0; self.attacking = false; // Explosion timer (8-15 seconds randomly) self.explosionTimer = 0; self.explosionDelay = Math.floor(Math.random() * (900 - 480) + 480); // 8-15 seconds at 60fps self.exploded = false; self.update = function () { if (!self.exploded) { self.explosionTimer++; if (self.explosionTimer >= self.explosionDelay) { self.explode(); return; } } if (!self.attacking) { self.x += self.speed; } else { self.attackTimer++; if (self.attackTimer >= self.attackDelay) { self.attack(); self.attackTimer = 0; } } }; self.explode = function () { if (self.exploded) return; self.exploded = true; var explosion = game.addChild(LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y })); // Destroy plants in 3x3 area around explosion var explosionRow = self.row; var explosionCol = Math.floor((self.x - GRID_START_X) / CELL_SIZE); for (var row = explosionRow - 1; row <= explosionRow + 1; row++) { for (var col = explosionCol - 1; col <= explosionCol + 1; col++) { if (row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS) { if (grid[row][col].occupied) { for (var i = plants.length - 1; i >= 0; i--) { var plant = plants[i]; if (plant.row === row && plant.col === col) { plants.splice(i, 1); plant.destroy(); grid[row][col].occupied = false; break; } } } } } } LK.getSound('explode').play(); // Remove explosion after animation tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Remove self from enemies array for (var j = 0; j < enemies.length; j++) { if (enemies[j] === self) { enemies.splice(j, 1); break; } } self.destroy(); }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('zombie_hit').play(); if (self.health <= 0) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } }; self.attack = function () { // Find plant to attack (but ignore spikeweed) for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) { // Skip spikeweed - zombies ignore it completely if (plant instanceof Spikeweed) { continue; } plant.health -= self.damage; LK.effects.flashObject(plant, 0xff0000, 200); if (plant.health <= 0) { plants.splice(i, 1); plant.destroy(); grid[plant.row][plant.col].occupied = false; self.attacking = false; } break; } } }; return self; }); var Pea = Container.expand(function () { var self = Container.call(this); var peaGraphics = self.attachAsset('pea', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 20; self.update = function () { self.x += self.speed; }; return self; }); var PeaShooter = Container.expand(function () { var self = Container.call(this); var shooterGraphics = self.attachAsset('peaShooter', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 100; self.health = 100; self.maxHealth = 100; self.shootTimer = 0; self.shootDelay = 90; // 1.5 seconds at 60fps self.row = 0; self.update = function () { self.shootTimer++; if (self.shootTimer >= self.shootDelay) { // Check if there's an enemy in this row var hasEnemyInRow = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].row === self.row && enemies[i].x > self.x) { hasEnemyInRow = true; break; } } if (hasEnemyInRow) { var pea = new Pea(); pea.x = self.x + 50; pea.y = self.y; pea.row = self.row; projectiles.push(pea); game.addChild(pea); LK.getSound('shoot').play(); self.shootTimer = 0; } } }; return self; }); var Pruner = Container.expand(function () { var self = Container.call(this); var prunerGraphics = self.attachAsset('pruner', { anchorX: 0.5, anchorY: 0.5 }); self.activated = false; self.speed = 0; self.row = 0; self.update = function () { if (self.activated) { self.x += self.speed; // Check collision with enemies while moving for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.row === self.row && Math.abs(enemy.x - self.x) <= 80) { enemy.takeDamage(1000); // Instant kill } } // Remove if off screen if (self.x > 2200) { for (var j = 0; j < pruners.length; j++) { if (pruners[j] === self) { pruners.splice(j, 1); break; } } self.destroy(); } } }; self.activate = function () { if (!self.activated) { self.activated = true; self.speed = 12; tween(self, { tint: 0xff0000 }, { duration: 200 }); } }; return self; }); var Repeater = Container.expand(function () { var self = Container.call(this); var repeaterGraphics = self.attachAsset('repeater', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 200; self.health = 100; self.maxHealth = 100; self.shootTimer = 0; self.shootDelay = 90; // 1.5 seconds at 60fps self.row = 0; self.update = function () { self.shootTimer++; if (self.shootTimer >= self.shootDelay) { // Check if there's an enemy in this row var hasEnemyInRow = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i].row === self.row && enemies[i].x > self.x) { hasEnemyInRow = true; break; } } if (hasEnemyInRow) { // Shoot first pea var pea1 = new Pea(); pea1.x = self.x + 50; pea1.y = self.y; pea1.row = self.row; projectiles.push(pea1); game.addChild(pea1); // Shoot second pea slightly delayed var pea2 = new Pea(); pea2.x = self.x + 30; pea2.y = self.y; pea2.row = self.row; projectiles.push(pea2); game.addChild(pea2); LK.getSound('shoot').play(); self.shootTimer = 0; } } }; return self; }); var Skeleton = Container.expand(function () { var self = Container.call(this); var skeletonGraphics = self.attachAsset('skeleton', { anchorX: 0.5, anchorY: 0.5 }); self.health = 120; self.maxHealth = 120; self.speed = -1.0; self.damage = 20; self.attackTimer = 0; self.attackDelay = 45; // Faster attack self.row = 0; self.attacking = false; self.update = function () { if (!self.attacking) { self.x += self.speed; } else { self.attackTimer++; if (self.attackTimer >= self.attackDelay) { self.attack(); self.attackTimer = 0; } } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('zombie_hit').play(); if (self.health <= 0) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } // No resources awarded on death self.destroy(); } }; self.attack = function () { // Find plant to attack for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) { // Skip spikeweed - zombies ignore it completely if (plant instanceof Spikeweed) { continue; } plant.health -= self.damage; LK.effects.flashObject(plant, 0xff0000, 200); if (plant.health <= 0) { plants.splice(i, 1); plant.destroy(); grid[plant.row][plant.col].occupied = false; self.attacking = false; } break; } } }; return self; }); var Spikeweed = Container.expand(function () { var self = Container.call(this); var spikeweedGraphics = self.attachAsset('spikeweed', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 100; self.health = 300; self.maxHealth = 300; self.damage = 20; self.damageTimer = 0; self.damageDelay = 36; // 0.6 seconds at 60fps self.update = function () { self.damageTimer++; // Check for enemies stepping on spikeweed for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.row === self.row && Math.abs(enemy.x - self.x) <= 80 && Math.abs(enemy.y - self.y) <= 80) { if (self.damageTimer >= self.damageDelay) { enemy.takeDamage(self.damage); LK.effects.flashObject(self, 0xff0000, 200); self.damageTimer = 0; } } } }; return self; }); var Sun = Container.expand(function () { var self = Container.call(this); var sunGraphics = self.attachAsset('sun', { anchorX: 0.5, anchorY: 0.5 }); self.value = 25; self.lifeTimer = 0; self.maxLifeTime = 600; // 10 seconds self.collected = false; self.update = function () { self.lifeTimer++; if (self.lifeTimer >= self.maxLifeTime && !self.collected) { // Remove from suns array for (var i = 0; i < suns.length; i++) { if (suns[i] === self) { suns.splice(i, 1); break; } } self.destroy(); } }; self.down = function (x, y, obj) { if (!self.collected) { self.collected = true; resources += self.value; updateResourcesDisplay(); // Animate collection tween(self, { scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 300, onFinish: function onFinish() { // Remove from suns array for (var i = 0; i < suns.length; i++) { if (suns[i] === self) { suns.splice(i, 1); break; } } self.destroy(); } }); } }; return self; }); var Sunflower = Container.expand(function () { var self = Container.call(this); var sunflowerGraphics = self.attachAsset('sunflower', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 50; self.health = 100; self.maxHealth = 100; self.sunTimer = 0; self.sunDelay = 480; // 8 seconds self.update = function () { self.sunTimer++; if (self.sunTimer >= self.sunDelay) { self.produceSun(); self.sunTimer = 0; } }; self.produceSun = function () { var sun = new Sun(); sun.x = self.x + (Math.random() - 0.5) * 60; sun.y = self.y + (Math.random() - 0.5) * 60; suns.push(sun); game.addChild(sun); }; return self; }); var WallNut = Container.expand(function () { var self = Container.call(this); var nutGraphics = self.attachAsset('wallNut', { anchorX: 0.5, anchorY: 0.5 }); self.cost = 50; self.health = 200; self.maxHealth = 200; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.health = 80; self.maxHealth = 80; self.speed = -0.6; self.damage = 25; self.attackTimer = 0; self.attackDelay = 60; // 1 second self.row = 0; self.attacking = false; self.update = function () { if (!self.attacking) { self.x += self.speed; } else { self.attackTimer++; if (self.attackTimer >= self.attackDelay) { self.attack(); self.attackTimer = 0; } } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('zombie_hit').play(); if (self.health <= 0) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } // No resources awarded on death self.destroy(); } }; self.attack = function () { // Find plant to attack for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) { // Skip spikeweed - zombies ignore it completely if (plant instanceof Spikeweed) { continue; } plant.health -= self.damage; LK.effects.flashObject(plant, 0xff0000, 200); if (plant.health <= 0) { plants.splice(i, 1); plant.destroy(); grid[plant.row][plant.col].occupied = false; self.attacking = false; } break; } } }; return self; }); var Zomboni = Container.expand(function () { var self = Container.call(this); var zomboniGraphics = self.attachAsset('zomboni', { anchorX: 0.5, anchorY: 0.5 }); // Tint to differentiate from other zombies zomboniGraphics.tint = 0x444444; self.health = 300; // Triple normal zombie health self.maxHealth = 300; self.speed = -0.8; // Faster than normal zombie self.damage = 1000; // Crushes plants instantly self.attackTimer = 0; self.attackDelay = 30; // Fast crushing self.row = 0; self.attacking = false; self.update = function () { if (!self.attacking) { self.x += self.speed; } else { self.attackTimer++; if (self.attackTimer >= self.attackDelay) { self.attack(); self.attackTimer = 0; } } }; self.takeDamage = function (damage) { self.health -= damage; LK.effects.flashObject(self, 0xff0000, 200); LK.getSound('zombie_hit').play(); if (self.health <= 0) { // Remove from enemies array for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); } }; self.attack = function () { // Find plant to attack and crush it instantly for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) { // Skip spikeweed - zombies ignore it completely if (plant instanceof Spikeweed) { continue; } // Zomboni crushes any plant instantly plant.health = 0; LK.effects.flashObject(plant, 0xff0000, 200); plants.splice(i, 1); plant.destroy(); grid[plant.row][plant.col].occupied = false; self.attacking = false; break; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d5016 }); /**** * Game Code ****/ // Grid setup var GRID_ROWS = 5; var GRID_COLS = 9; var CELL_SIZE = 200; var GRID_START_X = 224; // Center the grid: (2048 - 1800) / 2 var GRID_START_Y = 700; // Game state var resources = 150; var currentWave = 1; var enemiesSpawned = 0; var enemiesPerWave = 5; var waveDelay = 300; // 5 seconds between waves var waveTimer = 0; var gameStartTimer = 0; var firstWaveStarted = false; var alertShown = false; var gameState = 'playing'; // 'playing', 'won', 'lost' var isNight = false; var nightTransitionCompleted = false; var dayTransitionCompleted = false; // Game objects var grid = []; var plants = []; var enemies = []; var projectiles = []; var suns = []; var pruners = []; var selectedPlantType = 'peaShooter'; // Plant recharge system var plantRecharges = { peaShooter: 0, wallNut: 0, sunflower: 0, repeater: 0, cherryBomb: 0, spikeweed: 0, chomper: 0 }; var plantRechargeTimes = { peaShooter: 180, // 3 seconds wallNut: 300, // 5 seconds sunflower: 240, // 4 seconds repeater: 360, // 6 seconds cherryBomb: 600, // 10 seconds spikeweed: 240, // 4 seconds chomper: 420 // 7 seconds }; // UI elements var resourcesText = new Text2('Resources: ' + resources, { size: 60, fill: 0xFFFFFF }); resourcesText.anchor.set(0, 0); resourcesText.x = 120; resourcesText.y = 50; LK.gui.topLeft.addChild(resourcesText); var waveText = new Text2('Wave: ' + currentWave, { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); waveText.x = 0; waveText.y = 50; LK.gui.top.addChild(waveText); var instructionText = new Text2('Tap grid to place plants', { size: 40, fill: 0xFFFF00 }); instructionText.anchor.set(0.5, 0); instructionText.x = 0; instructionText.y = 120; LK.gui.top.addChild(instructionText); // Shovel button at top var shovelBtn = LK.getAsset('shovel', { anchorX: 0.5, anchorY: 0.5 }); shovelBtn.x = 0; shovelBtn.y = 200; LK.gui.top.addChild(shovelBtn); // Plant selection buttons with icons and prices var peaShooterBtn = LK.getAsset('peaShooter', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); peaShooterBtn.x = 75; peaShooterBtn.y = -160; LK.gui.bottomLeft.addChild(peaShooterBtn); var peaShooterPrice = new Text2('100', { size: 30, fill: 0xFFFFFF }); peaShooterPrice.anchor.set(0.5, 0); peaShooterPrice.x = 75; peaShooterPrice.y = -160; LK.gui.bottomLeft.addChild(peaShooterPrice); var wallNutBtn = LK.getAsset('wallNut', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); wallNutBtn.x = 225; wallNutBtn.y = -160; LK.gui.bottomLeft.addChild(wallNutBtn); var wallNutPrice = new Text2('50', { size: 30, fill: 0xFFFFFF }); wallNutPrice.anchor.set(0.5, 0); wallNutPrice.x = 225; wallNutPrice.y = -160; LK.gui.bottomLeft.addChild(wallNutPrice); var sunflowerBtn = LK.getAsset('sunflower', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); sunflowerBtn.x = 375; sunflowerBtn.y = -160; LK.gui.bottomLeft.addChild(sunflowerBtn); var sunflowerPrice = new Text2('50', { size: 30, fill: 0xFFFFFF }); sunflowerPrice.anchor.set(0.5, 0); sunflowerPrice.x = 375; sunflowerPrice.y = -160; LK.gui.bottomLeft.addChild(sunflowerPrice); var repeaterBtn = LK.getAsset('repeater', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); repeaterBtn.x = 525; repeaterBtn.y = -160; LK.gui.bottomLeft.addChild(repeaterBtn); var repeaterPrice = new Text2('200', { size: 30, fill: 0xFFFFFF }); repeaterPrice.anchor.set(0.5, 0); repeaterPrice.x = 525; repeaterPrice.y = -160; LK.gui.bottomLeft.addChild(repeaterPrice); var cherryBombBtn = LK.getAsset('cherryBomb', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); cherryBombBtn.x = 675; cherryBombBtn.y = -160; LK.gui.bottomLeft.addChild(cherryBombBtn); var cherryBombPrice = new Text2('150', { size: 30, fill: 0xFFFFFF }); cherryBombPrice.anchor.set(0.5, 0); cherryBombPrice.x = 675; cherryBombPrice.y = -160; LK.gui.bottomLeft.addChild(cherryBombPrice); var spikeweedBtn = LK.getAsset('spikeweed', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); spikeweedBtn.x = 825; spikeweedBtn.y = -160; LK.gui.bottomLeft.addChild(spikeweedBtn); var spikeweedPrice = new Text2('100', { size: 30, fill: 0xFFFFFF }); spikeweedPrice.anchor.set(0.5, 0); spikeweedPrice.x = 825; spikeweedPrice.y = -160; LK.gui.bottomLeft.addChild(spikeweedPrice); var chomperBtn = LK.getAsset('chomper', { anchorX: 0.5, anchorY: 1, scaleX: 0.8, scaleY: 0.8 }); chomperBtn.x = 975; chomperBtn.y = -160; LK.gui.bottomLeft.addChild(chomperBtn); var chomperPrice = new Text2('150', { size: 30, fill: 0xFFFFFF }); chomperPrice.anchor.set(0.5, 0); chomperPrice.x = 975; chomperPrice.y = -160; LK.gui.bottomLeft.addChild(chomperPrice); // Create grid for (var row = 0; row < GRID_ROWS; row++) { grid[row] = []; for (var col = 0; col < GRID_COLS; col++) { var cell = game.addChild(LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, x: GRID_START_X + col * CELL_SIZE + CELL_SIZE / 2, y: GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2, alpha: 0.3 })); cell.row = row; cell.col = col; cell.occupied = false; grid[row][col] = cell; } } // Create pruners (one per row at the back) for (var row = 0; row < GRID_ROWS; row++) { var pruner = new Pruner(); pruner.x = GRID_START_X - 50; // Further ahead pruner.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2; pruner.row = row; pruners.push(pruner); game.addChild(pruner); } function updateResourcesDisplay() { resourcesText.setText('Resources: ' + resources); } function updateWaveDisplay() { waveText.setText('Wave: ' + currentWave); } function getPlantCost(plantType) { switch (plantType) { case 'peaShooter': return 100; case 'wallNut': return 50; case 'sunflower': return 50; case 'repeater': return 200; case 'cherryBomb': return 150; case 'spikeweed': return 100; case 'chomper': return 150; default: return 100; } } function canAffordPlant(plantType) { return resources >= getPlantCost(plantType); } function placePlant(row, col, plantType) { if (grid[row][col].occupied || !canAffordPlant(plantType) || plantRecharges[plantType] > 0) { return false; } var plant; var cost = getPlantCost(plantType); switch (plantType) { case 'peaShooter': plant = new PeaShooter(); break; case 'wallNut': plant = new WallNut(); break; case 'sunflower': plant = new Sunflower(); break; case 'repeater': plant = new Repeater(); break; case 'cherryBomb': plant = new CherryBomb(); break; case 'spikeweed': plant = new Spikeweed(); break; case 'chomper': plant = new Chomper(); break; default: return false; } plant.x = GRID_START_X + col * CELL_SIZE + CELL_SIZE / 2; plant.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2; plant.row = row; plant.col = col; plants.push(plant); game.addChild(plant); grid[row][col].occupied = true; resources -= cost; plantRecharges[plantType] = plantRechargeTimes[plantType]; updateResourcesDisplay(); LK.getSound('plant').play(); return true; } function removePlant(row, col) { if (!grid[row][col].occupied) { return false; } // Find and remove plant from plants array for (var i = 0; i < plants.length; i++) { var plant = plants[i]; if (plant.row === row && plant.col === col) { plants.splice(i, 1); plant.destroy(); grid[row][col].occupied = false; return true; } } return false; } function spawnEnemy(row) { var enemy; var rand = Math.random(); var spawnMultiplier = isNight ? 1.5 : 1; // More enemies at night var waveMultiplier = Math.min(1 + (currentWave - 1) * 0.15, 2.5); // Increase spawn chance more aggressively with waves if (currentWave >= 5 && rand < 0.12 * waveMultiplier) { enemy = new Gargantuar(); } else if (currentWave >= 4 && rand < 0.18 * waveMultiplier) { enemy = new Zomboni(); } else if (currentWave >= 3 && rand < 0.25 * waveMultiplier) { enemy = new JackInTheBoxZombie(); } else if (currentWave >= 2 && rand < 0.35 * waveMultiplier) { enemy = new AllStarZombie(); } else if (currentWave >= 3 && rand < 0.6 * waveMultiplier) { enemy = new Skeleton(); } else { enemy = new Zombie(); } enemy.x = 2000; enemy.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2; enemy.row = row; enemiesSpawned++; enemies.push(enemy); game.addChild(enemy); // Spawn additional enemies at night and more as waves progress from wave 3 var extraSpawnChance = isNight ? 0.4 : 0.2; if (currentWave >= 3) { extraSpawnChance += (currentWave - 1) * 0.08; // More aggressive increase with waves } if (Math.random() < extraSpawnChance) { var extraEnemy = new Zombie(); extraEnemy.x = 2050; extraEnemy.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2; extraEnemy.row = row; enemies.push(extraEnemy); game.addChild(extraEnemy); } } function checkWaveComplete() { if (enemiesSpawned >= enemiesPerWave && enemies.length === 0) { currentWave++; updateWaveDisplay(); if (currentWave > 10) { gameState = 'won'; LK.showYouWin(); return; } enemiesSpawned = 0; enemiesPerWave = Math.min(5 + currentWave * 4, 30); waveTimer = 0; } } function checkGameOver() { for (var i = 0; i < enemies.length; i++) { if (enemies[i].x <= GRID_START_X - 50) { gameState = 'lost'; LK.showGameOver(); return; } } } // Button event handlers shovelBtn.down = function (x, y, obj) { selectedPlantType = 'shovel'; instructionText.setText('Selected: Shovel - Click plants to remove'); }; peaShooterBtn.down = function (x, y, obj) { if (plantRecharges.peaShooter === 0) { selectedPlantType = 'peaShooter'; instructionText.setText('Selected: Pea Shooter (100)'); } else { instructionText.setText('Pea Shooter recharging: ' + Math.ceil(plantRecharges.peaShooter / 60) + 's'); } }; wallNutBtn.down = function (x, y, obj) { if (plantRecharges.wallNut === 0) { selectedPlantType = 'wallNut'; instructionText.setText('Selected: Wall Nut (50)'); } else { instructionText.setText('Wall Nut recharging: ' + Math.ceil(plantRecharges.wallNut / 60) + 's'); } }; sunflowerBtn.down = function (x, y, obj) { if (plantRecharges.sunflower === 0) { selectedPlantType = 'sunflower'; instructionText.setText('Selected: Sunflower (50)'); } else { instructionText.setText('Sunflower recharging: ' + Math.ceil(plantRecharges.sunflower / 60) + 's'); } }; repeaterBtn.down = function (x, y, obj) { if (plantRecharges.repeater === 0) { selectedPlantType = 'repeater'; instructionText.setText('Selected: Repeater (200)'); } else { instructionText.setText('Repeater recharging: ' + Math.ceil(plantRecharges.repeater / 60) + 's'); } }; cherryBombBtn.down = function (x, y, obj) { if (plantRecharges.cherryBomb === 0) { selectedPlantType = 'cherryBomb'; instructionText.setText('Selected: Cherry Bomb (150)'); } else { instructionText.setText('Cherry Bomb recharging: ' + Math.ceil(plantRecharges.cherryBomb / 60) + 's'); } }; spikeweedBtn.down = function (x, y, obj) { if (plantRecharges.spikeweed === 0) { selectedPlantType = 'spikeweed'; instructionText.setText('Selected: Spikeweed (100)'); } else { instructionText.setText('Spikeweed recharging: ' + Math.ceil(plantRecharges.spikeweed / 60) + 's'); } }; chomperBtn.down = function (x, y, obj) { if (plantRecharges.chomper === 0) { selectedPlantType = 'chomper'; instructionText.setText('Selected: Chomper (150)'); } else { instructionText.setText('Chomper recharging: ' + Math.ceil(plantRecharges.chomper / 60) + 's'); } }; // Grid click handler game.down = function (x, y, obj) { if (gameState !== 'playing') return; // Check if click is within grid bounds if (x >= GRID_START_X && x <= GRID_START_X + GRID_COLS * CELL_SIZE && y >= GRID_START_Y && y <= GRID_START_Y + GRID_ROWS * CELL_SIZE) { var col = Math.floor((x - GRID_START_X) / CELL_SIZE); var row = Math.floor((y - GRID_START_Y) / CELL_SIZE); if (row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS) { if (selectedPlantType === 'shovel') { removePlant(row, col); } else { placePlant(row, col, selectedPlantType); } } } }; // Main game loop game.update = function () { if (gameState !== 'playing') return; // Handle day/night cycle if (currentWave === 6 && !nightTransitionCompleted) { // Transition to night after wave 5 isNight = true; nightTransitionCompleted = true; game.setBackgroundColor(0x001122); // Flash screen to indicate night LK.effects.flashScreen(0x000044, 1000); } else if (currentWave === 11 && !dayTransitionCompleted) { // Transition back to day after wave 10 isNight = false; dayTransitionCompleted = true; game.setBackgroundColor(0x2d5016); // Flash screen to indicate day LK.effects.flashScreen(0x88ff44, 1000); } // Keep background color consistent during night/day periods if (isNight && currentWave >= 6 && currentWave < 11) { game.setBackgroundColor(0x001122); } else if (!isNight && (currentWave < 6 || currentWave >= 11)) { game.setBackgroundColor(0x2d5016); } // Handle game start delay if (!firstWaveStarted) { gameStartTimer++; if (gameStartTimer >= 1500 && !alertShown) { // 25 seconds alertShown = true; LK.effects.flashScreen(0xff0000, 2000); // Create alert text var alertText = new Text2('ZOMBIES ARE COMING!', { size: 80, fill: 0xff0000 }); alertText.anchor.set(0.5, 0.5); alertText.x = 0; alertText.y = 0; LK.gui.center.addChild(alertText); // Animate alert text tween(alertText, { scaleX: 1.5, scaleY: 1.5 }, { duration: 500 }); tween(alertText, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { alertText.destroy(); firstWaveStarted = true; } }); } } // Spawn enemies if (firstWaveStarted) { waveTimer++; if (enemiesSpawned < enemiesPerWave && waveTimer % 120 === 0) { // Every 2 seconds var randomRow = Math.floor(Math.random() * GRID_ROWS); spawnEnemy(randomRow); } } // Update projectiles for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; // Check collision with enemies var hit = false; for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; if (projectile.row === enemy.row && projectile.x >= enemy.x - 35 && projectile.x <= enemy.x + 35) { enemy.takeDamage(projectile.damage); hit = true; break; } } // Remove projectile if hit or off screen if (hit || projectile.x > 2100) { projectiles.splice(i, 1); projectile.destroy(); } } // Check enemy-plant collisions for (var k = 0; k < enemies.length; k++) { var enemy = enemies[k]; enemy.attacking = false; for (var l = 0; l < plants.length; l++) { var plant = plants[l]; if (enemy.row === plant.row && enemy.x >= plant.x - 50 && enemy.x <= plant.x + 50) { // Skip spikeweed - zombies ignore it completely and don't stop for it if (plant instanceof Spikeweed) { continue; } enemy.attacking = true; break; } } } // Update plant recharges for (var plantType in plantRecharges) { if (plantRecharges[plantType] > 0) { plantRecharges[plantType]--; } } // Check pruner activation for (var m = 0; m < pruners.length; m++) { var pruner = pruners[m]; if (!pruner.activated) { for (var n = 0; n < enemies.length; n++) { var enemy = enemies[n]; if (enemy.row === pruner.row && Math.abs(enemy.x - pruner.x) <= 80) { pruner.activate(); break; } } } } checkWaveComplete(); checkGameOver(); }; // Play background music LK.playMusic('Grasswalk');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AllStarZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint to differentiate from normal zombie
zombieGraphics.tint = 0x888888;
self.health = 200;
self.maxHealth = 200;
self.speed = -1.2; // Faster than normal zombie
self.damage = 40;
self.attackTimer = 0;
self.attackDelay = 45; // Faster attack
self.row = 0;
self.attacking = false;
self.update = function () {
if (!self.attacking) {
self.x += self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
self.attack();
self.attackTimer = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
// No resources awarded on death
self.destroy();
}
};
self.attack = function () {
// Find plant to attack
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) {
// Skip spikeweed - zombies ignore it completely
if (plant instanceof Spikeweed) {
continue;
}
plant.health -= self.damage;
LK.effects.flashObject(plant, 0xff0000, 200);
if (plant.health <= 0) {
plants.splice(i, 1);
plant.destroy();
grid[plant.row][plant.col].occupied = false;
self.attacking = false;
}
break;
}
}
};
return self;
});
var CherryBomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('cherryBomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.health = 50;
self.maxHealth = 50;
self.fuseTimer = 0;
self.fuseDelay = 180; // 3 seconds
self.update = function () {
self.fuseTimer++;
if (self.fuseTimer >= self.fuseDelay) {
self.explode();
}
};
self.explode = function () {
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
}));
// Damage all enemies in 3x3 grid area
var explosionRow = self.row;
var explosionCol = self.col;
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var enemyRow = enemy.row;
var enemyCol = Math.floor((enemy.x - GRID_START_X) / CELL_SIZE);
// Check if enemy is within 3x3 grid area (1 cell in each direction)
if (enemyRow >= explosionRow - 1 && enemyRow <= explosionRow + 1 && enemyCol >= explosionCol - 1 && enemyCol <= explosionCol + 1) {
enemy.takeDamage(100);
}
}
LK.getSound('explode').play();
// Remove explosion after animation
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Remove self
for (var j = 0; j < plants.length; j++) {
if (plants[j] === self) {
plants.splice(j, 1);
// Clear grid occupied status
grid[self.row][self.col].occupied = false;
break;
}
}
self.destroy();
};
return self;
});
var Chomper = Container.expand(function () {
var self = Container.call(this);
var chomperGraphics = self.attachAsset('chomper', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.health = 200;
self.maxHealth = 200;
self.row = 0;
self.col = 0;
self.attackTimer = 0;
self.attackDelay = 1200; // 20 seconds at 60fps
self.canAttack = true;
self.update = function () {
if (self.attackTimer > 0) {
self.attackTimer--;
if (self.attackTimer === 0) {
self.canAttack = true;
// Change back to normal color when ready (no tint)
tween(chomperGraphics, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
}
if (self.canAttack) {
// Check for enemy one square ahead
var targetCol = self.col + 1;
if (targetCol < GRID_COLS) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyCol = Math.floor((enemy.x - GRID_START_X) / CELL_SIZE);
if (enemy.row === self.row && enemyCol === targetCol && Math.abs(enemy.x - (GRID_START_X + targetCol * CELL_SIZE + CELL_SIZE / 2)) <= 80) {
self.chomp(enemy);
break;
}
}
}
}
};
self.chomp = function (enemy) {
// Instantly kill the enemy
enemy.takeDamage(10000);
// Start cooldown
self.canAttack = false;
self.attackTimer = self.attackDelay;
// Visual feedback - change to black during cooldown
tween(chomperGraphics, {
tint: 0x000000
}, {
duration: 200
});
LK.getSound('zombie_hit').play();
};
return self;
});
var Gargantuar = Container.expand(function () {
var self = Container.call(this);
var gargantuarGraphics = self.attachAsset('gargantuar', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint to differentiate from other zombies
gargantuarGraphics.tint = 0x8B4513;
self.health = 500; // Very high health
self.maxHealth = 500;
self.speed = -0.3; // Slow movement
self.damage = 1000; // One-hit kill for plants
self.attackTimer = 0;
self.attackDelay = 90; // Slower attack
self.row = 0;
self.attacking = false;
self.update = function () {
if (!self.attacking) {
self.x += self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
self.attack();
self.attackTimer = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.attack = function () {
// Find plant to attack (but ignore spikeweed)
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) {
// Skip spikeweed - zombies ignore it completely
if (plant instanceof Spikeweed) {
continue;
}
// Gargantuar destroys any plant with one hit
plant.health = 0;
LK.effects.flashObject(plant, 0xff0000, 200);
plants.splice(i, 1);
plant.destroy();
grid[plant.row][plant.col].occupied = false;
self.attacking = false;
break;
}
}
};
return self;
});
var JackInTheBoxZombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('jackInTheBox', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 80;
self.maxHealth = 80;
self.speed = -0.8;
self.damage = 25;
self.attackTimer = 0;
self.attackDelay = 60;
self.row = 0;
self.attacking = false;
// Explosion timer (8-15 seconds randomly)
self.explosionTimer = 0;
self.explosionDelay = Math.floor(Math.random() * (900 - 480) + 480); // 8-15 seconds at 60fps
self.exploded = false;
self.update = function () {
if (!self.exploded) {
self.explosionTimer++;
if (self.explosionTimer >= self.explosionDelay) {
self.explode();
return;
}
}
if (!self.attacking) {
self.x += self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
self.attack();
self.attackTimer = 0;
}
}
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
var explosion = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y
}));
// Destroy plants in 3x3 area around explosion
var explosionRow = self.row;
var explosionCol = Math.floor((self.x - GRID_START_X) / CELL_SIZE);
for (var row = explosionRow - 1; row <= explosionRow + 1; row++) {
for (var col = explosionCol - 1; col <= explosionCol + 1; col++) {
if (row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS) {
if (grid[row][col].occupied) {
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.row === row && plant.col === col) {
plants.splice(i, 1);
plant.destroy();
grid[row][col].occupied = false;
break;
}
}
}
}
}
}
LK.getSound('explode').play();
// Remove explosion after animation
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
explosion.destroy();
}
});
// Remove self from enemies array
for (var j = 0; j < enemies.length; j++) {
if (enemies[j] === self) {
enemies.splice(j, 1);
break;
}
}
self.destroy();
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.attack = function () {
// Find plant to attack (but ignore spikeweed)
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) {
// Skip spikeweed - zombies ignore it completely
if (plant instanceof Spikeweed) {
continue;
}
plant.health -= self.damage;
LK.effects.flashObject(plant, 0xff0000, 200);
if (plant.health <= 0) {
plants.splice(i, 1);
plant.destroy();
grid[plant.row][plant.col].occupied = false;
self.attacking = false;
}
break;
}
}
};
return self;
});
var Pea = Container.expand(function () {
var self = Container.call(this);
var peaGraphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 20;
self.update = function () {
self.x += self.speed;
};
return self;
});
var PeaShooter = Container.expand(function () {
var self = Container.call(this);
var shooterGraphics = self.attachAsset('peaShooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 100;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 90; // 1.5 seconds at 60fps
self.row = 0;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's an enemy in this row
var hasEnemyInRow = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].row === self.row && enemies[i].x > self.x) {
hasEnemyInRow = true;
break;
}
}
if (hasEnemyInRow) {
var pea = new Pea();
pea.x = self.x + 50;
pea.y = self.y;
pea.row = self.row;
projectiles.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
self.shootTimer = 0;
}
}
};
return self;
});
var Pruner = Container.expand(function () {
var self = Container.call(this);
var prunerGraphics = self.attachAsset('pruner', {
anchorX: 0.5,
anchorY: 0.5
});
self.activated = false;
self.speed = 0;
self.row = 0;
self.update = function () {
if (self.activated) {
self.x += self.speed;
// Check collision with enemies while moving
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.row === self.row && Math.abs(enemy.x - self.x) <= 80) {
enemy.takeDamage(1000); // Instant kill
}
}
// Remove if off screen
if (self.x > 2200) {
for (var j = 0; j < pruners.length; j++) {
if (pruners[j] === self) {
pruners.splice(j, 1);
break;
}
}
self.destroy();
}
}
};
self.activate = function () {
if (!self.activated) {
self.activated = true;
self.speed = 12;
tween(self, {
tint: 0xff0000
}, {
duration: 200
});
}
};
return self;
});
var Repeater = Container.expand(function () {
var self = Container.call(this);
var repeaterGraphics = self.attachAsset('repeater', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 200;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 90; // 1.5 seconds at 60fps
self.row = 0;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's an enemy in this row
var hasEnemyInRow = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].row === self.row && enemies[i].x > self.x) {
hasEnemyInRow = true;
break;
}
}
if (hasEnemyInRow) {
// Shoot first pea
var pea1 = new Pea();
pea1.x = self.x + 50;
pea1.y = self.y;
pea1.row = self.row;
projectiles.push(pea1);
game.addChild(pea1);
// Shoot second pea slightly delayed
var pea2 = new Pea();
pea2.x = self.x + 30;
pea2.y = self.y;
pea2.row = self.row;
projectiles.push(pea2);
game.addChild(pea2);
LK.getSound('shoot').play();
self.shootTimer = 0;
}
}
};
return self;
});
var Skeleton = Container.expand(function () {
var self = Container.call(this);
var skeletonGraphics = self.attachAsset('skeleton', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 120;
self.maxHealth = 120;
self.speed = -1.0;
self.damage = 20;
self.attackTimer = 0;
self.attackDelay = 45; // Faster attack
self.row = 0;
self.attacking = false;
self.update = function () {
if (!self.attacking) {
self.x += self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
self.attack();
self.attackTimer = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
// No resources awarded on death
self.destroy();
}
};
self.attack = function () {
// Find plant to attack
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) {
// Skip spikeweed - zombies ignore it completely
if (plant instanceof Spikeweed) {
continue;
}
plant.health -= self.damage;
LK.effects.flashObject(plant, 0xff0000, 200);
if (plant.health <= 0) {
plants.splice(i, 1);
plant.destroy();
grid[plant.row][plant.col].occupied = false;
self.attacking = false;
}
break;
}
}
};
return self;
});
var Spikeweed = Container.expand(function () {
var self = Container.call(this);
var spikeweedGraphics = self.attachAsset('spikeweed', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 100;
self.health = 300;
self.maxHealth = 300;
self.damage = 20;
self.damageTimer = 0;
self.damageDelay = 36; // 0.6 seconds at 60fps
self.update = function () {
self.damageTimer++;
// Check for enemies stepping on spikeweed
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.row === self.row && Math.abs(enemy.x - self.x) <= 80 && Math.abs(enemy.y - self.y) <= 80) {
if (self.damageTimer >= self.damageDelay) {
enemy.takeDamage(self.damage);
LK.effects.flashObject(self, 0xff0000, 200);
self.damageTimer = 0;
}
}
}
};
return self;
});
var Sun = Container.expand(function () {
var self = Container.call(this);
var sunGraphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 25;
self.lifeTimer = 0;
self.maxLifeTime = 600; // 10 seconds
self.collected = false;
self.update = function () {
self.lifeTimer++;
if (self.lifeTimer >= self.maxLifeTime && !self.collected) {
// Remove from suns array
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.down = function (x, y, obj) {
if (!self.collected) {
self.collected = true;
resources += self.value;
updateResourcesDisplay();
// Animate collection
tween(self, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
// Remove from suns array
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
self.destroy();
}
});
}
};
return self;
});
var Sunflower = Container.expand(function () {
var self = Container.call(this);
var sunflowerGraphics = self.attachAsset('sunflower', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 100;
self.maxHealth = 100;
self.sunTimer = 0;
self.sunDelay = 480; // 8 seconds
self.update = function () {
self.sunTimer++;
if (self.sunTimer >= self.sunDelay) {
self.produceSun();
self.sunTimer = 0;
}
};
self.produceSun = function () {
var sun = new Sun();
sun.x = self.x + (Math.random() - 0.5) * 60;
sun.y = self.y + (Math.random() - 0.5) * 60;
suns.push(sun);
game.addChild(sun);
};
return self;
});
var WallNut = Container.expand(function () {
var self = Container.call(this);
var nutGraphics = self.attachAsset('wallNut', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 200;
self.maxHealth = 200;
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 80;
self.maxHealth = 80;
self.speed = -0.6;
self.damage = 25;
self.attackTimer = 0;
self.attackDelay = 60; // 1 second
self.row = 0;
self.attacking = false;
self.update = function () {
if (!self.attacking) {
self.x += self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
self.attack();
self.attackTimer = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
// No resources awarded on death
self.destroy();
}
};
self.attack = function () {
// Find plant to attack
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) {
// Skip spikeweed - zombies ignore it completely
if (plant instanceof Spikeweed) {
continue;
}
plant.health -= self.damage;
LK.effects.flashObject(plant, 0xff0000, 200);
if (plant.health <= 0) {
plants.splice(i, 1);
plant.destroy();
grid[plant.row][plant.col].occupied = false;
self.attacking = false;
}
break;
}
}
};
return self;
});
var Zomboni = Container.expand(function () {
var self = Container.call(this);
var zomboniGraphics = self.attachAsset('zomboni', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint to differentiate from other zombies
zomboniGraphics.tint = 0x444444;
self.health = 300; // Triple normal zombie health
self.maxHealth = 300;
self.speed = -0.8; // Faster than normal zombie
self.damage = 1000; // Crushes plants instantly
self.attackTimer = 0;
self.attackDelay = 30; // Fast crushing
self.row = 0;
self.attacking = false;
self.update = function () {
if (!self.attacking) {
self.x += self.speed;
} else {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
self.attack();
self.attackTimer = 0;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
// Remove from enemies array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.attack = function () {
// Find plant to attack and crush it instantly
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === self.row && Math.abs(plant.x - self.x) <= 50) {
// Skip spikeweed - zombies ignore it completely
if (plant instanceof Spikeweed) {
continue;
}
// Zomboni crushes any plant instantly
plant.health = 0;
LK.effects.flashObject(plant, 0xff0000, 200);
plants.splice(i, 1);
plant.destroy();
grid[plant.row][plant.col].occupied = false;
self.attacking = false;
break;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d5016
});
/****
* Game Code
****/
// Grid setup
var GRID_ROWS = 5;
var GRID_COLS = 9;
var CELL_SIZE = 200;
var GRID_START_X = 224; // Center the grid: (2048 - 1800) / 2
var GRID_START_Y = 700;
// Game state
var resources = 150;
var currentWave = 1;
var enemiesSpawned = 0;
var enemiesPerWave = 5;
var waveDelay = 300; // 5 seconds between waves
var waveTimer = 0;
var gameStartTimer = 0;
var firstWaveStarted = false;
var alertShown = false;
var gameState = 'playing'; // 'playing', 'won', 'lost'
var isNight = false;
var nightTransitionCompleted = false;
var dayTransitionCompleted = false;
// Game objects
var grid = [];
var plants = [];
var enemies = [];
var projectiles = [];
var suns = [];
var pruners = [];
var selectedPlantType = 'peaShooter';
// Plant recharge system
var plantRecharges = {
peaShooter: 0,
wallNut: 0,
sunflower: 0,
repeater: 0,
cherryBomb: 0,
spikeweed: 0,
chomper: 0
};
var plantRechargeTimes = {
peaShooter: 180,
// 3 seconds
wallNut: 300,
// 5 seconds
sunflower: 240,
// 4 seconds
repeater: 360,
// 6 seconds
cherryBomb: 600,
// 10 seconds
spikeweed: 240,
// 4 seconds
chomper: 420 // 7 seconds
};
// UI elements
var resourcesText = new Text2('Resources: ' + resources, {
size: 60,
fill: 0xFFFFFF
});
resourcesText.anchor.set(0, 0);
resourcesText.x = 120;
resourcesText.y = 50;
LK.gui.topLeft.addChild(resourcesText);
var waveText = new Text2('Wave: ' + currentWave, {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.x = 0;
waveText.y = 50;
LK.gui.top.addChild(waveText);
var instructionText = new Text2('Tap grid to place plants', {
size: 40,
fill: 0xFFFF00
});
instructionText.anchor.set(0.5, 0);
instructionText.x = 0;
instructionText.y = 120;
LK.gui.top.addChild(instructionText);
// Shovel button at top
var shovelBtn = LK.getAsset('shovel', {
anchorX: 0.5,
anchorY: 0.5
});
shovelBtn.x = 0;
shovelBtn.y = 200;
LK.gui.top.addChild(shovelBtn);
// Plant selection buttons with icons and prices
var peaShooterBtn = LK.getAsset('peaShooter', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
peaShooterBtn.x = 75;
peaShooterBtn.y = -160;
LK.gui.bottomLeft.addChild(peaShooterBtn);
var peaShooterPrice = new Text2('100', {
size: 30,
fill: 0xFFFFFF
});
peaShooterPrice.anchor.set(0.5, 0);
peaShooterPrice.x = 75;
peaShooterPrice.y = -160;
LK.gui.bottomLeft.addChild(peaShooterPrice);
var wallNutBtn = LK.getAsset('wallNut', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
wallNutBtn.x = 225;
wallNutBtn.y = -160;
LK.gui.bottomLeft.addChild(wallNutBtn);
var wallNutPrice = new Text2('50', {
size: 30,
fill: 0xFFFFFF
});
wallNutPrice.anchor.set(0.5, 0);
wallNutPrice.x = 225;
wallNutPrice.y = -160;
LK.gui.bottomLeft.addChild(wallNutPrice);
var sunflowerBtn = LK.getAsset('sunflower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
sunflowerBtn.x = 375;
sunflowerBtn.y = -160;
LK.gui.bottomLeft.addChild(sunflowerBtn);
var sunflowerPrice = new Text2('50', {
size: 30,
fill: 0xFFFFFF
});
sunflowerPrice.anchor.set(0.5, 0);
sunflowerPrice.x = 375;
sunflowerPrice.y = -160;
LK.gui.bottomLeft.addChild(sunflowerPrice);
var repeaterBtn = LK.getAsset('repeater', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
repeaterBtn.x = 525;
repeaterBtn.y = -160;
LK.gui.bottomLeft.addChild(repeaterBtn);
var repeaterPrice = new Text2('200', {
size: 30,
fill: 0xFFFFFF
});
repeaterPrice.anchor.set(0.5, 0);
repeaterPrice.x = 525;
repeaterPrice.y = -160;
LK.gui.bottomLeft.addChild(repeaterPrice);
var cherryBombBtn = LK.getAsset('cherryBomb', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
cherryBombBtn.x = 675;
cherryBombBtn.y = -160;
LK.gui.bottomLeft.addChild(cherryBombBtn);
var cherryBombPrice = new Text2('150', {
size: 30,
fill: 0xFFFFFF
});
cherryBombPrice.anchor.set(0.5, 0);
cherryBombPrice.x = 675;
cherryBombPrice.y = -160;
LK.gui.bottomLeft.addChild(cherryBombPrice);
var spikeweedBtn = LK.getAsset('spikeweed', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
spikeweedBtn.x = 825;
spikeweedBtn.y = -160;
LK.gui.bottomLeft.addChild(spikeweedBtn);
var spikeweedPrice = new Text2('100', {
size: 30,
fill: 0xFFFFFF
});
spikeweedPrice.anchor.set(0.5, 0);
spikeweedPrice.x = 825;
spikeweedPrice.y = -160;
LK.gui.bottomLeft.addChild(spikeweedPrice);
var chomperBtn = LK.getAsset('chomper', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
chomperBtn.x = 975;
chomperBtn.y = -160;
LK.gui.bottomLeft.addChild(chomperBtn);
var chomperPrice = new Text2('150', {
size: 30,
fill: 0xFFFFFF
});
chomperPrice.anchor.set(0.5, 0);
chomperPrice.x = 975;
chomperPrice.y = -160;
LK.gui.bottomLeft.addChild(chomperPrice);
// Create grid
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
var cell = game.addChild(LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
x: GRID_START_X + col * CELL_SIZE + CELL_SIZE / 2,
y: GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2,
alpha: 0.3
}));
cell.row = row;
cell.col = col;
cell.occupied = false;
grid[row][col] = cell;
}
}
// Create pruners (one per row at the back)
for (var row = 0; row < GRID_ROWS; row++) {
var pruner = new Pruner();
pruner.x = GRID_START_X - 50; // Further ahead
pruner.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2;
pruner.row = row;
pruners.push(pruner);
game.addChild(pruner);
}
function updateResourcesDisplay() {
resourcesText.setText('Resources: ' + resources);
}
function updateWaveDisplay() {
waveText.setText('Wave: ' + currentWave);
}
function getPlantCost(plantType) {
switch (plantType) {
case 'peaShooter':
return 100;
case 'wallNut':
return 50;
case 'sunflower':
return 50;
case 'repeater':
return 200;
case 'cherryBomb':
return 150;
case 'spikeweed':
return 100;
case 'chomper':
return 150;
default:
return 100;
}
}
function canAffordPlant(plantType) {
return resources >= getPlantCost(plantType);
}
function placePlant(row, col, plantType) {
if (grid[row][col].occupied || !canAffordPlant(plantType) || plantRecharges[plantType] > 0) {
return false;
}
var plant;
var cost = getPlantCost(plantType);
switch (plantType) {
case 'peaShooter':
plant = new PeaShooter();
break;
case 'wallNut':
plant = new WallNut();
break;
case 'sunflower':
plant = new Sunflower();
break;
case 'repeater':
plant = new Repeater();
break;
case 'cherryBomb':
plant = new CherryBomb();
break;
case 'spikeweed':
plant = new Spikeweed();
break;
case 'chomper':
plant = new Chomper();
break;
default:
return false;
}
plant.x = GRID_START_X + col * CELL_SIZE + CELL_SIZE / 2;
plant.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2;
plant.row = row;
plant.col = col;
plants.push(plant);
game.addChild(plant);
grid[row][col].occupied = true;
resources -= cost;
plantRecharges[plantType] = plantRechargeTimes[plantType];
updateResourcesDisplay();
LK.getSound('plant').play();
return true;
}
function removePlant(row, col) {
if (!grid[row][col].occupied) {
return false;
}
// Find and remove plant from plants array
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.row === row && plant.col === col) {
plants.splice(i, 1);
plant.destroy();
grid[row][col].occupied = false;
return true;
}
}
return false;
}
function spawnEnemy(row) {
var enemy;
var rand = Math.random();
var spawnMultiplier = isNight ? 1.5 : 1; // More enemies at night
var waveMultiplier = Math.min(1 + (currentWave - 1) * 0.15, 2.5); // Increase spawn chance more aggressively with waves
if (currentWave >= 5 && rand < 0.12 * waveMultiplier) {
enemy = new Gargantuar();
} else if (currentWave >= 4 && rand < 0.18 * waveMultiplier) {
enemy = new Zomboni();
} else if (currentWave >= 3 && rand < 0.25 * waveMultiplier) {
enemy = new JackInTheBoxZombie();
} else if (currentWave >= 2 && rand < 0.35 * waveMultiplier) {
enemy = new AllStarZombie();
} else if (currentWave >= 3 && rand < 0.6 * waveMultiplier) {
enemy = new Skeleton();
} else {
enemy = new Zombie();
}
enemy.x = 2000;
enemy.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2;
enemy.row = row;
enemiesSpawned++;
enemies.push(enemy);
game.addChild(enemy);
// Spawn additional enemies at night and more as waves progress from wave 3
var extraSpawnChance = isNight ? 0.4 : 0.2;
if (currentWave >= 3) {
extraSpawnChance += (currentWave - 1) * 0.08; // More aggressive increase with waves
}
if (Math.random() < extraSpawnChance) {
var extraEnemy = new Zombie();
extraEnemy.x = 2050;
extraEnemy.y = GRID_START_Y + row * CELL_SIZE + CELL_SIZE / 2;
extraEnemy.row = row;
enemies.push(extraEnemy);
game.addChild(extraEnemy);
}
}
function checkWaveComplete() {
if (enemiesSpawned >= enemiesPerWave && enemies.length === 0) {
currentWave++;
updateWaveDisplay();
if (currentWave > 10) {
gameState = 'won';
LK.showYouWin();
return;
}
enemiesSpawned = 0;
enemiesPerWave = Math.min(5 + currentWave * 4, 30);
waveTimer = 0;
}
}
function checkGameOver() {
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].x <= GRID_START_X - 50) {
gameState = 'lost';
LK.showGameOver();
return;
}
}
}
// Button event handlers
shovelBtn.down = function (x, y, obj) {
selectedPlantType = 'shovel';
instructionText.setText('Selected: Shovel - Click plants to remove');
};
peaShooterBtn.down = function (x, y, obj) {
if (plantRecharges.peaShooter === 0) {
selectedPlantType = 'peaShooter';
instructionText.setText('Selected: Pea Shooter (100)');
} else {
instructionText.setText('Pea Shooter recharging: ' + Math.ceil(plantRecharges.peaShooter / 60) + 's');
}
};
wallNutBtn.down = function (x, y, obj) {
if (plantRecharges.wallNut === 0) {
selectedPlantType = 'wallNut';
instructionText.setText('Selected: Wall Nut (50)');
} else {
instructionText.setText('Wall Nut recharging: ' + Math.ceil(plantRecharges.wallNut / 60) + 's');
}
};
sunflowerBtn.down = function (x, y, obj) {
if (plantRecharges.sunflower === 0) {
selectedPlantType = 'sunflower';
instructionText.setText('Selected: Sunflower (50)');
} else {
instructionText.setText('Sunflower recharging: ' + Math.ceil(plantRecharges.sunflower / 60) + 's');
}
};
repeaterBtn.down = function (x, y, obj) {
if (plantRecharges.repeater === 0) {
selectedPlantType = 'repeater';
instructionText.setText('Selected: Repeater (200)');
} else {
instructionText.setText('Repeater recharging: ' + Math.ceil(plantRecharges.repeater / 60) + 's');
}
};
cherryBombBtn.down = function (x, y, obj) {
if (plantRecharges.cherryBomb === 0) {
selectedPlantType = 'cherryBomb';
instructionText.setText('Selected: Cherry Bomb (150)');
} else {
instructionText.setText('Cherry Bomb recharging: ' + Math.ceil(plantRecharges.cherryBomb / 60) + 's');
}
};
spikeweedBtn.down = function (x, y, obj) {
if (plantRecharges.spikeweed === 0) {
selectedPlantType = 'spikeweed';
instructionText.setText('Selected: Spikeweed (100)');
} else {
instructionText.setText('Spikeweed recharging: ' + Math.ceil(plantRecharges.spikeweed / 60) + 's');
}
};
chomperBtn.down = function (x, y, obj) {
if (plantRecharges.chomper === 0) {
selectedPlantType = 'chomper';
instructionText.setText('Selected: Chomper (150)');
} else {
instructionText.setText('Chomper recharging: ' + Math.ceil(plantRecharges.chomper / 60) + 's');
}
};
// Grid click handler
game.down = function (x, y, obj) {
if (gameState !== 'playing') return;
// Check if click is within grid bounds
if (x >= GRID_START_X && x <= GRID_START_X + GRID_COLS * CELL_SIZE && y >= GRID_START_Y && y <= GRID_START_Y + GRID_ROWS * CELL_SIZE) {
var col = Math.floor((x - GRID_START_X) / CELL_SIZE);
var row = Math.floor((y - GRID_START_Y) / CELL_SIZE);
if (row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS) {
if (selectedPlantType === 'shovel') {
removePlant(row, col);
} else {
placePlant(row, col, selectedPlantType);
}
}
}
};
// Main game loop
game.update = function () {
if (gameState !== 'playing') return;
// Handle day/night cycle
if (currentWave === 6 && !nightTransitionCompleted) {
// Transition to night after wave 5
isNight = true;
nightTransitionCompleted = true;
game.setBackgroundColor(0x001122);
// Flash screen to indicate night
LK.effects.flashScreen(0x000044, 1000);
} else if (currentWave === 11 && !dayTransitionCompleted) {
// Transition back to day after wave 10
isNight = false;
dayTransitionCompleted = true;
game.setBackgroundColor(0x2d5016);
// Flash screen to indicate day
LK.effects.flashScreen(0x88ff44, 1000);
}
// Keep background color consistent during night/day periods
if (isNight && currentWave >= 6 && currentWave < 11) {
game.setBackgroundColor(0x001122);
} else if (!isNight && (currentWave < 6 || currentWave >= 11)) {
game.setBackgroundColor(0x2d5016);
}
// Handle game start delay
if (!firstWaveStarted) {
gameStartTimer++;
if (gameStartTimer >= 1500 && !alertShown) {
// 25 seconds
alertShown = true;
LK.effects.flashScreen(0xff0000, 2000);
// Create alert text
var alertText = new Text2('ZOMBIES ARE COMING!', {
size: 80,
fill: 0xff0000
});
alertText.anchor.set(0.5, 0.5);
alertText.x = 0;
alertText.y = 0;
LK.gui.center.addChild(alertText);
// Animate alert text
tween(alertText, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500
});
tween(alertText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
alertText.destroy();
firstWaveStarted = true;
}
});
}
}
// Spawn enemies
if (firstWaveStarted) {
waveTimer++;
if (enemiesSpawned < enemiesPerWave && waveTimer % 120 === 0) {
// Every 2 seconds
var randomRow = Math.floor(Math.random() * GRID_ROWS);
spawnEnemy(randomRow);
}
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
// Check collision with enemies
var hit = false;
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
if (projectile.row === enemy.row && projectile.x >= enemy.x - 35 && projectile.x <= enemy.x + 35) {
enemy.takeDamage(projectile.damage);
hit = true;
break;
}
}
// Remove projectile if hit or off screen
if (hit || projectile.x > 2100) {
projectiles.splice(i, 1);
projectile.destroy();
}
}
// Check enemy-plant collisions
for (var k = 0; k < enemies.length; k++) {
var enemy = enemies[k];
enemy.attacking = false;
for (var l = 0; l < plants.length; l++) {
var plant = plants[l];
if (enemy.row === plant.row && enemy.x >= plant.x - 50 && enemy.x <= plant.x + 50) {
// Skip spikeweed - zombies ignore it completely and don't stop for it
if (plant instanceof Spikeweed) {
continue;
}
enemy.attacking = true;
break;
}
}
}
// Update plant recharges
for (var plantType in plantRecharges) {
if (plantRecharges[plantType] > 0) {
plantRecharges[plantType]--;
}
}
// Check pruner activation
for (var m = 0; m < pruners.length; m++) {
var pruner = pruners[m];
if (!pruner.activated) {
for (var n = 0; n < enemies.length; n++) {
var enemy = enemies[n];
if (enemy.row === pruner.row && Math.abs(enemy.x - pruner.x) <= 80) {
pruner.activate();
break;
}
}
}
}
checkWaveComplete();
checkGameOver();
};
// Play background music
LK.playMusic('Grasswalk');
La Petacereza es un par de cerezas gemelas de un rojo intenso y brillante, unidas por sus delgados tallos que se juntan en una pequeña ramita coronada por tres hojas verdes oscuras. Cada fruto tiene rasgos agresivos: ojos amarillentos con iris rojizos y cejas fruncidas, y bocas torcidas en muecas de enfado que sugieren su inminente explosión. Sus superficies lucen suaves, con leves reflejos que acentúan su redondez perfecta.. In-Game asset. 2d. High contrast. No shadows
una planta de color verde brillante con forma de pistola vegetal: su “cañón” cilíndrico, ligeramente más ancho en la boca, luce un tono verde claro con un suave degradado y un brillo que le da volumen. Sus ojos son dos círculos lisos y oscuros, casi sin pupila, que le aportan un aire simpático y sencillo. El cuerpo descansa sobre un tallo delgado y flexible, sostenido por cuatro hojas arqueadas y onduladas en la base, de un verde más intenso, que sirven de “pies” y refuerzan su aparienciauna planta de color verde brillante con forma de pistola vegetal: su “cañón” cilíndrico, ligeramente más ancho en la boca, luce un tono verde claro con un suave degradado y un brillo que le da volumen. Sus ojos son dos círculos lisos y oscuros, casi sin pupila, que le aportan un aire simpático y sencillo. El cuerpo descansa sobre un tallo delgado y flexible, sostenido por cuatro hojas arqueadas y onduladas en la base, de un verde más intenso, que sirven de “pies” y refuerzan su apariencia. In-Game asset. High contrast. No shadows
Un girasol con una cabeza redonda y marrón con pétalos amarillos brillantes que lo rodean como una corona. Su tallo es verde y tiene dos hojas grandes y anchas que sobresalen a los lados. En su cara, tiene dos ojos pequeños y negros, y una boca curvada en una sonrisa amigable.. In-Game asset. High contrast. No shadows
varias nubes de Humo color rojo un poco oscuro saliendo de una explosión. In-Game asset. High contrast
Una planta de color verde brillante, con un cuerpo principal esférico que se estrecha hacia adelante para formar una boca grande y redonda. De la parte posterior de su cabeza, brotan varias hojas puntiagudas. Tiene dos ojos grandes y redondos, con pupilas negras, y su expresión es decidida. Su tallo es delgado y verde, anclado en un grupo de hojas más grandes en la base.. In-Game asset. High contrast. No shadows
Nuez de color marrón claro, con una textura rugosa que le da un aspecto robusto. Es de forma ovalada, ligeramente más ancha en la base. Tiene dos ojos grandes y blancos con pupilas negras, que miran hacia adelante, y una boca pequeña y sonriente que le da una expresión algo asustadiza o sorprendida.. In-Game asset. High contrast. No shadows
Una casilla de pasto con bordes color verde oscuro que cubra toda la imagen y unas pequeñas flores. In-Game asset. High contrast
Esqueleto zombi en cuerpo completo caminando. In-Game asset. 2d. High contrast. No shadows
Zombie de piel verdosa y pálida, con una cabeza ovalada y dos grandes ojos saltones con pupilas pequeñas y rojizas. Su boca está abierta, mostrando dientes irregulares. Viste un traje de negocios marrón claro, una camisa blanca y una corbata roja a rayas, todo rasgado y sucio. Sus pantalones azules están hechos jirones, dejando ver una pierna esquelética, y calza zapatos marrones desgastados. Sus brazos están extendidos ligeramente hacia adelante. In-Game asset. 2d. High contrast. No shadows
Un guisante. In-Game asset. 2d. High contrast. No shadows
Sol puntiagudo luminoso. In-Game asset. 2d. High contrast. No shadows
Zombi a cuerpo completo de piel verdosa y pálida, con cabello desordenado y ojos saltones, viste una camisa de fuerza con correas de cuero alrededor de sus brazos y torso. sostiene una caja de sorpresas roja y blanca con estrellas, que tiene una manivela en un lado. Su expresión es de una sonrisa macabra. In-Game asset. 2d. High contrast. No shadows
Una podadora de césped compacta y robusta, predominantemente de color rojo vibrante. Tiene un motor gris con una tapa roja en la parte superior. Cuenta con cuatro ruedas pequeñas y oscuras, y un mango largo y negro en forma de "U" invertida que se extiende hacia atrás.. In-Game asset. 2d. High contrast. No shadows
pequeña pala de acero, con mango de madera y un agarre de color rojo. In-Game asset. 2d. High contrast. No shadows
Un zombi gigante y musculoso de piel verdosa. Viste un chaleco marrón roto y pantalones oscuros desgarrados, dejando ver parte de sus piernas. En su mano derecha, blande un poste de luz improvisado con cuatro farolas, usándolo como arma. Sobre su espalda, lleva a un zombi pequeño, sujeto por unas correas. Sus ojos son grandes y saltones, y su boca está abierta mostrando una dentadura irregular, dándole una expresión de furia.. In-Game asset. 2d. High contrast. No shadows
Un zombi con piel verdosa y pálida, cabello rojizo despeinado y un gorro de lana rojo. Viste un chaleco de mezclilla sin mangas sobre una camiseta negra y pantalones de mezclilla. Está sentado en un vehículo de mantenimiento de hielo azul y gris, similar a una Zamboni, con una gran "Z" roja en el frente. El vehículo tiene ruedas pequeñas y negras, y el Zomboni lo conduce con una expresión de asombro y determinación, con la boca abierta.. In-Game asset. 2d. High contrast. No shadows
una planta que emerge del suelo como una masa de tierra marrón, semi-enterrada. De ella sobresalen una serie de púas afiladas de color beige, de diferentes tamaños y ángulos, dispuestas en varias filas. En el centro, asoman dos ojos pequeños y rojos, con una mirada intensa y ligeramente enojada.. In-Game asset. 2d. High contrast. No shadows
Planta Carnívora con una cabeza grande y ovalada de color púrpura oscuro, salpicada con manchas más claras del mismo tono. Su boca es enorme, bordeada por un labio verde lima y llena de dientes blancos y afilados. Dentro de su boca, se ve una lengua rosada y una garganta oscura. Un tallo delgado y púrpura sostiene la cabeza, y en su base, tiene varias hojas verdes que se curvan hacia abajo. Pequeñas hojas puntiagudas también brotan de la parte superior y trasera de su cabeza.. In-Game asset. 2d. High contrast. No shadows