/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BomberZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('bomberZombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
var head = self.attachAsset('bomberZombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
// Add explosive belt around waist
var belt = self.attachAsset('explosiveBelt', {
anchorX: 0.5,
anchorY: 0.5
});
belt.x = 0;
belt.y = 15;
// Add multiple bomb indicators on the belt
for (var i = 0; i < 3; i++) {
var bombIndicator = self.attachAsset('bombIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bombIndicator.x = (i - 1) * 25; // Spread across belt
bombIndicator.y = 15;
// Add blinking animation to bomb indicators
var delay = i * 200; // Stagger the blinking
LK.setTimeout(function () {
var blinkTimer = LK.setInterval(function () {
bombIndicator.alpha = bombIndicator.alpha > 0.5 ? 0.3 : 1.0;
}, 800);
}, delay);
}
// Add fuse wire coming from belt
var fuse = self.attachAsset('fuseWire', {
anchorX: 0.5,
anchorY: 1
});
fuse.x = 30;
fuse.y = 5;
fuse.rotation = Math.PI / 6; // Slight angle
// Add sparking effect to fuse tip
var fuseTip = self.attachAsset('bombIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
fuseTip.x = 35;
fuseTip.y = -5;
fuseTip.scaleX = 0.5;
fuseTip.scaleY = 0.5;
// Animate fuse tip sparking
LK.setInterval(function () {
fuseTip.alpha = Math.random() > 0.5 ? 1.0 : 0.2;
fuseTip.tint = Math.random() > 0.5 ? 0xFFFF00 : 0xFF4500;
}, 300);
self.health = 80; // Lower health than regular zombie - easy to kill but dangerous
self.speed = 0.4; // Slightly faster than regular zombie
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 8;
self.hasAttackedHouse = false;
self.hasExploded = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original orange tint
if (self.body) {
self.body.tint = 0xFF4500;
}
if (self.head) {
self.head.tint = 0xFF4500;
}
}
if (!self.isEating && !self.hasExploded) {
self.x -= self.speed;
}
// Explode when health drops to 0 or below
if (self.health <= 0 && !self.hasExploded) {
self.hasExploded = true;
// Explosive death - damages plants in 2x2 area around bomber
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
var distance = Math.sqrt(Math.pow(self.x - plant.x, 2) + Math.pow(self.y - plant.y, 2));
if (distance < 150) {
// Explosion radius
plant.health -= 40; // Significant damage to nearby plants
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
}
}
}
// Flash explosion effect
LK.effects.flashScreen(0xFF4500, 500);
// Remove self
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
self.destroy();
return;
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse && !self.hasExploded) {
houseHealth -= 20; // Extra damage when reaching house
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
// Explode at house for extra damage
self.hasExploded = true;
LK.effects.flashScreen(0xFF4500, 500);
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
if (!self.hasExploded) {
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 35 ticks while touching WallNut
if (LK.ticks - self.lastDamageTime >= 35) {
plant.health -= 8;
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 50 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 50) {
plant.health -= 20; // Bomber zombie destroys plants with explosive damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bulletCore', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.damage = 50;
self.row = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
if (zombie.gridRow === self.row && Math.abs(self.x - zombie.x) < 30) {
// Handle ShieldZombie shield mechanics
if (zombie instanceof ShieldZombie && zombie.hasShield) {
zombie.shieldHealth -= self.damage;
// Shield takes damage instead of zombie
} else {
zombie.health -= self.damage;
}
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
}
self.destroy();
for (var j = bullets.length - 1; j >= 0; j--) {
if (bullets[j] === self) {
bullets.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2048) {
self.destroy();
for (var k = bullets.length - 1; k >= 0; k--) {
if (bullets[k] === self) {
bullets.splice(k, 1);
break;
}
}
}
};
return self;
});
var CherryBomb = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('cherryImage', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 50;
self.gridRow = 0;
self.gridCol = 0;
self.fuseTime = 180; // 3 seconds at 60fps
self.planted = false;
self.update = function () {
if (!self.planted) {
self.planted = true;
self.plantedTick = LK.ticks;
}
// Check if any zombie is touching the cherry bomb
var shouldExplode = false;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(self.x - zombie.x, 2) + Math.pow(self.y - zombie.y, 2));
if (distance < 50) {
// Touch distance - much smaller than explosion radius
shouldExplode = true;
break;
}
}
// Explode after fuse time OR when touched by zombie
if (shouldExplode || LK.ticks - self.plantedTick > self.fuseTime) {
LK.getSound('cherybomb').play(); // Play cherrybomb sound when exploding
// Deal damage to all zombies in 3x3 area around bomb
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(self.x - zombie.x, 2) + Math.pow(self.y - zombie.y, 2));
if (distance < 200) {
// Check zombie type for damage calculation
if (zombie instanceof TankZombie) {
// Deal 75% percentage damage to tank zombies
var damageAmount = Math.floor(zombie.health * 0.75);
zombie.health -= damageAmount;
} else {
// Instantly kill all other zombie types (regular, fast, shield, jumper)
zombie.health = 0;
}
// Destroy zombie if health drops to or below 0
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
}
}
}
// Remove self
for (var j = plants.length - 1; j >= 0; j--) {
if (plants[j] === self) {
plants.splice(j, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var DoublePeashooter = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('placedDoublePeaBody', {
anchorX: 0.5,
anchorY: 0.5
});
var cannon1 = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.3
});
cannon1.x = 25;
cannon1.y = -10;
var cannon2 = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.7
});
cannon2.x = 25;
cannon2.y = 10;
self.health = 100;
self.lastShot = 0;
self.shootDelay = 100; // Faster than regular peashooter
self.gridRow = 0;
self.gridCol = 0;
self.shoot = function () {
// Shoot two bullets
var bullet1 = new Bullet();
bullet1.x = self.x + 50;
bullet1.y = self.y - 10;
bullet1.row = self.gridRow;
bullets.push(bullet1);
game.addChild(bullet1);
var bullet2 = new Bullet();
bullet2.x = self.x + 50;
bullet2.y = self.y + 10;
bullet2.row = self.gridRow;
bullets.push(bullet2);
game.addChild(bullet2);
LK.getSound('peasshooter').play(); // Play peashooter sound when shooting
};
self.update = function () {
if (LK.ticks - self.lastShot > self.shootDelay) {
var zombieInRow = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridRow === self.gridRow && zombies[i].x > self.x) {
zombieInRow = true;
break;
}
}
if (zombieInRow) {
self.shoot();
self.lastShot = LK.ticks;
}
}
};
return self;
});
var FastZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0xFF6666; // Reddish tint to distinguish from regular zombie
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
head.tint = 0xFF6666; // Reddish tint to distinguish from regular zombie
self.health = 100; // Lower health than regular zombie
self.speed = 0.8; // Faster than regular zombie
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 15; // More damage when eating
self.hasAttackedHouse = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original red tint
if (self.body) {
self.body.tint = 0xFF6666;
}
if (self.head) {
self.head.tint = 0xFF6666;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if reached house - zombie damages house once with 15 health
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 15;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
// Destroy zombie after attacking house
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
// Check game over condition
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 25 ticks (faster than regular zombie) while touching WallNut
if (LK.ticks - self.lastDamageTime >= 25) {
plant.health -= 15; // Fast zombie damages wallnut more
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 45 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 45) {
plant.health -= 15; // Fast zombie destroys plants quickly
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
var IceBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('iceBulletCore', {
anchorX: 0.5,
anchorY: 0.5
});
// Add ice crystal effects around the main bullet
for (var i = 0; i < 4; i++) {
var crystal = self.attachAsset('iceCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = i / 4 * Math.PI * 2;
crystal.x = Math.cos(angle) * 8;
crystal.y = Math.sin(angle) * 8;
crystal.rotation = angle;
crystal.scaleX = 0.3;
crystal.scaleY = 0.4;
crystal.alpha = 0.8;
// Add sparkle animation to crystals
tween(crystal, {
alpha: 0.3
}, {
duration: 300,
easing: tween.easeInOut
});
tween(crystal, {
alpha: 0.8
}, {
duration: 300,
easing: tween.easeInOut
});
}
self.speed = 6; // Slightly faster than regular bullet
self.damage = 40;
self.row = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
if (zombie.gridRow === self.row && Math.abs(self.x - zombie.x) < 30) {
// Handle ShieldZombie shield mechanics
if (zombie instanceof ShieldZombie && zombie.hasShield) {
zombie.shieldHealth -= self.damage;
// Shield takes damage instead of zombie
} else {
zombie.health -= self.damage;
}
// Apply freeze effect - slow down zombie for 3 seconds
if (!zombie.isFrozen) {
zombie.originalSpeed = zombie.speed;
zombie.speed = zombie.speed * 0.3; // Slow to 30% speed
zombie.isFrozen = true;
zombie.frozenUntil = LK.ticks + 180; // 3 seconds at 60fps
// Visual freeze effect
if (zombie.body) {
zombie.body.tint = 0x87CEEB;
}
if (zombie.head) {
zombie.head.tint = 0x87CEEB;
}
}
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
}
self.destroy();
for (var j = bullets.length - 1; j >= 0; j--) {
if (bullets[j] === self) {
bullets.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2048) {
self.destroy();
for (var k = bullets.length - 1; k >= 0; k--) {
if (bullets[k] === self) {
bullets.splice(k, 1);
break;
}
}
}
};
return self;
});
var IcePeashooter = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('icePeashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Add ice crystals around the body for visual effect
for (var i = 0; i < 6; i++) {
var crystal = self.attachAsset('iceCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = i / 6 * Math.PI * 2;
crystal.x = Math.cos(angle) * 30;
crystal.y = Math.sin(angle) * 30;
crystal.rotation = angle;
crystal.scaleX = 0.6;
crystal.scaleY = 0.8;
}
var cannon = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.5
});
cannon.x = 25;
cannon.y = 0;
cannon.tint = 0x87CEEB; // Light blue tint for ice effect
self.health = 100;
self.lastShot = 0;
self.shootDelay = 120; // Faster than regular peashooter
self.gridRow = 0;
self.gridCol = 0;
self.shoot = function () {
var bullet = new IceBullet();
bullet.x = self.x + 50;
bullet.y = self.y;
bullet.row = self.gridRow;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('peasshooter').play(); // Play peashooter sound when shooting
};
self.update = function () {
if (LK.ticks - self.lastShot > self.shootDelay) {
// Check if there's a zombie in this row
var zombieInRow = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridRow === self.gridRow && zombies[i].x > self.x) {
zombieInRow = true;
break;
}
}
if (zombieInRow) {
self.shoot();
self.lastShot = LK.ticks;
}
}
};
return self;
});
var JumperZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0x00FF00; // Green tint for jumper zombie
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
head.tint = 0x00FF00;
self.health = 120;
self.speed = 0.5;
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 12;
self.hasAttackedHouse = false;
self.canJump = true;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original green tint
if (self.body) {
self.body.tint = 0x00FF00;
}
if (self.head) {
self.head.tint = 0x00FF00;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Jump over first plant encountered
if (self.canJump) {
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 80 && self.x > plant.x) {
self.x = plant.x - 80; // Jump past the plant
self.canJump = false;
break;
}
}
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 12;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants (only if can't jump)
if (!self.canJump) {
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 30 ticks while touching WallNut
if (LK.ticks - self.lastDamageTime >= 30) {
plant.health -= 12; // Jumper zombie damages wallnut
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 60 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 60) {
plant.health -= 15; // Jumper zombie destroys plants with high damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating && !self.canJump) {
self.lastDamageTime = undefined;
}
}
};
return self;
});
// Event handlers
var LevelButton = Container.expand(function () {
var self = Container.call(this);
var buttonBg = self.attachAsset('levelButton', {
anchorX: 0.5,
anchorY: 0.5
});
self.levelNumber = 1;
self.setLevelNumber = function (number) {
self.levelNumber = number;
self.levelText.setText(number.toString());
};
self.isUnlocked = true;
self.stars = 0;
self.maxStars = 3;
// Create level number text
self.levelText = new Text2(self.levelNumber.toString(), {
size: 50,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0.5, 0.5);
self.levelText.y = -30;
self.addChild(self.levelText);
// Create stars display
self.starContainers = [];
for (var i = 0; i < self.maxStars; i++) {
var star = LK.getAsset('starShape', {
anchorX: 0.5,
anchorY: 0.5
});
star.x = (i - 1) * 50;
star.y = 30;
star.tint = 0x666666; // Dark by default
self.addChild(star);
self.starContainers.push(star);
}
self.setStars = function (starCount) {
self.stars = starCount;
for (var i = 0; i < self.maxStars; i++) {
if (i < starCount) {
self.starContainers[i].tint = 0xFFD700; // Golden
} else {
self.starContainers[i].tint = 0x666666; // Dark
}
}
};
self.setUnlocked = function (unlocked) {
self.isUnlocked = unlocked;
if (unlocked) {
buttonBg.tint = 0xFFFFFF;
self.levelText.tint = 0xFFFFFF;
} else {
buttonBg.tint = 0x666666;
self.levelText.tint = 0x666666;
}
};
self.down = function (x, y, obj) {
if (self.isUnlocked) {
LK.getSound('buttonClick').play();
// Start the selected level
startLevel(self.levelNumber);
} else {
// Play a different sound or show feedback for locked levels
console.log('Level ' + self.levelNumber + ' is locked. Complete previous levels first.');
}
};
return self;
});
// Plant classes
var Peashooter = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
var cannon = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.5
});
cannon.x = 25;
cannon.y = 0;
self.health = 100;
self.lastShot = 0;
self.shootDelay = 150; // 2.5 seconds at 60fps
self.gridRow = 0;
self.gridCol = 0;
self.shoot = function () {
var bullet = new Bullet();
bullet.x = self.x + 50;
bullet.y = self.y;
bullet.row = self.gridRow;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('peasshooter').play(); // Play peashooter sound when shooting
};
self.update = function () {
if (LK.ticks - self.lastShot > self.shootDelay) {
// Check if there's a zombie in this row
var zombieInRow = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridRow === self.gridRow && zombies[i].x > self.x) {
zombieInRow = true;
break;
}
}
if (zombieInRow) {
self.shoot();
self.lastShot = LK.ticks;
}
}
};
return self;
});
var ShieldZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0x8B4513; // Brown tint for shield zombie
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
head.tint = 0x8B4513;
// Create shield visual with rim, main body, and center decoration
var shieldRim = self.attachAsset('shieldRim', {
anchorX: 0.5,
anchorY: 0.5
});
shieldRim.x = 25; // Position shield in front of zombie
shieldRim.y = 0;
var shield = self.attachAsset('shieldVisual', {
anchorX: 0.5,
anchorY: 0.5
});
shield.x = 25; // Position shield in front of zombie
shield.y = 0;
var shieldCenter = self.attachAsset('shieldCenter', {
anchorX: 0.5,
anchorY: 0.5
});
shieldCenter.x = 25; // Position shield center decoration
shieldCenter.y = 0;
self.health = 150; // Zombie health (only takes damage after shield is destroyed)
self.shieldHealth = 150; // Shield health (must be destroyed first)
self.speed = 0.25; // Slower than regular zombie due to shield weight
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 12;
self.hasAttackedHouse = false;
self.hasShield = true; // Track if shield is still active
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original brown tint
if (self.body) {
self.body.tint = 0x8B4513;
}
if (self.head) {
self.head.tint = 0x8B4513;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if shield is destroyed
if (self.hasShield && self.shieldHealth <= 0) {
self.hasShield = false;
shield.destroy(); // Remove shield visual
shieldRim.destroy(); // Remove shield rim
shieldCenter.destroy(); // Remove shield center decoration
self.speed = 0.4; // Increase speed when shield is lost
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 12;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 30 ticks while touching WallNut
if (LK.ticks - self.lastDamageTime >= 30) {
plant.health -= 12;
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 60 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 60) {
plant.health -= 12; // Shield zombie destroys plants with moderate damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
var Sunflower = Container.expand(function () {
var self = Container.call(this);
// Create petals around center
for (var i = 0; i < 8; i++) {
var petal = self.attachAsset('sunflowerPetal', {
anchorX: 0.5,
anchorY: 1
});
var angle = i / 8 * Math.PI * 2;
petal.x = Math.cos(angle) * 25;
petal.y = Math.sin(angle) * 25;
petal.rotation = angle + Math.PI / 2;
}
var center = self.attachAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.lastSunGeneration = 0;
self.sunDelay = 300; // 5 seconds at 60fps
self.gridRow = 0;
self.gridCol = 0;
self.generateSun = function () {
sun += 25;
sunText.setText('Sun: ' + sun);
self.lastSunGeneration = LK.ticks;
};
self.update = function () {
if (LK.ticks - self.lastSunGeneration > self.sunDelay) {
self.generateSun();
}
};
return self;
});
var TankZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0x333333; // Dark gray tint for tank zombie
body.scaleX = 1.5;
body.scaleY = 1.5;
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -15;
head.tint = 0x333333;
head.scaleX = 1.5;
head.scaleY = 1.5;
self.health = 450; // Very high health
self.speed = 0.2; // Very slow
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 20;
self.hasAttackedHouse = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original dark gray tint
if (self.body) {
self.body.tint = 0x333333;
}
if (self.head) {
self.head.tint = 0x333333;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 25;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 40 ticks (slower but high damage) while touching WallNut
if (LK.ticks - self.lastDamageTime >= 40) {
plant.health -= 20; // Tank zombie destroys wallnut with high damage
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 70 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 70) {
plant.health -= 25; // Tank zombie destroys plants with massive damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
var WallNut = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('placedWallNutBody', {
anchorX: 0.5,
anchorY: 0.5
});
body.scaleX = 1.3;
body.scaleY = 1.3;
self.health = 60; // Increased health, breaks after 6 zombie hits
self.gridRow = 0;
self.gridCol = 0;
self.update = function () {
// Check if being touched by any zombie and take continuous damage
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridRow === self.gridRow && Math.abs(zombie.x - self.x) < 50) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Take damage every 30 ticks (0.5 seconds) from any zombie type
if (LK.ticks - self.lastDamageTime >= 30) {
var damageAmount = 10; // Base damage from regular zombies
if (zombie instanceof FastZombie) {
damageAmount = 15;
} else if (zombie instanceof TankZombie) {
damageAmount = 20;
} else if (zombie instanceof ShieldZombie) {
damageAmount = 12;
} else if (zombie instanceof JumperZombie) {
damageAmount = 12;
}
self.health -= damageAmount;
self.lastDamageTime = LK.ticks;
}
break; // Only take damage from one zombie at a time
}
}
// Reset damage timer when no zombies are touching
var anyZombieTouching = false;
for (var j = 0; j < zombies.length; j++) {
var zombie = zombies[j];
if (zombie.gridRow === self.gridRow && Math.abs(zombie.x - self.x) < 50) {
anyZombieTouching = true;
break;
}
}
if (!anyZombieTouching) {
self.lastDamageTime = undefined;
}
// Check if health decreased to play breaking sound
if (self.lastHealth === undefined) {
self.lastHealth = self.health;
}
if (self.lastHealth > self.health) {
LK.getSound('ceviz').play(); // Play walnut breaking sound when damaged
}
self.lastHealth = self.health;
// Check if wallnut is destroyed
if (self.health <= 0) {
// Play walnut breaking sound when completely destroyed
LK.getSound('ceviz').play();
// Remove from plants array and destroy
for (var k = plants.length - 1; k >= 0; k--) {
if (plants[k] === self) {
plants.splice(k, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
self.health = 150;
self.speed = 0.3;
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 10;
self.hasAttackedHouse = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect
if (self.body) {
self.body.tint = 0xFFFFFF;
}
if (self.head) {
self.head.tint = 0xFFFFFF;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if reached house - zombie damages house once with 10 health
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 10;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
// Destroy zombie after attacking house
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
// Check game over condition
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 30 ticks (0.5 seconds) while touching WallNut
if (LK.ticks - self.lastDamageTime >= 30) {
plant.health -= 10; // Continuous damage to wallnut
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 60 ticks (1 second) while touching other plants
if (LK.ticks - self.lastDamageTime >= 60) {
plant.health -= 10; // Increased damage to destroy plants faster
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x5D4E37 // Darker brown paper background color
});
/****
* Game Code
****/
// Level selection screen variables
// Import storage for persistent level progression
var levelButtons = [];
var totalLevels = 10;
var unlockedLevels = storage.unlockedLevels || 1; // Load from storage or default to 1
var levelProgress = storage.levelProgress || {}; // Load from storage or default to empty
// Global game variables
var plants = [];
var zombies = [];
var bullets = [];
var sun = 150;
var sunText;
var houseHealth;
var healthText;
// Music control variables
var isMusicOn = storage.isMusicOn !== undefined ? storage.isMusicOn : true; // Default music on
var musicButton;
// Create brown paper background
var background = LK.getAsset('paperBackground', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Create title
var titleBg = LK.getAsset('titleBackground', {
anchorX: 0.5,
anchorY: 0.5
});
titleBg.x = 1024;
titleBg.y = 300;
game.addChild(titleBg);
// Create level buttons in a grid layout
var buttonsPerRow = 5;
var buttonSpacingX = 350;
var buttonSpacingY = 250;
var startX = 400;
var startY = 700;
for (var i = 0; i < totalLevels; i++) {
var levelButton = new LevelButton();
levelButton.setLevelNumber(i + 1);
// Calculate position in grid
var row = Math.floor(i / buttonsPerRow);
var col = i % buttonsPerRow;
levelButton.x = startX + col * buttonSpacingX;
levelButton.y = startY + row * buttonSpacingY;
// Set unlock status - only unlock levels up to the player's progress
levelButton.setUnlocked(i < unlockedLevels);
// Set stars from progress (if any)
if (levelProgress[i + 1]) {
levelButton.setStars(levelProgress[i + 1]);
} else {
levelButton.setStars(0);
}
levelButtons.push(levelButton);
game.addChild(levelButton);
}
// Create music toggle button with new design
musicButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5
});
musicButton.x = 1700;
musicButton.y = 200;
musicButton.scaleX = 0.5;
musicButton.scaleY = 0.5;
musicButton.tint = isMusicOn ? 0x4CAF50 : 0xF44336;
game.addChild(musicButton);
// Add music icon symbol
var musicIcon = new Text2(isMusicOn ? '♪' : '♪', {
size: 50,
fill: 0xFFFFFF
});
musicIcon.anchor.set(0.5, 0.5);
musicIcon.x = 1700;
musicIcon.y = 185;
musicIcon.alpha = isMusicOn ? 1.0 : 0.3;
game.addChild(musicIcon);
// Add music status text
var musicText = new Text2(isMusicOn ? 'ON' : 'OFF', {
size: 24,
fill: 0xFFFFFF
});
musicText.anchor.set(0.5, 0.5);
musicText.x = 1700;
musicText.y = 215;
game.addChild(musicText);
// Music button event handler
musicButton.down = function () {
LK.getSound('buttonClick').play();
isMusicOn = !isMusicOn;
storage.isMusicOn = isMusicOn; // Save to storage
musicButton.tint = isMusicOn ? 0x4CAF50 : 0xF44336;
musicIcon.alpha = isMusicOn ? 1.0 : 0.3;
musicText.setText(isMusicOn ? 'ON' : 'OFF');
if (isMusicOn) {
// Resume music if we're in a level
if (currentLevel > 0) {
LK.playMusic('game');
}
} else {
LK.stopMusic();
}
};
// Function to start a level
function startLevel(levelNumber) {
LK.getSound('levelSelect').play();
console.log('Starting level: ' + levelNumber);
// Set current level for pause functionality
currentLevel = levelNumber;
// Clear the level selection screen
game.removeChildren();
// Initialize the appropriate level
if (levelNumber === 1) {
initializeLevel1();
} else if (levelNumber === 2) {
initializeLevel2();
} else if (levelNumber >= 3 && levelNumber <= 10) {
// Initialize dynamic levels for 3-10
initializeDynamicLevel(levelNumber);
}
}
// Initialize Level 1 Plants vs Zombies game
function initializeLevel1() {
// Start playing game music only if music is enabled
if (isMusicOn) {
LK.playMusic('game');
}
// Game variables
houseHealth = 100;
sun = 150; // Starting money for level 1
plants = []; // Make plants global
zombies = []; // Make zombies global
bullets = []; // Make bullets global
var sunflowers = [];
var lastSunGeneration = 0;
var lastZombieSpawn = 0;
var levelStartTime = null; // Track when level started - will be set when first plant is placed
var levelDuration = 7200; // 2 minutes at 60fps (120 seconds * 60 = 7200 ticks)
// Grid setup
var gridRows = 10;
var gridCols = 12;
var gridCellWidth = 180;
var gridCellHeight = 120;
var gridStartX = 200;
var gridStartY = 500;
// UI elements
sunText = new Text2('Sun: ' + sun, {
size: 40,
fill: 0x000000
});
sunText.x = 1500;
sunText.y = 50;
game.addChild(sunText);
healthText = new Text2('House Health: ' + houseHealth, {
size: 40,
fill: 0xFF0000
});
healthText.x = 1500;
healthText.y = 100;
game.addChild(healthText);
// Add timer display
var timerText = new Text2('Time: 2:00', {
size: 40,
fill: 0x0000FF
});
timerText.x = 1500;
timerText.y = 200;
game.addChild(timerText);
// Create grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var gridCell = LK.getAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.75,
scaleY: 0.75
});
gridCell.x = gridStartX + col * gridCellWidth;
gridCell.y = gridStartY + row * gridCellHeight;
gridCell.alpha = 0.8;
gridCell.tint = 0x8B7355;
game.addChild(gridCell);
}
}
// Plant selection buttons with proper spacing
var peashooterButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
peashooterButton.x = 400;
peashooterButton.y = 1800;
peashooterButton.tint = 0x006400;
game.addChild(peashooterButton);
// Add peashooter image to button
var peashooterImage = LK.getAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
peashooterImage.x = 400;
peashooterImage.y = 1780;
peashooterImage.scaleX = 1.0;
peashooterImage.scaleY = 1.0;
game.addChild(peashooterImage);
var peashooterCost = 50 + Math.max(0, 1 - 5) * 5; // Level 1 cost: 50, same for levels 1-5
var peashooterText = new Text2(peashooterCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
peashooterText.anchor.set(0.5, 0.5);
peashooterText.x = 400;
peashooterText.y = 1830;
game.addChild(peashooterText);
var sunflowerButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sunflowerButton.x = 1000;
sunflowerButton.y = 1800;
sunflowerButton.tint = 0xB8860B;
game.addChild(sunflowerButton);
// Add sunflower image to button
var sunflowerImage = LK.getAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
sunflowerImage.x = 1000;
sunflowerImage.y = 1780;
sunflowerImage.scaleX = 0.75;
sunflowerImage.scaleY = 0.75;
game.addChild(sunflowerImage);
var sunflowerCost = 50 + Math.max(0, 1 - 5) * 5; // Level 1 cost: 50, same for levels 1-5
var sunflowerText = new Text2(sunflowerCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
sunflowerText.anchor.set(0.5, 0.5);
sunflowerText.x = 1000;
sunflowerText.y = 1830;
game.addChild(sunflowerText);
var selectedPlant = null;
// Event handlers
peashooterButton.down = function () {
if (sun >= peashooterCost) {
selectedPlant = 'peashooter';
}
};
sunflowerButton.down = function () {
if (sun >= sunflowerCost) {
selectedPlant = 'sunflower';
}
};
game.down = function (x, y, obj) {
if (selectedPlant && x >= gridStartX && x <= gridStartX + gridCols * gridCellWidth && y >= gridStartY && y <= gridStartY + gridRows * gridCellHeight) {
var gridCol = Math.floor((x - gridStartX) / gridCellWidth);
var gridRow = Math.floor((y - gridStartY) / gridCellHeight);
// Check if position is valid and empty
var positionEmpty = true;
for (var i = 0; i < plants.length; i++) {
if (plants[i].gridRow === gridRow && plants[i].gridCol === gridCol) {
positionEmpty = false;
break;
}
}
if (positionEmpty && gridCol >= 0 && gridCol < gridCols && gridRow >= 0 && gridRow < gridRows) {
var plant = null;
if (selectedPlant === 'peashooter' && sun >= peashooterCost) {
plant = new Peashooter();
sun -= peashooterCost;
} else if (selectedPlant === 'sunflower' && sun >= sunflowerCost) {
plant = new Sunflower();
sun -= sunflowerCost;
}
if (plant) {
plant.x = gridStartX + gridCol * gridCellWidth;
plant.y = gridStartY + gridRow * gridCellHeight;
plant.gridRow = gridRow;
plant.gridCol = gridCol;
plants.push(plant);
game.addChild(plant);
sunText.setText('Sun: ' + sun);
selectedPlant = null;
}
}
}
};
var zombiesStarted = false; // Track if zombies have started spawning
// Game update function
game.update = function () {
// Start zombie spawning and timer only after first plant is placed
if (!zombiesStarted && plants.length > 0) {
zombiesStarted = true;
levelStartTime = LK.ticks; // Start level timer when first plant is placed
lastZombieSpawn = LK.ticks; // Initialize spawn timer
}
// Spawn zombies only after they have been started - progressive spawn rate
// Start slow and gradually increase spawn rate over time
var gameProgress = Math.min(LK.ticks / 3600, 1); // Progress from 0 to 1 over 60 seconds
var currentSpawnRate = 180 - gameProgress * 120; // Start at 180 (3 seconds), end at 60 (1 second)
if (zombiesStarted && LK.ticks - lastZombieSpawn > currentSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Update all game objects
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
for (var j = zombies.length - 1; j >= 0; j--) {
zombies[j].update();
}
for (var k = bullets.length - 1; k >= 0; k--) {
bullets[k].update();
}
// Generate sun from sunflowers - only if sunflowers exist
var sunflowerCount = 0;
for (var s = 0; s < plants.length; s++) {
if (plants[s] instanceof Sunflower) {
sunflowerCount++;
}
}
if (sunflowerCount > 0 && LK.ticks - lastSunGeneration > 600) {
// Every 10 seconds, only if sunflowers exist
sun += 10;
sunText.setText('Sun: ' + sun);
lastSunGeneration = LK.ticks;
}
// Increased spawn when 1.5 minutes (90 seconds = 5400 ticks) remain - starts at 1.5 minutes left
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
if (timeRemaining <= 5400) {
// 1.5 minutes = 90 seconds = 5400 ticks
var intensifiedSpawnRate = 30 - (5400 - timeRemaining) / 5400 * 20; // Start at 30 (0.5 seconds), end at 10 (0.17 seconds)
if (LK.ticks - lastZombieSpawn > intensifiedSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
}
// Update timer display
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 3600);
var seconds = Math.floor(timeRemaining % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
// Check win condition - survive for 2 minutes
if (levelStartTime && timeElapsed >= levelDuration) {
// Player survived 2 minutes - they win!
unlockNextLevel(); // Unlock next level when current level is completed
// Calculate stars based on house health: 3 stars = 80+%, 2 stars = 50+%, 1 star = any health remaining
var stars = 1;
if (houseHealth >= 80) {
stars = 3;
} else if (houseHealth >= 50) {
stars = 2;
}
setLevelStars(1, stars);
LK.showYouWin();
}
};
}
// Initialize Level 2 Plants vs Zombies game with more zombies and fast zombies
function initializeLevel2() {
// Start playing game music only if music is enabled
if (isMusicOn) {
LK.playMusic('game');
}
// Game variables
houseHealth = 100;
sun = 200; // Starting money for level 2 (50 more than level 1)
plants = []; // Make plants global
zombies = []; // Make zombies global
bullets = []; // Make bullets global
var levelStartTime = null; // Track when level started - will be set when first plant is placed
var levelDuration = 7200; // 2 minutes at 60fps (120 seconds * 60 = 7200 ticks)
// Night theme background
var nightBg = LK.getAsset('nightBackground', {
anchorX: 0,
anchorY: 0
});
game.addChild(nightBg);
var sunflowers = [];
var lastSunGeneration = 0;
var lastZombieSpawn = 0;
var lastFastZombieSpawn = 0;
// Grid setup
var gridRows = 10;
var gridCols = 12;
var gridCellWidth = 180;
var gridCellHeight = 120;
var gridStartX = 200;
var gridStartY = 500;
// UI elements
sunText = new Text2('Sun: ' + sun, {
size: 40,
fill: 0x000000
});
sunText.x = 1500;
sunText.y = 50;
game.addChild(sunText);
healthText = new Text2('House Health: ' + houseHealth, {
size: 40,
fill: 0xFF0000
});
healthText.x = 1500;
healthText.y = 100;
game.addChild(healthText);
// Level indicator
var levelText = new Text2('Level 2', {
size: 50,
fill: 0xFFFFFF
});
levelText.x = 1500;
levelText.y = 150;
game.addChild(levelText);
// Add timer display
var timerText = new Text2('Time: 2:00', {
size: 40,
fill: 0x0000FF
});
timerText.x = 1500;
timerText.y = 200;
game.addChild(timerText);
// Create grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var gridCell = LK.getAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.75,
scaleY: 0.75
});
gridCell.x = gridStartX + col * gridCellWidth;
gridCell.y = gridStartY + row * gridCellHeight;
gridCell.alpha = 0.9;
gridCell.tint = 0x1A0D33;
game.addChild(gridCell);
}
}
// Plant selection buttons with proper spacing
var peashooterButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
peashooterButton.x = 350;
peashooterButton.y = 1800;
peashooterButton.tint = 0x006400;
game.addChild(peashooterButton);
// Add peashooter image to button
var peashooterImage = LK.getAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
peashooterImage.x = 350;
peashooterImage.y = 1780;
peashooterImage.scaleX = 1.0;
peashooterImage.scaleY = 1.0;
game.addChild(peashooterImage);
var peashooterCost = 50 + Math.max(0, 2 - 5) * 5; // Level 2 cost: 50, same for levels 1-5
var peashooterText = new Text2(peashooterCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
peashooterText.anchor.set(0.5, 0.5);
peashooterText.x = 350;
peashooterText.y = 1830;
game.addChild(peashooterText);
var sunflowerButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sunflowerButton.x = 800;
sunflowerButton.y = 1800;
sunflowerButton.tint = 0xB8860B;
game.addChild(sunflowerButton);
// Add sunflower image to button
var sunflowerImage = LK.getAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
sunflowerImage.x = 800;
sunflowerImage.y = 1780;
sunflowerImage.scaleX = 0.75;
sunflowerImage.scaleY = 0.75;
game.addChild(sunflowerImage);
var sunflowerCost = 50 + Math.max(0, 2 - 5) * 5; // Level 2 cost: 50, same for levels 1-5
var sunflowerText = new Text2(sunflowerCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
sunflowerText.anchor.set(0.5, 0.5);
sunflowerText.x = 800;
sunflowerText.y = 1830;
game.addChild(sunflowerText);
var selectedPlant = null;
// Add CherryBomb button for level 2
var cherrybombButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
cherrybombButton.x = 1250;
cherrybombButton.y = 1800;
cherrybombButton.tint = 0xFF0000;
game.addChild(cherrybombButton);
var cherrybombImage = LK.getAsset('cherryImage', {
anchorX: 0.5,
anchorY: 0.5
});
cherrybombImage.x = 1250;
cherrybombImage.y = 1780;
cherrybombImage.scaleX = 0.75;
cherrybombImage.scaleY = 0.75;
game.addChild(cherrybombImage);
var cherrybombCost = 100 + Math.max(0, 2 - 5) * 10; // Level 2 cost: 100, same for levels 1-5
var cherrybombText = new Text2(cherrybombCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
cherrybombText.anchor.set(0.5, 0.5);
cherrybombText.x = 1250;
cherrybombText.y = 1830;
game.addChild(cherrybombText);
cherrybombButton.down = function () {
if (sun >= cherrybombCost) {
selectedPlant = 'cherrybomb';
}
};
// Event handlers
peashooterButton.down = function () {
if (sun >= peashooterCost) {
selectedPlant = 'peashooter';
}
};
sunflowerButton.down = function () {
if (sun >= sunflowerCost) {
selectedPlant = 'sunflower';
}
};
game.down = function (x, y, obj) {
if (selectedPlant && x >= gridStartX && x <= gridStartX + gridCols * gridCellWidth && y >= gridStartY && y <= gridStartY + gridRows * gridCellHeight) {
var gridCol = Math.floor((x - gridStartX) / gridCellWidth);
var gridRow = Math.floor((y - gridStartY) / gridCellHeight);
// Check if position is valid and empty
var positionEmpty = true;
for (var i = 0; i < plants.length; i++) {
if (plants[i].gridRow === gridRow && plants[i].gridCol === gridCol) {
positionEmpty = false;
break;
}
}
if (positionEmpty && gridCol >= 0 && gridCol < gridCols && gridRow >= 0 && gridRow < gridRows) {
var plant = null;
if (selectedPlant === 'peashooter' && sun >= peashooterCost) {
plant = new Peashooter();
sun -= peashooterCost;
} else if (selectedPlant === 'sunflower' && sun >= sunflowerCost) {
plant = new Sunflower();
sun -= sunflowerCost;
} else if (selectedPlant === 'cherrybomb' && sun >= cherrybombCost) {
plant = new CherryBomb();
sun -= cherrybombCost;
}
if (plant) {
plant.x = gridStartX + gridCol * gridCellWidth;
plant.y = gridStartY + gridRow * gridCellHeight;
plant.gridRow = gridRow;
plant.gridCol = gridCol;
plants.push(plant);
game.addChild(plant);
sunText.setText('Sun: ' + sun);
selectedPlant = null;
}
}
}
};
var zombiesStarted = false; // Track if zombies have started spawning
// Game update function for Level 2
game.update = function () {
// Start zombie spawning and timer only after first plant is placed
if (!zombiesStarted && plants.length > 0) {
zombiesStarted = true;
levelStartTime = LK.ticks; // Start level timer when first plant is placed
lastZombieSpawn = LK.ticks; // Initialize spawn timer
lastFastZombieSpawn = LK.ticks; // Initialize fast zombie spawn timer
}
// Spawn regular zombies with progressive spawn rate - only after zombies started
var gameProgress = Math.min(LK.ticks / 5400, 1); // Progress from 0 to 1 over 90 seconds
var currentSpawnRate = 120 - gameProgress * 80; // Start at 120 (2 seconds), end at 40 (0.67 seconds)
if (zombiesStarted && LK.ticks - lastZombieSpawn > currentSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Spawn fast zombies with progressive spawn rate - only after zombies started
var fastSpawnRate = 180 - gameProgress * 120; // Start at 180 (3 seconds), end at 60 (1 second)
if (zombiesStarted && LK.ticks - lastFastZombieSpawn > fastSpawnRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
// Spawn tank zombies with progressive spawn rate - only after zombies started
var tankSpawnRate = 300 - gameProgress * 150; // Start at 300 (5 seconds), end at 150 (2.5 seconds)
if (zombiesStarted && LK.ticks % tankSpawnRate === 0 && LK.ticks > 0) {
var tankZombie = new TankZombie();
var row = Math.floor(Math.random() * gridRows);
tankZombie.x = 1900;
tankZombie.y = gridStartY + row * gridCellHeight;
tankZombie.gridRow = row;
zombies.push(tankZombie);
game.addChild(tankZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
// Update all game objects
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
for (var j = zombies.length - 1; j >= 0; j--) {
zombies[j].update();
}
for (var k = bullets.length - 1; k >= 0; k--) {
bullets[k].update();
}
// Generate sun from sunflowers - only if sunflowers exist
var sunflowerCount = 0;
for (var s = 0; s < plants.length; s++) {
if (plants[s] instanceof Sunflower) {
sunflowerCount++;
}
}
if (sunflowerCount > 0 && LK.ticks - lastSunGeneration > 600) {
// Every 10 seconds, only if sunflowers exist
sun += 10;
sunText.setText('Sun: ' + sun);
lastSunGeneration = LK.ticks;
}
// Increased spawn when 1.5 minutes (90 seconds = 5400 ticks) remain - starts at 1.5 minutes left
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
if (timeRemaining <= 5400) {
// 1.5 minutes = 90 seconds = 5400 ticks
var intensifiedSpawnRate = 30 - (5400 - timeRemaining) / 5400 * 20; // Start at 30 (0.5 seconds), end at 10 (0.17 seconds)
if (LK.ticks - lastZombieSpawn > intensifiedSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Also spawn fast zombies more frequently during intensified wave
var intensifiedFastSpawnRate = 60 - (5400 - timeRemaining) / 5400 * 45; // Start at 60 (1 second), end at 15 (0.25 seconds)
if (LK.ticks - lastFastZombieSpawn > intensifiedFastSpawnRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
}
// Update timer display
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 3600);
var seconds = Math.floor(timeRemaining % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
// Check win condition - survive for 2 minutes
if (levelStartTime && timeElapsed >= levelDuration) {
// Player survived 2 minutes - they win!
unlockNextLevel(); // Unlock next level when current level is completed
// Calculate stars based on house health: 3 stars = 80+%, 2 stars = 50+%, 1 star = any health remaining
var stars = 1;
if (houseHealth >= 80) {
stars = 3;
} else if (houseHealth >= 50) {
stars = 2;
}
setLevelStars(2, stars);
LK.showYouWin();
}
};
}
// Initialize Dynamic Level for levels 3 and beyond
function initializeDynamicLevel(levelNumber) {
// Start playing game music only if music is enabled
if (isMusicOn) {
LK.playMusic('game');
}
// Game variables
houseHealth = 100;
sun = 150 + (levelNumber - 1) * 50; // Progressive starting money: level 1=150, level 2=200, level 3=250, etc.
plants = []; // Make plants global
zombies = []; // Make zombies global
bullets = []; // Make bullets global
// Dynamic theme based on level
var themeBackgrounds = ['desertBackground', 'iceBackground', 'jungleBackground'];
var themeIndex = (levelNumber - 3) % themeBackgrounds.length;
var themeBg = LK.getAsset(themeBackgrounds[themeIndex], {
anchorX: 0,
anchorY: 0
});
game.addChild(themeBg);
var sunflowers = [];
var lastSunGeneration = 0;
var lastZombieSpawn = 0;
var lastFastZombieSpawn = 0;
// Dynamic difficulty scaling based on level - with increased spawn rates
var difficultyMultiplier = levelNumber - 2; // Starts at 1 for level 3
var zombieSpawnRate = Math.max(30, 90 - difficultyMultiplier * 15); // Much faster spawning, increased each level
var fastZombieSpawnRate = Math.max(60, 180 - difficultyMultiplier * 20); // Much more fast zombies
// Dynamic level duration based on level number
var levelDuration;
if (levelNumber === 3) {
levelDuration = 7200; // 2 minutes at 60fps (120 seconds * 60 = 7200 ticks)
} else if (levelNumber >= 4 && levelNumber <= 6) {
levelDuration = 9000; // 2.5 minutes at 60fps (150 seconds * 60 = 9000 ticks)
} else if (levelNumber >= 7 && levelNumber <= 10) {
levelDuration = 12600; // 3.5 minutes at 60fps (210 seconds * 60 = 12600 ticks)
}
var finalWaveStart = levelDuration - 1800; // Final wave starts 30 seconds before end
var levelStartTime = null; // Track when level started - will be set when first plant is placed
// Grid setup
var gridRows = 10;
var gridCols = 12;
var gridCellWidth = 180;
var gridCellHeight = 120;
var gridStartX = 200;
var gridStartY = 500;
// UI elements
sunText = new Text2('Sun: ' + sun, {
size: 40,
fill: 0x000000
});
sunText.x = 1500;
sunText.y = 50;
game.addChild(sunText);
healthText = new Text2('House Health: ' + houseHealth, {
size: 40,
fill: 0xFF0000
});
healthText.x = 1500;
healthText.y = 100;
game.addChild(healthText);
// Level indicator
var levelText = new Text2('Level ' + levelNumber, {
size: 50,
fill: 0xFFFFFF
});
levelText.x = 1500;
levelText.y = 150;
game.addChild(levelText);
// Add timer display
var initialTimeString;
if (levelNumber === 3) {
initialTimeString = 'Time: 2:00';
} else if (levelNumber >= 4 && levelNumber <= 6) {
initialTimeString = 'Time: 2:30';
} else if (levelNumber >= 7 && levelNumber <= 10) {
initialTimeString = 'Time: 3:30';
}
var timerText = new Text2(initialTimeString, {
size: 40,
fill: 0x0000FF
});
timerText.x = 1500;
timerText.y = 200;
game.addChild(timerText);
// Create grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var gridCell = LK.getAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.75,
scaleY: 0.75
});
gridCell.x = gridStartX + col * gridCellWidth;
gridCell.y = gridStartY + row * gridCellHeight;
gridCell.alpha = 0.9;
// Apply theme-specific tinting with unique grid appearances
if (themeBackgrounds[themeIndex] === 'desertBackground') {
gridCell.tint = 0xD2691E; // Sandy orange for desert
} else if (themeBackgrounds[themeIndex] === 'iceBackground') {
gridCell.tint = 0x87CEEB; // Light blue for ice
} else if (themeBackgrounds[themeIndex] === 'jungleBackground') {
gridCell.tint = 0x228B22; // Forest green for jungle
}
game.addChild(gridCell);
}
}
// Plant selection buttons with proper spacing
var peashooterButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
peashooterButton.x = 300;
peashooterButton.y = 1800;
peashooterButton.tint = 0x006400;
game.addChild(peashooterButton);
// Add peashooter image to button
var peashooterImage = LK.getAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
peashooterImage.x = 300;
peashooterImage.y = 1780;
peashooterImage.scaleX = 1.0;
peashooterImage.scaleY = 1.0;
game.addChild(peashooterImage);
var peashooterCost = 50 + Math.max(0, levelNumber - 5) * 5; // Cost same for levels 1-5, then increases by 5 per level
var peashooterText = new Text2(peashooterCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
peashooterText.anchor.set(0.5, 0.5);
peashooterText.x = 300;
peashooterText.y = 1830;
game.addChild(peashooterText);
var sunflowerButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sunflowerButton.x = 700;
sunflowerButton.y = 1800;
sunflowerButton.tint = 0xB8860B;
game.addChild(sunflowerButton);
// Add sunflower image to button
var sunflowerImage = LK.getAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
sunflowerImage.x = 700;
sunflowerImage.y = 1780;
sunflowerImage.scaleX = 0.75;
sunflowerImage.scaleY = 0.75;
game.addChild(sunflowerImage);
var sunflowerCost = 50 + Math.max(0, levelNumber - 5) * 5; // Cost same for levels 1-5, then increases by 5 per level
var sunflowerText = new Text2(sunflowerCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
sunflowerText.anchor.set(0.5, 0.5);
sunflowerText.x = 700;
sunflowerText.y = 1830;
game.addChild(sunflowerText);
var selectedPlant = null;
// Add more plant options based on level
if (levelNumber >= 3) {
// Add DoublePeashooter button
var doublePeaButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
doublePeaButton.x = 1100;
doublePeaButton.y = 1800;
doublePeaButton.tint = 0x00AA00;
game.addChild(doublePeaButton);
var doublePeaImage = LK.getAsset('placedDoublePeaBody', {
anchorX: 0.5,
anchorY: 0.5
});
doublePeaImage.x = 1100;
doublePeaImage.y = 1780;
game.addChild(doublePeaImage);
var doublePeaCost = 100 + Math.max(0, levelNumber - 5) * 10; // Cost same for levels 1-5, then increases by 10 per level
var doublePeaText = new Text2(doublePeaCost + ' Sun', {
size: 28,
fill: 0xFFFFFF
});
doublePeaText.anchor.set(0.5, 0.5);
doublePeaText.x = 1100;
doublePeaText.y = 1830;
game.addChild(doublePeaText);
doublePeaButton.down = function () {
if (sun >= doublePeaCost) {
selectedPlant = 'doublepea';
}
};
}
if (levelNumber >= 4) {
// Add CherryBomb button
var cherrybombButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
cherrybombButton.x = 1500;
cherrybombButton.y = 1800;
cherrybombButton.tint = 0xFF0000;
game.addChild(cherrybombButton);
var cherrybombImage = LK.getAsset('cherryImage', {
anchorX: 0.5,
anchorY: 0.5
});
cherrybombImage.x = 1500;
cherrybombImage.y = 1780;
cherrybombImage.scaleX = 0.6;
cherrybombImage.scaleY = 0.6;
game.addChild(cherrybombImage);
var cherrybombCost = 100 + Math.max(0, levelNumber - 5) * 10; // Cost same for levels 1-5, then increases by 10 per level
var cherrybombText = new Text2(cherrybombCost + ' Sun', {
size: 28,
fill: 0xFFFFFF
});
cherrybombText.anchor.set(0.5, 0.5);
cherrybombText.x = 1500;
cherrybombText.y = 1830;
game.addChild(cherrybombText);
cherrybombButton.down = function () {
if (sun >= cherrybombCost) {
selectedPlant = 'cherrybomb';
}
};
}
if (levelNumber >= 6) {
// Add WallNut button below CherryBomb button
var wallnutButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
wallnutButton.x = 1500;
wallnutButton.y = 2000;
wallnutButton.tint = 0x8B4513;
game.addChild(wallnutButton);
var wallnutImage = LK.getAsset('placedWallNutBody', {
anchorX: 0.5,
anchorY: 0.5
});
wallnutImage.x = 1500;
wallnutImage.y = 1980;
wallnutImage.scaleX = 0.9;
wallnutImage.scaleY = 0.9;
game.addChild(wallnutImage);
var wallnutCost = 50 + Math.max(0, levelNumber - 5) * 5; // Cost same for levels 1-5, then increases by 5 per level
var wallnutText = new Text2(wallnutCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
wallnutText.anchor.set(0.5, 0.5);
wallnutText.x = 1500;
wallnutText.y = 2030;
game.addChild(wallnutText);
wallnutButton.down = function () {
if (sun >= wallnutCost) {
selectedPlant = 'wallnut';
}
};
}
if (levelNumber >= 8) {
// Add IcePeashooter button below Peashooter
var icepeaButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
icepeaButton.x = 300;
icepeaButton.y = 2000;
icepeaButton.tint = 0x87CEEB;
game.addChild(icepeaButton);
var icepeaImage = LK.getAsset('icePeashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
icepeaImage.x = 300;
icepeaImage.y = 1980;
icepeaImage.scaleX = 1.0;
icepeaImage.scaleY = 1.0;
game.addChild(icepeaImage);
var icepeaCost = 75 + Math.max(0, levelNumber - 5) * 8; // Cost same for levels 1-5, then increases by 8 per level
var icepeaText = new Text2(icepeaCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
icepeaText.anchor.set(0.5, 0.5);
icepeaText.x = 300;
icepeaText.y = 2030;
game.addChild(icepeaText);
icepeaButton.down = function () {
if (sun >= icepeaCost) {
selectedPlant = 'icepea';
}
};
}
// Event handlers
peashooterButton.down = function () {
if (sun >= peashooterCost) {
selectedPlant = 'peashooter';
}
};
sunflowerButton.down = function () {
if (sun >= sunflowerCost) {
selectedPlant = 'sunflower';
}
};
game.down = function (x, y, obj) {
if (selectedPlant && x >= gridStartX && x <= gridStartX + gridCols * gridCellWidth && y >= gridStartY && y <= gridStartY + gridRows * gridCellHeight) {
var gridCol = Math.floor((x - gridStartX) / gridCellWidth);
var gridRow = Math.floor((y - gridStartY) / gridCellHeight);
// Check if position is valid and empty
var positionEmpty = true;
for (var i = 0; i < plants.length; i++) {
if (plants[i].gridRow === gridRow && plants[i].gridCol === gridCol) {
positionEmpty = false;
break;
}
}
if (positionEmpty && gridCol >= 0 && gridCol < gridCols && gridRow >= 0 && gridRow < gridRows) {
var plant = null;
if (selectedPlant === 'peashooter' && sun >= peashooterCost) {
plant = new Peashooter();
sun -= peashooterCost;
} else if (selectedPlant === 'sunflower' && sun >= sunflowerCost) {
plant = new Sunflower();
sun -= sunflowerCost;
} else if (selectedPlant === 'doublepea' && sun >= doublePeaCost) {
plant = new DoublePeashooter();
sun -= doublePeaCost;
} else if (selectedPlant === 'cherrybomb' && sun >= cherrybombCost) {
plant = new CherryBomb();
sun -= cherrybombCost;
} else if (selectedPlant === 'wallnut' && sun >= wallnutCost) {
plant = new WallNut();
sun -= wallnutCost;
} else if (selectedPlant === 'icepea' && sun >= icepeaCost) {
plant = new IcePeashooter();
sun -= icepeaCost;
}
if (plant) {
plant.x = gridStartX + gridCol * gridCellWidth;
plant.y = gridStartY + gridRow * gridCellHeight;
plant.gridRow = gridRow;
plant.gridCol = gridCol;
plants.push(plant);
game.addChild(plant);
sunText.setText('Sun: ' + sun);
selectedPlant = null;
}
}
}
};
var zombiesStarted = false; // Track if zombies have started spawning
// Game update function for Dynamic Levels
game.update = function () {
// Start zombie spawning and timer only after first plant is placed
if (!zombiesStarted && plants.length > 0) {
zombiesStarted = true;
levelStartTime = LK.ticks; // Start level timer when first plant is placed
lastZombieSpawn = LK.ticks; // Initialize spawn timer
lastFastZombieSpawn = LK.ticks; // Initialize fast zombie spawn timer
}
// Spawn regular zombies with progressive and scaled difficulty - only after zombies started
var gameProgress = Math.min(LK.ticks / levelDuration, 1); // Progress from 0 to 1 over level duration
var progressiveSpawnRate = zombieSpawnRate * 2 - gameProgress * zombieSpawnRate; // Start at 2x spawn rate, end at 1x
if (zombiesStarted && LK.ticks - lastZombieSpawn > progressiveSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Spawn fast zombies with progressive and scaled difficulty - only after zombies started
var progressiveFastSpawnRate = fastZombieSpawnRate * 2 - gameProgress * fastZombieSpawnRate; // Start at 2x spawn rate, end at 1x
if (zombiesStarted && LK.ticks - lastFastZombieSpawn > progressiveFastSpawnRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
// Spawn tank zombies on higher levels with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 4) {
var baseTankSpawnRate = 180 - difficultyMultiplier * 30;
var progressiveTankSpawnRate = baseTankSpawnRate * 2 - gameProgress * baseTankSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks % progressiveTankSpawnRate === 0 && LK.ticks > 0) {
var tankZombie = new TankZombie();
var row = Math.floor(Math.random() * gridRows);
tankZombie.x = 1900;
tankZombie.y = gridStartY + row * gridCellHeight;
tankZombie.gridRow = row;
zombies.push(tankZombie);
game.addChild(tankZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Spawn shield zombies starting from level 5 with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 5) {
var baseShieldSpawnRate = 210 - difficultyMultiplier * 30;
var progressiveShieldSpawnRate = baseShieldSpawnRate * 2 - gameProgress * baseShieldSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks % progressiveShieldSpawnRate === 0 && LK.ticks > 0) {
var shieldZombie = new ShieldZombie();
var row = Math.floor(Math.random() * gridRows);
shieldZombie.x = 1900;
shieldZombie.y = gridStartY + row * gridCellHeight;
shieldZombie.gridRow = row;
zombies.push(shieldZombie);
game.addChild(shieldZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Spawn jumper zombies on even higher levels with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 6) {
var baseJumperSpawnRate = 150 - difficultyMultiplier * 20;
var progressiveJumperSpawnRate = baseJumperSpawnRate * 2 - gameProgress * baseJumperSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks % progressiveJumperSpawnRate === 0 && LK.ticks > 0) {
var jumperZombie = new JumperZombie();
var row = Math.floor(Math.random() * gridRows);
jumperZombie.x = 1900;
jumperZombie.y = gridStartY + row * gridCellHeight;
jumperZombie.gridRow = row;
zombies.push(jumperZombie);
game.addChild(jumperZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Spawn bomber zombies on levels 8, 9, and 10 with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 8) {
var baseBomberSpawnRate = Math.max(180, 240 - difficultyMultiplier * 25);
var progressiveBomberSpawnRate = baseBomberSpawnRate * 2 - gameProgress * baseBomberSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks - lastZombieSpawn > progressiveBomberSpawnRate && LK.ticks % 300 === 0) {
var bomberZombie = new BomberZombie();
var row = Math.floor(Math.random() * gridRows);
bomberZombie.x = 1900;
bomberZombie.y = gridStartY + row * gridCellHeight;
bomberZombie.gridRow = row;
zombies.push(bomberZombie);
game.addChild(bomberZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Update all game objects
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
for (var j = zombies.length - 1; j >= 0; j--) {
zombies[j].update();
}
for (var k = bullets.length - 1; k >= 0; k--) {
bullets[k].update();
}
// Generate sun from sunflowers - only if sunflowers exist
var sunflowerCount = 0;
for (var s = 0; s < plants.length; s++) {
if (plants[s] instanceof Sunflower) {
sunflowerCount++;
}
}
if (sunflowerCount > 0 && LK.ticks - lastSunGeneration > 600) {
// Every 10 seconds, only if sunflowers exist
sun += 10;
sunText.setText('Sun: ' + sun);
lastSunGeneration = LK.ticks;
}
// Intensified wave - spawn many more zombies rapidly when 1.5 minutes remain
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
if (levelStartTime && timeRemaining <= 5400) {
// 1.5 minutes = 90 seconds = 5400 ticks
var intensifiedProgress = (5400 - timeRemaining) / 5400; // Progress from 0 to 1 over intensified period
var baseIntensifiedSpawnRate = Math.max(8, zombieSpawnRate / 8); // Much faster in intensified wave
var progressiveIntensifiedSpawnRate = baseIntensifiedSpawnRate * 3 - intensifiedProgress * baseIntensifiedSpawnRate * 2; // Start at 3x, end at 1x
if (LK.ticks - lastZombieSpawn > progressiveIntensifiedSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Also spawn fast zombies more frequently during intensified wave
var baseIntensifiedFastRate = Math.max(15, fastZombieSpawnRate / 8);
var progressiveIntensifiedFastRate = baseIntensifiedFastRate * 3 - intensifiedProgress * baseIntensifiedFastRate * 2; // Start at 3x, end at 1x
if (LK.ticks - lastFastZombieSpawn > progressiveIntensifiedFastRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
}
// Update timer display
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 3600);
var seconds = Math.floor(timeRemaining % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
// Check win condition - survive for 2 minutes
if (levelStartTime && timeElapsed >= levelDuration) {
// Player survived 2 minutes - they win!
unlockNextLevel(); // Unlock next level when current level is completed
// Calculate stars based on house health: 3 stars = 80+%, 2 stars = 50+%, 1 star = any health remaining
var stars = 1;
if (houseHealth >= 80) {
stars = 3;
} else if (houseHealth >= 50) {
stars = 2;
}
setLevelStars(levelNumber, stars);
LK.showYouWin();
}
};
}
// Function to unlock next level
function unlockNextLevel() {
// Only unlock the next level if current level is completed and next level exists
if (currentLevel === unlockedLevels && unlockedLevels < totalLevels) {
unlockedLevels++;
storage.unlockedLevels = unlockedLevels; // Save to storage
if (levelButtons[unlockedLevels - 1]) {
levelButtons[unlockedLevels - 1].setUnlocked(true);
}
}
}
// Function to set stars for a level
function setLevelStars(levelNumber, stars) {
levelProgress[levelNumber] = stars;
storage.levelProgress = levelProgress; // Save to storage
if (levelButtons[levelNumber - 1]) {
levelButtons[levelNumber - 1].setStars(stars);
}
}
// Pause menu variables
var isPaused = false;
var pauseMenu = null;
var currentLevel = 0;
// Function to show pause menu
function showPauseMenu() {
if (pauseMenu) return; // Already showing
// Create pause menu background
pauseMenu = new Container();
var pauseBg = LK.getAsset('paperBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.4
});
pauseBg.x = 0;
pauseBg.y = 0;
pauseBg.tint = 0x000000;
pauseBg.alpha = 0.8;
pauseMenu.addChild(pauseBg);
// Pause title
var pauseTitle = new Text2('PAUSED', {
size: 60,
fill: 0xFFFFFF
});
pauseTitle.anchor.set(0.5, 0.5);
pauseTitle.x = 0;
pauseTitle.y = -100;
pauseMenu.addChild(pauseTitle);
// Restart button
var restartButton = LK.getAsset('levelButton', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.x = 0;
restartButton.y = 0;
restartButton.tint = 0x4CAF50;
pauseMenu.addChild(restartButton);
var restartText = new Text2('RESTART', {
size: 40,
fill: 0x000000
});
restartText.anchor.set(0.5, 0.5);
restartText.x = 0;
restartText.y = 0;
pauseMenu.addChild(restartText);
// Resume button
var resumeButton = LK.getAsset('levelButton', {
anchorX: 0.5,
anchorY: 0.5
});
resumeButton.x = 0;
resumeButton.y = 120;
resumeButton.tint = 0x2196F3;
pauseMenu.addChild(resumeButton);
var resumeText = new Text2('RESUME', {
size: 40,
fill: 0x000000
});
resumeText.anchor.set(0.5, 0.5);
resumeText.x = 0;
resumeText.y = 120;
pauseMenu.addChild(resumeText);
// Position pause menu at center of screen
pauseMenu.x = 1024;
pauseMenu.y = 1366;
// Add event handlers
restartButton.down = function () {
LK.getSound('buttonClick').play();
hidePauseMenu();
// Restart current level
if (currentLevel === 1) {
game.removeChildren();
initializeLevel1();
} else if (currentLevel === 2) {
game.removeChildren();
initializeLevel2();
} else if (currentLevel >= 3 && currentLevel <= 10) {
game.removeChildren();
initializeDynamicLevel(currentLevel);
}
};
resumeButton.down = function () {
LK.getSound('buttonClick').play();
hidePauseMenu();
};
game.addChild(pauseMenu);
}
// Function to hide pause menu
function hidePauseMenu() {
if (pauseMenu) {
game.removeChild(pauseMenu);
pauseMenu = null;
}
isPaused = false;
}
// Override game pause handler
LK.on('pause', function () {
if (currentLevel > 0) {
// Only show pause menu during gameplay
isPaused = true;
showPauseMenu();
}
});
// Simple game update for level selection screen
game.update = function () {
// No specific updates needed for level selection
// All interactions are handled by level button click events
}; /****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BomberZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('bomberZombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
var head = self.attachAsset('bomberZombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
// Add explosive belt around waist
var belt = self.attachAsset('explosiveBelt', {
anchorX: 0.5,
anchorY: 0.5
});
belt.x = 0;
belt.y = 15;
// Add multiple bomb indicators on the belt
for (var i = 0; i < 3; i++) {
var bombIndicator = self.attachAsset('bombIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bombIndicator.x = (i - 1) * 25; // Spread across belt
bombIndicator.y = 15;
// Add blinking animation to bomb indicators
var delay = i * 200; // Stagger the blinking
LK.setTimeout(function () {
var blinkTimer = LK.setInterval(function () {
bombIndicator.alpha = bombIndicator.alpha > 0.5 ? 0.3 : 1.0;
}, 800);
}, delay);
}
// Add fuse wire coming from belt
var fuse = self.attachAsset('fuseWire', {
anchorX: 0.5,
anchorY: 1
});
fuse.x = 30;
fuse.y = 5;
fuse.rotation = Math.PI / 6; // Slight angle
// Add sparking effect to fuse tip
var fuseTip = self.attachAsset('bombIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
fuseTip.x = 35;
fuseTip.y = -5;
fuseTip.scaleX = 0.5;
fuseTip.scaleY = 0.5;
// Animate fuse tip sparking
LK.setInterval(function () {
fuseTip.alpha = Math.random() > 0.5 ? 1.0 : 0.2;
fuseTip.tint = Math.random() > 0.5 ? 0xFFFF00 : 0xFF4500;
}, 300);
self.health = 80; // Lower health than regular zombie - easy to kill but dangerous
self.speed = 0.4; // Slightly faster than regular zombie
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 8;
self.hasAttackedHouse = false;
self.hasExploded = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original orange tint
if (self.body) {
self.body.tint = 0xFF4500;
}
if (self.head) {
self.head.tint = 0xFF4500;
}
}
if (!self.isEating && !self.hasExploded) {
self.x -= self.speed;
}
// Explode when health drops to 0 or below
if (self.health <= 0 && !self.hasExploded) {
self.hasExploded = true;
// Explosive death - damages plants in 2x2 area around bomber
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
var distance = Math.sqrt(Math.pow(self.x - plant.x, 2) + Math.pow(self.y - plant.y, 2));
if (distance < 150) {
// Explosion radius
plant.health -= 40; // Significant damage to nearby plants
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
}
}
}
// Flash explosion effect
LK.effects.flashScreen(0xFF4500, 500);
// Remove self
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
self.destroy();
return;
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse && !self.hasExploded) {
houseHealth -= 20; // Extra damage when reaching house
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
// Explode at house for extra damage
self.hasExploded = true;
LK.effects.flashScreen(0xFF4500, 500);
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
if (!self.hasExploded) {
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 35 ticks while touching WallNut
if (LK.ticks - self.lastDamageTime >= 35) {
plant.health -= 8;
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 50 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 50) {
plant.health -= 20; // Bomber zombie destroys plants with explosive damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bulletCore', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.damage = 50;
self.row = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
if (zombie.gridRow === self.row && Math.abs(self.x - zombie.x) < 30) {
// Handle ShieldZombie shield mechanics
if (zombie instanceof ShieldZombie && zombie.hasShield) {
zombie.shieldHealth -= self.damage;
// Shield takes damage instead of zombie
} else {
zombie.health -= self.damage;
}
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
}
self.destroy();
for (var j = bullets.length - 1; j >= 0; j--) {
if (bullets[j] === self) {
bullets.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2048) {
self.destroy();
for (var k = bullets.length - 1; k >= 0; k--) {
if (bullets[k] === self) {
bullets.splice(k, 1);
break;
}
}
}
};
return self;
});
var CherryBomb = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('cherryImage', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 50;
self.gridRow = 0;
self.gridCol = 0;
self.fuseTime = 180; // 3 seconds at 60fps
self.planted = false;
self.update = function () {
if (!self.planted) {
self.planted = true;
self.plantedTick = LK.ticks;
}
// Check if any zombie is touching the cherry bomb
var shouldExplode = false;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(self.x - zombie.x, 2) + Math.pow(self.y - zombie.y, 2));
if (distance < 50) {
// Touch distance - much smaller than explosion radius
shouldExplode = true;
break;
}
}
// Explode after fuse time OR when touched by zombie
if (shouldExplode || LK.ticks - self.plantedTick > self.fuseTime) {
LK.getSound('cherybomb').play(); // Play cherrybomb sound when exploding
// Deal damage to all zombies in 3x3 area around bomb
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(self.x - zombie.x, 2) + Math.pow(self.y - zombie.y, 2));
if (distance < 200) {
// Check zombie type for damage calculation
if (zombie instanceof TankZombie) {
// Deal 75% percentage damage to tank zombies
var damageAmount = Math.floor(zombie.health * 0.75);
zombie.health -= damageAmount;
} else {
// Instantly kill all other zombie types (regular, fast, shield, jumper)
zombie.health = 0;
}
// Destroy zombie if health drops to or below 0
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
}
}
}
// Remove self
for (var j = plants.length - 1; j >= 0; j--) {
if (plants[j] === self) {
plants.splice(j, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var DoublePeashooter = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('placedDoublePeaBody', {
anchorX: 0.5,
anchorY: 0.5
});
var cannon1 = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.3
});
cannon1.x = 25;
cannon1.y = -10;
var cannon2 = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.7
});
cannon2.x = 25;
cannon2.y = 10;
self.health = 100;
self.lastShot = 0;
self.shootDelay = 100; // Faster than regular peashooter
self.gridRow = 0;
self.gridCol = 0;
self.shoot = function () {
// Shoot two bullets
var bullet1 = new Bullet();
bullet1.x = self.x + 50;
bullet1.y = self.y - 10;
bullet1.row = self.gridRow;
bullets.push(bullet1);
game.addChild(bullet1);
var bullet2 = new Bullet();
bullet2.x = self.x + 50;
bullet2.y = self.y + 10;
bullet2.row = self.gridRow;
bullets.push(bullet2);
game.addChild(bullet2);
LK.getSound('peasshooter').play(); // Play peashooter sound when shooting
};
self.update = function () {
if (LK.ticks - self.lastShot > self.shootDelay) {
var zombieInRow = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridRow === self.gridRow && zombies[i].x > self.x) {
zombieInRow = true;
break;
}
}
if (zombieInRow) {
self.shoot();
self.lastShot = LK.ticks;
}
}
};
return self;
});
var FastZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0xFF6666; // Reddish tint to distinguish from regular zombie
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
head.tint = 0xFF6666; // Reddish tint to distinguish from regular zombie
self.health = 100; // Lower health than regular zombie
self.speed = 0.8; // Faster than regular zombie
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 15; // More damage when eating
self.hasAttackedHouse = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original red tint
if (self.body) {
self.body.tint = 0xFF6666;
}
if (self.head) {
self.head.tint = 0xFF6666;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if reached house - zombie damages house once with 15 health
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 15;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
// Destroy zombie after attacking house
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
// Check game over condition
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 25 ticks (faster than regular zombie) while touching WallNut
if (LK.ticks - self.lastDamageTime >= 25) {
plant.health -= 15; // Fast zombie damages wallnut more
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 45 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 45) {
plant.health -= 15; // Fast zombie destroys plants quickly
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
var IceBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('iceBulletCore', {
anchorX: 0.5,
anchorY: 0.5
});
// Add ice crystal effects around the main bullet
for (var i = 0; i < 4; i++) {
var crystal = self.attachAsset('iceCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = i / 4 * Math.PI * 2;
crystal.x = Math.cos(angle) * 8;
crystal.y = Math.sin(angle) * 8;
crystal.rotation = angle;
crystal.scaleX = 0.3;
crystal.scaleY = 0.4;
crystal.alpha = 0.8;
// Add sparkle animation to crystals
tween(crystal, {
alpha: 0.3
}, {
duration: 300,
easing: tween.easeInOut
});
tween(crystal, {
alpha: 0.8
}, {
duration: 300,
easing: tween.easeInOut
});
}
self.speed = 6; // Slightly faster than regular bullet
self.damage = 40;
self.row = 0;
self.update = function () {
self.x += self.speed;
// Check collision with zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
if (zombie.gridRow === self.row && Math.abs(self.x - zombie.x) < 30) {
// Handle ShieldZombie shield mechanics
if (zombie instanceof ShieldZombie && zombie.hasShield) {
zombie.shieldHealth -= self.damage;
// Shield takes damage instead of zombie
} else {
zombie.health -= self.damage;
}
// Apply freeze effect - slow down zombie for 3 seconds
if (!zombie.isFrozen) {
zombie.originalSpeed = zombie.speed;
zombie.speed = zombie.speed * 0.3; // Slow to 30% speed
zombie.isFrozen = true;
zombie.frozenUntil = LK.ticks + 180; // 3 seconds at 60fps
// Visual freeze effect
if (zombie.body) {
zombie.body.tint = 0x87CEEB;
}
if (zombie.head) {
zombie.head.tint = 0x87CEEB;
}
}
if (zombie.health <= 0) {
zombie.destroy();
zombies.splice(i, 1);
}
self.destroy();
for (var j = bullets.length - 1; j >= 0; j--) {
if (bullets[j] === self) {
bullets.splice(j, 1);
break;
}
}
return;
}
}
// Remove if off screen
if (self.x > 2048) {
self.destroy();
for (var k = bullets.length - 1; k >= 0; k--) {
if (bullets[k] === self) {
bullets.splice(k, 1);
break;
}
}
}
};
return self;
});
var IcePeashooter = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('icePeashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Add ice crystals around the body for visual effect
for (var i = 0; i < 6; i++) {
var crystal = self.attachAsset('iceCrystal', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = i / 6 * Math.PI * 2;
crystal.x = Math.cos(angle) * 30;
crystal.y = Math.sin(angle) * 30;
crystal.rotation = angle;
crystal.scaleX = 0.6;
crystal.scaleY = 0.8;
}
var cannon = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.5
});
cannon.x = 25;
cannon.y = 0;
cannon.tint = 0x87CEEB; // Light blue tint for ice effect
self.health = 100;
self.lastShot = 0;
self.shootDelay = 120; // Faster than regular peashooter
self.gridRow = 0;
self.gridCol = 0;
self.shoot = function () {
var bullet = new IceBullet();
bullet.x = self.x + 50;
bullet.y = self.y;
bullet.row = self.gridRow;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('peasshooter').play(); // Play peashooter sound when shooting
};
self.update = function () {
if (LK.ticks - self.lastShot > self.shootDelay) {
// Check if there's a zombie in this row
var zombieInRow = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridRow === self.gridRow && zombies[i].x > self.x) {
zombieInRow = true;
break;
}
}
if (zombieInRow) {
self.shoot();
self.lastShot = LK.ticks;
}
}
};
return self;
});
var JumperZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0x00FF00; // Green tint for jumper zombie
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
head.tint = 0x00FF00;
self.health = 120;
self.speed = 0.5;
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 12;
self.hasAttackedHouse = false;
self.canJump = true;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original green tint
if (self.body) {
self.body.tint = 0x00FF00;
}
if (self.head) {
self.head.tint = 0x00FF00;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Jump over first plant encountered
if (self.canJump) {
for (var i = 0; i < plants.length; i++) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 80 && self.x > plant.x) {
self.x = plant.x - 80; // Jump past the plant
self.canJump = false;
break;
}
}
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 12;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants (only if can't jump)
if (!self.canJump) {
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 30 ticks while touching WallNut
if (LK.ticks - self.lastDamageTime >= 30) {
plant.health -= 12; // Jumper zombie damages wallnut
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 60 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 60) {
plant.health -= 15; // Jumper zombie destroys plants with high damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating && !self.canJump) {
self.lastDamageTime = undefined;
}
}
};
return self;
});
// Event handlers
var LevelButton = Container.expand(function () {
var self = Container.call(this);
var buttonBg = self.attachAsset('levelButton', {
anchorX: 0.5,
anchorY: 0.5
});
self.levelNumber = 1;
self.setLevelNumber = function (number) {
self.levelNumber = number;
self.levelText.setText(number.toString());
};
self.isUnlocked = true;
self.stars = 0;
self.maxStars = 3;
// Create level number text
self.levelText = new Text2(self.levelNumber.toString(), {
size: 50,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0.5, 0.5);
self.levelText.y = -30;
self.addChild(self.levelText);
// Create stars display
self.starContainers = [];
for (var i = 0; i < self.maxStars; i++) {
var star = LK.getAsset('starShape', {
anchorX: 0.5,
anchorY: 0.5
});
star.x = (i - 1) * 50;
star.y = 30;
star.tint = 0x666666; // Dark by default
self.addChild(star);
self.starContainers.push(star);
}
self.setStars = function (starCount) {
self.stars = starCount;
for (var i = 0; i < self.maxStars; i++) {
if (i < starCount) {
self.starContainers[i].tint = 0xFFD700; // Golden
} else {
self.starContainers[i].tint = 0x666666; // Dark
}
}
};
self.setUnlocked = function (unlocked) {
self.isUnlocked = unlocked;
if (unlocked) {
buttonBg.tint = 0xFFFFFF;
self.levelText.tint = 0xFFFFFF;
} else {
buttonBg.tint = 0x666666;
self.levelText.tint = 0x666666;
}
};
self.down = function (x, y, obj) {
if (self.isUnlocked) {
LK.getSound('buttonClick').play();
// Start the selected level
startLevel(self.levelNumber);
} else {
// Play a different sound or show feedback for locked levels
console.log('Level ' + self.levelNumber + ' is locked. Complete previous levels first.');
}
};
return self;
});
// Plant classes
var Peashooter = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
var cannon = self.attachAsset('peashooterCannon', {
anchorX: 0,
anchorY: 0.5
});
cannon.x = 25;
cannon.y = 0;
self.health = 100;
self.lastShot = 0;
self.shootDelay = 150; // 2.5 seconds at 60fps
self.gridRow = 0;
self.gridCol = 0;
self.shoot = function () {
var bullet = new Bullet();
bullet.x = self.x + 50;
bullet.y = self.y;
bullet.row = self.gridRow;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('peasshooter').play(); // Play peashooter sound when shooting
};
self.update = function () {
if (LK.ticks - self.lastShot > self.shootDelay) {
// Check if there's a zombie in this row
var zombieInRow = false;
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].gridRow === self.gridRow && zombies[i].x > self.x) {
zombieInRow = true;
break;
}
}
if (zombieInRow) {
self.shoot();
self.lastShot = LK.ticks;
}
}
};
return self;
});
var ShieldZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0x8B4513; // Brown tint for shield zombie
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
head.tint = 0x8B4513;
// Create shield visual with rim, main body, and center decoration
var shieldRim = self.attachAsset('shieldRim', {
anchorX: 0.5,
anchorY: 0.5
});
shieldRim.x = 25; // Position shield in front of zombie
shieldRim.y = 0;
var shield = self.attachAsset('shieldVisual', {
anchorX: 0.5,
anchorY: 0.5
});
shield.x = 25; // Position shield in front of zombie
shield.y = 0;
var shieldCenter = self.attachAsset('shieldCenter', {
anchorX: 0.5,
anchorY: 0.5
});
shieldCenter.x = 25; // Position shield center decoration
shieldCenter.y = 0;
self.health = 150; // Zombie health (only takes damage after shield is destroyed)
self.shieldHealth = 150; // Shield health (must be destroyed first)
self.speed = 0.25; // Slower than regular zombie due to shield weight
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 12;
self.hasAttackedHouse = false;
self.hasShield = true; // Track if shield is still active
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original brown tint
if (self.body) {
self.body.tint = 0x8B4513;
}
if (self.head) {
self.head.tint = 0x8B4513;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if shield is destroyed
if (self.hasShield && self.shieldHealth <= 0) {
self.hasShield = false;
shield.destroy(); // Remove shield visual
shieldRim.destroy(); // Remove shield rim
shieldCenter.destroy(); // Remove shield center decoration
self.speed = 0.4; // Increase speed when shield is lost
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 12;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 30 ticks while touching WallNut
if (LK.ticks - self.lastDamageTime >= 30) {
plant.health -= 12;
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 60 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 60) {
plant.health -= 12; // Shield zombie destroys plants with moderate damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
var Sunflower = Container.expand(function () {
var self = Container.call(this);
// Create petals around center
for (var i = 0; i < 8; i++) {
var petal = self.attachAsset('sunflowerPetal', {
anchorX: 0.5,
anchorY: 1
});
var angle = i / 8 * Math.PI * 2;
petal.x = Math.cos(angle) * 25;
petal.y = Math.sin(angle) * 25;
petal.rotation = angle + Math.PI / 2;
}
var center = self.attachAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.lastSunGeneration = 0;
self.sunDelay = 300; // 5 seconds at 60fps
self.gridRow = 0;
self.gridCol = 0;
self.generateSun = function () {
sun += 25;
sunText.setText('Sun: ' + sun);
self.lastSunGeneration = LK.ticks;
};
self.update = function () {
if (LK.ticks - self.lastSunGeneration > self.sunDelay) {
self.generateSun();
}
};
return self;
});
var TankZombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
body.tint = 0x333333; // Dark gray tint for tank zombie
body.scaleX = 1.5;
body.scaleY = 1.5;
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -15;
head.tint = 0x333333;
head.scaleX = 1.5;
head.scaleY = 1.5;
self.health = 450; // Very high health
self.speed = 0.2; // Very slow
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 20;
self.hasAttackedHouse = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect but keep original dark gray tint
if (self.body) {
self.body.tint = 0x333333;
}
if (self.head) {
self.head.tint = 0x333333;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if reached house
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 25;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 40 ticks (slower but high damage) while touching WallNut
if (LK.ticks - self.lastDamageTime >= 40) {
plant.health -= 20; // Tank zombie destroys wallnut with high damage
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 70 ticks while touching other plants
if (LK.ticks - self.lastDamageTime >= 70) {
plant.health -= 25; // Tank zombie destroys plants with massive damage
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
var WallNut = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('placedWallNutBody', {
anchorX: 0.5,
anchorY: 0.5
});
body.scaleX = 1.3;
body.scaleY = 1.3;
self.health = 60; // Increased health, breaks after 6 zombie hits
self.gridRow = 0;
self.gridCol = 0;
self.update = function () {
// Check if being touched by any zombie and take continuous damage
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
if (zombie.gridRow === self.gridRow && Math.abs(zombie.x - self.x) < 50) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Take damage every 30 ticks (0.5 seconds) from any zombie type
if (LK.ticks - self.lastDamageTime >= 30) {
var damageAmount = 10; // Base damage from regular zombies
if (zombie instanceof FastZombie) {
damageAmount = 15;
} else if (zombie instanceof TankZombie) {
damageAmount = 20;
} else if (zombie instanceof ShieldZombie) {
damageAmount = 12;
} else if (zombie instanceof JumperZombie) {
damageAmount = 12;
}
self.health -= damageAmount;
self.lastDamageTime = LK.ticks;
}
break; // Only take damage from one zombie at a time
}
}
// Reset damage timer when no zombies are touching
var anyZombieTouching = false;
for (var j = 0; j < zombies.length; j++) {
var zombie = zombies[j];
if (zombie.gridRow === self.gridRow && Math.abs(zombie.x - self.x) < 50) {
anyZombieTouching = true;
break;
}
}
if (!anyZombieTouching) {
self.lastDamageTime = undefined;
}
// Check if health decreased to play breaking sound
if (self.lastHealth === undefined) {
self.lastHealth = self.health;
}
if (self.lastHealth > self.health) {
LK.getSound('ceviz').play(); // Play walnut breaking sound when damaged
}
self.lastHealth = self.health;
// Check if wallnut is destroyed
if (self.health <= 0) {
// Play walnut breaking sound when completely destroyed
LK.getSound('ceviz').play();
// Remove from plants array and destroy
for (var k = plants.length - 1; k >= 0; k--) {
if (plants[k] === self) {
plants.splice(k, 1);
break;
}
}
self.destroy();
}
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 1
});
body.y = 25;
var head = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 1
});
head.y = -10;
self.health = 150;
self.speed = 0.3;
self.gridRow = 0;
self.isEating = false;
self.eatDamage = 10;
self.hasAttackedHouse = false;
self.update = function () {
// Handle freeze effect
if (self.isFrozen && LK.ticks > self.frozenUntil) {
self.speed = self.originalSpeed;
self.isFrozen = false;
// Remove visual freeze effect
if (self.body) {
self.body.tint = 0xFFFFFF;
}
if (self.head) {
self.head.tint = 0xFFFFFF;
}
}
if (!self.isEating) {
self.x -= self.speed;
}
// Check if reached house - zombie damages house once with 10 health
if (self.x <= 150 && !self.hasAttackedHouse) {
houseHealth -= 10;
healthText.setText('House Health: ' + houseHealth);
self.hasAttackedHouse = true;
// Destroy zombie after attacking house
self.destroy();
for (var z = zombies.length - 1; z >= 0; z--) {
if (zombies[z] === self) {
zombies.splice(z, 1);
break;
}
}
// Check game over condition
if (houseHealth <= 0) {
LK.showGameOver();
}
}
// Check collision with plants
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
if (plant.gridRow === self.gridRow && Math.abs(self.x - plant.x) < 50) {
self.isEating = true;
// WallNut plants block zombies completely
if (plant instanceof WallNut) {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 30 ticks (0.5 seconds) while touching WallNut
if (LK.ticks - self.lastDamageTime >= 30) {
plant.health -= 10; // Continuous damage to wallnut
self.lastDamageTime = LK.ticks;
}
// Zombie cannot move past WallNut until it's destroyed
if (plant.health > 0) {
// Push zombie back slightly to prevent overlap
self.x = plant.x + 55;
}
} else {
// Initialize damage timer if not set
if (self.lastDamageTime === undefined) {
self.lastDamageTime = LK.ticks;
}
// Deal damage every 60 ticks (1 second) while touching other plants
if (LK.ticks - self.lastDamageTime >= 60) {
plant.health -= 10; // Increased damage to destroy plants faster
self.lastDamageTime = LK.ticks;
}
}
if (plant.health <= 0) {
plant.destroy();
plants.splice(i, 1);
self.isEating = false;
self.lastDamageTime = undefined; // Reset damage timer when plant is destroyed
}
break;
}
}
// Reset damage timer when not eating
if (!self.isEating) {
self.lastDamageTime = undefined;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x5D4E37 // Darker brown paper background color
});
/****
* Game Code
****/
// Level selection screen variables
// Import storage for persistent level progression
var levelButtons = [];
var totalLevels = 10;
var unlockedLevels = storage.unlockedLevels || 1; // Load from storage or default to 1
var levelProgress = storage.levelProgress || {}; // Load from storage or default to empty
// Global game variables
var plants = [];
var zombies = [];
var bullets = [];
var sun = 150;
var sunText;
var houseHealth;
var healthText;
// Music control variables
var isMusicOn = storage.isMusicOn !== undefined ? storage.isMusicOn : true; // Default music on
var musicButton;
// Create brown paper background
var background = LK.getAsset('paperBackground', {
anchorX: 0,
anchorY: 0
});
game.addChild(background);
// Create title
var titleBg = LK.getAsset('titleBackground', {
anchorX: 0.5,
anchorY: 0.5
});
titleBg.x = 1024;
titleBg.y = 300;
game.addChild(titleBg);
// Create level buttons in a grid layout
var buttonsPerRow = 5;
var buttonSpacingX = 350;
var buttonSpacingY = 250;
var startX = 400;
var startY = 700;
for (var i = 0; i < totalLevels; i++) {
var levelButton = new LevelButton();
levelButton.setLevelNumber(i + 1);
// Calculate position in grid
var row = Math.floor(i / buttonsPerRow);
var col = i % buttonsPerRow;
levelButton.x = startX + col * buttonSpacingX;
levelButton.y = startY + row * buttonSpacingY;
// Set unlock status - only unlock levels up to the player's progress
levelButton.setUnlocked(i < unlockedLevels);
// Set stars from progress (if any)
if (levelProgress[i + 1]) {
levelButton.setStars(levelProgress[i + 1]);
} else {
levelButton.setStars(0);
}
levelButtons.push(levelButton);
game.addChild(levelButton);
}
// Create music toggle button with new design
musicButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5
});
musicButton.x = 1700;
musicButton.y = 200;
musicButton.scaleX = 0.5;
musicButton.scaleY = 0.5;
musicButton.tint = isMusicOn ? 0x4CAF50 : 0xF44336;
game.addChild(musicButton);
// Add music icon symbol
var musicIcon = new Text2(isMusicOn ? '♪' : '♪', {
size: 50,
fill: 0xFFFFFF
});
musicIcon.anchor.set(0.5, 0.5);
musicIcon.x = 1700;
musicIcon.y = 185;
musicIcon.alpha = isMusicOn ? 1.0 : 0.3;
game.addChild(musicIcon);
// Add music status text
var musicText = new Text2(isMusicOn ? 'ON' : 'OFF', {
size: 24,
fill: 0xFFFFFF
});
musicText.anchor.set(0.5, 0.5);
musicText.x = 1700;
musicText.y = 215;
game.addChild(musicText);
// Music button event handler
musicButton.down = function () {
LK.getSound('buttonClick').play();
isMusicOn = !isMusicOn;
storage.isMusicOn = isMusicOn; // Save to storage
musicButton.tint = isMusicOn ? 0x4CAF50 : 0xF44336;
musicIcon.alpha = isMusicOn ? 1.0 : 0.3;
musicText.setText(isMusicOn ? 'ON' : 'OFF');
if (isMusicOn) {
// Resume music if we're in a level
if (currentLevel > 0) {
LK.playMusic('game');
}
} else {
LK.stopMusic();
}
};
// Function to start a level
function startLevel(levelNumber) {
LK.getSound('levelSelect').play();
console.log('Starting level: ' + levelNumber);
// Set current level for pause functionality
currentLevel = levelNumber;
// Clear the level selection screen
game.removeChildren();
// Initialize the appropriate level
if (levelNumber === 1) {
initializeLevel1();
} else if (levelNumber === 2) {
initializeLevel2();
} else if (levelNumber >= 3 && levelNumber <= 10) {
// Initialize dynamic levels for 3-10
initializeDynamicLevel(levelNumber);
}
}
// Initialize Level 1 Plants vs Zombies game
function initializeLevel1() {
// Start playing game music only if music is enabled
if (isMusicOn) {
LK.playMusic('game');
}
// Game variables
houseHealth = 100;
sun = 150; // Starting money for level 1
plants = []; // Make plants global
zombies = []; // Make zombies global
bullets = []; // Make bullets global
var sunflowers = [];
var lastSunGeneration = 0;
var lastZombieSpawn = 0;
var levelStartTime = null; // Track when level started - will be set when first plant is placed
var levelDuration = 7200; // 2 minutes at 60fps (120 seconds * 60 = 7200 ticks)
// Grid setup
var gridRows = 10;
var gridCols = 12;
var gridCellWidth = 180;
var gridCellHeight = 120;
var gridStartX = 200;
var gridStartY = 500;
// UI elements
sunText = new Text2('Sun: ' + sun, {
size: 40,
fill: 0x000000
});
sunText.x = 1500;
sunText.y = 50;
game.addChild(sunText);
healthText = new Text2('House Health: ' + houseHealth, {
size: 40,
fill: 0xFF0000
});
healthText.x = 1500;
healthText.y = 100;
game.addChild(healthText);
// Add timer display
var timerText = new Text2('Time: 2:00', {
size: 40,
fill: 0x0000FF
});
timerText.x = 1500;
timerText.y = 200;
game.addChild(timerText);
// Create grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var gridCell = LK.getAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.75,
scaleY: 0.75
});
gridCell.x = gridStartX + col * gridCellWidth;
gridCell.y = gridStartY + row * gridCellHeight;
gridCell.alpha = 0.8;
gridCell.tint = 0x8B7355;
game.addChild(gridCell);
}
}
// Plant selection buttons with proper spacing
var peashooterButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
peashooterButton.x = 400;
peashooterButton.y = 1800;
peashooterButton.tint = 0x006400;
game.addChild(peashooterButton);
// Add peashooter image to button
var peashooterImage = LK.getAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
peashooterImage.x = 400;
peashooterImage.y = 1780;
peashooterImage.scaleX = 1.0;
peashooterImage.scaleY = 1.0;
game.addChild(peashooterImage);
var peashooterCost = 50 + Math.max(0, 1 - 5) * 5; // Level 1 cost: 50, same for levels 1-5
var peashooterText = new Text2(peashooterCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
peashooterText.anchor.set(0.5, 0.5);
peashooterText.x = 400;
peashooterText.y = 1830;
game.addChild(peashooterText);
var sunflowerButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sunflowerButton.x = 1000;
sunflowerButton.y = 1800;
sunflowerButton.tint = 0xB8860B;
game.addChild(sunflowerButton);
// Add sunflower image to button
var sunflowerImage = LK.getAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
sunflowerImage.x = 1000;
sunflowerImage.y = 1780;
sunflowerImage.scaleX = 0.75;
sunflowerImage.scaleY = 0.75;
game.addChild(sunflowerImage);
var sunflowerCost = 50 + Math.max(0, 1 - 5) * 5; // Level 1 cost: 50, same for levels 1-5
var sunflowerText = new Text2(sunflowerCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
sunflowerText.anchor.set(0.5, 0.5);
sunflowerText.x = 1000;
sunflowerText.y = 1830;
game.addChild(sunflowerText);
var selectedPlant = null;
// Event handlers
peashooterButton.down = function () {
if (sun >= peashooterCost) {
selectedPlant = 'peashooter';
}
};
sunflowerButton.down = function () {
if (sun >= sunflowerCost) {
selectedPlant = 'sunflower';
}
};
game.down = function (x, y, obj) {
if (selectedPlant && x >= gridStartX && x <= gridStartX + gridCols * gridCellWidth && y >= gridStartY && y <= gridStartY + gridRows * gridCellHeight) {
var gridCol = Math.floor((x - gridStartX) / gridCellWidth);
var gridRow = Math.floor((y - gridStartY) / gridCellHeight);
// Check if position is valid and empty
var positionEmpty = true;
for (var i = 0; i < plants.length; i++) {
if (plants[i].gridRow === gridRow && plants[i].gridCol === gridCol) {
positionEmpty = false;
break;
}
}
if (positionEmpty && gridCol >= 0 && gridCol < gridCols && gridRow >= 0 && gridRow < gridRows) {
var plant = null;
if (selectedPlant === 'peashooter' && sun >= peashooterCost) {
plant = new Peashooter();
sun -= peashooterCost;
} else if (selectedPlant === 'sunflower' && sun >= sunflowerCost) {
plant = new Sunflower();
sun -= sunflowerCost;
}
if (plant) {
plant.x = gridStartX + gridCol * gridCellWidth;
plant.y = gridStartY + gridRow * gridCellHeight;
plant.gridRow = gridRow;
plant.gridCol = gridCol;
plants.push(plant);
game.addChild(plant);
sunText.setText('Sun: ' + sun);
selectedPlant = null;
}
}
}
};
var zombiesStarted = false; // Track if zombies have started spawning
// Game update function
game.update = function () {
// Start zombie spawning and timer only after first plant is placed
if (!zombiesStarted && plants.length > 0) {
zombiesStarted = true;
levelStartTime = LK.ticks; // Start level timer when first plant is placed
lastZombieSpawn = LK.ticks; // Initialize spawn timer
}
// Spawn zombies only after they have been started - progressive spawn rate
// Start slow and gradually increase spawn rate over time
var gameProgress = Math.min(LK.ticks / 3600, 1); // Progress from 0 to 1 over 60 seconds
var currentSpawnRate = 180 - gameProgress * 120; // Start at 180 (3 seconds), end at 60 (1 second)
if (zombiesStarted && LK.ticks - lastZombieSpawn > currentSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Update all game objects
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
for (var j = zombies.length - 1; j >= 0; j--) {
zombies[j].update();
}
for (var k = bullets.length - 1; k >= 0; k--) {
bullets[k].update();
}
// Generate sun from sunflowers - only if sunflowers exist
var sunflowerCount = 0;
for (var s = 0; s < plants.length; s++) {
if (plants[s] instanceof Sunflower) {
sunflowerCount++;
}
}
if (sunflowerCount > 0 && LK.ticks - lastSunGeneration > 600) {
// Every 10 seconds, only if sunflowers exist
sun += 10;
sunText.setText('Sun: ' + sun);
lastSunGeneration = LK.ticks;
}
// Increased spawn when 1.5 minutes (90 seconds = 5400 ticks) remain - starts at 1.5 minutes left
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
if (timeRemaining <= 5400) {
// 1.5 minutes = 90 seconds = 5400 ticks
var intensifiedSpawnRate = 30 - (5400 - timeRemaining) / 5400 * 20; // Start at 30 (0.5 seconds), end at 10 (0.17 seconds)
if (LK.ticks - lastZombieSpawn > intensifiedSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
}
// Update timer display
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 3600);
var seconds = Math.floor(timeRemaining % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
// Check win condition - survive for 2 minutes
if (levelStartTime && timeElapsed >= levelDuration) {
// Player survived 2 minutes - they win!
unlockNextLevel(); // Unlock next level when current level is completed
// Calculate stars based on house health: 3 stars = 80+%, 2 stars = 50+%, 1 star = any health remaining
var stars = 1;
if (houseHealth >= 80) {
stars = 3;
} else if (houseHealth >= 50) {
stars = 2;
}
setLevelStars(1, stars);
LK.showYouWin();
}
};
}
// Initialize Level 2 Plants vs Zombies game with more zombies and fast zombies
function initializeLevel2() {
// Start playing game music only if music is enabled
if (isMusicOn) {
LK.playMusic('game');
}
// Game variables
houseHealth = 100;
sun = 200; // Starting money for level 2 (50 more than level 1)
plants = []; // Make plants global
zombies = []; // Make zombies global
bullets = []; // Make bullets global
var levelStartTime = null; // Track when level started - will be set when first plant is placed
var levelDuration = 7200; // 2 minutes at 60fps (120 seconds * 60 = 7200 ticks)
// Night theme background
var nightBg = LK.getAsset('nightBackground', {
anchorX: 0,
anchorY: 0
});
game.addChild(nightBg);
var sunflowers = [];
var lastSunGeneration = 0;
var lastZombieSpawn = 0;
var lastFastZombieSpawn = 0;
// Grid setup
var gridRows = 10;
var gridCols = 12;
var gridCellWidth = 180;
var gridCellHeight = 120;
var gridStartX = 200;
var gridStartY = 500;
// UI elements
sunText = new Text2('Sun: ' + sun, {
size: 40,
fill: 0x000000
});
sunText.x = 1500;
sunText.y = 50;
game.addChild(sunText);
healthText = new Text2('House Health: ' + houseHealth, {
size: 40,
fill: 0xFF0000
});
healthText.x = 1500;
healthText.y = 100;
game.addChild(healthText);
// Level indicator
var levelText = new Text2('Level 2', {
size: 50,
fill: 0xFFFFFF
});
levelText.x = 1500;
levelText.y = 150;
game.addChild(levelText);
// Add timer display
var timerText = new Text2('Time: 2:00', {
size: 40,
fill: 0x0000FF
});
timerText.x = 1500;
timerText.y = 200;
game.addChild(timerText);
// Create grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var gridCell = LK.getAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.75,
scaleY: 0.75
});
gridCell.x = gridStartX + col * gridCellWidth;
gridCell.y = gridStartY + row * gridCellHeight;
gridCell.alpha = 0.9;
gridCell.tint = 0x1A0D33;
game.addChild(gridCell);
}
}
// Plant selection buttons with proper spacing
var peashooterButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
peashooterButton.x = 350;
peashooterButton.y = 1800;
peashooterButton.tint = 0x006400;
game.addChild(peashooterButton);
// Add peashooter image to button
var peashooterImage = LK.getAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
peashooterImage.x = 350;
peashooterImage.y = 1780;
peashooterImage.scaleX = 1.0;
peashooterImage.scaleY = 1.0;
game.addChild(peashooterImage);
var peashooterCost = 50 + Math.max(0, 2 - 5) * 5; // Level 2 cost: 50, same for levels 1-5
var peashooterText = new Text2(peashooterCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
peashooterText.anchor.set(0.5, 0.5);
peashooterText.x = 350;
peashooterText.y = 1830;
game.addChild(peashooterText);
var sunflowerButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sunflowerButton.x = 800;
sunflowerButton.y = 1800;
sunflowerButton.tint = 0xB8860B;
game.addChild(sunflowerButton);
// Add sunflower image to button
var sunflowerImage = LK.getAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
sunflowerImage.x = 800;
sunflowerImage.y = 1780;
sunflowerImage.scaleX = 0.75;
sunflowerImage.scaleY = 0.75;
game.addChild(sunflowerImage);
var sunflowerCost = 50 + Math.max(0, 2 - 5) * 5; // Level 2 cost: 50, same for levels 1-5
var sunflowerText = new Text2(sunflowerCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
sunflowerText.anchor.set(0.5, 0.5);
sunflowerText.x = 800;
sunflowerText.y = 1830;
game.addChild(sunflowerText);
var selectedPlant = null;
// Add CherryBomb button for level 2
var cherrybombButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
cherrybombButton.x = 1250;
cherrybombButton.y = 1800;
cherrybombButton.tint = 0xFF0000;
game.addChild(cherrybombButton);
var cherrybombImage = LK.getAsset('cherryImage', {
anchorX: 0.5,
anchorY: 0.5
});
cherrybombImage.x = 1250;
cherrybombImage.y = 1780;
cherrybombImage.scaleX = 0.75;
cherrybombImage.scaleY = 0.75;
game.addChild(cherrybombImage);
var cherrybombCost = 100 + Math.max(0, 2 - 5) * 10; // Level 2 cost: 100, same for levels 1-5
var cherrybombText = new Text2(cherrybombCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
cherrybombText.anchor.set(0.5, 0.5);
cherrybombText.x = 1250;
cherrybombText.y = 1830;
game.addChild(cherrybombText);
cherrybombButton.down = function () {
if (sun >= cherrybombCost) {
selectedPlant = 'cherrybomb';
}
};
// Event handlers
peashooterButton.down = function () {
if (sun >= peashooterCost) {
selectedPlant = 'peashooter';
}
};
sunflowerButton.down = function () {
if (sun >= sunflowerCost) {
selectedPlant = 'sunflower';
}
};
game.down = function (x, y, obj) {
if (selectedPlant && x >= gridStartX && x <= gridStartX + gridCols * gridCellWidth && y >= gridStartY && y <= gridStartY + gridRows * gridCellHeight) {
var gridCol = Math.floor((x - gridStartX) / gridCellWidth);
var gridRow = Math.floor((y - gridStartY) / gridCellHeight);
// Check if position is valid and empty
var positionEmpty = true;
for (var i = 0; i < plants.length; i++) {
if (plants[i].gridRow === gridRow && plants[i].gridCol === gridCol) {
positionEmpty = false;
break;
}
}
if (positionEmpty && gridCol >= 0 && gridCol < gridCols && gridRow >= 0 && gridRow < gridRows) {
var plant = null;
if (selectedPlant === 'peashooter' && sun >= peashooterCost) {
plant = new Peashooter();
sun -= peashooterCost;
} else if (selectedPlant === 'sunflower' && sun >= sunflowerCost) {
plant = new Sunflower();
sun -= sunflowerCost;
} else if (selectedPlant === 'cherrybomb' && sun >= cherrybombCost) {
plant = new CherryBomb();
sun -= cherrybombCost;
}
if (plant) {
plant.x = gridStartX + gridCol * gridCellWidth;
plant.y = gridStartY + gridRow * gridCellHeight;
plant.gridRow = gridRow;
plant.gridCol = gridCol;
plants.push(plant);
game.addChild(plant);
sunText.setText('Sun: ' + sun);
selectedPlant = null;
}
}
}
};
var zombiesStarted = false; // Track if zombies have started spawning
// Game update function for Level 2
game.update = function () {
// Start zombie spawning and timer only after first plant is placed
if (!zombiesStarted && plants.length > 0) {
zombiesStarted = true;
levelStartTime = LK.ticks; // Start level timer when first plant is placed
lastZombieSpawn = LK.ticks; // Initialize spawn timer
lastFastZombieSpawn = LK.ticks; // Initialize fast zombie spawn timer
}
// Spawn regular zombies with progressive spawn rate - only after zombies started
var gameProgress = Math.min(LK.ticks / 5400, 1); // Progress from 0 to 1 over 90 seconds
var currentSpawnRate = 120 - gameProgress * 80; // Start at 120 (2 seconds), end at 40 (0.67 seconds)
if (zombiesStarted && LK.ticks - lastZombieSpawn > currentSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Spawn fast zombies with progressive spawn rate - only after zombies started
var fastSpawnRate = 180 - gameProgress * 120; // Start at 180 (3 seconds), end at 60 (1 second)
if (zombiesStarted && LK.ticks - lastFastZombieSpawn > fastSpawnRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
// Spawn tank zombies with progressive spawn rate - only after zombies started
var tankSpawnRate = 300 - gameProgress * 150; // Start at 300 (5 seconds), end at 150 (2.5 seconds)
if (zombiesStarted && LK.ticks % tankSpawnRate === 0 && LK.ticks > 0) {
var tankZombie = new TankZombie();
var row = Math.floor(Math.random() * gridRows);
tankZombie.x = 1900;
tankZombie.y = gridStartY + row * gridCellHeight;
tankZombie.gridRow = row;
zombies.push(tankZombie);
game.addChild(tankZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
// Update all game objects
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
for (var j = zombies.length - 1; j >= 0; j--) {
zombies[j].update();
}
for (var k = bullets.length - 1; k >= 0; k--) {
bullets[k].update();
}
// Generate sun from sunflowers - only if sunflowers exist
var sunflowerCount = 0;
for (var s = 0; s < plants.length; s++) {
if (plants[s] instanceof Sunflower) {
sunflowerCount++;
}
}
if (sunflowerCount > 0 && LK.ticks - lastSunGeneration > 600) {
// Every 10 seconds, only if sunflowers exist
sun += 10;
sunText.setText('Sun: ' + sun);
lastSunGeneration = LK.ticks;
}
// Increased spawn when 1.5 minutes (90 seconds = 5400 ticks) remain - starts at 1.5 minutes left
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
if (timeRemaining <= 5400) {
// 1.5 minutes = 90 seconds = 5400 ticks
var intensifiedSpawnRate = 30 - (5400 - timeRemaining) / 5400 * 20; // Start at 30 (0.5 seconds), end at 10 (0.17 seconds)
if (LK.ticks - lastZombieSpawn > intensifiedSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Also spawn fast zombies more frequently during intensified wave
var intensifiedFastSpawnRate = 60 - (5400 - timeRemaining) / 5400 * 45; // Start at 60 (1 second), end at 15 (0.25 seconds)
if (LK.ticks - lastFastZombieSpawn > intensifiedFastSpawnRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
}
// Update timer display
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 3600);
var seconds = Math.floor(timeRemaining % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
// Check win condition - survive for 2 minutes
if (levelStartTime && timeElapsed >= levelDuration) {
// Player survived 2 minutes - they win!
unlockNextLevel(); // Unlock next level when current level is completed
// Calculate stars based on house health: 3 stars = 80+%, 2 stars = 50+%, 1 star = any health remaining
var stars = 1;
if (houseHealth >= 80) {
stars = 3;
} else if (houseHealth >= 50) {
stars = 2;
}
setLevelStars(2, stars);
LK.showYouWin();
}
};
}
// Initialize Dynamic Level for levels 3 and beyond
function initializeDynamicLevel(levelNumber) {
// Start playing game music only if music is enabled
if (isMusicOn) {
LK.playMusic('game');
}
// Game variables
houseHealth = 100;
sun = 150 + (levelNumber - 1) * 50; // Progressive starting money: level 1=150, level 2=200, level 3=250, etc.
plants = []; // Make plants global
zombies = []; // Make zombies global
bullets = []; // Make bullets global
// Dynamic theme based on level
var themeBackgrounds = ['desertBackground', 'iceBackground', 'jungleBackground'];
var themeIndex = (levelNumber - 3) % themeBackgrounds.length;
var themeBg = LK.getAsset(themeBackgrounds[themeIndex], {
anchorX: 0,
anchorY: 0
});
game.addChild(themeBg);
var sunflowers = [];
var lastSunGeneration = 0;
var lastZombieSpawn = 0;
var lastFastZombieSpawn = 0;
// Dynamic difficulty scaling based on level - with increased spawn rates
var difficultyMultiplier = levelNumber - 2; // Starts at 1 for level 3
var zombieSpawnRate = Math.max(30, 90 - difficultyMultiplier * 15); // Much faster spawning, increased each level
var fastZombieSpawnRate = Math.max(60, 180 - difficultyMultiplier * 20); // Much more fast zombies
// Dynamic level duration based on level number
var levelDuration;
if (levelNumber === 3) {
levelDuration = 7200; // 2 minutes at 60fps (120 seconds * 60 = 7200 ticks)
} else if (levelNumber >= 4 && levelNumber <= 6) {
levelDuration = 9000; // 2.5 minutes at 60fps (150 seconds * 60 = 9000 ticks)
} else if (levelNumber >= 7 && levelNumber <= 10) {
levelDuration = 12600; // 3.5 minutes at 60fps (210 seconds * 60 = 12600 ticks)
}
var finalWaveStart = levelDuration - 1800; // Final wave starts 30 seconds before end
var levelStartTime = null; // Track when level started - will be set when first plant is placed
// Grid setup
var gridRows = 10;
var gridCols = 12;
var gridCellWidth = 180;
var gridCellHeight = 120;
var gridStartX = 200;
var gridStartY = 500;
// UI elements
sunText = new Text2('Sun: ' + sun, {
size: 40,
fill: 0x000000
});
sunText.x = 1500;
sunText.y = 50;
game.addChild(sunText);
healthText = new Text2('House Health: ' + houseHealth, {
size: 40,
fill: 0xFF0000
});
healthText.x = 1500;
healthText.y = 100;
game.addChild(healthText);
// Level indicator
var levelText = new Text2('Level ' + levelNumber, {
size: 50,
fill: 0xFFFFFF
});
levelText.x = 1500;
levelText.y = 150;
game.addChild(levelText);
// Add timer display
var initialTimeString;
if (levelNumber === 3) {
initialTimeString = 'Time: 2:00';
} else if (levelNumber >= 4 && levelNumber <= 6) {
initialTimeString = 'Time: 2:30';
} else if (levelNumber >= 7 && levelNumber <= 10) {
initialTimeString = 'Time: 3:30';
}
var timerText = new Text2(initialTimeString, {
size: 40,
fill: 0x0000FF
});
timerText.x = 1500;
timerText.y = 200;
game.addChild(timerText);
// Create grid
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var gridCell = LK.getAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.75,
scaleY: 0.75
});
gridCell.x = gridStartX + col * gridCellWidth;
gridCell.y = gridStartY + row * gridCellHeight;
gridCell.alpha = 0.9;
// Apply theme-specific tinting with unique grid appearances
if (themeBackgrounds[themeIndex] === 'desertBackground') {
gridCell.tint = 0xD2691E; // Sandy orange for desert
} else if (themeBackgrounds[themeIndex] === 'iceBackground') {
gridCell.tint = 0x87CEEB; // Light blue for ice
} else if (themeBackgrounds[themeIndex] === 'jungleBackground') {
gridCell.tint = 0x228B22; // Forest green for jungle
}
game.addChild(gridCell);
}
}
// Plant selection buttons with proper spacing
var peashooterButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
peashooterButton.x = 300;
peashooterButton.y = 1800;
peashooterButton.tint = 0x006400;
game.addChild(peashooterButton);
// Add peashooter image to button
var peashooterImage = LK.getAsset('peashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
peashooterImage.x = 300;
peashooterImage.y = 1780;
peashooterImage.scaleX = 1.0;
peashooterImage.scaleY = 1.0;
game.addChild(peashooterImage);
var peashooterCost = 50 + Math.max(0, levelNumber - 5) * 5; // Cost same for levels 1-5, then increases by 5 per level
var peashooterText = new Text2(peashooterCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
peashooterText.anchor.set(0.5, 0.5);
peashooterText.x = 300;
peashooterText.y = 1830;
game.addChild(peashooterText);
var sunflowerButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
sunflowerButton.x = 700;
sunflowerButton.y = 1800;
sunflowerButton.tint = 0xB8860B;
game.addChild(sunflowerButton);
// Add sunflower image to button
var sunflowerImage = LK.getAsset('sunflowerCenter', {
anchorX: 0.5,
anchorY: 0.5
});
sunflowerImage.x = 700;
sunflowerImage.y = 1780;
sunflowerImage.scaleX = 0.75;
sunflowerImage.scaleY = 0.75;
game.addChild(sunflowerImage);
var sunflowerCost = 50 + Math.max(0, levelNumber - 5) * 5; // Cost same for levels 1-5, then increases by 5 per level
var sunflowerText = new Text2(sunflowerCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
sunflowerText.anchor.set(0.5, 0.5);
sunflowerText.x = 700;
sunflowerText.y = 1830;
game.addChild(sunflowerText);
var selectedPlant = null;
// Add more plant options based on level
if (levelNumber >= 3) {
// Add DoublePeashooter button
var doublePeaButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
doublePeaButton.x = 1100;
doublePeaButton.y = 1800;
doublePeaButton.tint = 0x00AA00;
game.addChild(doublePeaButton);
var doublePeaImage = LK.getAsset('placedDoublePeaBody', {
anchorX: 0.5,
anchorY: 0.5
});
doublePeaImage.x = 1100;
doublePeaImage.y = 1780;
game.addChild(doublePeaImage);
var doublePeaCost = 100 + Math.max(0, levelNumber - 5) * 10; // Cost same for levels 1-5, then increases by 10 per level
var doublePeaText = new Text2(doublePeaCost + ' Sun', {
size: 28,
fill: 0xFFFFFF
});
doublePeaText.anchor.set(0.5, 0.5);
doublePeaText.x = 1100;
doublePeaText.y = 1830;
game.addChild(doublePeaText);
doublePeaButton.down = function () {
if (sun >= doublePeaCost) {
selectedPlant = 'doublepea';
}
};
}
if (levelNumber >= 4) {
// Add CherryBomb button
var cherrybombButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
cherrybombButton.x = 1500;
cherrybombButton.y = 1800;
cherrybombButton.tint = 0xFF0000;
game.addChild(cherrybombButton);
var cherrybombImage = LK.getAsset('cherryImage', {
anchorX: 0.5,
anchorY: 0.5
});
cherrybombImage.x = 1500;
cherrybombImage.y = 1780;
cherrybombImage.scaleX = 0.6;
cherrybombImage.scaleY = 0.6;
game.addChild(cherrybombImage);
var cherrybombCost = 100 + Math.max(0, levelNumber - 5) * 10; // Cost same for levels 1-5, then increases by 10 per level
var cherrybombText = new Text2(cherrybombCost + ' Sun', {
size: 28,
fill: 0xFFFFFF
});
cherrybombText.anchor.set(0.5, 0.5);
cherrybombText.x = 1500;
cherrybombText.y = 1830;
game.addChild(cherrybombText);
cherrybombButton.down = function () {
if (sun >= cherrybombCost) {
selectedPlant = 'cherrybomb';
}
};
}
if (levelNumber >= 6) {
// Add WallNut button below CherryBomb button
var wallnutButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
wallnutButton.x = 1500;
wallnutButton.y = 2000;
wallnutButton.tint = 0x8B4513;
game.addChild(wallnutButton);
var wallnutImage = LK.getAsset('placedWallNutBody', {
anchorX: 0.5,
anchorY: 0.5
});
wallnutImage.x = 1500;
wallnutImage.y = 1980;
wallnutImage.scaleX = 0.9;
wallnutImage.scaleY = 0.9;
game.addChild(wallnutImage);
var wallnutCost = 50 + Math.max(0, levelNumber - 5) * 5; // Cost same for levels 1-5, then increases by 5 per level
var wallnutText = new Text2(wallnutCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
wallnutText.anchor.set(0.5, 0.5);
wallnutText.x = 1500;
wallnutText.y = 2030;
game.addChild(wallnutText);
wallnutButton.down = function () {
if (sun >= wallnutCost) {
selectedPlant = 'wallnut';
}
};
}
if (levelNumber >= 8) {
// Add IcePeashooter button below Peashooter
var icepeaButton = LK.getAsset('selectionBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
icepeaButton.x = 300;
icepeaButton.y = 2000;
icepeaButton.tint = 0x87CEEB;
game.addChild(icepeaButton);
var icepeaImage = LK.getAsset('icePeashooterBody', {
anchorX: 0.5,
anchorY: 0.5
});
icepeaImage.x = 300;
icepeaImage.y = 1980;
icepeaImage.scaleX = 1.0;
icepeaImage.scaleY = 1.0;
game.addChild(icepeaImage);
var icepeaCost = 75 + Math.max(0, levelNumber - 5) * 8; // Cost same for levels 1-5, then increases by 8 per level
var icepeaText = new Text2(icepeaCost + ' Sun', {
size: 32,
fill: 0xFFFFFF
});
icepeaText.anchor.set(0.5, 0.5);
icepeaText.x = 300;
icepeaText.y = 2030;
game.addChild(icepeaText);
icepeaButton.down = function () {
if (sun >= icepeaCost) {
selectedPlant = 'icepea';
}
};
}
// Event handlers
peashooterButton.down = function () {
if (sun >= peashooterCost) {
selectedPlant = 'peashooter';
}
};
sunflowerButton.down = function () {
if (sun >= sunflowerCost) {
selectedPlant = 'sunflower';
}
};
game.down = function (x, y, obj) {
if (selectedPlant && x >= gridStartX && x <= gridStartX + gridCols * gridCellWidth && y >= gridStartY && y <= gridStartY + gridRows * gridCellHeight) {
var gridCol = Math.floor((x - gridStartX) / gridCellWidth);
var gridRow = Math.floor((y - gridStartY) / gridCellHeight);
// Check if position is valid and empty
var positionEmpty = true;
for (var i = 0; i < plants.length; i++) {
if (plants[i].gridRow === gridRow && plants[i].gridCol === gridCol) {
positionEmpty = false;
break;
}
}
if (positionEmpty && gridCol >= 0 && gridCol < gridCols && gridRow >= 0 && gridRow < gridRows) {
var plant = null;
if (selectedPlant === 'peashooter' && sun >= peashooterCost) {
plant = new Peashooter();
sun -= peashooterCost;
} else if (selectedPlant === 'sunflower' && sun >= sunflowerCost) {
plant = new Sunflower();
sun -= sunflowerCost;
} else if (selectedPlant === 'doublepea' && sun >= doublePeaCost) {
plant = new DoublePeashooter();
sun -= doublePeaCost;
} else if (selectedPlant === 'cherrybomb' && sun >= cherrybombCost) {
plant = new CherryBomb();
sun -= cherrybombCost;
} else if (selectedPlant === 'wallnut' && sun >= wallnutCost) {
plant = new WallNut();
sun -= wallnutCost;
} else if (selectedPlant === 'icepea' && sun >= icepeaCost) {
plant = new IcePeashooter();
sun -= icepeaCost;
}
if (plant) {
plant.x = gridStartX + gridCol * gridCellWidth;
plant.y = gridStartY + gridRow * gridCellHeight;
plant.gridRow = gridRow;
plant.gridCol = gridCol;
plants.push(plant);
game.addChild(plant);
sunText.setText('Sun: ' + sun);
selectedPlant = null;
}
}
}
};
var zombiesStarted = false; // Track if zombies have started spawning
// Game update function for Dynamic Levels
game.update = function () {
// Start zombie spawning and timer only after first plant is placed
if (!zombiesStarted && plants.length > 0) {
zombiesStarted = true;
levelStartTime = LK.ticks; // Start level timer when first plant is placed
lastZombieSpawn = LK.ticks; // Initialize spawn timer
lastFastZombieSpawn = LK.ticks; // Initialize fast zombie spawn timer
}
// Spawn regular zombies with progressive and scaled difficulty - only after zombies started
var gameProgress = Math.min(LK.ticks / levelDuration, 1); // Progress from 0 to 1 over level duration
var progressiveSpawnRate = zombieSpawnRate * 2 - gameProgress * zombieSpawnRate; // Start at 2x spawn rate, end at 1x
if (zombiesStarted && LK.ticks - lastZombieSpawn > progressiveSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Spawn fast zombies with progressive and scaled difficulty - only after zombies started
var progressiveFastSpawnRate = fastZombieSpawnRate * 2 - gameProgress * fastZombieSpawnRate; // Start at 2x spawn rate, end at 1x
if (zombiesStarted && LK.ticks - lastFastZombieSpawn > progressiveFastSpawnRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
// Spawn tank zombies on higher levels with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 4) {
var baseTankSpawnRate = 180 - difficultyMultiplier * 30;
var progressiveTankSpawnRate = baseTankSpawnRate * 2 - gameProgress * baseTankSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks % progressiveTankSpawnRate === 0 && LK.ticks > 0) {
var tankZombie = new TankZombie();
var row = Math.floor(Math.random() * gridRows);
tankZombie.x = 1900;
tankZombie.y = gridStartY + row * gridCellHeight;
tankZombie.gridRow = row;
zombies.push(tankZombie);
game.addChild(tankZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Spawn shield zombies starting from level 5 with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 5) {
var baseShieldSpawnRate = 210 - difficultyMultiplier * 30;
var progressiveShieldSpawnRate = baseShieldSpawnRate * 2 - gameProgress * baseShieldSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks % progressiveShieldSpawnRate === 0 && LK.ticks > 0) {
var shieldZombie = new ShieldZombie();
var row = Math.floor(Math.random() * gridRows);
shieldZombie.x = 1900;
shieldZombie.y = gridStartY + row * gridCellHeight;
shieldZombie.gridRow = row;
zombies.push(shieldZombie);
game.addChild(shieldZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Spawn jumper zombies on even higher levels with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 6) {
var baseJumperSpawnRate = 150 - difficultyMultiplier * 20;
var progressiveJumperSpawnRate = baseJumperSpawnRate * 2 - gameProgress * baseJumperSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks % progressiveJumperSpawnRate === 0 && LK.ticks > 0) {
var jumperZombie = new JumperZombie();
var row = Math.floor(Math.random() * gridRows);
jumperZombie.x = 1900;
jumperZombie.y = gridStartY + row * gridCellHeight;
jumperZombie.gridRow = row;
zombies.push(jumperZombie);
game.addChild(jumperZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Spawn bomber zombies on levels 8, 9, and 10 with progressive spawn rate - only after zombies started
if (zombiesStarted && levelNumber >= 8) {
var baseBomberSpawnRate = Math.max(180, 240 - difficultyMultiplier * 25);
var progressiveBomberSpawnRate = baseBomberSpawnRate * 2 - gameProgress * baseBomberSpawnRate; // Start at 2x spawn rate, end at 1x
if (LK.ticks - lastZombieSpawn > progressiveBomberSpawnRate && LK.ticks % 300 === 0) {
var bomberZombie = new BomberZombie();
var row = Math.floor(Math.random() * gridRows);
bomberZombie.x = 1900;
bomberZombie.y = gridStartY + row * gridCellHeight;
bomberZombie.gridRow = row;
zombies.push(bomberZombie);
game.addChild(bomberZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
}
}
// Update all game objects
for (var i = plants.length - 1; i >= 0; i--) {
plants[i].update();
}
for (var j = zombies.length - 1; j >= 0; j--) {
zombies[j].update();
}
for (var k = bullets.length - 1; k >= 0; k--) {
bullets[k].update();
}
// Generate sun from sunflowers - only if sunflowers exist
var sunflowerCount = 0;
for (var s = 0; s < plants.length; s++) {
if (plants[s] instanceof Sunflower) {
sunflowerCount++;
}
}
if (sunflowerCount > 0 && LK.ticks - lastSunGeneration > 600) {
// Every 10 seconds, only if sunflowers exist
sun += 10;
sunText.setText('Sun: ' + sun);
lastSunGeneration = LK.ticks;
}
// Intensified wave - spawn many more zombies rapidly when 1.5 minutes remain
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
if (levelStartTime && timeRemaining <= 5400) {
// 1.5 minutes = 90 seconds = 5400 ticks
var intensifiedProgress = (5400 - timeRemaining) / 5400; // Progress from 0 to 1 over intensified period
var baseIntensifiedSpawnRate = Math.max(8, zombieSpawnRate / 8); // Much faster in intensified wave
var progressiveIntensifiedSpawnRate = baseIntensifiedSpawnRate * 3 - intensifiedProgress * baseIntensifiedSpawnRate * 2; // Start at 3x, end at 1x
if (LK.ticks - lastZombieSpawn > progressiveIntensifiedSpawnRate) {
var zombie = new Zombie();
var row = Math.floor(Math.random() * gridRows);
zombie.x = 1900;
zombie.y = gridStartY + row * gridCellHeight;
zombie.gridRow = row;
zombies.push(zombie);
game.addChild(zombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastZombieSpawn = LK.ticks;
}
// Also spawn fast zombies more frequently during intensified wave
var baseIntensifiedFastRate = Math.max(15, fastZombieSpawnRate / 8);
var progressiveIntensifiedFastRate = baseIntensifiedFastRate * 3 - intensifiedProgress * baseIntensifiedFastRate * 2; // Start at 3x, end at 1x
if (LK.ticks - lastFastZombieSpawn > progressiveIntensifiedFastRate) {
var fastZombie = new FastZombie();
var row = Math.floor(Math.random() * gridRows);
fastZombie.x = 1900;
fastZombie.y = gridStartY + row * gridCellHeight;
fastZombie.gridRow = row;
zombies.push(fastZombie);
game.addChild(fastZombie);
LK.getSound('zombisesi').play(); // Play zombie sound when spawning
lastFastZombieSpawn = LK.ticks;
}
}
// Update timer display
var timeElapsed = levelStartTime ? LK.ticks - levelStartTime : 0;
var timeRemaining = Math.max(0, levelDuration - timeElapsed);
var minutes = Math.floor(timeRemaining / 3600);
var seconds = Math.floor(timeRemaining % 3600 / 60);
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
// Check win condition - survive for 2 minutes
if (levelStartTime && timeElapsed >= levelDuration) {
// Player survived 2 minutes - they win!
unlockNextLevel(); // Unlock next level when current level is completed
// Calculate stars based on house health: 3 stars = 80+%, 2 stars = 50+%, 1 star = any health remaining
var stars = 1;
if (houseHealth >= 80) {
stars = 3;
} else if (houseHealth >= 50) {
stars = 2;
}
setLevelStars(levelNumber, stars);
LK.showYouWin();
}
};
}
// Function to unlock next level
function unlockNextLevel() {
// Only unlock the next level if current level is completed and next level exists
if (currentLevel === unlockedLevels && unlockedLevels < totalLevels) {
unlockedLevels++;
storage.unlockedLevels = unlockedLevels; // Save to storage
if (levelButtons[unlockedLevels - 1]) {
levelButtons[unlockedLevels - 1].setUnlocked(true);
}
}
}
// Function to set stars for a level
function setLevelStars(levelNumber, stars) {
levelProgress[levelNumber] = stars;
storage.levelProgress = levelProgress; // Save to storage
if (levelButtons[levelNumber - 1]) {
levelButtons[levelNumber - 1].setStars(stars);
}
}
// Pause menu variables
var isPaused = false;
var pauseMenu = null;
var currentLevel = 0;
// Function to show pause menu
function showPauseMenu() {
if (pauseMenu) return; // Already showing
// Create pause menu background
pauseMenu = new Container();
var pauseBg = LK.getAsset('paperBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.4
});
pauseBg.x = 0;
pauseBg.y = 0;
pauseBg.tint = 0x000000;
pauseBg.alpha = 0.8;
pauseMenu.addChild(pauseBg);
// Pause title
var pauseTitle = new Text2('PAUSED', {
size: 60,
fill: 0xFFFFFF
});
pauseTitle.anchor.set(0.5, 0.5);
pauseTitle.x = 0;
pauseTitle.y = -100;
pauseMenu.addChild(pauseTitle);
// Restart button
var restartButton = LK.getAsset('levelButton', {
anchorX: 0.5,
anchorY: 0.5
});
restartButton.x = 0;
restartButton.y = 0;
restartButton.tint = 0x4CAF50;
pauseMenu.addChild(restartButton);
var restartText = new Text2('RESTART', {
size: 40,
fill: 0x000000
});
restartText.anchor.set(0.5, 0.5);
restartText.x = 0;
restartText.y = 0;
pauseMenu.addChild(restartText);
// Resume button
var resumeButton = LK.getAsset('levelButton', {
anchorX: 0.5,
anchorY: 0.5
});
resumeButton.x = 0;
resumeButton.y = 120;
resumeButton.tint = 0x2196F3;
pauseMenu.addChild(resumeButton);
var resumeText = new Text2('RESUME', {
size: 40,
fill: 0x000000
});
resumeText.anchor.set(0.5, 0.5);
resumeText.x = 0;
resumeText.y = 120;
pauseMenu.addChild(resumeText);
// Position pause menu at center of screen
pauseMenu.x = 1024;
pauseMenu.y = 1366;
// Add event handlers
restartButton.down = function () {
LK.getSound('buttonClick').play();
hidePauseMenu();
// Restart current level
if (currentLevel === 1) {
game.removeChildren();
initializeLevel1();
} else if (currentLevel === 2) {
game.removeChildren();
initializeLevel2();
} else if (currentLevel >= 3 && currentLevel <= 10) {
game.removeChildren();
initializeDynamicLevel(currentLevel);
}
};
resumeButton.down = function () {
LK.getSound('buttonClick').play();
hidePauseMenu();
};
game.addChild(pauseMenu);
}
// Function to hide pause menu
function hidePauseMenu() {
if (pauseMenu) {
game.removeChild(pauseMenu);
pauseMenu = null;
}
isPaused = false;
}
// Override game pause handler
LK.on('pause', function () {
if (currentLevel > 0) {
// Only show pause menu during gameplay
isPaused = true;
showPauseMenu();
}
});
// Simple game update for level selection screen
game.update = function () {
// No specific updates needed for level selection
// All interactions are handled by level button click events
};
Kahverengi sayfa yaprağı. In-Game asset. 2d. High contrast. No shadows
Kağıdın içinde plants vs zombies yazsin
Plant vs zombies ay çiçeği. In-Game asset. 2d. High contrast. No shadows
boş buton. In-Game asset. 2d. High contrast. No shadows
plans vs zombies double shoters. In-Game asset. 2d. High contrast. No shadows
çöl. In-Game asset. 2d. High contrast. No shadows
buz arkaplan. In-Game asset. 2d. High contrast. No shadows
gece gökyüzü. In-Game asset. 2d. High contrast. No shadows
orman. In-Game asset. 2d. High contrast. No shadows
toprak arka plan. In-Game asset. 2d. High contrast. No shadows
plants vs zombies ceviz. In-Game asset. 2d. High contrast. No shadows
ıcepeasshooter plants vs zombies. In-Game asset. 2d. High contrast. No shadows
kankanlı zombi plants vs zombies. In-Game asset. 2d. High contrast. No shadows
plants vs zombies olü ceviz. In-Game asset. 2d. High contrast. No shadows
bombacı zombi plants vs zombies. In-Game asset. 2d. High contrast. No shadows