forms

Garden Defense Saga (Plants Vs Zombies)

In Garden Defense Saga, you must strategically plant a variety of bio-engineered flora to defend your home from an ever-evolving horde of comedic but deadly zombies. This 20-level arcade defense game blends classic resource management with high-octane tactical upgrades.

Key Features:
Dynamic Arsenal: Start with basic Peashooters and Sunflowers, then unlock advanced tech like Laser Beans that pierce through entire rows, Plasma Shells for high-energy damage, and Automated Drones that provide aerial support.

Tactical Depth: Use Melon-pults for heavy area-of-effect splash damage or Starfruit to cover five different angles at once.

Challenging Enemies: Face off against Bucket-heads, Shield-bearers, and the massive Zombie Giant as the difficulty scales with every wave.

Level Progression: Progress through 20 increasingly difficult stages. Each new wave refreshes your "Emergency Lawnmowers," giving you a second chance to refine your strategy.

Resource Management: Balance your Sun production to afford heavy artillery while maintaining a frontline of sturdy Wall-nuts.

Code Snippet

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>Garden Defense Saga: 20 Levels</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Bangers&display=swap');
        
        :root {
            --wood-dark: #3d1e09;
            --wood-light: #8b4513;
        }

        body {
            background-color: #020617;
            color: white;
            font-family: 'Segoe UI', sans-serif;
            margin: 0;
            overflow: hidden;
            touch-action: manipulation;
        }

        .game-font { font-family: 'Bangers', cursive; letter-spacing: 1.5px; }

        #game-container {
            position: relative;
            width: 100vw;
            height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: radial-gradient(circle, #1e293b 0%, #020617 100%);
        }

        canvas {
            max-width: 100%;
            max-height: 100%;
            box-shadow: 0 0 100px rgba(0,0,0,0.8);
            image-rendering: pixelated;
        }

        .seed-bar {
            background: rgba(0,0,0,0.8);
            backdrop-filter: blur(12px);
            border-top: 4px solid var(--wood-dark);
            box-shadow: 0 -10px 30px rgba(0,0,0,0.5);
        }

        .packet {
            width: 70px;
            height: 95px;
            background: linear-gradient(135deg, #e4e4e7 0%, #a1a1aa 100%);
            border: 2px solid #52525b;
            position: relative;
            transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            cursor: pointer;
            flex-shrink: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        .packet:hover:not(.locked) { transform: scale(1.1); z-index: 10; }
        .packet.selected { border-color: #fbbf24; transform: translateY(-10px); box-shadow: 0 10px 20px rgba(251, 191, 36, 0.5); }
        .packet.disabled { filter: grayscale(1) brightness(0.3); pointer-events: none; }
        
        .packet-cost {
            position: absolute;
            bottom: 0; right: 0; left: 0;
            background: rgba(0,0,0,0.8);
            color: #fbbf24;
            font-size: 11px;
            text-align: center;
            font-weight: bold;
            border-top: 1px solid rgba(255,255,255,0.1);
        }

        .level-announcement {
            animation: slideIn 0.8s forwards, fadeOut 0.8s 2.5s forwards;
        }

        @keyframes slideIn { from { transform: scale(0) rotate(-10deg); opacity: 0; } to { transform: scale(1) rotate(0); opacity: 1; } }
        @keyframes fadeOut { to { transform: scale(1.5); opacity: 0; pointer-events: none; } }

        @media (max-height: 500px) {
            .packet { width: 55px; height: 75px; }
        }
    </style>
</head>
<body>

    <div id="game-container">
        <canvas id="gameCanvas"></canvas>

        <!-- Dynamic UI -->
        <div id="ui-layer" class="fixed inset-0 pointer-events-none flex flex-col justify-between p-4">
            
            <!-- Top Nav -->
            <div class="flex justify-between items-start pointer-events-auto">
                <div class="flex flex-col gap-2">
                    <div class="bg-amber-900 border-4 border-amber-950 rounded-xl p-2 flex items-center space-x-3 shadow-2xl">
                        <div class="w-10 h-10 bg-yellow-400 rounded-full flex items-center justify-center border-2 border-yellow-600 shadow-inner">
                            <span class="text-xl">☀️</span>
                        </div>
                        <span id="sun-count" class="text-3xl game-font text-white pr-2">150</span>
                    </div>
                    <div class="bg-black/60 px-3 py-1 rounded-full text-xs font-bold border border-white/20 tracking-widest uppercase">
                        LEVEL <span id="current-level-display">1</span> / 20
                    </div>
                </div>

                <div class="text-right flex flex-col items-end">
                    <div class="game-font text-3xl text-red-500 drop-shadow-md">HORDE INCOMING</div>
                    <div class="w-48 md:w-80 h-4 bg-gray-950 border-2 border-gray-700 rounded-full mt-1 overflow-hidden relative shadow-inner">
                        <div id="progress-bar" class="h-full bg-gradient-to-r from-green-500 via-yellow-500 to-red-600 transition-all duration-500 shadow-[0_0_15px_rgba(239,68,68,0.5)]" style="width: 0%"></div>
                    </div>
                    <div id="mower-notif" class="text-cyan-400 font-bold text-xs mt-1 opacity-0 transition-opacity">WEAPONS RECHARGED & MOWERS RESTORED!</div>
                </div>
            </div>

            <!-- Enhanced Seed Bar -->
            <div class="flex justify-center mb-2 pointer-events-auto">
                <div class="flex space-x-2 seed-bar p-4 rounded-3xl border-b-8 border-black/50 overflow-x-auto max-w-full">
                    <!-- Standard -->
                    <div class="packet rounded-lg" id="p-sunflower" onclick="selectPlant('sunflower', 50)">
                        <div class="text-3xl">🌻</div><div class="packet-cost">50</div>
                    </div>
                    <div class="packet rounded-lg" id="p-peashooter" onclick="selectPlant('peashooter', 100)">
                        <div class="text-3xl">🔫</div><div class="packet-cost">100</div>
                    </div>
                    <div class="packet rounded-lg" id="p-wallnut" onclick="selectPlant('wallnut', 50)">
                        <div class="text-3xl">🌰</div><div class="packet-cost">50</div>
                    </div>
                    <!-- Advanced -->
                    <div class="packet rounded-lg" id="p-plasma" onclick="selectPlant('plasma', 150)">
                        <div class="text-3xl">🐚</div><div class="packet-cost">150</div>
                    </div>
                    <div class="packet rounded-lg" id="p-laser" onclick="selectPlant('laser', 200)">
                        <div class="text-3xl">💎</div><div class="packet-cost">200</div>
                    </div>
                    <div class="packet rounded-lg" id="p-melon" onclick="selectPlant('melon', 325)">
                        <div class="text-3xl">🍉</div><div class="packet-cost">325</div>
                    </div>
                    <div class="packet rounded-lg" id="p-star" onclick="selectPlant('star', 125)">
                        <div class="text-3xl">⭐</div><div class="packet-cost">125</div>
                    </div>
                    <div class="packet rounded-lg" id="p-drone" onclick="selectPlant('drone', 250)">
                        <div class="text-3xl">📡</div><div class="packet-cost">250</div>
                    </div>
                    <div class="packet rounded-lg" id="p-cherry" onclick="selectPlant('cherry', 150)">
                        <div class="text-3xl">🍒</div><div class="packet-cost">150</div>
                    </div>
                </div>
            </div>
        </div>

        <div id="level-popup" class="absolute inset-0 flex items-center justify-center pointer-events-none z-50 hidden">
            <h2 id="level-text" class="text-7xl md:text-9xl game-font text-white drop-shadow-[0_10px_20px_rgba(0,0,0,1)] level-announcement">LEVEL 1</h2>
        </div>

        <div id="screen-overlay" class="absolute inset-0 bg-black/80 flex flex-col items-center justify-center hidden z-[100]">
            <h1 id="overlay-title" class="text-6xl md:text-8xl game-font text-red-600 mb-8 px-4 text-center">THE ZOMBIES ATE YOUR BRAINS!</h1>
            <button onclick="restartGame()" class="bg-green-600 hover:bg-green-500 text-white game-font text-4xl px-12 py-4 rounded-full border-b-8 border-green-800 transition-all active:translate-y-2">RETRY MISSION</button>
        </div>
    </div>

<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const sunEl = document.getElementById('sun-count');
    const progressEl = document.getElementById('progress-bar');
    const levelDisplayEl = document.getElementById('current-level-display');
    const levelPopup = document.getElementById('level-popup');
    const overlay = document.getElementById('screen-overlay');
    const mowerNotif = document.getElementById('mower-notif');

    const ROWS = 5;
    const COLS = 9;
    const CELL_W = 100;
    const CELL_H = 100;
    const OFFSET_X = 100;

    let gameState = {
        sun: 150,
        level: 1,
        frame: 0,
        selectedPlant: null,
        isGameOver: false,
        lastSpawn: 0,
        zombiesSpawned: 0,
        zombiesKilled: 0,
        levelActive: false
    };

    let plants = [];
    let zombies = [];
    let bullets = [];
    let suns = [];
    let mowers = [];
    let particles = [];
    let beams = []; // For Laser weapon

    class Mower {
        constructor(row) {
            this.row = row;
            this.reset();
        }
        reset() {
            this.x = 20;
            this.y = this.row * CELL_H + 20;
            this.active = false;
            this.used = false;
        }
        update() {
            if (this.active) {
                this.x += 10;
                zombies.forEach(z => {
                    if (z.row === this.row && z.x < this.x + 80 && z.x > this.x - 20) z.health = 0;
                });
                if (this.x > canvas.width) {
                    this.active = false;
                    this.used = true;
                }
            }
        }
        draw() {
            if (this.used) return;
            ctx.fillStyle = '#475569';
            ctx.fillRect(this.x, this.y, 65, 55);
            ctx.fillStyle = '#ef4444';
            ctx.fillRect(this.x + 5, this.y + 5, 55, 30);
            ctx.fillStyle = '#111';
            ctx.beginPath(); ctx.arc(this.x+15, this.y+55, 10, 0, Math.PI*2); ctx.fill();
            ctx.beginPath(); ctx.arc(this.x+50, this.y+55, 10, 0, Math.PI*2); ctx.fill();
        }
    }

    class Sun {
        constructor(x, y, auto) {
            this.x = x;
            this.y = auto ? -50 : y;
            this.targetY = auto ? (Math.random() * 300 + 100) : y + (Math.random() * 60 - 30);
            this.vy = auto ? 1.8 : -1.2;
            this.life = 500;
        }
        update() {
            if (this.y < this.targetY) this.y += this.vy;
            this.life--;
        }
        draw() {
            ctx.save();
            ctx.translate(this.x, this.y);
            ctx.rotate(gameState.frame * 0.04);
            ctx.fillStyle = '#facc15';
            for(let i=0; i<8; i++) {
                ctx.rotate(Math.PI/4);
                ctx.fillRect(-3, -25, 6, 50);
            }
            ctx.beginPath(); ctx.arc(0,0, 18, 0, Math.PI*2); ctx.fill();
            ctx.restore();
        }
    }

    class Plant {
        constructor(c, r, type) {
            this.c = c; this.r = r;
            this.x = c * CELL_W + OFFSET_X;
            this.y = r * CELL_H;
            this.type = type;
            this.timer = 0;
            const stats = {
                'sunflower': { h: 120, rate: 400, emoji: '🌻' },
                'peashooter': { h: 100, rate: 80, emoji: '🔫' },
                'wallnut': { h: 1500, rate: 0, emoji: '🌰' },
                'plasma': { h: 120, rate: 60, emoji: '🐚' },
                'laser': { h: 150, rate: 120, emoji: '💎' },
                'melon': { h: 150, rate: 150, emoji: '🍉' },
                'star': { h: 120, rate: 100, emoji: '⭐' },
                'drone': { h: 100, rate: 180, emoji: '📡' },
                'cherry': { h: 100, rate: 45, emoji: '🍒' }
            };
            this.health = stats[type].h;
            this.maxHealth = stats[type].h;
            this.rate = stats[type].rate;
            this.emoji = stats[type].emoji;
            this.droneX = 0; // For drone patrol
        }
        update() {
            this.timer++;
            if (this.type === 'sunflower' && this.timer % this.rate === 0) suns.push(new Sun(this.x + 50, this.y + 50, false));
            
            if (['peashooter', 'plasma', 'melon', 'star'].includes(this.type) && this.timer % this.rate === 0) {
                if (zombies.some(z => z.row === this.r && z.x > this.x) || this.type === 'star') {
                    this.shoot();
                }
            }
            
            if (this.type === 'laser' && this.timer % this.rate === 0) {
                if (zombies.some(z => z.row === this.r && z.x > this.x)) {
                    beams.push({ y: this.y + 45, x: this.x + 60, r: this.r, life: 20 });
                    zombies.forEach(z => {
                        if (z.row === this.r && z.x > this.x) z.health -= 60;
                    });
                }
            }

            if (this.type === 'drone') {
                this.droneX = 150 + Math.sin(this.timer * 0.05) * 150;
                if (this.timer % 40 === 0) {
                    const target = zombies.find(z => z.row === this.r && Math.abs(z.x - (this.x + this.droneX)) < 150);
                    if (target) bullets.push({ x: this.x + this.droneX, y: this.y + 10, row: this.r, type: 'zap', vx: 0, vy: 5 });
                }
            }

            if (this.type === 'cherry' && this.timer >= this.rate) this.explode();
        }
        shoot() { 
            if (this.type === 'peashooter') bullets.push({ x: this.x + 60, y: this.y + 40, row: this.r, type: 'pea', vx: 6 });
            if (this.type === 'plasma') bullets.push({ x: this.x + 60, y: this.y + 40, row: this.r, type: 'plasma', vx: 8 });
            if (this.type === 'melon') bullets.push({ x: this.x + 60, y: this.y + 40, row: this.r, type: 'melon', vx: 4, vy: -5, startY: this.y + 40 });
            if (this.type === 'star') {
                const angles = [0, Math.PI/2, Math.PI, -Math.PI/2, Math.PI/4];
                angles.forEach(a => bullets.push({ x: this.x + 50, y: this.y + 50, row: -1, type: 'star', vx: Math.cos(a)*5, vy: Math.sin(a)*5 }));
            }
        }
        explode() {
            zombies.forEach(z => {
                if (Math.hypot(z.x - (this.x+50), (z.y+50) - (this.y+50)) < 220) z.health = 0;
            });
            particles.push({ x: this.x+50, y: this.y+50, r: 0, max: 220, c: '#ef4444' });
            this.health = 0;
        }
        draw() {
            ctx.font = '60px serif'; ctx.textAlign = 'center';
            ctx.fillText(this.emoji, this.x + 50, this.y + 70 + Math.sin(gameState.frame * 0.1) * 3);
            
            if (this.type === 'drone') {
                ctx.font = '30px serif';
                ctx.fillText('🛸', this.x + this.droneX, this.y + 20);
                ctx.strokeStyle = 'cyan'; ctx.setLineDash([2, 2]);
                ctx.beginPath(); ctx.moveTo(this.x + 50, this.y + 40); ctx.lineTo(this.x + this.droneX, this.y + 20); ctx.stroke();
                ctx.setLineDash([]);
            }
        }
    }

    class Zombie {
        constructor(row, type = 'normal') {
            this.row = row;
            this.x = canvas.width + 100;
            this.y = row * CELL_H;
            this.type = type;
            this.slowed = 0;
            const diff = 1 + (gameState.level * 0.2);
            const data = {
                'normal': { h: 100, s: 0.45, e: '🧟' },
                'bucket': { h: 500, s: 0.35, e: '🪣' },
                'runner': { h: 100, s: 1.1, e: '🏃' },
                'shield': { h: 800, s: 0.25, e: '🛡️' },
                'giant': { h: 2500, s: 0.2, e: '👹' }
            };
            this.health = data[type].h * diff;
            this.maxHealth = this.health;
            this.speed = data[type].s;
            this.emoji = data[type].e;
        }
        update() {
            let s = this.slowed > 0 ? this.speed * 0.5 : this.speed;
            if (this.slowed > 0) this.slowed--;
            let eating = false;
            plants.forEach(p => {
                if (p.r === this.row && this.x < p.x + 80 && this.x > p.x + 10) {
                    eating = true;
                    p.health -= 0.8;
                }
            });
            if (!eating) this.x -= s;
            if (this.x < 80) {
                const m = mowers.find(m => m.row === this.row && !m.used);
                if (m) m.active = true; else gameOver();
            }
        }
        draw() {
            ctx.save();
            if (this.slowed > 0) ctx.filter = 'hue-rotate(180deg)';
            ctx.font = '65px serif'; ctx.textAlign = 'center';
            ctx.fillText(this.emoji, this.x, this.y + 75 + Math.cos(gameState.frame * 0.1) * 4);
            ctx.restore();
            if (this.health < this.maxHealth) {
                ctx.fillStyle = '#111'; ctx.fillRect(this.x - 25, this.y + 5, 50, 6);
                ctx.fillStyle = '#ef4444'; ctx.fillRect(this.x - 25, this.y + 5, 50 * (this.health/this.maxHealth), 6);
            }
        }
    }

    function init() {
        canvas.width = 1100; canvas.height = 500;
        mowers = Array.from({length: ROWS}, (_, i) => new Mower(i));
        startLevel(1);
        requestAnimationFrame(tick);
    }

    function startLevel(lvl) {
        gameState.level = lvl;
        gameState.zombiesKilled = 0;
        gameState.zombiesSpawned = 0;
        gameState.levelActive = true;
        levelDisplayEl.innerText = lvl;
        document.getElementById('level-text').innerText = `WAVE ${lvl}`;
        levelPopup.classList.remove('hidden');
        
        // Restore All Mowers
        mowers.forEach(m => m.reset());
        mowerNotif.style.opacity = '1';
        setTimeout(() => mowerNotif.style.opacity = '0', 4000);
        
        setTimeout(() => levelPopup.classList.add('hidden'), 3000);
    }

    function tick() {
        if (gameState.isGameOver) return;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // Background
        for(let r=0; r<ROWS; r++) {
            for(let c=0; c<COLS; c++) {
                ctx.fillStyle = (r+c)%2 ? '#166534' : '#15803d';
                ctx.fillRect(c*CELL_W + OFFSET_X, r*CELL_H, CELL_W, CELL_H);
                ctx.strokeStyle = 'rgba(255,255,255,0.05)';
                ctx.strokeRect(c*CELL_W + OFFSET_X, r*CELL_H, CELL_W, CELL_H);
            }
        }
        ctx.fillStyle = '#0f172a'; ctx.fillRect(0, 0, OFFSET_X, canvas.height);

        // Spawning
        const total = 5 + (gameState.level * 4);
        const rate = Math.max(40, 150 - (gameState.level * 7));
        if (gameState.zombiesSpawned < total && gameState.frame > gameState.lastSpawn + rate) {
            let t = 'normal'; const r = Math.random() * 100;
            if (gameState.level >= 2 && r < 30) t = 'bucket';
            if (gameState.level >= 4 && r < 25) t = 'runner';
            if (gameState.level >= 7 && r < 20) t = 'shield';
            if (gameState.level >= 10 && r < 15) t = 'giant';
            zombies.push(new Zombie(Math.floor(Math.random() * ROWS), t));
            gameState.zombiesSpawned++; gameState.lastSpawn = gameState.frame;
        }

        if (gameState.frame % 380 === 0) suns.push(new Sun(Math.random()*700 + 200, 0, true));

        // Update loops
        mowers.forEach(m => { m.update(); m.draw(); });
        suns.forEach((s, i) => { s.update(); s.draw(); if(s.life <= 0) suns.splice(i, 1); });
        plants.forEach((p, i) => { p.update(); p.draw(); if(p.health <= 0) plants.splice(i, 1); });
        zombies.forEach((z, i) => { 
            z.update(); z.draw(); 
            if(z.health <= 0) { 
                zombies.splice(i, 1); 
                gameState.zombiesKilled++; 
                particles.push({ x: z.x, y: z.y + 40, r: 10, max: 40, c: '#4ade80' });
            } 
        });

        // Laser Beams
        beams.forEach((b, i) => {
            ctx.strokeStyle = 'rgba(168, 85, 247, 0.8)';
            ctx.lineWidth = b.life;
            ctx.beginPath(); ctx.moveTo(b.x, b.y); ctx.lineTo(canvas.width, b.y); ctx.stroke();
            b.life -= 2; if(b.life <= 0) beams.splice(i, 1);
        });

        // Projectiles
        bullets.forEach((b, i) => {
            b.x += b.vx;
            if (b.type === 'melon') {
                b.vy += 0.2; b.y += b.vy;
                if (b.y >= b.startY) {
                    particles.push({ x: b.x, y: b.y, r: 0, max: 100, c: '#22c55e' });
                    zombies.forEach(z => {
                        if (Math.hypot(z.x - b.x, (z.y+40) - b.y) < 100) z.health -= 150;
                    });
                    bullets.splice(i, 1); return;
                }
            } else if (b.type === 'zap') {
                b.y += b.vy;
            } else {
                b.y += (b.vy || 0);
            }

            // Visuals
            const colors = { pea: '#84cc16', plasma: '#3b82f6', star: '#facc15', zap: '#22d3ee' };
            ctx.fillStyle = colors[b.type] || '#fff';
            ctx.beginPath(); ctx.arc(b.x, b.y, b.type==='plasma'?10:7, 0, Math.PI*2); ctx.fill();
            if (b.type === 'plasma') {
                ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke();
            }

            zombies.forEach(z => {
                if (b.row === -1 || z.row === b.row) {
                    if (Math.hypot(b.x - z.x, b.y - (z.y+40)) < 30) {
                        const dmg = { pea: 25, plasma: 45, star: 35, zap: 15 };
                        z.health -= dmg[b.type] || 20;
                        bullets.splice(i, 1);
                    }
                }
            });
            if (b.x > canvas.width || b.x < 0 || b.y > canvas.height || b.y < 0) bullets.splice(i, 1);
        });

        particles.forEach((p, i) => {
            p.r += 10; ctx.strokeStyle = p.c; ctx.lineWidth = 4;
            ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.stroke();
            if (p.r > p.max) particles.splice(i, 1);
        });

        progressEl.style.width = `${(gameState.zombiesKilled / total) * 100}%`;
        sunEl.innerText = gameState.sun;

        if (gameState.zombiesKilled >= total && zombies.length === 0) {
            if (gameState.level < 20) startLevel(gameState.level + 1); else winGame();
        }

        updateSeedUI();
        gameState.frame++;
        requestAnimationFrame(tick);
    }

    function selectPlant(type, cost) {
        if (gameState.sun >= cost) gameState.selectedPlant = (gameState.selectedPlant === type) ? null : type;
    }

    function updateSeedUI() {
        const packets = document.querySelectorAll('.packet');
        packets.forEach(p => {
            const costEl = p.querySelector('.packet-cost');
            const cost = parseInt(costEl.innerText);
            const type = p.id.split('-')[1];
            p.classList.toggle('disabled', gameState.sun < cost);
            p.classList.toggle('selected', gameState.selectedPlant === type);
        });
    }

    canvas.addEventListener('click', (e) => {
        const rect = canvas.getBoundingClientRect();
        const x = (e.clientX - rect.left) * (canvas.width / rect.width);
        const y = (e.clientY - rect.top) * (canvas.height / rect.height);
        
        for (let i = suns.length - 1; i >= 0; i--) {
            if (Math.hypot(suns[i].x - x, suns[i].y - y) < 50) {
                gameState.sun += 25; suns.splice(i, 1); return;
            }
        }
        
        if (gameState.selectedPlant) {
            const c = Math.floor((x - OFFSET_X) / CELL_W), r = Math.floor(y / CELL_H);
            if (c >= 0 && c < COLS && r >= 0 && r < ROWS && !plants.some(p => p.c === c && p.r === r)) {
                const costs = { sunflower: 50, peashooter: 100, wallnut: 50, plasma: 150, laser: 200, melon: 325, star: 125, drone: 250, cherry: 150 };
                gameState.sun -= costs[gameState.selectedPlant];
                plants.push(new Plant(c, r, gameState.selectedPlant));
                gameState.selectedPlant = null;
            }
        }
    });

    function gameOver() { gameState.isGameOver = true; overlay.classList.remove('hidden'); }
    function winGame() { 
        gameState.isGameOver = true; 
        document.getElementById('overlay-title').innerText = "WORLD DEFENDED!";
        document.getElementById('overlay-title').className = "text-6xl md:text-8xl game-font text-yellow-400 mb-8 px-4 text-center";
        overlay.classList.remove('hidden'); 
    }
    function restartGame() { location.reload(); }

    window.onload = init;
</script>
</body>
</html>

Media & Assets

Garden Defense Saga (Plants Vs Zombies)
Resource ID: #00031
Posted: December 23, 2025
© 2025 Your Code Library.