/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var PlantBase = Container.expand(function (plantType) {
var self = Container.call(this);
self.plantType = plantType;
self.health = 100;
self.maxHealth = 100;
self.cost = 50;
self.shootTimer = 0;
self.shootDelay = 60;
self.gridX = 0;
self.gridY = 0;
self.level = 1;
var graphics = self.attachAsset(plantType, {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
plants[self.gridY][self.gridX] = null;
self.destroy();
};
self.upgrade = function () {
if (sunPoints >= 25 && self.level < 3) {
sunPoints -= 25;
self.level++;
self.shootDelay = Math.max(30, self.shootDelay - 10);
graphics.scaleX = graphics.scaleY = 1 + (self.level - 1) * 0.2;
updateSunDisplay();
}
};
self.down = function (x, y, obj) {
self.upgrade();
};
return self;
});
var Wallnut = PlantBase.expand(function () {
var self = PlantBase.call(this, 'wallnut');
self.cost = 50;
self.health = 300;
self.maxHealth = 300;
return self;
});
var Sunflower = PlantBase.expand(function () {
var self = PlantBase.call(this, 'sunflower');
self.cost = 50;
self.sunTimer = 0;
self.update = function () {
self.sunTimer++;
if (self.sunTimer >= 900) {
// 15 seconds
var sun = new Sun(self.x, self.y - 60);
suns.push(sun);
game.addChild(sun);
self.sunTimer = 0;
LK.effects.flashObject(self, 0xFFFF00, 300);
}
};
return self;
});
var SnowPea = PlantBase.expand(function () {
var self = PlantBase.call(this, 'snowpea');
self.cost = 175;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var snowball = new SlowProjectile('snowball', self.x, self.y, 8, 20 * self.level);
projectiles.push(snowball);
game.addChild(snowball);
LK.getSound('shoot').play();
};
return self;
});
var Peashooter = PlantBase.expand(function () {
var self = PlantBase.call(this, 'peashooter');
self.cost = 100;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var pea = new Projectile('pea', self.x, self.y, 8, 20 * self.level);
projectiles.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
};
return self;
});
var Projectile = Container.expand(function (type, startX, startY, speed, damage) {
var self = Container.call(this);
self.speed = speed;
self.damage = damage;
self.x = startX;
self.y = startY;
var graphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.x += self.speed;
if (self.x > 2048) {
self.removeFromGame();
return;
}
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage);
self.removeFromGame();
break;
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < projectiles.length; i++) {
if (projectiles[i] === self) {
projectiles.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var SlowProjectile = Projectile.expand(function (type, startX, startY, speed, damage) {
var self = Projectile.call(this, type, startX, startY, speed, damage);
var originalUpdate = self.update;
self.update = function () {
originalUpdate();
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.applySlowEffect();
break;
}
}
};
return self;
});
var Sun = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.value = 25;
self.collectTimer = 0;
var graphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function (x, y, obj) {
sunPoints += self.value;
updateSunDisplay();
self.collect();
};
self.collect = function () {
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
self.destroy();
};
self.update = function () {
self.collectTimer++;
if (self.collectTimer > 600) {
// 10 seconds timeout
self.collect();
}
};
return self;
});
var ZombieBase = Container.expand(function (zombieType) {
var self = Container.call(this);
self.zombieType = zombieType;
self.health = 190;
self.maxHealth = 190;
self.speed = 1;
self.damage = 25;
self.gridY = 0;
self.attackTimer = 0;
self.slowTimer = 0;
self.originalSpeed = self.speed;
var graphics = self.attachAsset(zombieType, {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
sunPoints += 25;
updateSunDisplay();
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
self.applySlowEffect = function () {
self.slowTimer = 180;
self.speed = self.originalSpeed * 0.5;
graphics.tint = 0x81C784;
};
self.update = function () {
if (self.slowTimer > 0) {
self.slowTimer--;
if (self.slowTimer === 0) {
self.speed = self.originalSpeed;
graphics.tint = 0xFFFFFF;
}
}
var plantInFront = self.getPlantInFront();
if (plantInFront) {
self.attackTimer++;
if (self.attackTimer >= 60) {
plantInFront.takeDamage(self.damage);
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
if (self.x < -100) {
self.reachSanctuary();
}
}
};
self.getPlantInFront = function () {
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX >= 0 && cellX < gridCols && plants[self.gridY] && plants[self.gridY][cellX]) {
return plants[self.gridY][cellX];
}
return null;
};
self.reachSanctuary = function () {
lives--;
updateLivesDisplay();
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
if (lives <= 0) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
};
return self;
});
var Zombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie');
self.health = 190;
self.maxHealth = 190;
self.speed = 1;
self.originalSpeed = 1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1B5E20
});
/****
* Game Code
****/
// Sounds
// UI and grid
// Undead enemies
// Projectiles
// Plant defenders
// Grid setup - bigger grid centered on screen
var gridRows = 5;
var gridCols = 9;
var cellSize = 144;
var gridStartX = (2048 - gridCols * cellSize) / 2;
var gridStartY = (2732 - gridRows * cellSize) / 2;
// Game state
var plants = [];
var enemies = [];
var projectiles = [];
var suns = [];
var sunPoints = 300;
var lives = 3;
var currentWave = 1;
var wavesCompleted = 0;
var totalWaves = 10;
var waveInProgress = false;
var enemiesInWave = 0;
var enemiesKilled = 0;
var sunTimer = 0;
// Selected plant type
var selectedPlantType = null;
var plantTypes = ['sunflower', 'peashooter'];
var plantCosts = {
'peashooter': 100,
'sunflower': 50
};
// Ghost plant for preview
var ghostPlant = null;
// Initialize grid cells array
var gridCells = [];
// Initialize grid
for (var row = 0; row < gridRows; row++) {
plants[row] = [];
gridCells[row] = [];
for (var col = 0; col < gridCols; col++) {
plants[row][col] = null;
var cell = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
x: gridStartX + col * cellSize,
y: gridStartY + row * cellSize
});
gridCells[row][col] = cell;
game.addChild(cell);
}
}
// Sanctuary
var sanctuary = LK.getAsset('sanctuary', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: gridStartY + gridRows * cellSize / 2 - cellSize / 2
});
game.addChild(sanctuary);
// UI Elements
var sunDisplay = new Text2('Sun: ' + sunPoints, {
size: 60,
fill: 0xFFEB3B
});
sunDisplay.anchor.set(0, 0);
sunDisplay.x = 120; // Offset from left edge to avoid menu icon
LK.gui.topLeft.addChild(sunDisplay);
var livesDisplay = new Text2('Lives: ' + lives, {
size: 60,
fill: 0xF44336
});
livesDisplay.anchor.set(0, 0);
livesDisplay.y = 80;
LK.gui.topRight.addChild(livesDisplay);
var waveDisplay = new Text2('Wave: ' + currentWave + '/' + totalWaves, {
size: 50,
fill: 0x4CAF50
});
waveDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(waveDisplay);
// Plant selection UI at bottom
var plantButtons = [];
var uiY = 2732 - 150;
for (var i = 0; i < plantTypes.length; i++) {
var button = LK.getAsset(plantTypes[i], {
anchorX: 0.5,
anchorY: 0.5,
x: 200 + i * 220,
y: uiY,
scaleX: 2.25,
scaleY: 2.25
});
button.plantType = plantTypes[i];
button.interactive = true; // Enable button interactivity
button.buttonMode = true; // Make it behave like a button
button.down = function (x, y, obj) {
console.log("Plant button clicked:", this.plantType, "Cost:", plantCosts[this.plantType], "Current suns:", sunPoints);
if (sunPoints >= plantCosts[this.plantType]) {
selectedPlantType = this.plantType;
updatePlantSelection();
}
};
game.addChild(button);
plantButtons.push(button);
// Add cost text
var costText = new Text2(plantCosts[plantTypes[i]], {
size: 40,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0);
costText.x = button.x;
costText.y = button.y + 80;
game.addChild(costText);
}
function updatePlantSelection() {
for (var i = 0; i < plantButtons.length; i++) {
if (plantButtons[i].plantType === selectedPlantType) {
plantButtons[i].alpha = 1.0;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.55;
} else {
plantButtons[i].alpha = 0.6;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.25;
}
}
// Remove existing ghost plant
if (ghostPlant) {
ghostPlant.destroy();
ghostPlant = null;
}
// Create new ghost plant if a type is selected
if (selectedPlantType) {
ghostPlant = LK.getAsset(selectedPlantType, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x88FF88
});
game.addChild(ghostPlant);
}
// Highlight valid placement squares
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
if (selectedPlantType && canPlacePlant(col, row, selectedPlantType)) {
// Green highlight for valid empty squares
gridCells[row][col].tint = 0x00FF00;
gridCells[row][col].alpha = 0.7;
} else if (selectedPlantType && plants[row][col] !== null) {
// Red highlight for occupied squares when plant is selected
gridCells[row][col].tint = 0xFF0000;
gridCells[row][col].alpha = 0.5;
} else {
// Normal appearance for unselected or invalid squares
gridCells[row][col].tint = 0xFFFFFF;
gridCells[row][col].alpha = 0.3;
}
}
}
}
function updateSunDisplay() {
sunDisplay.setText('Sun: ' + sunPoints);
}
function updateLivesDisplay() {
livesDisplay.setText('Lives: ' + lives);
}
function updateWaveDisplay() {
waveDisplay.setText('Wave: ' + currentWave + '/' + totalWaves);
}
function getGridPosition(x, y) {
var gridX = Math.floor((x - gridStartX + cellSize / 2) / cellSize);
var gridY = Math.floor((y - gridStartY + cellSize / 2) / cellSize);
if (gridX >= 0 && gridX < gridCols && gridY >= 0 && gridY < gridRows) {
return {
x: gridX,
y: gridY
};
}
return null;
}
function canPlacePlant(gridX, gridY, plantType) {
// Check if grid position is valid
if (gridX < 0 || gridX >= gridCols || gridY < 0 || gridY >= gridRows) {
return false;
}
// Check if square is empty and player has enough suns
return plants[gridY][gridX] === null && sunPoints >= plantCosts[plantType];
}
function placePlant(gridX, gridY, plantType) {
// Double-check that placement is valid before proceeding
if (!canPlacePlant(gridX, gridY, plantType)) {
return false;
}
var plant;
switch (plantType) {
case 'peashooter':
plant = new Peashooter();
break;
case 'sunflower':
plant = new Sunflower();
break;
case 'wallnut':
plant = new Wallnut();
break;
case 'snowpea':
plant = new SnowPea();
break;
default:
return false;
}
// Set plant position and grid reference
plant.gridX = gridX;
plant.gridY = gridY;
plant.x = gridStartX + gridX * cellSize;
plant.y = gridStartY + gridY * cellSize;
// Place plant in grid and add to game
plants[gridY][gridX] = plant;
game.addChild(plant);
// Deduct cost and update UI
sunPoints -= plantCosts[plantType];
updateSunDisplay();
LK.getSound('plant').play();
// Flash the grid cell to show successful placement
LK.effects.flashObject(gridCells[gridY][gridX], 0x00FF00, 300);
return true;
}
function spawnZombie(type, row) {
var zombie;
switch (type) {
case 'zombie':
zombie = new Zombie();
break;
default:
return;
}
zombie.gridY = row;
zombie.x = 2100;
zombie.y = gridStartY + row * cellSize;
enemies.push(zombie);
game.addChild(zombie);
}
function startWave() {
if (waveInProgress) {
return;
}
waveInProgress = true;
enemiesInWave = currentWave === 1 ? Math.floor(Math.random() * 2) + 3 : 5 + currentWave * 2; // 3-4 zombies for wave 1
enemiesKilled = 0;
var spawnTimer = LK.setInterval(function () {
if (enemiesInWave > 0) {
var row = Math.floor(Math.random() * gridRows);
var zombieType = 'zombie';
spawnZombie(zombieType, row);
enemiesInWave--;
} else {
LK.clearInterval(spawnTimer);
}
}, 1000);
}
// Mouse move handler to update ghost plant position
game.move = function (x, y, obj) {
if (ghostPlant && selectedPlantType) {
var gridPos = getGridPosition(x, y);
if (gridPos) {
// Snap to grid position
ghostPlant.x = gridStartX + gridPos.x * cellSize;
ghostPlant.y = gridStartY + gridPos.y * cellSize;
// Change color based on validity
if (canPlacePlant(gridPos.x, gridPos.y, selectedPlantType)) {
ghostPlant.tint = 0x88FF88; // Green tint for valid placement
} else {
ghostPlant.tint = 0xFF8888; // Red tint for invalid placement
}
} else {
// Follow cursor if outside grid
ghostPlant.x = x;
ghostPlant.y = y;
ghostPlant.tint = 0xFF8888; // Red tint when outside grid
}
}
};
// Game input handling
game.down = function (x, y, obj) {
// Check if clicking on a sun
for (var i = 0; i < suns.length; i++) {
if (suns[i].intersects({
x: x,
y: y,
width: 1,
height: 1
})) {
return; // Let sun handle its own click
}
}
// Check if placing a plant
if (selectedPlantType) {
var gridPos = getGridPosition(x, y);
if (gridPos) {
if (canPlacePlant(gridPos.x, gridPos.y, selectedPlantType)) {
// Successfully place the plant
if (placePlant(gridPos.x, gridPos.y, selectedPlantType)) {
selectedPlantType = null;
updatePlantSelection();
return;
}
} else if (plants[gridPos.y][gridPos.x] !== null) {
// Flash red if trying to place on occupied square
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFF0000, 300);
} else if (sunPoints < plantCosts[selectedPlantType]) {
// Flash yellow if not enough suns
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFFFF00, 300);
}
} else {
// Clicking outside grid cancels selection
selectedPlantType = null;
updatePlantSelection();
}
}
};
// Initialize UI
updatePlantSelection();
updateSunDisplay();
updateLivesDisplay();
updateWaveDisplay();
// Start first wave after a delay
LK.setTimeout(function () {
startWave();
}, 5000);
// Main game loop
game.update = function () {
// Falling sun system - every 15 seconds
sunTimer++;
if (sunTimer >= 900) {
// 15 seconds at 60fps
var fallingSun = new Sun(Math.random() * (gridStartX + gridCols * cellSize - gridStartX) + gridStartX, -50);
suns.push(fallingSun);
game.addChild(fallingSun);
// Animate falling sun
tween(fallingSun, {
y: Math.random() * 200 + 300
}, {
duration: 2000,
easing: tween.easeOut
});
sunTimer = 0;
}
// Check wave completion
if (waveInProgress && enemiesInWave === 0 && enemies.length === 0) {
waveInProgress = false;
wavesCompleted++;
if (wavesCompleted >= totalWaves) {
LK.effects.flashScreen(0x4CAF50, 1000);
LK.showYouWin();
return;
}
currentWave++;
updateWaveDisplay();
// Start next wave after delay
LK.setTimeout(function () {
startWave();
}, 3000);
}
// Auto-start next wave if no enemies and no wave in progress
if (!waveInProgress && enemies.length === 0 && enemiesInWave === 0 && wavesCompleted < totalWaves) {
LK.setTimeout(function () {
startWave();
}, 1000);
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var PlantBase = Container.expand(function (plantType) {
var self = Container.call(this);
self.plantType = plantType;
self.health = 100;
self.maxHealth = 100;
self.cost = 50;
self.shootTimer = 0;
self.shootDelay = 60;
self.gridX = 0;
self.gridY = 0;
self.level = 1;
var graphics = self.attachAsset(plantType, {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
plants[self.gridY][self.gridX] = null;
self.destroy();
};
self.upgrade = function () {
if (sunPoints >= 25 && self.level < 3) {
sunPoints -= 25;
self.level++;
self.shootDelay = Math.max(30, self.shootDelay - 10);
graphics.scaleX = graphics.scaleY = 1 + (self.level - 1) * 0.2;
updateSunDisplay();
}
};
self.down = function (x, y, obj) {
self.upgrade();
};
return self;
});
var Wallnut = PlantBase.expand(function () {
var self = PlantBase.call(this, 'wallnut');
self.cost = 50;
self.health = 300;
self.maxHealth = 300;
return self;
});
var Sunflower = PlantBase.expand(function () {
var self = PlantBase.call(this, 'sunflower');
self.cost = 50;
self.sunTimer = 0;
self.update = function () {
self.sunTimer++;
if (self.sunTimer >= 900) {
// 15 seconds
var sun = new Sun(self.x, self.y - 60);
suns.push(sun);
game.addChild(sun);
self.sunTimer = 0;
LK.effects.flashObject(self, 0xFFFF00, 300);
}
};
return self;
});
var SnowPea = PlantBase.expand(function () {
var self = PlantBase.call(this, 'snowpea');
self.cost = 175;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var snowball = new SlowProjectile('snowball', self.x, self.y, 8, 20 * self.level);
projectiles.push(snowball);
game.addChild(snowball);
LK.getSound('shoot').play();
};
return self;
});
var Peashooter = PlantBase.expand(function () {
var self = PlantBase.call(this, 'peashooter');
self.cost = 100;
self.update = function () {
self.shootTimer++;
if (self.shootTimer >= self.shootDelay) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.shootTimer = 0;
}
}
};
self.findTarget = function () {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.gridY === self.gridY && enemy.x > self.x) {
return enemy;
}
}
return null;
};
self.shoot = function (target) {
var pea = new Projectile('pea', self.x, self.y, 8, 20 * self.level);
projectiles.push(pea);
game.addChild(pea);
LK.getSound('shoot').play();
};
return self;
});
var Projectile = Container.expand(function (type, startX, startY, speed, damage) {
var self = Container.call(this);
self.speed = speed;
self.damage = damage;
self.x = startX;
self.y = startY;
var graphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.x += self.speed;
if (self.x > 2048) {
self.removeFromGame();
return;
}
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage);
self.removeFromGame();
break;
}
}
};
self.removeFromGame = function () {
for (var i = 0; i < projectiles.length; i++) {
if (projectiles[i] === self) {
projectiles.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var SlowProjectile = Projectile.expand(function (type, startX, startY, speed, damage) {
var self = Projectile.call(this, type, startX, startY, speed, damage);
var originalUpdate = self.update;
self.update = function () {
originalUpdate();
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy)) {
enemy.applySlowEffect();
break;
}
}
};
return self;
});
var Sun = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.value = 25;
self.collectTimer = 0;
var graphics = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.down = function (x, y, obj) {
sunPoints += self.value;
updateSunDisplay();
self.collect();
};
self.collect = function () {
for (var i = 0; i < suns.length; i++) {
if (suns[i] === self) {
suns.splice(i, 1);
break;
}
}
self.destroy();
};
self.update = function () {
self.collectTimer++;
if (self.collectTimer > 600) {
// 10 seconds timeout
self.collect();
}
};
return self;
});
var ZombieBase = Container.expand(function (zombieType) {
var self = Container.call(this);
self.zombieType = zombieType;
self.health = 190;
self.maxHealth = 190;
self.speed = 1;
self.damage = 25;
self.gridY = 0;
self.attackTimer = 0;
self.slowTimer = 0;
self.originalSpeed = self.speed;
var graphics = self.attachAsset(zombieType, {
anchorX: 0.5,
anchorY: 0.5
});
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 200);
LK.getSound('zombie_hit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
sunPoints += 25;
updateSunDisplay();
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
self.applySlowEffect = function () {
self.slowTimer = 180;
self.speed = self.originalSpeed * 0.5;
graphics.tint = 0x81C784;
};
self.update = function () {
if (self.slowTimer > 0) {
self.slowTimer--;
if (self.slowTimer === 0) {
self.speed = self.originalSpeed;
graphics.tint = 0xFFFFFF;
}
}
var plantInFront = self.getPlantInFront();
if (plantInFront) {
self.attackTimer++;
if (self.attackTimer >= 60) {
plantInFront.takeDamage(self.damage);
self.attackTimer = 0;
}
} else {
self.x -= self.speed;
if (self.x < -100) {
self.reachSanctuary();
}
}
};
self.getPlantInFront = function () {
var cellX = Math.floor((self.x - gridStartX) / cellSize);
if (cellX >= 0 && cellX < gridCols && plants[self.gridY] && plants[self.gridY][cellX]) {
return plants[self.gridY][cellX];
}
return null;
};
self.reachSanctuary = function () {
lives--;
updateLivesDisplay();
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
if (lives <= 0) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
};
return self;
});
var Zombie = ZombieBase.expand(function () {
var self = ZombieBase.call(this, 'zombie');
self.health = 190;
self.maxHealth = 190;
self.speed = 1;
self.originalSpeed = 1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1B5E20
});
/****
* Game Code
****/
// Sounds
// UI and grid
// Undead enemies
// Projectiles
// Plant defenders
// Grid setup - bigger grid centered on screen
var gridRows = 5;
var gridCols = 9;
var cellSize = 144;
var gridStartX = (2048 - gridCols * cellSize) / 2;
var gridStartY = (2732 - gridRows * cellSize) / 2;
// Game state
var plants = [];
var enemies = [];
var projectiles = [];
var suns = [];
var sunPoints = 300;
var lives = 3;
var currentWave = 1;
var wavesCompleted = 0;
var totalWaves = 10;
var waveInProgress = false;
var enemiesInWave = 0;
var enemiesKilled = 0;
var sunTimer = 0;
// Selected plant type
var selectedPlantType = null;
var plantTypes = ['sunflower', 'peashooter'];
var plantCosts = {
'peashooter': 100,
'sunflower': 50
};
// Ghost plant for preview
var ghostPlant = null;
// Initialize grid cells array
var gridCells = [];
// Initialize grid
for (var row = 0; row < gridRows; row++) {
plants[row] = [];
gridCells[row] = [];
for (var col = 0; col < gridCols; col++) {
plants[row][col] = null;
var cell = LK.getAsset('gridcell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
x: gridStartX + col * cellSize,
y: gridStartY + row * cellSize
});
gridCells[row][col] = cell;
game.addChild(cell);
}
}
// Sanctuary
var sanctuary = LK.getAsset('sanctuary', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: gridStartY + gridRows * cellSize / 2 - cellSize / 2
});
game.addChild(sanctuary);
// UI Elements
var sunDisplay = new Text2('Sun: ' + sunPoints, {
size: 60,
fill: 0xFFEB3B
});
sunDisplay.anchor.set(0, 0);
sunDisplay.x = 120; // Offset from left edge to avoid menu icon
LK.gui.topLeft.addChild(sunDisplay);
var livesDisplay = new Text2('Lives: ' + lives, {
size: 60,
fill: 0xF44336
});
livesDisplay.anchor.set(0, 0);
livesDisplay.y = 80;
LK.gui.topRight.addChild(livesDisplay);
var waveDisplay = new Text2('Wave: ' + currentWave + '/' + totalWaves, {
size: 50,
fill: 0x4CAF50
});
waveDisplay.anchor.set(0.5, 0);
LK.gui.top.addChild(waveDisplay);
// Plant selection UI at bottom
var plantButtons = [];
var uiY = 2732 - 150;
for (var i = 0; i < plantTypes.length; i++) {
var button = LK.getAsset(plantTypes[i], {
anchorX: 0.5,
anchorY: 0.5,
x: 200 + i * 220,
y: uiY,
scaleX: 2.25,
scaleY: 2.25
});
button.plantType = plantTypes[i];
button.interactive = true; // Enable button interactivity
button.buttonMode = true; // Make it behave like a button
button.down = function (x, y, obj) {
console.log("Plant button clicked:", this.plantType, "Cost:", plantCosts[this.plantType], "Current suns:", sunPoints);
if (sunPoints >= plantCosts[this.plantType]) {
selectedPlantType = this.plantType;
updatePlantSelection();
}
};
game.addChild(button);
plantButtons.push(button);
// Add cost text
var costText = new Text2(plantCosts[plantTypes[i]], {
size: 40,
fill: 0xFFFFFF
});
costText.anchor.set(0.5, 0);
costText.x = button.x;
costText.y = button.y + 80;
game.addChild(costText);
}
function updatePlantSelection() {
for (var i = 0; i < plantButtons.length; i++) {
if (plantButtons[i].plantType === selectedPlantType) {
plantButtons[i].alpha = 1.0;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.55;
} else {
plantButtons[i].alpha = 0.6;
plantButtons[i].scaleX = plantButtons[i].scaleY = 2.25;
}
}
// Remove existing ghost plant
if (ghostPlant) {
ghostPlant.destroy();
ghostPlant = null;
}
// Create new ghost plant if a type is selected
if (selectedPlantType) {
ghostPlant = LK.getAsset(selectedPlantType, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0x88FF88
});
game.addChild(ghostPlant);
}
// Highlight valid placement squares
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
if (selectedPlantType && canPlacePlant(col, row, selectedPlantType)) {
// Green highlight for valid empty squares
gridCells[row][col].tint = 0x00FF00;
gridCells[row][col].alpha = 0.7;
} else if (selectedPlantType && plants[row][col] !== null) {
// Red highlight for occupied squares when plant is selected
gridCells[row][col].tint = 0xFF0000;
gridCells[row][col].alpha = 0.5;
} else {
// Normal appearance for unselected or invalid squares
gridCells[row][col].tint = 0xFFFFFF;
gridCells[row][col].alpha = 0.3;
}
}
}
}
function updateSunDisplay() {
sunDisplay.setText('Sun: ' + sunPoints);
}
function updateLivesDisplay() {
livesDisplay.setText('Lives: ' + lives);
}
function updateWaveDisplay() {
waveDisplay.setText('Wave: ' + currentWave + '/' + totalWaves);
}
function getGridPosition(x, y) {
var gridX = Math.floor((x - gridStartX + cellSize / 2) / cellSize);
var gridY = Math.floor((y - gridStartY + cellSize / 2) / cellSize);
if (gridX >= 0 && gridX < gridCols && gridY >= 0 && gridY < gridRows) {
return {
x: gridX,
y: gridY
};
}
return null;
}
function canPlacePlant(gridX, gridY, plantType) {
// Check if grid position is valid
if (gridX < 0 || gridX >= gridCols || gridY < 0 || gridY >= gridRows) {
return false;
}
// Check if square is empty and player has enough suns
return plants[gridY][gridX] === null && sunPoints >= plantCosts[plantType];
}
function placePlant(gridX, gridY, plantType) {
// Double-check that placement is valid before proceeding
if (!canPlacePlant(gridX, gridY, plantType)) {
return false;
}
var plant;
switch (plantType) {
case 'peashooter':
plant = new Peashooter();
break;
case 'sunflower':
plant = new Sunflower();
break;
case 'wallnut':
plant = new Wallnut();
break;
case 'snowpea':
plant = new SnowPea();
break;
default:
return false;
}
// Set plant position and grid reference
plant.gridX = gridX;
plant.gridY = gridY;
plant.x = gridStartX + gridX * cellSize;
plant.y = gridStartY + gridY * cellSize;
// Place plant in grid and add to game
plants[gridY][gridX] = plant;
game.addChild(plant);
// Deduct cost and update UI
sunPoints -= plantCosts[plantType];
updateSunDisplay();
LK.getSound('plant').play();
// Flash the grid cell to show successful placement
LK.effects.flashObject(gridCells[gridY][gridX], 0x00FF00, 300);
return true;
}
function spawnZombie(type, row) {
var zombie;
switch (type) {
case 'zombie':
zombie = new Zombie();
break;
default:
return;
}
zombie.gridY = row;
zombie.x = 2100;
zombie.y = gridStartY + row * cellSize;
enemies.push(zombie);
game.addChild(zombie);
}
function startWave() {
if (waveInProgress) {
return;
}
waveInProgress = true;
enemiesInWave = currentWave === 1 ? Math.floor(Math.random() * 2) + 3 : 5 + currentWave * 2; // 3-4 zombies for wave 1
enemiesKilled = 0;
var spawnTimer = LK.setInterval(function () {
if (enemiesInWave > 0) {
var row = Math.floor(Math.random() * gridRows);
var zombieType = 'zombie';
spawnZombie(zombieType, row);
enemiesInWave--;
} else {
LK.clearInterval(spawnTimer);
}
}, 1000);
}
// Mouse move handler to update ghost plant position
game.move = function (x, y, obj) {
if (ghostPlant && selectedPlantType) {
var gridPos = getGridPosition(x, y);
if (gridPos) {
// Snap to grid position
ghostPlant.x = gridStartX + gridPos.x * cellSize;
ghostPlant.y = gridStartY + gridPos.y * cellSize;
// Change color based on validity
if (canPlacePlant(gridPos.x, gridPos.y, selectedPlantType)) {
ghostPlant.tint = 0x88FF88; // Green tint for valid placement
} else {
ghostPlant.tint = 0xFF8888; // Red tint for invalid placement
}
} else {
// Follow cursor if outside grid
ghostPlant.x = x;
ghostPlant.y = y;
ghostPlant.tint = 0xFF8888; // Red tint when outside grid
}
}
};
// Game input handling
game.down = function (x, y, obj) {
// Check if clicking on a sun
for (var i = 0; i < suns.length; i++) {
if (suns[i].intersects({
x: x,
y: y,
width: 1,
height: 1
})) {
return; // Let sun handle its own click
}
}
// Check if placing a plant
if (selectedPlantType) {
var gridPos = getGridPosition(x, y);
if (gridPos) {
if (canPlacePlant(gridPos.x, gridPos.y, selectedPlantType)) {
// Successfully place the plant
if (placePlant(gridPos.x, gridPos.y, selectedPlantType)) {
selectedPlantType = null;
updatePlantSelection();
return;
}
} else if (plants[gridPos.y][gridPos.x] !== null) {
// Flash red if trying to place on occupied square
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFF0000, 300);
} else if (sunPoints < plantCosts[selectedPlantType]) {
// Flash yellow if not enough suns
LK.effects.flashObject(gridCells[gridPos.y][gridPos.x], 0xFFFF00, 300);
}
} else {
// Clicking outside grid cancels selection
selectedPlantType = null;
updatePlantSelection();
}
}
};
// Initialize UI
updatePlantSelection();
updateSunDisplay();
updateLivesDisplay();
updateWaveDisplay();
// Start first wave after a delay
LK.setTimeout(function () {
startWave();
}, 5000);
// Main game loop
game.update = function () {
// Falling sun system - every 15 seconds
sunTimer++;
if (sunTimer >= 900) {
// 15 seconds at 60fps
var fallingSun = new Sun(Math.random() * (gridStartX + gridCols * cellSize - gridStartX) + gridStartX, -50);
suns.push(fallingSun);
game.addChild(fallingSun);
// Animate falling sun
tween(fallingSun, {
y: Math.random() * 200 + 300
}, {
duration: 2000,
easing: tween.easeOut
});
sunTimer = 0;
}
// Check wave completion
if (waveInProgress && enemiesInWave === 0 && enemies.length === 0) {
waveInProgress = false;
wavesCompleted++;
if (wavesCompleted >= totalWaves) {
LK.effects.flashScreen(0x4CAF50, 1000);
LK.showYouWin();
return;
}
currentWave++;
updateWaveDisplay();
// Start next wave after delay
LK.setTimeout(function () {
startWave();
}, 3000);
}
// Auto-start next wave if no enemies and no wave in progress
if (!waveInProgress && enemies.length === 0 && enemiesInWave === 0 && wavesCompleted < totalWaves) {
LK.setTimeout(function () {
startWave();
}, 1000);
}
};