Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Зомби Башня: Последний Рубеж</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
            margin: 0;
            padding: 0;
        }
        body, html {
            width: 100%;
            height: 100%;
            overflow: hidden;
            background-color: #111;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        #gameCanvas {
            display: block;
            background: #1e241e;
            cursor: crosshair;
        }
        #uiOverlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            padding: 15px;
            color: #fff;
            text-shadow: 2px 2px 4px #000;
        }
        .stats {
            font-size: 18px;
            font-weight: bold;
        }
        #hpBarContainer {
            width: 200px;
            height: 20px;
            background: #444;
            border: 2px solid #fff;
            border-radius: 10px;
            overflow: hidden;
            margin-top: 5px;
        }
        #hpBar {
            width: 100%;
            height: 100%;
            background: #e74c3c;
            transition: width 0.1s;
        }
        #upgradeModal {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.85);
            display: none;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            pointer-events: auto;
            z-index: 10;
        }
        #upgradeModal h2 {
            color: #f1c40f;
            margin-bottom: 20px;
            font-size: 28px;
        }
        .cards-container {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            justify-content: center;
        }
        .card {
            background: #2c3e50;
            border: 2px solid #3498db;
            border-radius: 8px;
            padding: 15px;
            width: 180px;
            text-align: center;
            cursor: pointer;
            transition: transform 0.2s, background 0.2s;
        }
        .card:hover {
            transform: scale(1.05);
            background: #34495e;
        }
        .card h3 {
            font-size: 16px;
            color: #2ecc71;
            margin-bottom: 8px;
        }
        .card p {
            font-size: 13px;
            color: #bdc3c7;
        }
        #gameOverScreen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.9);
            display: none;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            color: #fff;
            pointer-events: auto;
            z-index: 20;
        }
        #gameOverScreen h1 {
            color: #e74c3c;
            font-size: 40px;
            margin-bottom: 10px;
        }
        .btn {
            background: #27ae60;
            color: white;
            border: none;
            padding: 12px 24px;
            font-size: 18px;
            font-weight: bold;
            border-radius: 5px;
            cursor: pointer;
            margin-top: 20px;
        }
        .btn:hover { background: #2ecc71; }
    </style>
</head>
<body>

    <canvas id="gameCanvas"></canvas>

    <div id="uiOverlay">
        <div class="stats">
            <div>ВОЛНА: <span id="waveTxt">1</span></div>
            <div>МОНЕТЫ: <span id="coinsTxt">0</span></div>
            <div>БАШНЯ HP:</div>
            <div id="hpBarContainer"><div id="hpBar"></div></div>
        </div>
    </div>

    <div id="upgradeModal">
        <h2>ВЫБЕРИТЕ УЛУЧШЕНИЕ</h2>
        <div class="cards-container" id="cardsContainer"></div>
    </div>

    <div id="gameOverScreen">
        <h1>ВЫ ПОГИБЛИ</h1>
        <p>Вы продержались волн: <span id="finalWave">0</span></p>
        <p>Заработано монет: <span id="finalCoins">0</span></p>
        <button class="btn" onclick="restartGame()">ИГРАТЬ СНОВА</button>
    </div>

<script>
// --- АУДИОСИСТЕМА (Web Audio API) ---
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;

function initAudio() {
    if (!audioCtx) audioCtx = new AudioContext();
}

function playSound(type) {
    if (!audioCtx) return;
    
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.connect(gain);
    gain.connect(audioCtx.destination);

    const now = audioCtx.currentTime;

    if (type === 'shoot') {
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(300, now);
        osc.frequency.exponentialRampToValueAtTime(0.01, now + 0.15);
        gain.gain.setValueAtTime(0.3, now);
        gain.gain.linearRampToValueAtTime(0.01, now + 0.15);
        osc.start(now);
        osc.stop(now + 0.15);
    } else if (type === 'hit') {
        osc.type = 'square';
        osc.frequency.setValueAtTime(120, now);
        osc.frequency.exponentialRampToValueAtTime(0.01, now + 0.08);
        gain.gain.setValueAtTime(0.2, now);
        gain.gain.linearRampToValueAtTime(0.01, now + 0.08);
        osc.start(now);
        osc.stop(now + 0.08);
    } else if (type === 'hurt') {
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(80, now);
        osc.frequency.linearRampToValueAtTime(30, now + 0.2);
        gain.gain.setValueAtTime(0.4, now);
        gain.gain.linearRampToValueAtTime(0.01, now + 0.2);
        osc.start(now);
        osc.stop(now + 0.2);
    } else if (type === 'upgrade') {
        osc.type = 'sine';
        osc.frequency.setValueAtTime(260, now);
        osc.frequency.setValueAtTime(400, now + 0.1);
        gain.gain.setValueAtTime(0.3, now);
        gain.gain.linearRampToValueAtTime(0.01, now + 0.25);
        osc.start(now);
        osc.stop(now + 0.25);
    }
}

// --- НАСТРОЙКИ ХОСТА И СЦЕНЫ ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();

// --- ИГРОВЫЕ ПЕРЕМЕННЫЕ ---
let gameState = 'PLAYING'; // PLAYING, UPGRADE, GAMEOVER
let wave = 1;
let coins = 0;
let zombiesToSpawn = 0;
let spawnTimer = 0;

const tower = {
    x: 0,
    y: 0,
    radius: 40,
    maxHp: 100,
    hp: 100,
    damage: 25,
    fireRate: 300, // мс между выстрелами
    lastShot: 0,
    multishot: 1,
    pierce: 1
};

let bullets = [];
let zombies = [];
let particles = [];
let mousePos = { x: 0, y: 0 };
let isMouseDown = false;

// --- ИНИЦИАЛИЗАЦИЯ ---
function init() {
    tower.x = canvas.width / 2;
    tower.y = canvas.height / 2;
    tower.hp = tower.maxHp;
    tower.damage = 25;
    tower.fireRate = 300;
    tower.multishot = 1;
    
    wave = 1;
    coins = 0;
    bullets = [];
    zombies = [];
    particles = [];
    
    updateUI();
    startWave();
}

function startWave() {
    zombiesToSpawn = 10 + wave * 5;
    if (wave % 5 === 0) zombiesToSpawn = 1; // Волна Босса
    document.getElementById('waveTxt').innerText = wave;
}

// --- УПРАВЛЕНИЕ ---
window.addEventListener('mousemove', (e) => {
    mousePos.x = e.clientX;
    mousePos.y = e.clientY;
});

window.addEventListener('mousedown', (e) => {
    initAudio();
    isMouseDown = true;
    mousePos.x = e.clientX;
    mousePos.y = e.clientY;
});

window.addEventListener('mouseup', () => isMouseDown = false);

window.addEventListener('touchmove', (e) => {
    if (e.touches.length > 0) {
        mousePos.x = e.touches[0].clientX;
        mousePos.y = e.touches[0].clientY;
    }
});
window.addEventListener('touchstart', (e) => {
    initAudio();
    isMouseDown = true;
    if (e.touches.length > 0) {
        mousePos.x = e.touches[0].clientX;
        mousePos.y = e.touches[0].clientY;
    }
});
window.addEventListener('touchend', () => isMouseDown = false);

// --- СТРЕЛЬБА ---
function shoot() {
    const now = Date.now();
    if (now - tower.lastShot < tower.fireRate) return;
    tower.lastShot = now;

    playSound('shoot');

    const baseAngle = Math.atan2(mousePos.y - tower.y, mousePos.x - tower.x);
    const spread = 0.15;

    for (let i = 0; i < tower.multishot; i++) {
        let angle = baseAngle;
        if (tower.multishot > 1) {
            angle = baseAngle + (i - (tower.multishot - 1) / 2) * spread;
        }

        bullets.push({
            x: tower.x,
            y: tower.y,
            vx: Math.cos(angle) * 10,
            vy: Math.sin(angle) * 10,
            damage: tower.damage,
            pierce: tower.pierce
        });
    }
}

// --- СПАВН ЗОМБИ ---
function spawnZombie() {
    let angle = Math.random() * Math.PI * 2;
    let dist = Math.max(canvas.width, canvas.height) / 2 + 50;
    let x = tower.x + Math.cos(angle) * dist;
    let y = tower.y + Math.sin(angle) * dist;

    let isBoss = (wave % 5 === 0);
    
    // Типы: 0 - Обычный, 1 - Бегун, 2 - Танк
    let type = 0;
    if (!isBoss) {
        let rand = Math.random();
        if (rand > 0.7) type = 1;
        else if (rand > 0.9) type = 2;
    }

    let z = {
        x: x,
        y: y,
        type: type,
        isBoss: isBoss,
        radius: isBoss ? 35 : (type === 2 ? 22 : 14),
        speed: isBoss ? 0.8 : (type === 1 ? 2.5 : (type === 2 ? 0.9 : 1.4)),
        hp: isBoss ? (300 + wave * 150) : (type === 2 ? 80 : (type === 1 ? 20 : 35 + wave * 5)),
        maxHp: 0,
        color: isBoss ? '#8e44ad' : (type === 1 ? '#e67e22' : (type === 2 ? '#27ae60' : '#c0392b')),
        reward: isBoss ? 50 : (type === 2 ? 5 : 1)
    };
    z.maxHp = z.hp;
    zombies.push(z);
}

// --- ИГРОВОЙ ЦИКЛ ---
function update() {
    if (gameState !== 'PLAYING') return;

    if (isMouseDown) shoot();

    // Генерация волн
    spawnTimer++;
    if (zombiesToSpawn > 0 && spawnTimer > 40) {
        spawnZombie();
        zombiesToSpawn--;
        spawnTimer = 0;
    }

    // Движение пуль
    for (let i = bullets.length - 1; i >= 0; i--) {
        let b = bullets[i];
        b.x += b.vx;
        b.y += b.vy;

        if (b.x < 0 || b.x > canvas.width || b.y < 0 || b.y > canvas.height) {
            bullets.splice(i, 1);
            continue;
        }

        // Попадание в зомби
        for (let j = zombies.length - 1; j >= 0; j--) {
            let z = zombies[j];
            let dist = Math.hypot(b.x - z.x, b.y - z.y);

            if (dist < z.radius + 4) {
                z.hp -= b.damage;
                playSound('hit');

                // Частицы крови
                createParticles(b.x, b.y, z.color);

                b.pierce--;
                if (b.pierce <= 0) {
                    bullets.splice(i, 1);
                }

                if (z.hp <= 0) {
                    coins += z.reward;
                    zombies.splice(j, 1);
                    updateUI();
                }
                break;
            }
        }
    }

    // Движение зомби
    for (let i = zombies.length - 1; i >= 0; i--) {
        let z = zombies[i];
        let angle = Math.atan2(tower.y - z.y, tower.x - z.x);
        
        z.x += Math.cos(angle) * z.speed;
        z.y += Math.sin(angle) * z.speed;

        // Зомби атакует башню
        let distToTower = Math.hypot(tower.x - z.x, tower.y - z.y);
        if (distToTower < tower.radius + z.radius) {
            tower.hp -= (z.isBoss ? 2 : 0.5);
            playSound('hurt');
            createParticles(z.x, z.y, '#e74c3c');
            updateUI();

            if (!z.isBoss) {
                z.hp -= 5;
                if (z.hp <= 0) zombies.splice(i, 1);
            }

            if (tower.hp <= 0) {
                gameOver();
            }
        }
    }

    // Анимация частиц
    for (let i = particles.length - 1; i >= 0; i--) {
        let p = particles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.life -= 0.05;
        if (p.life <= 0) particles.splice(i, 1);
    }

    // Проверка окончания волны
    if (zombiesToSpawn === 0 && zombies.length === 0) {
        wave++;
        gameState = 'UPGRADE';
        showUpgradeModal();
    }
}

function createParticles(x, y, color) {
    for (let i = 0; i < 4; i++) {
        particles.push({
            x: x, y: y,
            vx: (Math.random() - 0.5) * 4,
            vy: (Math.random() - 0.5) * 4,
            life: 1.0,
            color: color
        });
    }
}

// --- ОТРИСОВКА (RENDER) ---
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Сетка на полу
    ctx.strokeStyle = '#252e25';
    ctx.lineWidth = 1;
    let gridSize = 50;
    for (let x = 0; x < canvas.width; x += gridSize) {
        ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
    }
    for (let y = 0; y < canvas.height; y += gridSize) {
        ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
    }

    // Отрисовка Башни
    ctx.fillStyle = '#7f8c8d';
    ctx.beginPath();
    ctx.arc(tower.x, tower.y, tower.radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#2c3e50';
    ctx.lineWidth = 6;
    ctx.stroke();

    // Оружие / Прицел героя
    let angle = Math.atan2(mousePos.y - tower.y, mousePos.x - tower.x);
    ctx.save();
    ctx.translate(tower.x, tower.y);
    ctx.rotate(angle);
    ctx.fillStyle = '#34495e';
    ctx.fillRect(0, -5, 25, 10);
    ctx.restore();

    // Отрисовка Пуль
    ctx.fillStyle = '#f1c40f';
    bullets.forEach(b => {
        ctx.beginPath();
        ctx.arc(b.x, b.y, 4, 0, Math.PI * 2);
        ctx.fill();
    });

    // Отрисовка Зомби
    zombies.forEach(z => {
        ctx.fillStyle = z.color;
        ctx.beginPath();
        ctx.arc(z.x, z.y, z.radius, 0, Math.PI * 2);
        ctx.fill();

        // Полоска здоровья зомби (если ранен)
        if (z.hp < z.maxHp) {
            ctx.fillStyle = 'red';
            ctx.fillRect(z.x - 15, z.y - z.radius - 8, 30, 4);
            ctx.fillStyle = 'green';
            ctx.fillRect(z.x - 15, z.y - z.radius - 8, (z.hp / z.maxHp) * 30, 4);
        }
    });

    // Отрисовка частиц
    particles.forEach(p => {
        ctx.fillStyle = p.color;
        ctx.globalAlpha = p.life;
        ctx.fillRect(p.x, p.y, 3, 3);
        ctx.globalAlpha = 1.0;
    });

    // Линия прицеливания
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(tower.x, tower.y);
    ctx.lineTo(mousePos.x, mousePos.y);
    ctx.stroke();
}

// --- UI И УЛУЧШЕНИЯ ---
function updateUI() {
    document.getElementById('coinsTxt').innerText = coins;
    let hpPercent = Math.max(0, (tower.hp / tower.maxHp) * 100);
    document.getElementById('hpBar').style.width = hpPercent + '%';
}

const allUpgrades = [
    { title: 'Урон +30%', desc: 'Увеличивает урон пуль', apply: () => tower.damage *= 1.3 },
    { title: 'Скорострельность', desc: 'Стрельба на 20% быстрее', apply: () => tower.fireRate *= 0.8 },
    { title: 'Дробовик', desc: '+1 дополнительная пуля за выстрел', apply: () => tower.multishot += 1 },
    { title: 'Ремонт Башни', desc: 'Восстанавливает 50% HP', apply: () => tower.hp = Math.min(tower.maxHp, tower.hp + tower.maxHp * 0.5) },
    { title: 'Пробивание', desc: 'Пули пробивают +1 зомби', apply: () => tower.pierce += 1 },
    { title: 'Броня Башни', desc: 'Увеличивает макс. HP на 40', apply: () => { tower.maxHp += 40; tower.hp += 40; } }
];

function showUpgradeModal() {
    playSound('upgrade');
    const container = document.getElementById('cardsContainer');
    container.innerHTML = '';

    // Перемешиваем и выбираем 3 карточки
    let shuffled = [...allUpgrades].sort(() => 0.5 - Math.random()).slice(0, 3);

    shuffled.forEach(upg => {
        let card = document.createElement('div');
        card.className = 'card';
        card.innerHTML = `<h3>${upg.title}</h3><p>${upg.desc}</p>`;
        card.onclick = () => {
            upg.apply();
            updateUI();
            document.getElementById('upgradeModal').style.display = 'none';
            gameState = 'PLAYING';
            startWave();
        };
        container.appendChild(card);
    });

    document.getElementById('upgradeModal').style.display = 'flex';
}

function gameOver() {
    gameState = 'GAMEOVER';
    document.getElementById('finalWave').innerText = wave;
    document.getElementById('finalCoins').innerText = coins;
    document.getElementById('gameOverScreen').style.display = 'flex';
}

function restartGame() {
    document.getElementById('gameOverScreen').style.display = 'none';
    gameState = 'PLAYING';
    init();
}

// --- ГЛАВНЫЙ ЦИКЛ ---
function loop() {
    update();
    draw();
    requestAnimationFrame(loop);
}

// Запуск
init();
loop();
</script>
</body>
</html>