User prompt
make it so that when you start the game a menu appears in which two buttons can be selected, the first button says NORMAL and the second button says ENDLESS and when you press the button that says "normal" the game will start normally with waves and when you press the "endless" button the game will start but the waves will be infinite and little by little the zombie spawn will increase in this endless mode
User prompt
Make the slow effect not slow the zombie down as much.
User prompt
Make the zombie spawn rate higher on wave 5 and onwards and make the sound asset called zombie_eat play when a zombie damages a plant.
User prompt
Make the sound asset called zombie_freezed play when a zombie is slowed and the sound asset called explosion play when the jackInBoxZombie and cherry popper plant explode.
User prompt
Make the veteran newspaper zombie unstoppable and have the sound asset called zombie_hit play when a projectile hits a zombie and so that the sound when placing a plant is not so repetitive have a 50% chance that seedplant1 will play and another 50% chance that seedplant2 will play
User prompt
Haz que el zombie veterano del periódico no pueda ser frenado y haz que el activo de sonido llamado zombie_hit se reproduzca cuando un proyectil golpee a un zombie y para que el sonido al colocar una planta no sea tan repetitivo que haiga un 50% de que suene el seedplant1 y otro 50% de que suene el seedplant2
User prompt
Make one of these two assets sound when a plant is placed in the garden: seed_plant1 or seed_plant2
User prompt
Make one of these two assets sound when a plant is placed in the garden: seed_plant1 or seed_plant2
User prompt
Make the Ultimate Battle music asset the game's music.
User prompt
create an asset that is the game's music
User prompt
the music doesn't play
User prompt
The musical asset called "grasswalk" has been used as music for waves 1 to 10
User prompt
Make the asset veteranewspaperZombie a zombie and have double the life of a basiczombie and since it has the damage life, its speed and damage it does to plants are x5.
User prompt
creates a asset zombie called veteranewspaper that has twice the life of the newspaper and by having half its life its speed and damage that it does to plants is x5 more
User prompt
creates a zombie called veteranewspaper that has twice the life of the newspaper and by having half its life its speed and damage that it does to plants is x5 more
User prompt
Make the gargantuar when it has half health, the fastzombie that spawns appears in the third square of the garden
User prompt
Creates a plant called puffshroom that costs 0 suns and shoots spores and creates a separate resource for the spore and has a range to detect a zombie of 3 tiles and can only be used from wave 11 onwards and appears on page 2 of the plant selector ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* 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; ===================================================================
--- original.js
+++ change.js
@@ -1680,14 +1680,18 @@
/****
* Game Code
****/
-// Game variables
-// Game grid and UI elements
-// Plants
-// Projectiles and collectibles
-// Zombies
// 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;
@@ -2119,14 +2123,26 @@
game.addChild(zombie);
zombiesSpawned++;
}
function startNextWave() {
- if (currentWave <= maxWaves) {
- zombiesPerWave = 5 + Math.floor(currentWave / 2);
+ // 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 = [];
- waveDisplay.setText('Wave: ' + currentWave + '/' + maxWaves);
+ // 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) {
@@ -2140,15 +2156,32 @@
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 (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)
+ 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 {
- currentSpawnDelay = zombieSpawnDelay; // Normal speed (4 seconds)
+ // 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,
@@ -2247,8 +2280,12 @@
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
@@ -2327,8 +2364,12 @@
}
}
};
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();
@@ -2372,10 +2413,10 @@
waveTimer = 0;
}
}
}
- // Check victory condition
- if (currentWave > maxWaves && zombies.length === 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++) {
@@ -2419,6 +2460,127 @@
function updateArrowButtons() {
leftArrow.alpha = currentSelectorPage > 1 ? 1.0 : 0.5;
rightArrow.alpha = currentSelectorPage < maxSelectorPages ? 1.0 : 0.5;
}
-// Start first wave
-startNextWave();
\ No newline at end of file
+// 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;
\ No newline at end of file