pvzclone / index.html
furuknap's picture
Add 2 files
ae970f3 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Plants vs Zombies Clone</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #2c3e50;
font-family: 'Press Start 2P', cursive;
overflow: hidden;
color: #ecf0f1;
}
#game-container {
width: 100vw;
height: 100vh;
position: relative;
background-image: linear-gradient(#5da130, #3b7a1f);
}
#lawn {
width: 100%;
height: calc(100% - 120px);
position: relative;
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(5, 1fr);
}
.lawn-cell {
border: 1px dashed rgba(255, 255, 255, 0.2);
position: relative;
}
#ui-container {
height: 120px;
background-color: #4a2512;
border-top: 4px solid #6d4724;
display: flex;
justify-content: space-between;
padding: 10px;
}
#sun-counter {
width: 100px;
height: 100px;
background-color: #f1c40f;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: #e67e22;
box-shadow: 0 0 10px rgba(241, 196, 15, 0.5);
position: relative;
}
#plant-selection {
display: flex;
gap: 10px;
}
.plant-option {
width: 80px;
height: 100px;
background-color: #3498db;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 5px;
cursor: pointer;
transition: all 0.2s;
border: 3px solid transparent;
}
.plant-option:hover {
transform: scale(1.05);
}
.plant-option.selected {
border-color: #f1c40f;
box-shadow: 0 0 10px rgba(241, 196, 15, 0.8);
}
.plant-option .plant-icon {
width: 50px;
height: 50px;
border-radius: 50%;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.plant-option .cost {
font-size: 12px;
color: #f1c40f;
}
/* Plant elements */
.plant {
width: 80%;
height: 80%;
position: absolute;
top: 10%;
left: 10%;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
z-index: 10;
}
.sunflower .plant {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="yellow"/><circle cx="25" cy="25" r="10" fill="orange"/><path d="M10,25 Q25,10 40,25 Q25,40 10,25" fill="yellow"/><path d="M25,10 Q40,25 25,40 Q10,25 25,10" fill="yellow"/></svg>');
}
.peashooter .plant {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><ellipse cx="25" cy="30" rx="15" ry="20" fill="green"/><circle cx="25" cy="15" r="10" fill="lime"/><circle cx="30" cy="12" r="3" fill="black"/></svg>');
}
.wallnut .plant {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="saddlebrown"/><path d="M15,15 Q25,10 35,15 Q40,25 35,35 Q25,40 15,35 Q10,25 15,15" fill="peru"/></svg>');
}
/* Projectiles */
.pea {
width: 15px;
height: 15px;
background-color: lime;
border-radius: 50%;
position: absolute;
z-index: 20;
}
/* Zombie elements */
.zombie {
width: 60px;
height: 100px;
position: absolute;
right: -60px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
z-index: 5;
animation: zombie-walk 3s linear infinite;
}
@keyframes zombie-walk {
0% { transform: translateX(0); }
50% { transform: translateX(-5px) rotate(2deg); }
100% { transform: translateX(0); }
}
.basic-zombie {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="15" r="10" fill="darkgreen"/><rect x="15" y="25" width="20" height="15" fill="green"/><circle cx="20" cy="12" r="2" fill="black"/><circle cx="30" cy="12" r="2" fill="black"/></svg>');
}
.conehead-zombie {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path d="M15,5 L35,5 L25,15 Z" fill="gray"/><circle cx="25" cy="15" r="10" fill="darkgreen"/><rect x="15" y="25" width="20" height="15" fill="green"/><circle cx="20" cy="12" r="2" fill="black"/><circle cx="30" cy="12" r="2" fill="black"/></svg>');
}
.buckethead-zombie {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="15" r="10" fill="darkgreen"/><rect x="20" y="15" width="10" height="10" fill="silver"/><rect x="15" y="25" width="20" height="15" fill="green"/><circle cx="20" cy="12" r="2" fill="black"/><circle cx="30" cy="12" r="2" fill="black"/></svg>');
}
/* Sun elements */
.sun {
width: 50px;
height: 50px;
position: absolute;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="25" r="15" fill="yellow"/></svg>');
background-size: contain;
cursor: pointer;
z-index: 30;
animation: sun-fall 6s linear, sun-spin 2s linear infinite;
}
@keyframes sun-fall {
0% { transform: translateY(-100px); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(calc(100vh - 170px)); opacity: 0; }
}
@keyframes sun-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Game messages */
.game-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
background-color: rgba(0, 0, 0, 0.7);
padding: 20px 40px;
border-radius: 10px;
z-index: 100;
display: none;
}
#start-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}
#start-screen h1 {
font-size: 3rem;
color: #f1c40f;
text-shadow: 5px 5px #e67e22;
}
#start-button {
padding: 15px 30px;
background-color: #e74c3c;
border: none;
border-radius: 10px;
font-family: 'Press Start 2P', cursive;
font-size: 1.2rem;
color: white;
cursor: pointer;
transition: all 0.2s;
}
#start-button:hover {
transform: scale(1.1);
background-color: #c0392b;
}
#level-select {
display: flex;
gap: 20px;
}
.level-button {
padding: 10px 20px;
background-color: #3498db;
border: none;
border-radius: 5px;
font-family: 'Press Start 2P', cursive;
font-size: 0.8rem;
color: white;
cursor: pointer;
}
.level-button:hover {
background-color: #2980b9;
}
/* Progress bars */
.health-bar {
width: 100%;
height: 5px;
background-color: #2ecc71;
position: absolute;
bottom: -10px;
left: 0;
}
#wave-indicator {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.5);
padding: 5px 10px;
border-radius: 5px;
z-index: 50;
}
/* Plant cooldown indicator */
.cooldown-overlay {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
transition: height 0.5s linear;
}
</style>
</head>
<body>
<div id="game-container">
<div id="start-screen" class="game-message">
<h1>PLANTS vs ZOMBIES</h1>
<div id="level-select">
<button class="level-button" data-level="1">Backyard Day</button>
<button class="level-button" data-level="2">Backyard Night</button>
</div>
<button id="start-button">START GAME</button>
</div>
<div id="game-over-message" class="game-message">
<h2>GAME OVER</h2>
<p id="game-over-text"></p>
<button id="restart-button">TRY AGAIN</button>
</div>
<div id="wave-indicator">Wave: 1</div>
<div id="lawn"></div>
<div id="ui-container">
<div id="sun-counter">0</div>
<div id="plant-selection">
<div class="plant-option" data-plant="sunflower" data-cost="50">
<div class="plant-icon" style="background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 50 50\"><circle cx=\"25\" cy=\"25\" r=\"20\" fill=\"yellow\"/><circle cx=\"25\" cy=\"25\" r=\"10\" fill=\"orange\"/></svg>')"></div>
<div class="cost">50</div>
</div>
<div class="plant-option" data-plant="peashooter" data-cost="100">
<div class="plant-icon" style="background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 50 50\"><ellipse cx=\"25\" cy=\"30\" rx=\"15\" ry=\"20\" fill=\"green\"/><circle cx=\"25\" cy=\"15\" r=\"10\" fill=\"lime\"/></svg>')"></div>
<div class="cost">100</div>
</div>
<div class="plant-option" data-plant="wallnut" data-cost="50">
<div class="plant-icon" style="background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 50 50\"><circle cx=\"25\" cy=\"25\" r=\"20\" fill=\"saddlebrown\"/></svg>')"></div>
<div class="cost">50</div>
</div>
</div>
</div>
</div>
<script>
// Game state
const gameState = {
suns: 50,
selectedPlant: null,
plants: [],
zombies: [],
projectiles: [],
sunDrops: [],
currentWave: 0,
totalWaves: 5,
gameActive: false,
currentLevel: 1,
zombieSpeed: 0.5,
gameClock: 0,
lastSunProduced: 0,
lastZombieSpawned: 0,
plantCooldowns: {
sunflower: 0,
peashooter: 0,
wallnut: 0
},
zombiesReachedEnd: 0
};
// Plant properties
const plantProperties = {
sunflower: {
cost: 50,
health: 100,
cooldown: 10,
produceSunInterval: 10,
produceSunAmount: 25
},
peashooter: {
cost: 100,
health: 100,
cooldown: 7.5,
shootInterval: 3,
damage: 20
},
wallnut: {
cost: 50,
health: 400,
cooldown: 30,
damage: 0
}
};
// Zombie properties
const zombieProperties = {
basic: {
health: 100,
damage: 1,
speed: 0.5, // px per frame
value: 10
},
conehead: {
health: 200,
damage: 1,
speed: 0.4,
value: 20
},
buckethead: {
health: 300,
damage: 2,
speed: 0.3,
value: 30
}
};
// Level definitions
const levels = {
1: {
name: "Backyard Day",
background: "#5da130",
zombieTypes: ["basic", "conehead"],
waves: [
{ delay: 5, zombies: 5, zombieTypeWeights: [0.8, 0.2] },
{ delay: 15, zombies: 8, zombieTypeWeights: [0.7, 0.3] },
{ delay: 25, zombies: 12, zombieTypeWeights: [0.6, 0.4] },
{ delay: 35, zombies: 15, zombieTypeWeights: [0.5, 0.5] },
{ delay: 45, zombies: 20, zombieTypeWeights: [0.4, 0.6] }
]
},
2: {
name: "Backyard Night",
background: "#1a2330",
zombieTypes: ["basic", "conehead", "buckethead"],
waves: [
{ delay: 5, zombies: 6, zombieTypeWeights: [0.6, 0.3, 0.1] },
{ delay: 15, zombies: 10, zombieTypeWeights: [0.5, 0.3, 0.2] },
{ delay: 25, zombies: 14, zombieTypeWeights: [0.4, 0.3, 0.3] },
{ delay: 35, zombies: 18, zombieTypeWeights: [0.3, 0.3, 0.4] },
{ delay: 45, zombies: 25, zombieTypeWeights: [0.2, 0.3, 0.5] }
]
}
};
// DOM elements
const gameContainer = document.getElementById('game-container');
const lawn = document.getElementById('lawn');
const sunCounter = document.getElementById('sun-counter');
const plantSelection = document.getElementById('plant-selection');
const plantOptions = document.querySelectorAll('.plant-option');
const startScreen = document.getElementById('start-screen');
const gameOverMessage = document.getElementById('game-over-message');
const gameOverText = document.getElementById('game-over-text');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button');
const levelButtons = document.querySelectorAll('.level-button');
const waveIndicator = document.getElementById('wave-indicator');
// Initialize the game
function initLawn() {
lawn.innerHTML = '';
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 9; j++) {
const cell = document.createElement('div');
cell.className = 'lawn-cell';
cell.dataset.row = i;
cell.dataset.col = j;
cell.addEventListener('click', placePlant);
lawn.appendChild(cell);
}
}
}
// Initialize event listeners
function initEventListeners() {
plantOptions.forEach(option => {
option.addEventListener('click', () => {
const plantType = option.dataset.plant;
const plantCost = parseInt(option.dataset.cost);
if (gameState.plantCooldowns[plantType] > 0) return;
if (gameState.selectedPlant === plantType) {
gameState.selectedPlant = null;
plantOptions.forEach(opt => opt.classList.remove('selected'));
} else if (gameState.suns >= plantCost) {
gameState.selectedPlant = plantType;
plantOptions.forEach(opt => opt.classList.remove('selected'));
option.classList.add('selected');
}
});
});
document.addEventListener('click', (e) => {
if (e.target === gameContainer || e.target === lawn) {
gameState.selectedPlant = null;
plantOptions.forEach(opt => opt.classList.remove('selected'));
}
});
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', resetGame);
levelButtons.forEach(button => {
button.addEventListener('click', () => {
const level = parseInt(button.dataset.level);
selectLevel(level);
});
});
}
// Select level
function selectLevel(level) {
gameState.currentLevel = level;
levelButtons.forEach(button => {
button.style.backgroundColor = button.dataset.level == level ? '#2980b9' : '#3498db';
});
}
// Start game
function startGame() {
if (gameState.gameActive) return;
gameState.gameActive = true;
gameState.suns = 50;
gameState.plants = [];
gameState.zombies = [];
gameState.projectiles = [];
gameState.sunDrops = [];
gameState.currentWave = 0;
gameState.gameClock = 0;
gameState.lastSunProduced = 0;
gameState.lastZombieSpawned = 0;
gameState.zombiesReachedEnd = 0;
// Reset plant cooldowns
for (const plantType in gameState.plantCooldowns) {
gameState.plantCooldowns[plantType] = 0;
}
// Set background based on level
gameContainer.style.backgroundImage = `linear-gradient(${levels[gameState.currentLevel].background}, #3b7a1f)`;
// Hide start screen
startScreen.style.display = 'none';
gameOverMessage.style.display = 'none';
// Start game loop
gameLoop();
// Start random sun drops
setInterval(createRandomSun, 10000);
}
// Reset game
function resetGame() {
gameState.gameActive = false;
// Clear game elements
document.querySelectorAll('.plant, .zombie, .pea, .sun').forEach(el => el.remove());
// Show start screen
startScreen.style.display = 'flex';
gameOverMessage.style.display = 'none';
}
// Place plant
function placePlant(e) {
if (!gameState.selectedPlant || !gameState.gameActive) return;
const row = parseInt(e.currentTarget.dataset.row);
const col = parseInt(e.currentTarget.dataset.col);
// Check if cell is already occupied
const cellOccupied = gameState.plants.some(plant => plant.row === row && plant.col === col);
if (cellOccupied) return;
const plantType = gameState.selectedPlant;
const plantCost = plantProperties[plantType].cost;
if (gameState.suns >= plantCost) {
// Deduct sun cost
gameState.suns -= plantCost;
updateSunCounter();
// Create plant
const plant = {
type: plantType,
row: row,
col: col,
health: plantProperties[plantType].health,
lastActionTime: 0,
element: createPlantElement(plantType, row, col)
};
gameState.plants.push(plant);
// Start cooldown
gameState.plantCooldowns[plantType] = plantProperties[plantType].cooldown;
updateCooldownDisplay(plantType);
// Deselect plant
gameState.selectedPlant = null;
plantOptions.forEach(opt => opt.classList.remove('selected'));
}
}
// Create plant element
function createPlantElement(plantType, row, col) {
const plant = document.createElement('div');
plant.className = `plant ${plantType}`;
// Create health bar
const healthBar = document.createElement('div');
healthBar.className = 'health-bar';
healthBar.style.width = '100%';
plant.appendChild(healthBar);
// Find the cell and append the plant
const cell = document.querySelector(`.lawn-cell[data-row="${row}"][data-col="${col}"]`);
cell.appendChild(plant);
return plant;
}
// Create zombie
function createZombie(type, row) {
const zombie = document.createElement('div');
zombie.className = `zombie ${type}-zombie`;
// Create health bar
const healthBar = document.createElement('div');
healthBar.className = 'health-bar';
healthBar.style.width = '100%';
zombie.appendChild(healthBar);
// Position zombie
const cellHeight = lawn.clientHeight / 5;
zombie.style.top = `${row * cellHeight + cellHeight / 2 - 50}px`;
gameContainer.appendChild(zombie);
// Add to game state
const zombieObj = {
type: type,
row: row,
x: gameContainer.clientWidth,
element: zombie,
health: zombieProperties[type].health,
maxHealth: zombieProperties[type].health,
speed: zombieProperties[type].speed * (gameState.currentLevel === 2 ? 0.8 : 1), // Slower at night
damage: zombieProperties[type].damage,
moving: true
};
gameState.zombies.push(zombieObj);
return zombieObj;
}
// Create projectile
function createProjectile(x, y) {
const pea = document.createElement('div');
pea.className = 'pea';
pea.style.left = `${x}px`;
pea.style.top = `${y}px`;
gameContainer.appendChild(pea);
const projectile = {
x: x,
y: y,
speed: 5,
damage: 20,
element: pea
};
gameState.projectiles.push(projectile);
return projectile;
}
// Create sun drop
function createSunDrop(x, y, autoCollect = false) {
const sun = document.createElement('div');
sun.className = 'sun';
sun.style.left = `${x}px`;
sun.style.top = `${y}px`;
if (autoCollect) {
// Animation for suns produced by sunflowers
sun.style.animation = 'sun-bounce 4s ease-in-out, sun-spin 2s linear infinite';
} else {
// Regular falling sun animation
sun.style.animation = 'sun-fall 6s linear, sun-spin 2s linear infinite';
}
sun.addEventListener('click', collectSun);
gameContainer.appendChild(sun);
const sunDrop = {
x: x,
y: y,
element: sun,
autoCollect: autoCollect,
collected: false,
value: 25
};
gameState.sunDrops.push(sunDrop);
if (autoCollect) {
// Auto-collect after animation
setTimeout(() => {
if (!sunDrop.collected) {
collectSun({ target: sun });
}
}, 4000);
} else {
// Remove if not collected
setTimeout(() => {
if (!sunDrop.collected) {
sun.remove();
gameState.sunDrops = gameState.sunDrops.filter(s => s !== sunDrop);
}
}, 6000);
}
return sunDrop;
}
// Create random sun
function createRandomSun() {
if (!gameState.gameActive) return;
const x = Math.random() * (gameContainer.clientWidth - 100) + 50;
createSunDrop(x, -50);
}
// Collect sun
function collectSun(e) {
e.stopPropagation();
const sunElement = e.target;
const sunDrop = gameState.sunDrops.find(sun => sun.element === sunElement);
if (sunDrop && !sunDrop.collected) {
sunDrop.collected = true;
gameState.suns += sunDrop.value;
updateSunCounter();
// Animate collection
sunElement.style.transition = 'all 0.3s ease-out';
sunElement.style.transform = 'translateY(-20px) scale(1.5)';
sunElement.style.opacity = '0';
// Remove after animation
setTimeout(() => {
sunElement.remove();
gameState.sunDrops = gameState.sunDrops.filter(s => s !== sunDrop);
}, 300);
}
}
// Update sun counter display
function updateSunCounter() {
sunCounter.textContent = gameState.suns;
// Update plant option availability
plantOptions.forEach(option => {
const plantType = option.dataset.plant;
const plantCost = parseInt(option.dataset.cost);
if (gameState.suns < plantCost || gameState.plantCooldowns[plantType] > 0) {
option.style.opacity = '0.5';
} else {
option.style.opacity = '1';
}
});
}
// Update cooldown display
function updateCooldownDisplay(plantType) {
const option = document.querySelector(`.plant-option[data-plant="${plantType}"]`);
if (!option) return;
// Remove existing cooldown overlay
const existingOverlay = option.querySelector('.cooldown-overlay');
if (existingOverlay) existingOverlay.remove();
// Create new cooldown overlay
const overlay = document.createElement('div');
overlay.className = 'cooldown-overlay';
overlay.style.height = '100%';
option.appendChild(overlay);
// Animate cooldown
const cooldown = plantProperties[plantType].cooldown;
const startTime = Date.now();
const animateCooldown = () => {
const elapsed = (Date.now() - startTime) / 1000;
const remaining = Math.max(0, cooldown - elapsed);
overlay.style.height = `${(remaining / cooldown) * 100}%`;
if (remaining > 0) {
gameState.plantCooldowns[plantType] = remaining;
requestAnimationFrame(animateCooldown);
} else {
gameState.plantCooldowns[plantType] = 0;
overlay.remove();
// Update sun counter to refresh availability
updateSunCounter();
}
};
requestAnimationFrame(animateCooldown);
}
// Game loop
function gameLoop() {
if (!gameState.gameActive) return;
// Update game clock
gameState.gameClock += 0.016; // Assuming 60 FPS
// Check for wave completion and spawn new waves
checkWaveCompletion();
// Update zombies
updateZombies();
// Update plants
updatePlants();
// Update projectiles
updateProjectiles();
// Check game over conditions
checkGameOver();
// Continue game loop
requestAnimationFrame(gameLoop);
}
// Check wave completion
function checkWaveCompletion() {
const level = levels[gameState.currentLevel];
// Check if we need to start a new wave
if (gameState.currentWave < level.waves.length) {
const currentWaveData = level.waves[gameState.currentWave];
if (gameState.gameClock >= currentWaveData.delay &&
gameState.lastZombieSpawned + 1 < gameState.gameClock &&
gameState.zombies.length < 5 + gameState.currentWave) {
// Spawn zombie
const zombieType = getRandomZombieType(currentWaveData.zombieTypeWeights);
const row = Math.floor(Math.random() * 5);
createZombie(zombieType, row);
// Update last spawned time
gameState.lastZombieSpawned = gameState.gameClock;
// Update wave indicator if this is the first zombie of a new wave
if (gameState.zombies.length === 1) {
waveIndicator.textContent = `Wave: ${gameState.currentWave + 1}/${level.waves.length}`;
}
}
// Check if we've spawned enough zombies for this wave
const zombiesSpawnedThisWave = gameState.zombies.filter(z =>
z.spawnTime >= currentWaveData.delay - 1
).length;
if (zombiesSpawnedThisWave >= currentWaveData.zombies &&
gameState.zombies.length === 0 &&
gameState.currentWave < level.waves.length - 1) {
// Move to next wave
gameState.currentWave++;
}
}
}
// Get random zombie type based on weights
function getRandomZombieType(weights) {
const randomValue = Math.random();
let cumulativeWeight = 0;
for (let i = 0; i < weights.length; i++) {
cumulativeWeight += weights[i];
if (randomValue < cumulativeWeight) {
return levels[gameState.currentLevel].zombieTypes[i];
}
}
return levels[gameState.currentLevel].zombieTypes[0]; // Fallback
}
// Update zombies
function updateZombies() {
const cellWidth = lawn.clientWidth / 9;
const cellHeight = lawn.clientHeight / 5;
for (let i = gameState.zombies.length - 1; i >= 0; i--) {
const zombie = gameState.zombies[i];
if (zombie.moving) {
// Move zombie
zombie.x -= zombie.speed;
zombie.element.style.left = `${zombie.x}px`;
// Check if zombie reached left side
if (zombie.x < -zombie.element.clientWidth) {
gameState.zombiesReachedEnd++;
zombie.element.remove();
gameState.zombies.splice(i, 1);
continue;
}
// Check for collision with plants
const zombieRect = {
left: zombie.x,
right: zombie.x + zombie.element.clientWidth,
top: parseInt(zombie.element.style.top),
bottom: parseInt(zombie.element.style.top) + zombie.element.clientHeight
};
// Find plants in the same row
const plantsInRow = gameState.plants.filter(p => p.row === zombie.row);
for (const plant of plantsInRow) {
const cell = document.querySelector(`.lawn-cell[data-row="${plant.row}"][data-col="${plant.col}"]`);
const rect = cell.getBoundingClientRect();
const plantRect = {
left: rect.left - gameContainer.getBoundingClientRect().left,
right: rect.right - gameContainer.getBoundingClientRect().left,
top: rect.top - gameContainer.getBoundingClientRect().top,
bottom: rect.bottom - gameContainer.getBoundingClientRect().top
};
// Simple collision detection
if (zombieRect.right > plantRect.left && zombieRect.left < plantRect.right &&
zombieRect.bottom > plantRect.top && zombieRect.top < plantRect.bottom) {
zombie.moving = false;
// Damage plant
plant.health -= zombie.damage / 60; // Assuming 60 FPS
updatePlantHealth(plant);
// Zombie eats animation
if (Math.random() < 0.02) {
zombie.element.style.transform = 'scaleX(1.1)';
setTimeout(() => {
if (zombie.element) {
zombie.element.style.transform = 'scaleX(1)';
}
}, 100);
}
// Check if plant is dead
if (plant.health <= 0) {
plant.element.remove();
gameState.plants = gameState.plants.filter(p => p !== plant);
zombie.moving = true;
}
break;
}
}
}
// Update zombie appearance based on health
updateZombieHealth(zombie);
}
}
// Update plants
function updatePlants() {
for (const plant of gameState.plants) {
// Sunflower behavior
if (plant.type === 'sunflower') {
if (gameState.gameClock - plant.lastActionTime >= plantProperties.sunflower.produceSunInterval) {
plant.lastActionTime = gameState.gameClock;
// Produce sun
const cell = document.querySelector(`.lawn-cell[data-row="${plant.row}"][data-col="${plant.col}"]`);
const rect = cell.getBoundingClientRect();
const x = rect.left - gameContainer.getBoundingClientRect().left + rect.width / 2;
const y = rect.top - gameContainer.getBoundingClientRect().top + rect.height / 2;
createSunDrop(x, y, true);
}
}
// Peashooter behavior
if (plant.type === 'peashooter' && gameState.zombies.some(z => z.row === plant.row)) {
if (gameState.gameClock - plant.lastActionTime >= plantProperties.peashooter.shootInterval) {
plant.lastActionTime = gameState.gameClock;
// Shoot pea
const cell = document.querySelector(`.lawn-cell[data-row="${plant.row}"][data-col="${plant.col}"]`);
const rect = cell.getBoundingClientRect();
const x = rect.right - gameContainer.getBoundingClientRect().left;
const y = rect.top - gameContainer.getBoundingClientRect().top + rect.height / 2;
createProjectile(x, y);
}
}
// Update plant appearance based on health
updatePlantHealth(plant);
}
}
// Update projectiles
function updateProjectiles() {
for (let i = gameState.projectiles.length - 1; i >= 0; i--) {
const projectile = gameState.projectiles[i];
// Move projectile
projectile.x += projectile.speed;
projectile.element.style.left = `${projectile.x}px`;
// Remove if off screen
if (projectile.x > gameContainer.clientWidth) {
projectile.element.remove();
gameState.projectiles.splice(i, 1);
continue;
}
// Check for collision with zombies
const peaRect = {
left: projectile.x,
right: projectile.x + projectile.element.clientWidth,
top: parseInt(projectile.element.style.top),
bottom: parseInt(projectile.element.style.top) + projectile.element.clientHeight
};
for (const zombie of gameState.zombies) {
const zombieRect = {
left: zombie.x,
right: zombie.x + zombie.element.clientWidth,
top: parseInt(zombie.element.style.top),
bottom: parseInt(zombie.element.style.top) + zombie.element.clientHeight
};
// Simple collision detection
if (peaRect.right > zombieRect.left && peaRect.left < zombieRect.right &&
peaRect.bottom > zombieRect.top && peaRect.top < zombieRect.bottom) {
// Damage zombie
zombie.health -= projectile.damage;
// Update zombie appearance
updateZombieHealth(zombie);
// Remove projectile
projectile.element.remove();
gameState.projectiles.splice(i, 1);
// Check if zombie is dead
if (zombie.health <= 0) {
// Add suns
gameState.suns += zombieProperties[zombie.type].value;
updateSunCounter();
// Remove zombie
zombie.element.remove();
gameState.zombies = gameState.zombies.filter(z => z !== zombie);
}
break;
}
}
}
}
// Update plant health display
function updatePlantHealth(plant) {
const healthPercent = Math.max(0, plant.health / plantProperties[plant.type].health * 100);
const healthBar = plant.element.querySelector('.health-bar');
if (healthBar) {
healthBar.style.width = `${healthPercent}%`;
healthBar.style.backgroundColor = healthPercent > 50 ? '#2ecc71' :
healthPercent > 25 ? '#f39c12' :
'#e74c3c';
}
}
// Update zombie health display
function updateZombieHealth(zombie) {
const healthPercent = Math.max(0, zombie.health / zombie.maxHealth * 100);
const healthBar = zombie.element.querySelector('.health-bar');
if (healthBar) {
healthBar.style.width = `${healthPercent}%`;
// Visual damage effects
if (healthPercent < 50) {
zombie.element.style.filter = 'brightness(0.8)';
}
if (healthPercent < 25) {
zombie.element.style.filter = 'brightness(0.6) hue-rotate(30deg)';
}
}
}
// Check game over conditions
function checkGameOver() {
// Check for too many zombies reaching the end
if (gameState.zombiesReachedEnd >= 5) {
endGame(false);
return;
}
// Check for level completion
const level = levels[gameState.currentLevel];
if (gameState.currentWave >= level.waves.length - 1 &&
gameState.zombies.length === 0 &&
gameState.gameClock - gameState.lastZombieSpawned > 5) {
endGame(true);
return;
}
}
// End game
function endGame(victory) {
gameState.gameActive = false;
gameOverText.textContent = victory ?
`You survived the zombie attack! Final score: ${gameState.suns}` :
`The zombies ate your brains! You survived ${gameState.currentWave + 1} waves.`;
gameOverMessage.style.display = 'flex';
}
// Initialize the game
function initGame() {
initLawn();
initEventListeners();
// Select first level by default
selectLevel(1);
// Show start screen
startScreen.style.display = 'flex';
}
// Start the game
window.onload = initGame;
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: absolute; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">This website has been generated by <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>