/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BackwardPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint green to show it's from splitpea
graphics.tint = 0x44ff44;
self.speed = -8; // Negative speed to go backwards
self.damage = 15;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected pea going forward
var reflectedPea = new ReflectedPea();
reflectedPea.x = zombie.x + 50;
reflectedPea.y = zombie.y;
reflectedPea.gridY = zombie.gridY;
reflectedpeas.push(reflectedPea);
game.addChild(reflectedPea);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'pea');
LK.getSound('zombie_hit').play();
}
self.destroy();
for (var j = 0; j < backwardpeas.length; j++) {
if (backwardpeas[j] === self) {
backwardpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < backwardpeas.length; k++) {
if (backwardpeas[k] === self) {
backwardpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var BasicZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('basicZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 100;
self.maxHealth = 100;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.update = function () {
// Check if there's a plant to attack
var plantToAttack = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 80) {
plantToAttack = plant;
break;
}
}
if (plantToAttack) {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
if (plantToAttack.takeDamage) {
plantToAttack.takeDamage(self.damage);
LK.getSound('zombie_eat').play();
}
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
self.attackTimer = 0;
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
zombiesKilled++;
}
};
return self;
});
var Zomboni = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with zomboni asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('zomboni', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5; // Fast movement
self.health = 150;
self.maxHealth = 150;
self.damage = 100;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 30; // Faster elimination
self.update = function () {
// Move forward continuously
self.x -= self.speed;
// Check for plants to eliminate (in a wider area due to size)
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 100) {
// Instantly eliminate the plant
LK.effects.flashObject(plant, 0xff0000, 200);
// Remove from plant grid
var gridKey = plant.gridX + ',' + plant.gridY;
delete plantGrid[gridKey];
plant.destroy();
plants.splice(i, 1);
}
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
return self;
});
var VeteranNewspaperZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with veteran newspaper zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('veteranewspaperZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 200; // Double the life of basic zombie (100 * 2)
self.maxHealth = 200;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.hasRaged = false;
self.takeDamage = function (damage) {
// Veteran newspaper zombie is now unstoppable - no damage taken
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
// Always rage mode - increase speed and damage by 5x permanently
if (!self.hasRaged) {
self.hasRaged = true;
self.speed = 2.5; // 0.5 * 5
self.damage = 200; // 40 * 5
// Visual effect for rage mode - flash red and change tint
LK.effects.flashObject(self, 0xff0000, 500);
tween(graphics, {
tint: 0xff0000
}, {
duration: 300,
easing: tween.easeOut
});
}
// Veteran newspaper zombie never dies
};
return self;
});
var TankZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with tank zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.3;
self.health = 200;
self.maxHealth = 200;
self.damage = 80;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
return self;
});
var NewspaperZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with newspaper zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('newspaperZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 100;
self.maxHealth = 100;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.hasRaged = false;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
// Check if health dropped to half or below and hasn't raged yet
if (self.health <= self.maxHealth / 2 && !self.hasRaged) {
self.hasRaged = true;
// Increase speed and damage
self.speed = 1.5;
self.damage = 80;
// Visual effect for rage mode - flash red and change tint
LK.effects.flashObject(self, 0xff0000, 500);
tween(graphics, {
tint: 0xff4444
}, {
duration: 300,
easing: tween.easeOut
});
}
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
zombiesKilled++;
}
};
return self;
});
var MinerZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with miner zombie asset (appears as normal zombie)
self.removeChild(self.children[0]);
var graphics = self.attachAsset('minerZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 80;
self.maxHealth = 80;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.teleportTimer = 0;
self.teleportDelay = 300; // 5 seconds at 60fps
self.hasTeleported = false;
self.update = function () {
// If not teleported yet, count down to teleport
if (!self.hasTeleported) {
self.teleportTimer++;
if (self.teleportTimer >= self.teleportDelay) {
self.teleport();
} else {
// Move normally before teleporting
self.x -= self.speed;
}
} else {
// After teleporting, behave like normal zombie but attacking from behind
var plantToAttack = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 80) {
plantToAttack = plant;
break;
}
}
if (plantToAttack) {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
if (plantToAttack.takeDamage) {
plantToAttack.takeDamage(self.damage);
LK.getSound('zombie_eat').play();
LK.getSound('zombie_eat').play();
}
self.attackTimer = 0;
}
} else {
self.x += self.speed; // Move forward (right) after teleporting
self.attackTimer = 0;
}
}
// Check if reached house (only matters before teleporting)
if (!self.hasTeleported && self.x < 150) {
LK.showGameOver();
}
// Check if moved off screen after teleporting
if (self.hasTeleported && self.x > 2200) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
}
};
self.teleport = function () {
if (self.hasTeleported) return;
self.hasTeleported = true;
// Flash effect before teleporting
LK.effects.flashObject(self, 0xffff00, 300);
// Teleport to the first column (leftmost position behind plants)
var newX = gridStartX + gridWidth / 2;
var newY = gridStartY + self.gridY * gridHeight + gridHeight / 2;
// Animate teleportation with tween
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
// Move to new position
self.x = newX;
self.y = newY;
// Animate appearing at new location
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOut
});
// Flash effect when appearing
LK.effects.flashObject(self, 0x00ff00, 300);
}
});
};
return self;
});
var JesterZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with jester zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('jesterZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.8;
self.health = 120;
self.maxHealth = 120;
self.damage = 50;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.isJester = true; // Flag to identify this zombie type
// Override takeDamage to be immune to peas
self.takeDamage = function (damage, source) {
// Only take damage from non-pea sources
if (source !== 'pea' && source !== 'frozenpea') {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
zombiesKilled++;
}
}
};
return self;
});
var JackInBoxZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with jack in box zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('jackInBoxZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.7;
self.health = 80;
self.maxHealth = 80;
self.damage = 60;
self.hasExploded = false;
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Flash the jack in box zombie white before exploding
LK.effects.flashObject(self, 0xffffff, 200);
// Scale up effect for explosion
tween(graphics, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from zombies array
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
// Destroy the jack in box zombie
self.destroy();
}
});
// Eliminate plants in a 250 pixel radius
var explosionRadius = 250;
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
var distance = Math.sqrt(Math.pow(plant.x - self.x, 2) + Math.pow(plant.y - self.y, 2));
if (distance <= explosionRadius) {
// Flash plant red before destroying
LK.effects.flashObject(plant, 0xff0000, 200);
// Remove from plant grid
var gridKey = plant.gridX + ',' + plant.gridY;
delete plantGrid[gridKey];
plant.destroy();
plants.splice(i, 1);
}
}
};
self.update = function () {
// Check if there's a plant to explode on
var plantToExplode = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 80) {
plantToExplode = plant;
break;
}
}
if (plantToExplode && !self.hasExploded) {
self.explode();
} else {
self.x -= self.speed;
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
return self;
});
var Gargantuar = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with gargantuar asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('gargantuar', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.2; // Very slow
self.health = 400; // Very resistant
self.maxHealth = 400;
self.damage = 200; // High damage to eliminate plants instantly
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 30; // Fast elimination
self.hasSpawnedFastZombie = false;
self.isResistantToExplosion = true; // Flag for cherry popper resistance
self.update = function () {
// Check if there's a plant to eliminate
var plantToEliminate = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 120) {
plantToEliminate = plant;
break;
}
}
if (plantToEliminate) {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
// Instantly eliminate the plant
LK.effects.flashObject(plantToEliminate, 0xff0000, 200);
// Remove from plant grid
var gridKey = plantToEliminate.gridX + ',' + plantToEliminate.gridY;
delete plantGrid[gridKey];
plantToEliminate.destroy();
for (var j = 0; j < plants.length; j++) {
if (plants[j] === plantToEliminate) {
plants.splice(j, 1);
break;
}
}
LK.getSound('zombie_eat').play();
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
self.attackTimer = 0;
}
// Spawn FastZombie when at half health
if (self.health <= self.maxHealth / 2 && !self.hasSpawnedFastZombie) {
self.hasSpawnedFastZombie = true;
var fastZombie = new FastZombie();
// Spawn in the third square of the garden (gridStartX + 2 * gridWidth for third column)
fastZombie.x = gridStartX + 2 * gridWidth + gridWidth / 2;
fastZombie.y = gridStartY + self.gridY * gridHeight + gridHeight / 2;
fastZombie.gridY = self.gridY;
zombies.push(fastZombie);
game.addChild(fastZombie);
// Visual effect for spawning
LK.effects.flashObject(self, 0xffff00, 500);
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
return self;
});
var FastZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with fast zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('fastZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.2;
self.health = 60;
self.maxHealth = 60;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
return self;
});
var CherryPopper = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('cherrypopper', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.gridX = 0;
self.gridY = 0;
self.hasExploded = false;
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Flash the cherry popper white before exploding
LK.effects.flashObject(self, 0xffffff, 200);
// Scale up effect for explosion
tween(graphics, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from plants array
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
// Remove from plant grid
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
// Destroy the cherry popper
self.destroy();
}
});
// Eliminate zombies in a 300 pixel radius
var explosionRadius = 300;
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance <= explosionRadius) {
// Special handling for Gargantuar
if (zombie.constructor === Gargantuar) {
// Flash zombie red
LK.effects.flashObject(zombie, 0xff0000, 200);
// If at full health or more than half, reduce to half
if (zombie.health > zombie.maxHealth / 2) {
zombie.health = zombie.maxHealth / 2;
// Visual effect for being damaged but not destroyed
tween(zombie, {
tint: 0xff4444
}, {
duration: 500,
easing: tween.easeOut
});
} else {
// Second explosion - eliminate Gargantuar
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
} else {
// Normal zombies - eliminate immediately
LK.effects.flashObject(zombie, 0xff0000, 200);
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
}
}
};
// Auto-explode when planted (immediately after placement)
self.update = function () {
if (!self.hasExploded) {
self.explode();
}
};
return self;
});
var Chomper = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('chomper', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.health = 100;
self.maxHealth = 100;
self.gridX = 0;
self.gridY = 0;
self.eatCooldown = 0;
self.maxEatCooldown = 900; // 15 seconds at 60fps
self.isOnCooldown = false;
self.digestionTimer = 0;
self.maxDigestionTime = 180; // 3 seconds to digest
self.isDigesting = false;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
// Handle digestion timer
if (self.isDigesting) {
self.digestionTimer++;
if (self.digestionTimer >= self.maxDigestionTime) {
self.isDigesting = false;
self.digestionTimer = 0;
// Reset color after digesting
tween(graphics, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
return; // Don't eat while digesting
}
// Handle cooldown
if (self.isOnCooldown) {
self.eatCooldown--;
if (self.eatCooldown <= 0) {
self.isOnCooldown = false;
// Reset color when cooldown ends
tween(graphics, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
} else {
// Ensure black color is maintained during cooldown
if (graphics.tint !== 0x000000) {
tween(graphics, {
tint: 0x000000
}, {
duration: 100,
easing: tween.easeOut
});
}
}
return; // Don't eat while on cooldown
}
// Look for the nearest zombie in adjacent squares (two square range)
var nearestZombie = null;
var nearestDistance = Infinity;
var nearestIndex = -1;
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
// Check if zombie is in same lane and within two grid square range
var gridDistance = Math.abs(zombie.x - self.x) / gridWidth;
if (zombie.gridY === self.gridY && gridDistance <= 2.2) {
var distance = Math.abs(zombie.x - self.x);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
nearestIndex = i;
}
}
}
// Eat the nearest zombie if found
if (nearestZombie) {
self.eatZombie(nearestZombie, nearestIndex);
}
};
self.eatZombie = function (zombie, zombieIndex) {
// Check if zombie is a Gargantuar - cannot be eaten
if (zombie.constructor === Gargantuar) {
// Flash red to show chomper can't eat this zombie
LK.effects.flashObject(self, 0xff0000, 300);
return; // Exit without eating
}
// Flash white to show eating action
LK.effects.flashObject(self, 0xffffff, 300);
// Remove zombie immediately
zombie.destroy();
zombies.splice(zombieIndex, 1);
zombiesKilled++;
// Start digestion period
self.isDigesting = true;
self.digestionTimer = 0;
// Visual feedback for digesting (green tint)
tween(graphics, {
tint: 0x44ff44
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Start cooldown after digestion
self.isOnCooldown = true;
self.eatCooldown = self.maxEatCooldown;
// Visual feedback for cooldown (black tint)
tween(graphics, {
tint: 0x000000
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
return self;
});
var FrozenPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('frozenpea', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 15;
self.gridY = 0;
self.slowDuration = 300; // 5 seconds at 60fps
self.slowFactor = 0.6; // Reduce speed to 60%
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected frozen pea going back towards plants
var reflectedFrozenPea = new ReflectedFrozenPea();
reflectedFrozenPea.x = zombie.x - 50;
reflectedFrozenPea.y = zombie.y;
reflectedFrozenPea.gridY = zombie.gridY;
reflectedfrozenpeas.push(reflectedFrozenPea);
game.addChild(reflectedFrozenPea);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'frozenpea');
LK.getSound('zombie_hit').play();
// Apply slow effect only if not a Zomboni or NewspaperZombie
if (zombie.constructor !== Zomboni && zombie.constructor !== NewspaperZombie) {
if (!zombie.originalSpeed) {
zombie.originalSpeed = zombie.speed;
}
zombie.speed = zombie.originalSpeed * self.slowFactor;
zombie.slowTimer = self.slowDuration;
// Play zombie_freezed sound when slowing
LK.getSound('zombie_freezed').play();
// Visual effect for slowed zombie
tween(zombie, {
tint: 0x87ceeb
}, {
duration: 300,
easing: tween.easeOut
});
}
}
self.destroy();
for (var j = 0; j < frozenpeas.length; j++) {
if (frozenpeas[j] === self) {
frozenpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2200) {
self.destroy();
for (var k = 0; k < frozenpeas.length; k++) {
if (frozenpeas[k] === self) {
frozenpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var Pea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 15;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected pea going back towards plants
var reflectedPea = new ReflectedPea();
reflectedPea.x = zombie.x - 50;
reflectedPea.y = zombie.y;
reflectedPea.gridY = zombie.gridY;
reflectedpeas.push(reflectedPea);
game.addChild(reflectedPea);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'pea');
LK.getSound('zombie_hit').play();
}
self.destroy();
for (var j = 0; j < peas.length; j++) {
if (peas[j] === self) {
peas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2200) {
self.destroy();
for (var k = 0; k < peas.length; k++) {
if (peas[k] === self) {
peas.splice(k, 1);
break;
}
}
}
};
return self;
});
var Peashooter = Container.expand(function () {
var self = Container.call(this);
var graphics = 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.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY && zombies[i].x > self.x) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
var pea = new Pea();
pea.x = self.x + 60;
pea.y = self.y;
pea.gridY = self.gridY;
peas.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
};
return self;
});
var Potatomine = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('potatomine', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 25;
self.health = 100;
self.maxHealth = 100;
self.gridX = 0;
self.gridY = 0;
self.chargeTimer = 0;
self.maxChargeTime = 900; // 15 seconds at 60fps
self.isCharged = false;
self.hasExploded = false;
// Start with black color (charging state)
graphics.tint = 0x000000;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
// Handle charging timer
if (!self.isCharged && !self.hasExploded) {
self.chargeTimer++;
if (self.chargeTimer >= self.maxChargeTime) {
self.isCharged = true;
// Change to normal color when charged
tween(graphics, {
tint: 0xffffff
}, {
duration: 500,
easing: tween.easeOut
});
}
return; // Don't explode while charging
}
// Check for zombies touching this potatomine
if (self.isCharged && !self.hasExploded) {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
self.explode();
break;
}
}
}
};
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Flash white before exploding
LK.effects.flashObject(self, 0xffffff, 200);
// Scale up effect for explosion
tween(graphics, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from plants array
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
// Remove from plant grid
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
// Destroy the potatomine
self.destroy();
}
});
// Eliminate zombies in a 200 pixel radius
var explosionRadius = 200;
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance <= explosionRadius) {
// Special handling for Gargantuar and NewspaperZombie
if (zombie.constructor === Gargantuar || zombie.constructor === NewspaperZombie) {
// Flash zombie red
LK.effects.flashObject(zombie, 0xff0000, 200);
// Reduce to half health
zombie.health = zombie.maxHealth / 2;
// Visual effect for being damaged but not destroyed
tween(zombie, {
tint: 0xff4444
}, {
duration: 500,
easing: tween.easeOut
});
// Check if zombie should be eliminated after damage
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
} else {
// Normal zombies - eliminate immediately
LK.effects.flashObject(zombie, 0xff0000, 200);
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
}
}
};
return self;
});
var Puffshroom = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('puffshroom', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 0;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 120; // 2 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.detectionRange = 3; // 3 tiles
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie within 3 tiles in this lane
var hasZombieInRange = false;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && zombie.x > self.x) {
var distance = Math.abs(zombie.x - self.x);
var tileDistance = distance / gridWidth;
if (tileDistance <= self.detectionRange) {
hasZombieInRange = true;
break;
}
}
}
if (hasZombieInRange) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
var spore = new Spore();
spore.x = self.x + 40;
spore.y = self.y;
spore.gridY = self.gridY;
spores.push(spore);
game.addChild(spore);
LK.getSound('shoot').play();
};
return self;
});
var ReflectedFrozenPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('frozenpea', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint dark red to show it's reflected
graphics.tint = 0xaa2222;
self.speed = -8; // Negative speed to go backwards
self.damage = 20; // Slightly more damage than regular frozen peas
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with plants
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && self.intersects(plant)) {
if (plant.takeDamage) {
plant.takeDamage(self.damage);
}
self.destroy();
for (var j = 0; j < reflectedfrozenpeas.length; j++) {
if (reflectedfrozenpeas[j] === self) {
reflectedfrozenpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < reflectedfrozenpeas.length; k++) {
if (reflectedfrozenpeas[k] === self) {
reflectedfrozenpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var ReflectedPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint red to show it's reflected
graphics.tint = 0xff4444;
self.speed = -8; // Negative speed to go backwards
self.damage = 20; // Slightly more damage than regular peas
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with plants
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && self.intersects(plant)) {
if (plant.takeDamage) {
plant.takeDamage(self.damage);
}
self.destroy();
for (var j = 0; j < reflectedpeas.length; j++) {
if (reflectedpeas[j] === self) {
reflectedpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < reflectedpeas.length; k++) {
if (reflectedpeas[k] === self) {
reflectedpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var ReflectedSpore = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spore', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint dark brown to show it's reflected
graphics.tint = 0x654321;
self.speed = -6;
self.damage = 15;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with plants
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && self.intersects(plant)) {
if (plant.takeDamage) {
plant.takeDamage(self.damage);
}
self.destroy();
for (var j = 0; j < reflectedspores.length; j++) {
if (reflectedspores[j] === self) {
reflectedspores.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < reflectedspores.length; k++) {
if (reflectedspores[k] === self) {
reflectedspores.splice(k, 1);
break;
}
}
}
};
return self;
});
var Repeater = Container.expand(function () {
var self = Container.call(this);
var graphics = 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.secondShotTimer = 0;
self.secondShotDelay = 15; // 0.25 seconds delay between shots
self.waitingForSecondShot = false;
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
// Handle second shot timing
if (self.waitingForSecondShot) {
self.secondShotTimer++;
if (self.secondShotTimer >= self.secondShotDelay) {
self.shoot(); // Fire second pea
self.waitingForSecondShot = false;
self.secondShotTimer = 0;
}
} else {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY && zombies[i].x > self.x) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot(); // Fire first pea
self.waitingForSecondShot = true; // Queue second shot
self.shootTimer = 0;
}
}
}
};
self.shoot = function () {
var pea = new Pea();
pea.x = self.x + 60;
pea.y = self.y;
pea.gridY = self.gridY;
peas.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
};
return self;
});
var Snowshooter = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('snowshooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 175;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 90; // 1.5 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY && zombies[i].x > self.x) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
var frozenpea = new FrozenPea();
frozenpea.x = self.x + 60;
frozenpea.y = self.y;
frozenpea.gridY = self.gridY;
frozenpeas.push(frozenpea);
game.addChild(frozenpea);
LK.getSound('shoot').play();
};
return self;
});
var SplitPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('splitpea', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 125;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 90; // 1.5 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane (both front and back)
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
// Shoot one pea forward
var forwardPea = new Pea();
forwardPea.x = self.x + 60;
forwardPea.y = self.y;
forwardPea.gridY = self.gridY;
peas.push(forwardPea);
game.addChild(forwardPea);
// Shoot two peas backward
var backwardPea1 = new BackwardPea();
backwardPea1.x = self.x - 60;
backwardPea1.y = self.y - 20;
backwardPea1.gridY = self.gridY;
backwardpeas.push(backwardPea1);
game.addChild(backwardPea1);
var backwardPea2 = new BackwardPea();
backwardPea2.x = self.x - 60;
backwardPea2.y = self.y + 20;
backwardPea2.gridY = self.gridY;
backwardpeas.push(backwardPea2);
game.addChild(backwardPea2);
LK.getSound('shoot').play();
};
return self;
});
var Spore = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spore', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.damage = 10;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected spore going back towards plants
var reflectedSpore = new ReflectedSpore();
reflectedSpore.x = zombie.x - 50;
reflectedSpore.y = zombie.y;
reflectedSpore.gridY = zombie.gridY;
reflectedspores.push(reflectedSpore);
game.addChild(reflectedSpore);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'spore');
LK.getSound('zombie_hit').play();
}
self.destroy();
for (var j = 0; j < spores.length; j++) {
if (spores[j] === self) {
spores.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2200) {
self.destroy();
for (var k = 0; k < spores.length; k++) {
if (spores[k] === self) {
spores.splice(k, 1);
break;
}
}
}
};
return self;
});
var Sun = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 25;
self.lifeTimer = 0;
self.maxLife = 600; // 10 seconds
self.update = function () {
self.lifeTimer++;
if (self.lifeTimer > self.maxLife) {
self.destroy();
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
}
};
self.down = function (x, y, obj) {
sunPoints += self.value;
updateSunDisplay();
self.destroy();
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
LK.getSound('collect').play();
};
return self;
});
var Sunflower = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('sunflower', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 100;
self.maxHealth = 100;
self.sunTimer = 0;
self.sunDelay = 600; // 10 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
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) * 50;
sun.y = self.y + (Math.random() - 0.5) * 50;
suns.push(sun);
game.addChild(sun);
};
return self;
});
var WallNut = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('wallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 300;
self.maxHealth = 300;
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8BC34A
});
/****
* Game Code
****/
// Sounds
// Zombies
// Projectiles and collectibles
// Plants
// Game grid and UI elements
// Game mode variables
var gameMode = null; // 'normal' or 'endless'
var gameStarted = false;
var menuVisible = true;
// Game variables
var sunPoints = 150;
var currentWave = 1;
var maxWaves = 20;
var waveTimer = 0;
var waveDelay = 1800; // 30 seconds
var zombieSpawnTimer = 0;
var zombieSpawnDelay = 240; // 4 seconds
var zombiesPerWave = 5;
var zombiesSpawned = 0;
var zombiesKilled = 0;
var totalZombiesToKill = 0;
var waveStarted = false;
var zombieSpawnQueue = [];
// Game arrays
var plants = [];
var zombies = [];
var peas = [];
var frozenpeas = [];
var reflectedpeas = [];
var reflectedfrozenpeas = [];
var backwardpeas = [];
var spores = [];
var reflectedspores = [];
var suns = [];
var plantGrid = {}; // Track occupied grid positions
// Grid settings
var gridStartX = 400;
var gridStartY = 400;
var gridWidth = 320;
var gridHeight = 227;
var gridCols = 5;
var gridRows = 5;
// Selected plant type
var selectedPlantType = null;
// Plant selector management
var currentSelectorPage = 1;
var maxSelectorPages = 2;
// Create lawn grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var tile = game.addChild(LK.getAsset('grass', {
x: gridStartX + col * gridWidth,
y: gridStartY + row * gridHeight,
anchorX: 0,
anchorY: 0
}));
}
}
// Create paths (zombie lanes)
for (var lane = 0; lane < gridRows; lane++) {
var pathTile = game.addChild(LK.getAsset('path', {
x: gridStartX + gridCols * gridWidth,
y: gridStartY + lane * gridHeight,
anchorX: 0,
anchorY: 0
}));
}
// Create house
var house = game.addChild(LK.getAsset('house', {
x: 50,
y: gridStartY + gridRows * gridHeight / 2 - 200,
anchorX: 0,
anchorY: 0
}));
// UI Setup
var sunDisplay = new Text2(sunPoints.toString(), {
size: 80,
fill: 0xFFD700,
stroke: 0x000000,
strokeThickness: 3
});
sunDisplay.anchor.set(0, 0);
LK.gui.topLeft.addChild(sunDisplay);
sunDisplay.x = 120;
sunDisplay.y = 20;
var waveDisplay = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 60,
fill: 0x000000
});
waveDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(waveDisplay);
waveDisplay.y = 20;
// Plant selection buttons - repositioned for new order
var peashooterBtn = LK.getAsset('peashooter', {
x: 250,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(peashooterBtn);
var sunflowerBtn = LK.getAsset('sunflower', {
x: 450,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(sunflowerBtn);
var cherrypopperBtn = LK.getAsset('cherrypopper', {
x: 650,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(cherrypopperBtn);
var wallnutBtn = LK.getAsset('wallnut', {
x: 850,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(wallnutBtn);
var potatomineBtn = LK.getAsset('potatomine', {
x: 1050,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(potatomineBtn);
var snowshooterBtn = LK.getAsset('snowshooter', {
x: 1250,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(snowshooterBtn);
var chomperBtn = LK.getAsset('chomper', {
x: 1450,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(chomperBtn);
var repeaterBtn = LK.getAsset('repeater', {
x: 1650,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(repeaterBtn);
var splitpeaBtn = LK.getAsset('splitpea', {
x: 250,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(splitpeaBtn);
var puffshroomBtn = LK.getAsset('puffshroom', {
x: 450,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(puffshroomBtn);
var shovelBtn = LK.getAsset('shovel', {
x: 1900,
y: 150,
scaleX: 1.5,
scaleY: 1.5,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(shovelBtn);
// Cost display texts - updated positions to match new layout
var peaCostTxt = new Text2('100', {
size: 70,
fill: 0x000000
});
peaCostTxt.anchor.set(0.5, 0);
game.addChild(peaCostTxt);
peaCostTxt.x = 250;
peaCostTxt.y = 2580;
var sunCostTxt = new Text2('50', {
size: 70,
fill: 0x000000
});
sunCostTxt.anchor.set(0.5, 0);
game.addChild(sunCostTxt);
sunCostTxt.x = 450;
sunCostTxt.y = 2580;
var cherryCostTxt = new Text2('150', {
size: 70,
fill: 0x000000
});
cherryCostTxt.anchor.set(0.5, 0);
game.addChild(cherryCostTxt);
cherryCostTxt.x = 650;
cherryCostTxt.y = 2580;
var wallCostTxt = new Text2('50', {
size: 70,
fill: 0x000000
});
wallCostTxt.anchor.set(0.5, 0);
game.addChild(wallCostTxt);
wallCostTxt.x = 850;
wallCostTxt.y = 2580;
var potatomineCosTxt = new Text2('25', {
size: 70,
fill: 0x000000
});
potatomineCosTxt.anchor.set(0.5, 0);
game.addChild(potatomineCosTxt);
potatomineCosTxt.x = 1050;
potatomineCosTxt.y = 2580;
var snowCostTxt = new Text2('175', {
size: 70,
fill: 0x000000
});
snowCostTxt.anchor.set(0.5, 0);
game.addChild(snowCostTxt);
snowCostTxt.x = 1250;
snowCostTxt.y = 2580;
var chomperCostTxt = new Text2('150', {
size: 70,
fill: 0x000000
});
chomperCostTxt.anchor.set(0.5, 0);
game.addChild(chomperCostTxt);
chomperCostTxt.x = 1450;
chomperCostTxt.y = 2580;
var repeaterCostTxt = new Text2('200', {
size: 70,
fill: 0x000000
});
repeaterCostTxt.anchor.set(0.5, 0);
game.addChild(repeaterCostTxt);
repeaterCostTxt.x = 1650;
repeaterCostTxt.y = 2580;
var splitpeaCostTxt = new Text2('125', {
size: 70,
fill: 0x000000
});
splitpeaCostTxt.anchor.set(0.5, 0);
game.addChild(splitpeaCostTxt);
splitpeaCostTxt.x = 250;
splitpeaCostTxt.y = 2580;
var puffshroomCostTxt = new Text2('0', {
size: 70,
fill: 0x000000
});
puffshroomCostTxt.anchor.set(0.5, 0);
game.addChild(puffshroomCostTxt);
puffshroomCostTxt.x = 450;
puffshroomCostTxt.y = 2580;
var shovelCostTxt = new Text2('Free', {
size: 70,
fill: 0x000000
});
shovelCostTxt.anchor.set(0.5, 0);
game.addChild(shovelCostTxt);
shovelCostTxt.x = 1900;
shovelCostTxt.y = 230;
// Add left arrow for plant selector
var leftArrow = new Text2('<', {
size: 120,
fill: 0x000000
});
leftArrow.anchor.set(0.5, 0.5);
game.addChild(leftArrow);
leftArrow.x = 50;
leftArrow.y = 2500;
// Add right arrow for plant selector
var rightArrow = new Text2('>', {
size: 120,
fill: 0x000000
});
rightArrow.anchor.set(0.5, 0.5);
game.addChild(rightArrow);
rightArrow.x = 2000;
rightArrow.y = 2500;
// Calculate total zombies for victory condition
for (var w = 1; w <= maxWaves; w++) {
totalZombiesToKill += zombiesPerWave + Math.floor(w / 2);
}
function updateSunDisplay() {
sunDisplay.setText(sunPoints.toString());
}
function updatePlantSelector() {
// Hide all plants first
peashooterBtn.visible = false;
sunflowerBtn.visible = false;
wallnutBtn.visible = false;
snowshooterBtn.visible = false;
repeaterBtn.visible = false;
cherrypopperBtn.visible = false;
splitpeaBtn.visible = false;
chomperBtn.visible = false;
potatomineBtn.visible = false;
puffshroomBtn.visible = false;
// Hide all cost texts
peaCostTxt.visible = false;
sunCostTxt.visible = false;
wallCostTxt.visible = false;
snowCostTxt.visible = false;
repeaterCostTxt.visible = false;
cherryCostTxt.visible = false;
splitpeaCostTxt.visible = false;
chomperCostTxt.visible = false;
potatomineCosTxt.visible = false;
puffshroomCostTxt.visible = false;
if (currentSelectorPage === 1) {
// First page - show: Peashooter, Sunflower, Cherrypopper, Wallnut, Potatomine, Snowshooter, Chomper, and Repeater
peashooterBtn.visible = true;
sunflowerBtn.visible = true;
cherrypopperBtn.visible = true;
wallnutBtn.visible = true;
potatomineBtn.visible = true;
snowshooterBtn.visible = true;
chomperBtn.visible = true;
repeaterBtn.visible = true;
// Show corresponding cost texts
peaCostTxt.visible = true;
sunCostTxt.visible = true;
cherryCostTxt.visible = true;
wallCostTxt.visible = true;
potatomineCosTxt.visible = true;
snowCostTxt.visible = true;
chomperCostTxt.visible = true;
repeaterCostTxt.visible = true;
} else if (currentSelectorPage === 2) {
// Second page - show splitpea and puffshroom (only from wave 11)
splitpeaBtn.visible = true;
splitpeaCostTxt.visible = true;
if (currentWave >= 11) {
puffshroomBtn.visible = true;
puffshroomCostTxt.visible = true;
}
}
}
function getGridPosition(x, y) {
var col = Math.floor((x - gridStartX) / gridWidth);
var row = Math.floor((y - gridStartY) / gridHeight);
if (col >= 0 && col < gridCols && row >= 0 && row < gridRows) {
return {
col: col,
row: row,
x: gridStartX + col * gridWidth + gridWidth / 2,
y: gridStartY + row * gridHeight + gridHeight / 2
};
}
return null;
}
function spawnZombie() {
var lane = Math.floor(Math.random() * gridRows);
var zombieType = Math.random();
var zombie;
// Higher spawn rates for special zombies from wave 15 onwards
if (currentWave >= 15) {
if (zombieType < 0.22) {
zombie = new Gargantuar();
} else if (zombieType < 0.44) {
zombie = new Zomboni();
} else if (zombieType < 0.58) {
zombie = new JesterZombie();
} else if (zombieType < 0.68) {
zombie = new TankZombie();
} else if (zombieType < 0.73) {
zombie = new JackInBoxZombie();
} else if (zombieType < 0.79) {
zombie = new MinerZombie();
} else if (zombieType < 0.85) {
zombie = new FastZombie();
} else if (zombieType < 0.91) {
zombie = new NewspaperZombie();
} else if (zombieType < 0.97) {
zombie = new VeteranNewspaperZombie();
} else {
zombie = new BasicZombie();
}
} else {
// Original spawn rates for waves before 15
if (currentWave > 8 && zombieType < 0.05) {
zombie = new Gargantuar();
} else if (currentWave > 6 && zombieType < 0.15) {
zombie = new Zomboni();
} else if (currentWave > 5 && zombieType < 0.25) {
zombie = new TankZombie();
} else if (currentWave > 4 && zombieType < 0.35) {
zombie = new JesterZombie();
} else if (currentWave > 2 && zombieType < 0.45) {
zombie = new JackInBoxZombie();
} else if (currentWave > 3 && zombieType < 0.60) {
zombie = new MinerZombie();
} else if (currentWave > 3 && zombieType < 0.75) {
zombie = new FastZombie();
} else if (currentWave > 1 && zombieType < 0.85) {
zombie = new NewspaperZombie();
} else if (currentWave > 7 && zombieType < 0.95) {
zombie = new VeteranNewspaperZombie();
} else {
zombie = new BasicZombie();
}
}
zombie.x = gridStartX + gridCols * gridWidth + 100;
zombie.y = gridStartY + lane * gridHeight + gridHeight / 2;
zombie.gridY = lane;
// Add spawn delay animation - zombie starts invisible and fades in
zombie.alpha = 0;
zombie.scaleX = 0.5;
zombie.scaleY = 0.5;
// Animate zombie spawning in
tween(zombie, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeOut
});
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
}
function startNextWave() {
// In endless mode, there's no wave limit
var shouldStartWave = gameMode === 'normal' && currentWave <= maxWaves || gameMode === 'endless';
if (shouldStartWave) {
// In endless mode, increase difficulty more aggressively
if (gameMode === 'endless') {
zombiesPerWave = 5 + Math.floor(currentWave / 1.5); // Faster increase in endless mode
} else {
zombiesPerWave = 5 + Math.floor(currentWave / 2);
}
zombiesSpawned = 0;
waveStarted = false;
zombieSpawnQueue = [];
// Display wave differently for endless mode
if (gameMode === 'endless') {
waveDisplay.setText('Wave: ' + currentWave + ' (Endless)');
} else {
waveDisplay.setText('Wave: ' + currentWave + '/' + maxWaves);
}
// Play Ultimate Battle music for all waves
LK.playMusic('ultimate_battle');
// Change background color to dark blue from wave 11
if (currentWave >= 11) {
game.setBackgroundColor(0x191970); // Dark blue
}
// Flash screen red when wave increases
LK.effects.flashScreen(0xff0000, 1000);
// Add delay before wave actually starts spawning zombies
// For wave 1, add 10 second delay. For other waves, 2 second delay
var waveStartDelay = currentWave === 1 ? 10000 : 2000;
LK.setTimeout(function () {
waveStarted = true;
// Pre-queue zombie spawn times with delays
// Use faster spawn delay from wave 5 onwards with higher spawn rate, and even faster from wave 11
// In endless mode, spawn rate increases even more aggressively
var currentSpawnDelay;
if (gameMode === 'endless') {
// Endless mode has progressively faster spawns
if (currentWave >= 20) {
currentSpawnDelay = 30; // Very fast from wave 20 (0.5 seconds)
} else if (currentWave >= 15) {
currentSpawnDelay = 45; // Faster from wave 15 (0.75 seconds)
} else if (currentWave >= 10) {
currentSpawnDelay = 60; // Fast from wave 10 (1 second)
} else if (currentWave >= 5) {
currentSpawnDelay = 90; // Medium speed from wave 5 (1.5 seconds)
} else {
currentSpawnDelay = zombieSpawnDelay; // Normal speed (4 seconds)
}
} else {
// Normal mode spawn rates
if (currentWave >= 11) {
currentSpawnDelay = 60; // Even faster from wave 11 (1 second)
} else if (currentWave >= 5) {
currentSpawnDelay = 90; // Higher spawn rate from wave 5 (1.5 seconds)
} else {
currentSpawnDelay = zombieSpawnDelay; // Normal speed (4 seconds)
}
}
for (var i = 0; i < zombiesPerWave; i++) {
zombieSpawnQueue.push({
spawnTime: LK.ticks + i * currentSpawnDelay + Math.random() * 60,
// Add some randomness (reduced for faster waves)
spawned: false
});
}
}, waveStartDelay);
}
}
// Generate sun periodically
var sunGenerationTimer = 0;
var sunGenerationDelay = 480; // 8 seconds
// Event handlers
peashooterBtn.down = function () {
if (sunPoints >= 100) {
selectedPlantType = 'peashooter';
LK.effects.flashObject(peashooterBtn, 0x00ff00, 300);
}
};
sunflowerBtn.down = function () {
if (sunPoints >= 50) {
selectedPlantType = 'sunflower';
LK.effects.flashObject(sunflowerBtn, 0x00ff00, 300);
}
};
wallnutBtn.down = function () {
if (sunPoints >= 50) {
selectedPlantType = 'wallnut';
LK.effects.flashObject(wallnutBtn, 0x00ff00, 300);
}
};
snowshooterBtn.down = function () {
if (sunPoints >= 175) {
selectedPlantType = 'snowshooter';
LK.effects.flashObject(snowshooterBtn, 0x00ff00, 300);
}
};
repeaterBtn.down = function () {
if (sunPoints >= 200) {
selectedPlantType = 'repeater';
LK.effects.flashObject(repeaterBtn, 0x00ff00, 300);
}
};
cherrypopperBtn.down = function () {
if (sunPoints >= 150) {
selectedPlantType = 'cherrypopper';
LK.effects.flashObject(cherrypopperBtn, 0x00ff00, 300);
}
};
splitpeaBtn.down = function () {
if (sunPoints >= 125) {
selectedPlantType = 'splitpea';
LK.effects.flashObject(splitpeaBtn, 0x00ff00, 300);
}
};
chomperBtn.down = function () {
if (sunPoints >= 150) {
selectedPlantType = 'chomper';
LK.effects.flashObject(chomperBtn, 0x00ff00, 300);
}
};
potatomineBtn.down = function () {
if (sunPoints >= 25) {
selectedPlantType = 'potatomine';
LK.effects.flashObject(potatomineBtn, 0x00ff00, 300);
}
};
puffshroomBtn.down = function () {
if (currentWave >= 11) {
selectedPlantType = 'puffshroom';
LK.effects.flashObject(puffshroomBtn, 0x00ff00, 300);
}
};
shovelBtn.down = function () {
selectedPlantType = 'shovel';
LK.effects.flashObject(shovelBtn, 0x00ff00, 300);
};
leftArrow.down = function () {
if (currentSelectorPage > 1) {
currentSelectorPage--;
updatePlantSelector();
LK.effects.flashObject(leftArrow, 0x00ff00, 200);
} else {
// Flash gray if can't go back further
LK.effects.flashObject(leftArrow, 0x888888, 200);
}
};
rightArrow.down = function () {
if (currentSelectorPage < maxSelectorPages) {
currentSelectorPage++;
updatePlantSelector();
LK.effects.flashObject(rightArrow, 0x00ff00, 200);
} else {
// Flash gray if can't go forward further
LK.effects.flashObject(rightArrow, 0x888888, 200);
}
};
game.down = function (x, y, obj) {
// Don't allow plant placement if menu is visible
if (menuVisible || !gameStarted) {
return;
}
var gridPos = getGridPosition(x, y);
if (gridPos && selectedPlantType) {
var gridKey = gridPos.col + ',' + gridPos.row;
// Handle shovel - remove existing plant
if (selectedPlantType === 'shovel') {
if (plantGrid[gridKey]) {
var plantToRemove = plantGrid[gridKey];
// Remove from plants array
for (var i = 0; i < plants.length; i++) {
if (plants[i] === plantToRemove) {
plants.splice(i, 1);
break;
}
}
// Remove from grid
delete plantGrid[gridKey];
// Destroy the plant
plantToRemove.destroy();
selectedPlantType = null;
}
return;
}
// Check if position is already occupied
if (plantGrid[gridKey]) {
return;
}
var plant = null;
var cost = 0;
if (selectedPlantType === 'peashooter' && sunPoints >= 100) {
plant = new Peashooter();
cost = 100;
} else if (selectedPlantType === 'sunflower' && sunPoints >= 50) {
plant = new Sunflower();
cost = 50;
} else if (selectedPlantType === 'wallnut' && sunPoints >= 50) {
plant = new WallNut();
cost = 50;
} else if (selectedPlantType === 'snowshooter' && sunPoints >= 175) {
plant = new Snowshooter();
cost = 175;
} else if (selectedPlantType === 'repeater' && sunPoints >= 200) {
plant = new Repeater();
cost = 200;
} else if (selectedPlantType === 'cherrypopper' && sunPoints >= 150) {
plant = new CherryPopper();
cost = 150;
} else if (selectedPlantType === 'splitpea' && sunPoints >= 125) {
plant = new SplitPea();
cost = 125;
} else if (selectedPlantType === 'chomper' && sunPoints >= 150) {
plant = new Chomper();
cost = 150;
} else if (selectedPlantType === 'potatomine' && sunPoints >= 25) {
plant = new Potatomine();
cost = 25;
} else if (selectedPlantType === 'puffshroom' && currentWave >= 11) {
plant = new Puffshroom();
cost = 0;
}
if (plant) {
plant.x = gridPos.x;
plant.y = gridPos.y;
plant.gridX = gridPos.col;
plant.gridY = gridPos.row;
plants.push(plant);
game.addChild(plant);
plantGrid[gridKey] = plant;
sunPoints -= cost;
updateSunDisplay();
selectedPlantType = null;
// 50% chance to play either seed_plant1 or seed_plant2
if (Math.random() < 0.5) {
LK.getSound('seed_plant1').play();
} else {
LK.getSound('seed_plant2').play();
}
}
}
};
game.update = function () {
// Don't update game logic if menu is visible
if (menuVisible || !gameStarted) {
return;
}
// Generate sun periodically
sunGenerationTimer++;
if (sunGenerationTimer >= sunGenerationDelay) {
var sun = new Sun();
sun.x = 200 + Math.random() * 1600;
sun.y = -100; // Start above screen
sun.targetY = 200 + Math.random() * 1000; // Final position
sun.isFalling = true; // Mark as falling sun
// Animate falling from top to bottom
tween(sun, {
y: sun.targetY
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
sun.isFalling = false;
}
});
suns.push(sun);
game.addChild(sun);
sunGenerationTimer = 0;
}
// Wave management
if (currentWave <= maxWaves) {
waveTimer++;
// Spawn zombies for current wave using queue system
if (waveStarted && zombiesSpawned < zombiesPerWave) {
for (var i = 0; i < zombieSpawnQueue.length; i++) {
var queueItem = zombieSpawnQueue[i];
if (!queueItem.spawned && LK.ticks >= queueItem.spawnTime) {
spawnZombie();
queueItem.spawned = true;
break; // Only spawn one zombie per frame
}
}
}
// Start next wave
if (zombiesSpawned >= zombiesPerWave && zombies.length === 0) {
currentWave++;
if (currentWave <= maxWaves) {
startNextWave();
waveTimer = 0;
}
}
}
// Check victory condition (only for normal mode)
if (gameMode === 'normal' && currentWave > maxWaves && zombies.length === 0) {
LK.showYouWin();
}
// Handle slow effects on zombies
for (var z = 0; z < zombies.length; z++) {
var zombie = zombies[z];
if (zombie.slowTimer) {
zombie.slowTimer--;
if (zombie.slowTimer <= 0) {
// Restore original speed
if (zombie.originalSpeed) {
zombie.speed = zombie.originalSpeed;
}
// Remove slow tint
tween(zombie, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
zombie.slowTimer = null;
}
}
}
// Update button availability
peashooterBtn.alpha = sunPoints >= 100 ? 1.0 : 0.5;
sunflowerBtn.alpha = sunPoints >= 50 ? 1.0 : 0.5;
wallnutBtn.alpha = sunPoints >= 50 ? 1.0 : 0.5;
snowshooterBtn.alpha = sunPoints >= 175 ? 1.0 : 0.5;
repeaterBtn.alpha = sunPoints >= 200 ? 1.0 : 0.5;
cherrypopperBtn.alpha = sunPoints >= 150 ? 1.0 : 0.5;
splitpeaBtn.alpha = sunPoints >= 125 ? 1.0 : 0.5;
chomperBtn.alpha = sunPoints >= 150 ? 1.0 : 0.5;
potatomineBtn.alpha = sunPoints >= 25 ? 1.0 : 0.5;
puffshroomBtn.alpha = currentWave >= 11 && sunPoints >= 0 ? 1.0 : 0.5;
shovelBtn.alpha = 1.0; // Shovel is always available
// Update arrow button availability
updateArrowButtons();
};
// Initialize plant selector
updatePlantSelector();
// Update arrow button availability in main update loop
function updateArrowButtons() {
leftArrow.alpha = currentSelectorPage > 1 ? 1.0 : 0.5;
rightArrow.alpha = currentSelectorPage < maxSelectorPages ? 1.0 : 0.5;
}
// Create menu buttons
var normalBtn = new Text2('NORMAL', {
size: 120,
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 4
});
normalBtn.anchor.set(0.5, 0.5);
normalBtn.x = 1024;
normalBtn.y = 1200;
game.addChild(normalBtn);
var endlessBtn = new Text2('ENDLESS', {
size: 120,
fill: 0xff6600,
stroke: 0x000000,
strokeThickness: 4
});
endlessBtn.anchor.set(0.5, 0.5);
endlessBtn.x = 1024;
endlessBtn.y = 1400;
game.addChild(endlessBtn);
var modeDescriptionTxt = new Text2('Choose Game Mode:', {
size: 100,
fill: 0x000000
});
modeDescriptionTxt.anchor.set(0.5, 0.5);
modeDescriptionTxt.x = 1024;
modeDescriptionTxt.y = 1000;
game.addChild(modeDescriptionTxt);
var normalDescTxt = new Text2('20 waves with victory condition', {
size: 60,
fill: 0x333333
});
normalDescTxt.anchor.set(0.5, 0.5);
normalDescTxt.x = 1024;
normalDescTxt.y = 1280;
game.addChild(normalDescTxt);
var endlessDescTxt = new Text2('Infinite waves with increasing difficulty', {
size: 60,
fill: 0x333333
});
endlessDescTxt.anchor.set(0.5, 0.5);
endlessDescTxt.x = 1024;
endlessDescTxt.y = 1480;
game.addChild(endlessDescTxt);
// Menu button event handlers
normalBtn.down = function () {
gameMode = 'normal';
startGame();
};
endlessBtn.down = function () {
gameMode = 'endless';
startGame();
};
function startGame() {
gameStarted = true;
menuVisible = false;
// Hide menu elements
normalBtn.visible = false;
endlessBtn.visible = false;
modeDescriptionTxt.visible = false;
normalDescTxt.visible = false;
endlessDescTxt.visible = false;
// Show game UI elements
sunDisplay.visible = true;
waveDisplay.visible = true;
peashooterBtn.visible = true;
sunflowerBtn.visible = true;
cherrypopperBtn.visible = true;
wallnutBtn.visible = true;
potatomineBtn.visible = true;
snowshooterBtn.visible = true;
chomperBtn.visible = true;
repeaterBtn.visible = true;
splitpeaBtn.visible = true;
puffshroomBtn.visible = true;
shovelBtn.visible = true;
leftArrow.visible = true;
rightArrow.visible = true;
// Show cost texts
peaCostTxt.visible = true;
sunCostTxt.visible = true;
cherryCostTxt.visible = true;
wallCostTxt.visible = true;
potatomineCosTxt.visible = true;
snowCostTxt.visible = true;
chomperCostTxt.visible = true;
repeaterCostTxt.visible = true;
splitpeaCostTxt.visible = true;
puffshroomCostTxt.visible = true;
shovelCostTxt.visible = true;
// Update plant selector to show correct page
updatePlantSelector();
// Start first wave
startNextWave();
}
// Initially hide game UI elements
sunDisplay.visible = false;
waveDisplay.visible = false;
peashooterBtn.visible = false;
sunflowerBtn.visible = false;
cherrypopperBtn.visible = false;
wallnutBtn.visible = false;
potatomineBtn.visible = false;
snowshooterBtn.visible = false;
chomperBtn.visible = false;
repeaterBtn.visible = false;
splitpeaBtn.visible = false;
puffshroomBtn.visible = false;
shovelBtn.visible = false;
leftArrow.visible = false;
rightArrow.visible = false;
peaCostTxt.visible = false;
sunCostTxt.visible = false;
cherryCostTxt.visible = false;
wallCostTxt.visible = false;
potatomineCosTxt.visible = false;
snowCostTxt.visible = false;
chomperCostTxt.visible = false;
repeaterCostTxt.visible = false;
splitpeaCostTxt.visible = false;
puffshroomCostTxt.visible = false;
shovelCostTxt.visible = false; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BackwardPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint green to show it's from splitpea
graphics.tint = 0x44ff44;
self.speed = -8; // Negative speed to go backwards
self.damage = 15;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected pea going forward
var reflectedPea = new ReflectedPea();
reflectedPea.x = zombie.x + 50;
reflectedPea.y = zombie.y;
reflectedPea.gridY = zombie.gridY;
reflectedpeas.push(reflectedPea);
game.addChild(reflectedPea);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'pea');
LK.getSound('zombie_hit').play();
}
self.destroy();
for (var j = 0; j < backwardpeas.length; j++) {
if (backwardpeas[j] === self) {
backwardpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < backwardpeas.length; k++) {
if (backwardpeas[k] === self) {
backwardpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var BasicZombie = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('basicZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 100;
self.maxHealth = 100;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.update = function () {
// Check if there's a plant to attack
var plantToAttack = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 80) {
plantToAttack = plant;
break;
}
}
if (plantToAttack) {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
if (plantToAttack.takeDamage) {
plantToAttack.takeDamage(self.damage);
LK.getSound('zombie_eat').play();
}
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
self.attackTimer = 0;
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
zombiesKilled++;
}
};
return self;
});
var Zomboni = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with zomboni asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('zomboni', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5; // Fast movement
self.health = 150;
self.maxHealth = 150;
self.damage = 100;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 30; // Faster elimination
self.update = function () {
// Move forward continuously
self.x -= self.speed;
// Check for plants to eliminate (in a wider area due to size)
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 100) {
// Instantly eliminate the plant
LK.effects.flashObject(plant, 0xff0000, 200);
// Remove from plant grid
var gridKey = plant.gridX + ',' + plant.gridY;
delete plantGrid[gridKey];
plant.destroy();
plants.splice(i, 1);
}
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
return self;
});
var VeteranNewspaperZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with veteran newspaper zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('veteranewspaperZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 200; // Double the life of basic zombie (100 * 2)
self.maxHealth = 200;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.hasRaged = false;
self.takeDamage = function (damage) {
// Veteran newspaper zombie is now unstoppable - no damage taken
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
// Always rage mode - increase speed and damage by 5x permanently
if (!self.hasRaged) {
self.hasRaged = true;
self.speed = 2.5; // 0.5 * 5
self.damage = 200; // 40 * 5
// Visual effect for rage mode - flash red and change tint
LK.effects.flashObject(self, 0xff0000, 500);
tween(graphics, {
tint: 0xff0000
}, {
duration: 300,
easing: tween.easeOut
});
}
// Veteran newspaper zombie never dies
};
return self;
});
var TankZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with tank zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('tankZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.3;
self.health = 200;
self.maxHealth = 200;
self.damage = 80;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
return self;
});
var NewspaperZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with newspaper zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('newspaperZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 100;
self.maxHealth = 100;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.hasRaged = false;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
// Check if health dropped to half or below and hasn't raged yet
if (self.health <= self.maxHealth / 2 && !self.hasRaged) {
self.hasRaged = true;
// Increase speed and damage
self.speed = 1.5;
self.damage = 80;
// Visual effect for rage mode - flash red and change tint
LK.effects.flashObject(self, 0xff0000, 500);
tween(graphics, {
tint: 0xff4444
}, {
duration: 300,
easing: tween.easeOut
});
}
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
zombiesKilled++;
}
};
return self;
});
var MinerZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with miner zombie asset (appears as normal zombie)
self.removeChild(self.children[0]);
var graphics = self.attachAsset('minerZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.5;
self.health = 80;
self.maxHealth = 80;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.teleportTimer = 0;
self.teleportDelay = 300; // 5 seconds at 60fps
self.hasTeleported = false;
self.update = function () {
// If not teleported yet, count down to teleport
if (!self.hasTeleported) {
self.teleportTimer++;
if (self.teleportTimer >= self.teleportDelay) {
self.teleport();
} else {
// Move normally before teleporting
self.x -= self.speed;
}
} else {
// After teleporting, behave like normal zombie but attacking from behind
var plantToAttack = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 80) {
plantToAttack = plant;
break;
}
}
if (plantToAttack) {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
if (plantToAttack.takeDamage) {
plantToAttack.takeDamage(self.damage);
LK.getSound('zombie_eat').play();
LK.getSound('zombie_eat').play();
}
self.attackTimer = 0;
}
} else {
self.x += self.speed; // Move forward (right) after teleporting
self.attackTimer = 0;
}
}
// Check if reached house (only matters before teleporting)
if (!self.hasTeleported && self.x < 150) {
LK.showGameOver();
}
// Check if moved off screen after teleporting
if (self.hasTeleported && self.x > 2200) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
}
};
self.teleport = function () {
if (self.hasTeleported) return;
self.hasTeleported = true;
// Flash effect before teleporting
LK.effects.flashObject(self, 0xffff00, 300);
// Teleport to the first column (leftmost position behind plants)
var newX = gridStartX + gridWidth / 2;
var newY = gridStartY + self.gridY * gridHeight + gridHeight / 2;
// Animate teleportation with tween
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
// Move to new position
self.x = newX;
self.y = newY;
// Animate appearing at new location
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOut
});
// Flash effect when appearing
LK.effects.flashObject(self, 0x00ff00, 300);
}
});
};
return self;
});
var JesterZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with jester zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('jesterZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.8;
self.health = 120;
self.maxHealth = 120;
self.damage = 50;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
self.isJester = true; // Flag to identify this zombie type
// Override takeDamage to be immune to peas
self.takeDamage = function (damage, source) {
// Only take damage from non-pea sources
if (source !== 'pea' && source !== 'frozenpea') {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
zombiesKilled++;
}
}
};
return self;
});
var JackInBoxZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with jack in box zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('jackInBoxZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.7;
self.health = 80;
self.maxHealth = 80;
self.damage = 60;
self.hasExploded = false;
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Flash the jack in box zombie white before exploding
LK.effects.flashObject(self, 0xffffff, 200);
// Scale up effect for explosion
tween(graphics, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from zombies array
for (var i = 0; i < zombies.length; i++) {
if (zombies[i] === self) {
zombies.splice(i, 1);
break;
}
}
// Destroy the jack in box zombie
self.destroy();
}
});
// Eliminate plants in a 250 pixel radius
var explosionRadius = 250;
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
var distance = Math.sqrt(Math.pow(plant.x - self.x, 2) + Math.pow(plant.y - self.y, 2));
if (distance <= explosionRadius) {
// Flash plant red before destroying
LK.effects.flashObject(plant, 0xff0000, 200);
// Remove from plant grid
var gridKey = plant.gridX + ',' + plant.gridY;
delete plantGrid[gridKey];
plant.destroy();
plants.splice(i, 1);
}
}
};
self.update = function () {
// Check if there's a plant to explode on
var plantToExplode = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 80) {
plantToExplode = plant;
break;
}
}
if (plantToExplode && !self.hasExploded) {
self.explode();
} else {
self.x -= self.speed;
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
return self;
});
var Gargantuar = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with gargantuar asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('gargantuar', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0.2; // Very slow
self.health = 400; // Very resistant
self.maxHealth = 400;
self.damage = 200; // High damage to eliminate plants instantly
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 30; // Fast elimination
self.hasSpawnedFastZombie = false;
self.isResistantToExplosion = true; // Flag for cherry popper resistance
self.update = function () {
// Check if there's a plant to eliminate
var plantToEliminate = null;
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && Math.abs(plant.x - self.x) < 120) {
plantToEliminate = plant;
break;
}
}
if (plantToEliminate) {
self.attackTimer++;
if (self.attackTimer >= self.attackDelay) {
// Instantly eliminate the plant
LK.effects.flashObject(plantToEliminate, 0xff0000, 200);
// Remove from plant grid
var gridKey = plantToEliminate.gridX + ',' + plantToEliminate.gridY;
delete plantGrid[gridKey];
plantToEliminate.destroy();
for (var j = 0; j < plants.length; j++) {
if (plants[j] === plantToEliminate) {
plants.splice(j, 1);
break;
}
}
LK.getSound('zombie_eat').play();
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
self.attackTimer = 0;
}
// Spawn FastZombie when at half health
if (self.health <= self.maxHealth / 2 && !self.hasSpawnedFastZombie) {
self.hasSpawnedFastZombie = true;
var fastZombie = new FastZombie();
// Spawn in the third square of the garden (gridStartX + 2 * gridWidth for third column)
fastZombie.x = gridStartX + 2 * gridWidth + gridWidth / 2;
fastZombie.y = gridStartY + self.gridY * gridHeight + gridHeight / 2;
fastZombie.gridY = self.gridY;
zombies.push(fastZombie);
game.addChild(fastZombie);
// Visual effect for spawning
LK.effects.flashObject(self, 0xffff00, 500);
}
// Check if reached house
if (self.x < 150) {
LK.showGameOver();
}
};
return self;
});
var FastZombie = BasicZombie.expand(function () {
var self = BasicZombie.call(this);
// Replace the graphics with fast zombie asset
self.removeChild(self.children[0]);
var graphics = self.attachAsset('fastZombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.2;
self.health = 60;
self.maxHealth = 60;
self.damage = 40;
self.gridY = 0;
self.attackTimer = 0;
self.attackDelay = 60;
return self;
});
var CherryPopper = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('cherrypopper', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.gridX = 0;
self.gridY = 0;
self.hasExploded = false;
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Flash the cherry popper white before exploding
LK.effects.flashObject(self, 0xffffff, 200);
// Scale up effect for explosion
tween(graphics, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from plants array
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
// Remove from plant grid
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
// Destroy the cherry popper
self.destroy();
}
});
// Eliminate zombies in a 300 pixel radius
var explosionRadius = 300;
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance <= explosionRadius) {
// Special handling for Gargantuar
if (zombie.constructor === Gargantuar) {
// Flash zombie red
LK.effects.flashObject(zombie, 0xff0000, 200);
// If at full health or more than half, reduce to half
if (zombie.health > zombie.maxHealth / 2) {
zombie.health = zombie.maxHealth / 2;
// Visual effect for being damaged but not destroyed
tween(zombie, {
tint: 0xff4444
}, {
duration: 500,
easing: tween.easeOut
});
} else {
// Second explosion - eliminate Gargantuar
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
} else {
// Normal zombies - eliminate immediately
LK.effects.flashObject(zombie, 0xff0000, 200);
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
}
}
};
// Auto-explode when planted (immediately after placement)
self.update = function () {
if (!self.hasExploded) {
self.explode();
}
};
return self;
});
var Chomper = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('chomper', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 150;
self.health = 100;
self.maxHealth = 100;
self.gridX = 0;
self.gridY = 0;
self.eatCooldown = 0;
self.maxEatCooldown = 900; // 15 seconds at 60fps
self.isOnCooldown = false;
self.digestionTimer = 0;
self.maxDigestionTime = 180; // 3 seconds to digest
self.isDigesting = false;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
// Handle digestion timer
if (self.isDigesting) {
self.digestionTimer++;
if (self.digestionTimer >= self.maxDigestionTime) {
self.isDigesting = false;
self.digestionTimer = 0;
// Reset color after digesting
tween(graphics, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
return; // Don't eat while digesting
}
// Handle cooldown
if (self.isOnCooldown) {
self.eatCooldown--;
if (self.eatCooldown <= 0) {
self.isOnCooldown = false;
// Reset color when cooldown ends
tween(graphics, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
} else {
// Ensure black color is maintained during cooldown
if (graphics.tint !== 0x000000) {
tween(graphics, {
tint: 0x000000
}, {
duration: 100,
easing: tween.easeOut
});
}
}
return; // Don't eat while on cooldown
}
// Look for the nearest zombie in adjacent squares (two square range)
var nearestZombie = null;
var nearestDistance = Infinity;
var nearestIndex = -1;
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
// Check if zombie is in same lane and within two grid square range
var gridDistance = Math.abs(zombie.x - self.x) / gridWidth;
if (zombie.gridY === self.gridY && gridDistance <= 2.2) {
var distance = Math.abs(zombie.x - self.x);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
nearestIndex = i;
}
}
}
// Eat the nearest zombie if found
if (nearestZombie) {
self.eatZombie(nearestZombie, nearestIndex);
}
};
self.eatZombie = function (zombie, zombieIndex) {
// Check if zombie is a Gargantuar - cannot be eaten
if (zombie.constructor === Gargantuar) {
// Flash red to show chomper can't eat this zombie
LK.effects.flashObject(self, 0xff0000, 300);
return; // Exit without eating
}
// Flash white to show eating action
LK.effects.flashObject(self, 0xffffff, 300);
// Remove zombie immediately
zombie.destroy();
zombies.splice(zombieIndex, 1);
zombiesKilled++;
// Start digestion period
self.isDigesting = true;
self.digestionTimer = 0;
// Visual feedback for digesting (green tint)
tween(graphics, {
tint: 0x44ff44
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Start cooldown after digestion
self.isOnCooldown = true;
self.eatCooldown = self.maxEatCooldown;
// Visual feedback for cooldown (black tint)
tween(graphics, {
tint: 0x000000
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
return self;
});
var FrozenPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('frozenpea', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 15;
self.gridY = 0;
self.slowDuration = 300; // 5 seconds at 60fps
self.slowFactor = 0.6; // Reduce speed to 60%
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected frozen pea going back towards plants
var reflectedFrozenPea = new ReflectedFrozenPea();
reflectedFrozenPea.x = zombie.x - 50;
reflectedFrozenPea.y = zombie.y;
reflectedFrozenPea.gridY = zombie.gridY;
reflectedfrozenpeas.push(reflectedFrozenPea);
game.addChild(reflectedFrozenPea);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'frozenpea');
LK.getSound('zombie_hit').play();
// Apply slow effect only if not a Zomboni or NewspaperZombie
if (zombie.constructor !== Zomboni && zombie.constructor !== NewspaperZombie) {
if (!zombie.originalSpeed) {
zombie.originalSpeed = zombie.speed;
}
zombie.speed = zombie.originalSpeed * self.slowFactor;
zombie.slowTimer = self.slowDuration;
// Play zombie_freezed sound when slowing
LK.getSound('zombie_freezed').play();
// Visual effect for slowed zombie
tween(zombie, {
tint: 0x87ceeb
}, {
duration: 300,
easing: tween.easeOut
});
}
}
self.destroy();
for (var j = 0; j < frozenpeas.length; j++) {
if (frozenpeas[j] === self) {
frozenpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2200) {
self.destroy();
for (var k = 0; k < frozenpeas.length; k++) {
if (frozenpeas[k] === self) {
frozenpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var Pea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 15;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected pea going back towards plants
var reflectedPea = new ReflectedPea();
reflectedPea.x = zombie.x - 50;
reflectedPea.y = zombie.y;
reflectedPea.gridY = zombie.gridY;
reflectedpeas.push(reflectedPea);
game.addChild(reflectedPea);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'pea');
LK.getSound('zombie_hit').play();
}
self.destroy();
for (var j = 0; j < peas.length; j++) {
if (peas[j] === self) {
peas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2200) {
self.destroy();
for (var k = 0; k < peas.length; k++) {
if (peas[k] === self) {
peas.splice(k, 1);
break;
}
}
}
};
return self;
});
var Peashooter = Container.expand(function () {
var self = Container.call(this);
var graphics = 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.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY && zombies[i].x > self.x) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
var pea = new Pea();
pea.x = self.x + 60;
pea.y = self.y;
pea.gridY = self.gridY;
peas.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
};
return self;
});
var Potatomine = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('potatomine', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 25;
self.health = 100;
self.maxHealth = 100;
self.gridX = 0;
self.gridY = 0;
self.chargeTimer = 0;
self.maxChargeTime = 900; // 15 seconds at 60fps
self.isCharged = false;
self.hasExploded = false;
// Start with black color (charging state)
graphics.tint = 0x000000;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
// Handle charging timer
if (!self.isCharged && !self.hasExploded) {
self.chargeTimer++;
if (self.chargeTimer >= self.maxChargeTime) {
self.isCharged = true;
// Change to normal color when charged
tween(graphics, {
tint: 0xffffff
}, {
duration: 500,
easing: tween.easeOut
});
}
return; // Don't explode while charging
}
// Check for zombies touching this potatomine
if (self.isCharged && !self.hasExploded) {
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
self.explode();
break;
}
}
}
};
self.explode = function () {
if (self.hasExploded) return;
self.hasExploded = true;
// Flash white before exploding
LK.effects.flashObject(self, 0xffffff, 200);
// Scale up effect for explosion
tween(graphics, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from plants array
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
// Remove from plant grid
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
// Destroy the potatomine
self.destroy();
}
});
// Eliminate zombies in a 200 pixel radius
var explosionRadius = 200;
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance <= explosionRadius) {
// Special handling for Gargantuar and NewspaperZombie
if (zombie.constructor === Gargantuar || zombie.constructor === NewspaperZombie) {
// Flash zombie red
LK.effects.flashObject(zombie, 0xff0000, 200);
// Reduce to half health
zombie.health = zombie.maxHealth / 2;
// Visual effect for being damaged but not destroyed
tween(zombie, {
tint: 0xff4444
}, {
duration: 500,
easing: tween.easeOut
});
// Check if zombie should be eliminated after damage
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
} else {
// Normal zombies - eliminate immediately
LK.effects.flashObject(zombie, 0xff0000, 200);
zombie.destroy();
zombies.splice(i, 1);
zombiesKilled++;
}
}
}
};
return self;
});
var Puffshroom = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('puffshroom', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 0;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 120; // 2 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.detectionRange = 3; // 3 tiles
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie within 3 tiles in this lane
var hasZombieInRange = false;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && zombie.x > self.x) {
var distance = Math.abs(zombie.x - self.x);
var tileDistance = distance / gridWidth;
if (tileDistance <= self.detectionRange) {
hasZombieInRange = true;
break;
}
}
}
if (hasZombieInRange) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
var spore = new Spore();
spore.x = self.x + 40;
spore.y = self.y;
spore.gridY = self.gridY;
spores.push(spore);
game.addChild(spore);
LK.getSound('shoot').play();
};
return self;
});
var ReflectedFrozenPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('frozenpea', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint dark red to show it's reflected
graphics.tint = 0xaa2222;
self.speed = -8; // Negative speed to go backwards
self.damage = 20; // Slightly more damage than regular frozen peas
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with plants
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && self.intersects(plant)) {
if (plant.takeDamage) {
plant.takeDamage(self.damage);
}
self.destroy();
for (var j = 0; j < reflectedfrozenpeas.length; j++) {
if (reflectedfrozenpeas[j] === self) {
reflectedfrozenpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < reflectedfrozenpeas.length; k++) {
if (reflectedfrozenpeas[k] === self) {
reflectedfrozenpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var ReflectedPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint red to show it's reflected
graphics.tint = 0xff4444;
self.speed = -8; // Negative speed to go backwards
self.damage = 20; // Slightly more damage than regular peas
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with plants
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && self.intersects(plant)) {
if (plant.takeDamage) {
plant.takeDamage(self.damage);
}
self.destroy();
for (var j = 0; j < reflectedpeas.length; j++) {
if (reflectedpeas[j] === self) {
reflectedpeas.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < reflectedpeas.length; k++) {
if (reflectedpeas[k] === self) {
reflectedpeas.splice(k, 1);
break;
}
}
}
};
return self;
});
var ReflectedSpore = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spore', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint dark brown to show it's reflected
graphics.tint = 0x654321;
self.speed = -6;
self.damage = 15;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with plants
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridY === self.gridY && self.intersects(plant)) {
if (plant.takeDamage) {
plant.takeDamage(self.damage);
}
self.destroy();
for (var j = 0; j < reflectedspores.length; j++) {
if (reflectedspores[j] === self) {
reflectedspores.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen (left side)
if (self.x < -100) {
self.destroy();
for (var k = 0; k < reflectedspores.length; k++) {
if (reflectedspores[k] === self) {
reflectedspores.splice(k, 1);
break;
}
}
}
};
return self;
});
var Repeater = Container.expand(function () {
var self = Container.call(this);
var graphics = 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.secondShotTimer = 0;
self.secondShotDelay = 15; // 0.25 seconds delay between shots
self.waitingForSecondShot = false;
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
// Handle second shot timing
if (self.waitingForSecondShot) {
self.secondShotTimer++;
if (self.secondShotTimer >= self.secondShotDelay) {
self.shoot(); // Fire second pea
self.waitingForSecondShot = false;
self.secondShotTimer = 0;
}
} else {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY && zombies[i].x > self.x) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot(); // Fire first pea
self.waitingForSecondShot = true; // Queue second shot
self.shootTimer = 0;
}
}
}
};
self.shoot = function () {
var pea = new Pea();
pea.x = self.x + 60;
pea.y = self.y;
pea.gridY = self.gridY;
peas.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
};
return self;
});
var Snowshooter = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('snowshooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 175;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 90; // 1.5 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY && zombies[i].x > self.x) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
var frozenpea = new FrozenPea();
frozenpea.x = self.x + 60;
frozenpea.y = self.y;
frozenpea.gridY = self.gridY;
frozenpeas.push(frozenpea);
game.addChild(frozenpea);
LK.getSound('shoot').play();
};
return self;
});
var SplitPea = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('splitpea', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 125;
self.health = 100;
self.maxHealth = 100;
self.shootTimer = 0;
self.shootDelay = 90; // 1.5 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
// Check if there's a zombie in this lane (both front and back)
var hasZombieInLane = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridY === self.gridY) {
hasZombieInLane = true;
break;
}
}
if (hasZombieInLane) {
self.shoot();
self.shootTimer = 0;
}
}
};
self.shoot = function () {
// Shoot one pea forward
var forwardPea = new Pea();
forwardPea.x = self.x + 60;
forwardPea.y = self.y;
forwardPea.gridY = self.gridY;
peas.push(forwardPea);
game.addChild(forwardPea);
// Shoot two peas backward
var backwardPea1 = new BackwardPea();
backwardPea1.x = self.x - 60;
backwardPea1.y = self.y - 20;
backwardPea1.gridY = self.gridY;
backwardpeas.push(backwardPea1);
game.addChild(backwardPea1);
var backwardPea2 = new BackwardPea();
backwardPea2.x = self.x - 60;
backwardPea2.y = self.y + 20;
backwardPea2.gridY = self.gridY;
backwardpeas.push(backwardPea2);
game.addChild(backwardPea2);
LK.getSound('shoot').play();
};
return self;
});
var Spore = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('spore', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.damage = 10;
self.gridY = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridY === self.gridY && self.intersects(zombie)) {
// Check if it's a JesterZombie
if (zombie.isJester) {
// Create reflected spore going back towards plants
var reflectedSpore = new ReflectedSpore();
reflectedSpore.x = zombie.x - 50;
reflectedSpore.y = zombie.y;
reflectedSpore.gridY = zombie.gridY;
reflectedspores.push(reflectedSpore);
game.addChild(reflectedSpore);
// Flash jester zombie to show deflection
LK.effects.flashObject(zombie, 0xffff00, 200);
} else {
zombie.takeDamage(self.damage, 'spore');
LK.getSound('zombie_hit').play();
}
self.destroy();
for (var j = 0; j < spores.length; j++) {
if (spores[j] === self) {
spores.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2200) {
self.destroy();
for (var k = 0; k < spores.length; k++) {
if (spores[k] === self) {
spores.splice(k, 1);
break;
}
}
}
};
return self;
});
var Sun = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 25;
self.lifeTimer = 0;
self.maxLife = 600; // 10 seconds
self.update = function () {
self.lifeTimer++;
if (self.lifeTimer > self.maxLife) {
self.destroy();
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
}
};
self.down = function (x, y, obj) {
sunPoints += self.value;
updateSunDisplay();
self.destroy();
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
LK.getSound('collect').play();
};
return self;
});
var Sunflower = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('sunflower', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 100;
self.maxHealth = 100;
self.sunTimer = 0;
self.sunDelay = 600; // 10 seconds at 60fps
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
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) * 50;
sun.y = self.y + (Math.random() - 0.5) * 50;
suns.push(sun);
game.addChild(sun);
};
return self;
});
var WallNut = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('wallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.cost = 50;
self.health = 300;
self.maxHealth = 300;
self.gridX = 0;
self.gridY = 0;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.destroy();
for (var i = 0; i < plants.length; i++) {
if (plants[i] === self) {
plants.splice(i, 1);
break;
}
}
var gridKey = self.gridX + ',' + self.gridY;
delete plantGrid[gridKey];
} else {
// Visual damage feedback
var damageRatio = self.health / self.maxHealth;
graphics.alpha = 0.3 + damageRatio * 0.7;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8BC34A
});
/****
* Game Code
****/
// Sounds
// Zombies
// Projectiles and collectibles
// Plants
// Game grid and UI elements
// Game mode variables
var gameMode = null; // 'normal' or 'endless'
var gameStarted = false;
var menuVisible = true;
// Game variables
var sunPoints = 150;
var currentWave = 1;
var maxWaves = 20;
var waveTimer = 0;
var waveDelay = 1800; // 30 seconds
var zombieSpawnTimer = 0;
var zombieSpawnDelay = 240; // 4 seconds
var zombiesPerWave = 5;
var zombiesSpawned = 0;
var zombiesKilled = 0;
var totalZombiesToKill = 0;
var waveStarted = false;
var zombieSpawnQueue = [];
// Game arrays
var plants = [];
var zombies = [];
var peas = [];
var frozenpeas = [];
var reflectedpeas = [];
var reflectedfrozenpeas = [];
var backwardpeas = [];
var spores = [];
var reflectedspores = [];
var suns = [];
var plantGrid = {}; // Track occupied grid positions
// Grid settings
var gridStartX = 400;
var gridStartY = 400;
var gridWidth = 320;
var gridHeight = 227;
var gridCols = 5;
var gridRows = 5;
// Selected plant type
var selectedPlantType = null;
// Plant selector management
var currentSelectorPage = 1;
var maxSelectorPages = 2;
// Create lawn grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var tile = game.addChild(LK.getAsset('grass', {
x: gridStartX + col * gridWidth,
y: gridStartY + row * gridHeight,
anchorX: 0,
anchorY: 0
}));
}
}
// Create paths (zombie lanes)
for (var lane = 0; lane < gridRows; lane++) {
var pathTile = game.addChild(LK.getAsset('path', {
x: gridStartX + gridCols * gridWidth,
y: gridStartY + lane * gridHeight,
anchorX: 0,
anchorY: 0
}));
}
// Create house
var house = game.addChild(LK.getAsset('house', {
x: 50,
y: gridStartY + gridRows * gridHeight / 2 - 200,
anchorX: 0,
anchorY: 0
}));
// UI Setup
var sunDisplay = new Text2(sunPoints.toString(), {
size: 80,
fill: 0xFFD700,
stroke: 0x000000,
strokeThickness: 3
});
sunDisplay.anchor.set(0, 0);
LK.gui.topLeft.addChild(sunDisplay);
sunDisplay.x = 120;
sunDisplay.y = 20;
var waveDisplay = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 60,
fill: 0x000000
});
waveDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(waveDisplay);
waveDisplay.y = 20;
// Plant selection buttons - repositioned for new order
var peashooterBtn = LK.getAsset('peashooter', {
x: 250,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(peashooterBtn);
var sunflowerBtn = LK.getAsset('sunflower', {
x: 450,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(sunflowerBtn);
var cherrypopperBtn = LK.getAsset('cherrypopper', {
x: 650,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(cherrypopperBtn);
var wallnutBtn = LK.getAsset('wallnut', {
x: 850,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(wallnutBtn);
var potatomineBtn = LK.getAsset('potatomine', {
x: 1050,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(potatomineBtn);
var snowshooterBtn = LK.getAsset('snowshooter', {
x: 1250,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(snowshooterBtn);
var chomperBtn = LK.getAsset('chomper', {
x: 1450,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(chomperBtn);
var repeaterBtn = LK.getAsset('repeater', {
x: 1650,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(repeaterBtn);
var splitpeaBtn = LK.getAsset('splitpea', {
x: 250,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(splitpeaBtn);
var puffshroomBtn = LK.getAsset('puffshroom', {
x: 450,
y: 2500,
scaleX: 0.8,
scaleY: 0.8,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(puffshroomBtn);
var shovelBtn = LK.getAsset('shovel', {
x: 1900,
y: 150,
scaleX: 1.5,
scaleY: 1.5,
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(shovelBtn);
// Cost display texts - updated positions to match new layout
var peaCostTxt = new Text2('100', {
size: 70,
fill: 0x000000
});
peaCostTxt.anchor.set(0.5, 0);
game.addChild(peaCostTxt);
peaCostTxt.x = 250;
peaCostTxt.y = 2580;
var sunCostTxt = new Text2('50', {
size: 70,
fill: 0x000000
});
sunCostTxt.anchor.set(0.5, 0);
game.addChild(sunCostTxt);
sunCostTxt.x = 450;
sunCostTxt.y = 2580;
var cherryCostTxt = new Text2('150', {
size: 70,
fill: 0x000000
});
cherryCostTxt.anchor.set(0.5, 0);
game.addChild(cherryCostTxt);
cherryCostTxt.x = 650;
cherryCostTxt.y = 2580;
var wallCostTxt = new Text2('50', {
size: 70,
fill: 0x000000
});
wallCostTxt.anchor.set(0.5, 0);
game.addChild(wallCostTxt);
wallCostTxt.x = 850;
wallCostTxt.y = 2580;
var potatomineCosTxt = new Text2('25', {
size: 70,
fill: 0x000000
});
potatomineCosTxt.anchor.set(0.5, 0);
game.addChild(potatomineCosTxt);
potatomineCosTxt.x = 1050;
potatomineCosTxt.y = 2580;
var snowCostTxt = new Text2('175', {
size: 70,
fill: 0x000000
});
snowCostTxt.anchor.set(0.5, 0);
game.addChild(snowCostTxt);
snowCostTxt.x = 1250;
snowCostTxt.y = 2580;
var chomperCostTxt = new Text2('150', {
size: 70,
fill: 0x000000
});
chomperCostTxt.anchor.set(0.5, 0);
game.addChild(chomperCostTxt);
chomperCostTxt.x = 1450;
chomperCostTxt.y = 2580;
var repeaterCostTxt = new Text2('200', {
size: 70,
fill: 0x000000
});
repeaterCostTxt.anchor.set(0.5, 0);
game.addChild(repeaterCostTxt);
repeaterCostTxt.x = 1650;
repeaterCostTxt.y = 2580;
var splitpeaCostTxt = new Text2('125', {
size: 70,
fill: 0x000000
});
splitpeaCostTxt.anchor.set(0.5, 0);
game.addChild(splitpeaCostTxt);
splitpeaCostTxt.x = 250;
splitpeaCostTxt.y = 2580;
var puffshroomCostTxt = new Text2('0', {
size: 70,
fill: 0x000000
});
puffshroomCostTxt.anchor.set(0.5, 0);
game.addChild(puffshroomCostTxt);
puffshroomCostTxt.x = 450;
puffshroomCostTxt.y = 2580;
var shovelCostTxt = new Text2('Free', {
size: 70,
fill: 0x000000
});
shovelCostTxt.anchor.set(0.5, 0);
game.addChild(shovelCostTxt);
shovelCostTxt.x = 1900;
shovelCostTxt.y = 230;
// Add left arrow for plant selector
var leftArrow = new Text2('<', {
size: 120,
fill: 0x000000
});
leftArrow.anchor.set(0.5, 0.5);
game.addChild(leftArrow);
leftArrow.x = 50;
leftArrow.y = 2500;
// Add right arrow for plant selector
var rightArrow = new Text2('>', {
size: 120,
fill: 0x000000
});
rightArrow.anchor.set(0.5, 0.5);
game.addChild(rightArrow);
rightArrow.x = 2000;
rightArrow.y = 2500;
// Calculate total zombies for victory condition
for (var w = 1; w <= maxWaves; w++) {
totalZombiesToKill += zombiesPerWave + Math.floor(w / 2);
}
function updateSunDisplay() {
sunDisplay.setText(sunPoints.toString());
}
function updatePlantSelector() {
// Hide all plants first
peashooterBtn.visible = false;
sunflowerBtn.visible = false;
wallnutBtn.visible = false;
snowshooterBtn.visible = false;
repeaterBtn.visible = false;
cherrypopperBtn.visible = false;
splitpeaBtn.visible = false;
chomperBtn.visible = false;
potatomineBtn.visible = false;
puffshroomBtn.visible = false;
// Hide all cost texts
peaCostTxt.visible = false;
sunCostTxt.visible = false;
wallCostTxt.visible = false;
snowCostTxt.visible = false;
repeaterCostTxt.visible = false;
cherryCostTxt.visible = false;
splitpeaCostTxt.visible = false;
chomperCostTxt.visible = false;
potatomineCosTxt.visible = false;
puffshroomCostTxt.visible = false;
if (currentSelectorPage === 1) {
// First page - show: Peashooter, Sunflower, Cherrypopper, Wallnut, Potatomine, Snowshooter, Chomper, and Repeater
peashooterBtn.visible = true;
sunflowerBtn.visible = true;
cherrypopperBtn.visible = true;
wallnutBtn.visible = true;
potatomineBtn.visible = true;
snowshooterBtn.visible = true;
chomperBtn.visible = true;
repeaterBtn.visible = true;
// Show corresponding cost texts
peaCostTxt.visible = true;
sunCostTxt.visible = true;
cherryCostTxt.visible = true;
wallCostTxt.visible = true;
potatomineCosTxt.visible = true;
snowCostTxt.visible = true;
chomperCostTxt.visible = true;
repeaterCostTxt.visible = true;
} else if (currentSelectorPage === 2) {
// Second page - show splitpea and puffshroom (only from wave 11)
splitpeaBtn.visible = true;
splitpeaCostTxt.visible = true;
if (currentWave >= 11) {
puffshroomBtn.visible = true;
puffshroomCostTxt.visible = true;
}
}
}
function getGridPosition(x, y) {
var col = Math.floor((x - gridStartX) / gridWidth);
var row = Math.floor((y - gridStartY) / gridHeight);
if (col >= 0 && col < gridCols && row >= 0 && row < gridRows) {
return {
col: col,
row: row,
x: gridStartX + col * gridWidth + gridWidth / 2,
y: gridStartY + row * gridHeight + gridHeight / 2
};
}
return null;
}
function spawnZombie() {
var lane = Math.floor(Math.random() * gridRows);
var zombieType = Math.random();
var zombie;
// Higher spawn rates for special zombies from wave 15 onwards
if (currentWave >= 15) {
if (zombieType < 0.22) {
zombie = new Gargantuar();
} else if (zombieType < 0.44) {
zombie = new Zomboni();
} else if (zombieType < 0.58) {
zombie = new JesterZombie();
} else if (zombieType < 0.68) {
zombie = new TankZombie();
} else if (zombieType < 0.73) {
zombie = new JackInBoxZombie();
} else if (zombieType < 0.79) {
zombie = new MinerZombie();
} else if (zombieType < 0.85) {
zombie = new FastZombie();
} else if (zombieType < 0.91) {
zombie = new NewspaperZombie();
} else if (zombieType < 0.97) {
zombie = new VeteranNewspaperZombie();
} else {
zombie = new BasicZombie();
}
} else {
// Original spawn rates for waves before 15
if (currentWave > 8 && zombieType < 0.05) {
zombie = new Gargantuar();
} else if (currentWave > 6 && zombieType < 0.15) {
zombie = new Zomboni();
} else if (currentWave > 5 && zombieType < 0.25) {
zombie = new TankZombie();
} else if (currentWave > 4 && zombieType < 0.35) {
zombie = new JesterZombie();
} else if (currentWave > 2 && zombieType < 0.45) {
zombie = new JackInBoxZombie();
} else if (currentWave > 3 && zombieType < 0.60) {
zombie = new MinerZombie();
} else if (currentWave > 3 && zombieType < 0.75) {
zombie = new FastZombie();
} else if (currentWave > 1 && zombieType < 0.85) {
zombie = new NewspaperZombie();
} else if (currentWave > 7 && zombieType < 0.95) {
zombie = new VeteranNewspaperZombie();
} else {
zombie = new BasicZombie();
}
}
zombie.x = gridStartX + gridCols * gridWidth + 100;
zombie.y = gridStartY + lane * gridHeight + gridHeight / 2;
zombie.gridY = lane;
// Add spawn delay animation - zombie starts invisible and fades in
zombie.alpha = 0;
zombie.scaleX = 0.5;
zombie.scaleY = 0.5;
// Animate zombie spawning in
tween(zombie, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeOut
});
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawned++;
}
function startNextWave() {
// In endless mode, there's no wave limit
var shouldStartWave = gameMode === 'normal' && currentWave <= maxWaves || gameMode === 'endless';
if (shouldStartWave) {
// In endless mode, increase difficulty more aggressively
if (gameMode === 'endless') {
zombiesPerWave = 5 + Math.floor(currentWave / 1.5); // Faster increase in endless mode
} else {
zombiesPerWave = 5 + Math.floor(currentWave / 2);
}
zombiesSpawned = 0;
waveStarted = false;
zombieSpawnQueue = [];
// Display wave differently for endless mode
if (gameMode === 'endless') {
waveDisplay.setText('Wave: ' + currentWave + ' (Endless)');
} else {
waveDisplay.setText('Wave: ' + currentWave + '/' + maxWaves);
}
// Play Ultimate Battle music for all waves
LK.playMusic('ultimate_battle');
// Change background color to dark blue from wave 11
if (currentWave >= 11) {
game.setBackgroundColor(0x191970); // Dark blue
}
// Flash screen red when wave increases
LK.effects.flashScreen(0xff0000, 1000);
// Add delay before wave actually starts spawning zombies
// For wave 1, add 10 second delay. For other waves, 2 second delay
var waveStartDelay = currentWave === 1 ? 10000 : 2000;
LK.setTimeout(function () {
waveStarted = true;
// Pre-queue zombie spawn times with delays
// Use faster spawn delay from wave 5 onwards with higher spawn rate, and even faster from wave 11
// In endless mode, spawn rate increases even more aggressively
var currentSpawnDelay;
if (gameMode === 'endless') {
// Endless mode has progressively faster spawns
if (currentWave >= 20) {
currentSpawnDelay = 30; // Very fast from wave 20 (0.5 seconds)
} else if (currentWave >= 15) {
currentSpawnDelay = 45; // Faster from wave 15 (0.75 seconds)
} else if (currentWave >= 10) {
currentSpawnDelay = 60; // Fast from wave 10 (1 second)
} else if (currentWave >= 5) {
currentSpawnDelay = 90; // Medium speed from wave 5 (1.5 seconds)
} else {
currentSpawnDelay = zombieSpawnDelay; // Normal speed (4 seconds)
}
} else {
// Normal mode spawn rates
if (currentWave >= 11) {
currentSpawnDelay = 60; // Even faster from wave 11 (1 second)
} else if (currentWave >= 5) {
currentSpawnDelay = 90; // Higher spawn rate from wave 5 (1.5 seconds)
} else {
currentSpawnDelay = zombieSpawnDelay; // Normal speed (4 seconds)
}
}
for (var i = 0; i < zombiesPerWave; i++) {
zombieSpawnQueue.push({
spawnTime: LK.ticks + i * currentSpawnDelay + Math.random() * 60,
// Add some randomness (reduced for faster waves)
spawned: false
});
}
}, waveStartDelay);
}
}
// Generate sun periodically
var sunGenerationTimer = 0;
var sunGenerationDelay = 480; // 8 seconds
// Event handlers
peashooterBtn.down = function () {
if (sunPoints >= 100) {
selectedPlantType = 'peashooter';
LK.effects.flashObject(peashooterBtn, 0x00ff00, 300);
}
};
sunflowerBtn.down = function () {
if (sunPoints >= 50) {
selectedPlantType = 'sunflower';
LK.effects.flashObject(sunflowerBtn, 0x00ff00, 300);
}
};
wallnutBtn.down = function () {
if (sunPoints >= 50) {
selectedPlantType = 'wallnut';
LK.effects.flashObject(wallnutBtn, 0x00ff00, 300);
}
};
snowshooterBtn.down = function () {
if (sunPoints >= 175) {
selectedPlantType = 'snowshooter';
LK.effects.flashObject(snowshooterBtn, 0x00ff00, 300);
}
};
repeaterBtn.down = function () {
if (sunPoints >= 200) {
selectedPlantType = 'repeater';
LK.effects.flashObject(repeaterBtn, 0x00ff00, 300);
}
};
cherrypopperBtn.down = function () {
if (sunPoints >= 150) {
selectedPlantType = 'cherrypopper';
LK.effects.flashObject(cherrypopperBtn, 0x00ff00, 300);
}
};
splitpeaBtn.down = function () {
if (sunPoints >= 125) {
selectedPlantType = 'splitpea';
LK.effects.flashObject(splitpeaBtn, 0x00ff00, 300);
}
};
chomperBtn.down = function () {
if (sunPoints >= 150) {
selectedPlantType = 'chomper';
LK.effects.flashObject(chomperBtn, 0x00ff00, 300);
}
};
potatomineBtn.down = function () {
if (sunPoints >= 25) {
selectedPlantType = 'potatomine';
LK.effects.flashObject(potatomineBtn, 0x00ff00, 300);
}
};
puffshroomBtn.down = function () {
if (currentWave >= 11) {
selectedPlantType = 'puffshroom';
LK.effects.flashObject(puffshroomBtn, 0x00ff00, 300);
}
};
shovelBtn.down = function () {
selectedPlantType = 'shovel';
LK.effects.flashObject(shovelBtn, 0x00ff00, 300);
};
leftArrow.down = function () {
if (currentSelectorPage > 1) {
currentSelectorPage--;
updatePlantSelector();
LK.effects.flashObject(leftArrow, 0x00ff00, 200);
} else {
// Flash gray if can't go back further
LK.effects.flashObject(leftArrow, 0x888888, 200);
}
};
rightArrow.down = function () {
if (currentSelectorPage < maxSelectorPages) {
currentSelectorPage++;
updatePlantSelector();
LK.effects.flashObject(rightArrow, 0x00ff00, 200);
} else {
// Flash gray if can't go forward further
LK.effects.flashObject(rightArrow, 0x888888, 200);
}
};
game.down = function (x, y, obj) {
// Don't allow plant placement if menu is visible
if (menuVisible || !gameStarted) {
return;
}
var gridPos = getGridPosition(x, y);
if (gridPos && selectedPlantType) {
var gridKey = gridPos.col + ',' + gridPos.row;
// Handle shovel - remove existing plant
if (selectedPlantType === 'shovel') {
if (plantGrid[gridKey]) {
var plantToRemove = plantGrid[gridKey];
// Remove from plants array
for (var i = 0; i < plants.length; i++) {
if (plants[i] === plantToRemove) {
plants.splice(i, 1);
break;
}
}
// Remove from grid
delete plantGrid[gridKey];
// Destroy the plant
plantToRemove.destroy();
selectedPlantType = null;
}
return;
}
// Check if position is already occupied
if (plantGrid[gridKey]) {
return;
}
var plant = null;
var cost = 0;
if (selectedPlantType === 'peashooter' && sunPoints >= 100) {
plant = new Peashooter();
cost = 100;
} else if (selectedPlantType === 'sunflower' && sunPoints >= 50) {
plant = new Sunflower();
cost = 50;
} else if (selectedPlantType === 'wallnut' && sunPoints >= 50) {
plant = new WallNut();
cost = 50;
} else if (selectedPlantType === 'snowshooter' && sunPoints >= 175) {
plant = new Snowshooter();
cost = 175;
} else if (selectedPlantType === 'repeater' && sunPoints >= 200) {
plant = new Repeater();
cost = 200;
} else if (selectedPlantType === 'cherrypopper' && sunPoints >= 150) {
plant = new CherryPopper();
cost = 150;
} else if (selectedPlantType === 'splitpea' && sunPoints >= 125) {
plant = new SplitPea();
cost = 125;
} else if (selectedPlantType === 'chomper' && sunPoints >= 150) {
plant = new Chomper();
cost = 150;
} else if (selectedPlantType === 'potatomine' && sunPoints >= 25) {
plant = new Potatomine();
cost = 25;
} else if (selectedPlantType === 'puffshroom' && currentWave >= 11) {
plant = new Puffshroom();
cost = 0;
}
if (plant) {
plant.x = gridPos.x;
plant.y = gridPos.y;
plant.gridX = gridPos.col;
plant.gridY = gridPos.row;
plants.push(plant);
game.addChild(plant);
plantGrid[gridKey] = plant;
sunPoints -= cost;
updateSunDisplay();
selectedPlantType = null;
// 50% chance to play either seed_plant1 or seed_plant2
if (Math.random() < 0.5) {
LK.getSound('seed_plant1').play();
} else {
LK.getSound('seed_plant2').play();
}
}
}
};
game.update = function () {
// Don't update game logic if menu is visible
if (menuVisible || !gameStarted) {
return;
}
// Generate sun periodically
sunGenerationTimer++;
if (sunGenerationTimer >= sunGenerationDelay) {
var sun = new Sun();
sun.x = 200 + Math.random() * 1600;
sun.y = -100; // Start above screen
sun.targetY = 200 + Math.random() * 1000; // Final position
sun.isFalling = true; // Mark as falling sun
// Animate falling from top to bottom
tween(sun, {
y: sun.targetY
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
sun.isFalling = false;
}
});
suns.push(sun);
game.addChild(sun);
sunGenerationTimer = 0;
}
// Wave management
if (currentWave <= maxWaves) {
waveTimer++;
// Spawn zombies for current wave using queue system
if (waveStarted && zombiesSpawned < zombiesPerWave) {
for (var i = 0; i < zombieSpawnQueue.length; i++) {
var queueItem = zombieSpawnQueue[i];
if (!queueItem.spawned && LK.ticks >= queueItem.spawnTime) {
spawnZombie();
queueItem.spawned = true;
break; // Only spawn one zombie per frame
}
}
}
// Start next wave
if (zombiesSpawned >= zombiesPerWave && zombies.length === 0) {
currentWave++;
if (currentWave <= maxWaves) {
startNextWave();
waveTimer = 0;
}
}
}
// Check victory condition (only for normal mode)
if (gameMode === 'normal' && currentWave > maxWaves && zombies.length === 0) {
LK.showYouWin();
}
// Handle slow effects on zombies
for (var z = 0; z < zombies.length; z++) {
var zombie = zombies[z];
if (zombie.slowTimer) {
zombie.slowTimer--;
if (zombie.slowTimer <= 0) {
// Restore original speed
if (zombie.originalSpeed) {
zombie.speed = zombie.originalSpeed;
}
// Remove slow tint
tween(zombie, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
zombie.slowTimer = null;
}
}
}
// Update button availability
peashooterBtn.alpha = sunPoints >= 100 ? 1.0 : 0.5;
sunflowerBtn.alpha = sunPoints >= 50 ? 1.0 : 0.5;
wallnutBtn.alpha = sunPoints >= 50 ? 1.0 : 0.5;
snowshooterBtn.alpha = sunPoints >= 175 ? 1.0 : 0.5;
repeaterBtn.alpha = sunPoints >= 200 ? 1.0 : 0.5;
cherrypopperBtn.alpha = sunPoints >= 150 ? 1.0 : 0.5;
splitpeaBtn.alpha = sunPoints >= 125 ? 1.0 : 0.5;
chomperBtn.alpha = sunPoints >= 150 ? 1.0 : 0.5;
potatomineBtn.alpha = sunPoints >= 25 ? 1.0 : 0.5;
puffshroomBtn.alpha = currentWave >= 11 && sunPoints >= 0 ? 1.0 : 0.5;
shovelBtn.alpha = 1.0; // Shovel is always available
// Update arrow button availability
updateArrowButtons();
};
// Initialize plant selector
updatePlantSelector();
// Update arrow button availability in main update loop
function updateArrowButtons() {
leftArrow.alpha = currentSelectorPage > 1 ? 1.0 : 0.5;
rightArrow.alpha = currentSelectorPage < maxSelectorPages ? 1.0 : 0.5;
}
// Create menu buttons
var normalBtn = new Text2('NORMAL', {
size: 120,
fill: 0x00ff00,
stroke: 0x000000,
strokeThickness: 4
});
normalBtn.anchor.set(0.5, 0.5);
normalBtn.x = 1024;
normalBtn.y = 1200;
game.addChild(normalBtn);
var endlessBtn = new Text2('ENDLESS', {
size: 120,
fill: 0xff6600,
stroke: 0x000000,
strokeThickness: 4
});
endlessBtn.anchor.set(0.5, 0.5);
endlessBtn.x = 1024;
endlessBtn.y = 1400;
game.addChild(endlessBtn);
var modeDescriptionTxt = new Text2('Choose Game Mode:', {
size: 100,
fill: 0x000000
});
modeDescriptionTxt.anchor.set(0.5, 0.5);
modeDescriptionTxt.x = 1024;
modeDescriptionTxt.y = 1000;
game.addChild(modeDescriptionTxt);
var normalDescTxt = new Text2('20 waves with victory condition', {
size: 60,
fill: 0x333333
});
normalDescTxt.anchor.set(0.5, 0.5);
normalDescTxt.x = 1024;
normalDescTxt.y = 1280;
game.addChild(normalDescTxt);
var endlessDescTxt = new Text2('Infinite waves with increasing difficulty', {
size: 60,
fill: 0x333333
});
endlessDescTxt.anchor.set(0.5, 0.5);
endlessDescTxt.x = 1024;
endlessDescTxt.y = 1480;
game.addChild(endlessDescTxt);
// Menu button event handlers
normalBtn.down = function () {
gameMode = 'normal';
startGame();
};
endlessBtn.down = function () {
gameMode = 'endless';
startGame();
};
function startGame() {
gameStarted = true;
menuVisible = false;
// Hide menu elements
normalBtn.visible = false;
endlessBtn.visible = false;
modeDescriptionTxt.visible = false;
normalDescTxt.visible = false;
endlessDescTxt.visible = false;
// Show game UI elements
sunDisplay.visible = true;
waveDisplay.visible = true;
peashooterBtn.visible = true;
sunflowerBtn.visible = true;
cherrypopperBtn.visible = true;
wallnutBtn.visible = true;
potatomineBtn.visible = true;
snowshooterBtn.visible = true;
chomperBtn.visible = true;
repeaterBtn.visible = true;
splitpeaBtn.visible = true;
puffshroomBtn.visible = true;
shovelBtn.visible = true;
leftArrow.visible = true;
rightArrow.visible = true;
// Show cost texts
peaCostTxt.visible = true;
sunCostTxt.visible = true;
cherryCostTxt.visible = true;
wallCostTxt.visible = true;
potatomineCosTxt.visible = true;
snowCostTxt.visible = true;
chomperCostTxt.visible = true;
repeaterCostTxt.visible = true;
splitpeaCostTxt.visible = true;
puffshroomCostTxt.visible = true;
shovelCostTxt.visible = true;
// Update plant selector to show correct page
updatePlantSelector();
// Start first wave
startNextWave();
}
// Initially hide game UI elements
sunDisplay.visible = false;
waveDisplay.visible = false;
peashooterBtn.visible = false;
sunflowerBtn.visible = false;
cherrypopperBtn.visible = false;
wallnutBtn.visible = false;
potatomineBtn.visible = false;
snowshooterBtn.visible = false;
chomperBtn.visible = false;
repeaterBtn.visible = false;
splitpeaBtn.visible = false;
puffshroomBtn.visible = false;
shovelBtn.visible = false;
leftArrow.visible = false;
rightArrow.visible = false;
peaCostTxt.visible = false;
sunCostTxt.visible = false;
cherryCostTxt.visible = false;
wallCostTxt.visible = false;
potatomineCosTxt.visible = false;
snowCostTxt.visible = false;
chomperCostTxt.visible = false;
repeaterCostTxt.visible = false;
splitpeaCostTxt.visible = false;
puffshroomCostTxt.visible = false;
shovelCostTxt.visible = false;