/**** * 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; } } // Award resources for killing all star zombie resources += 200; updateResourcesDisplay(); 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 = 200; 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; } } // Award resources for killing gargantuar resources += 50; updateResourcesDisplay(); 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; } } // Award resources for killing jack in the box zombie resources += 12; updateResourcesDisplay(); 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; } } // Award resources for killing skeleton resources += 15; updateResourcesDisplay(); 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; } } // Award resources for killing zombie resources += 50; updateResourcesDisplay(); 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; } } // Award resources for killing zomboni resources += 30; updateResourcesDisplay(); 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 = 200; 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;
}
}
// Award resources for killing all star zombie
resources += 200;
updateResourcesDisplay();
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 = 200;
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;
}
}
// Award resources for killing gargantuar
resources += 50;
updateResourcesDisplay();
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;
}
}
// Award resources for killing jack in the box zombie
resources += 12;
updateResourcesDisplay();
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;
}
}
// Award resources for killing skeleton
resources += 15;
updateResourcesDisplay();
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;
}
}
// Award resources for killing zombie
resources += 50;
updateResourcesDisplay();
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;
}
}
// Award resources for killing zomboni
resources += 30;
updateResourcesDisplay();
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 = 200;
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');
varias nubes de Humo color rojo un poco oscuro saliendo de una explosión. In-Game asset. High contrast
Una casilla de pasto con bordes color verde oscuro que cubra toda la imagen y unas pequeñas flores. In-Game asset. High contrast
Sol puntiagudo luminoso. 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