У меня есть сайт имеющий следующую структуру:

index.html - главная страница игры
script.js - логика игры
style.css - оформление игры

Предоставляю код страниц:

index.html:

<!DOCTYPE html>
<html>

<head>
  <title>Зомби Апокалипсис</title>
  <link rel="stylesheet" href="style.css">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
</head>

<body>
  <div id="game-info">
    <p>Здоровье: <span id="player-health">100</span></p>
    <div id="health-bar">
      <div id="health-bar-inner"></div>
    </div>
    <p>Уровень: <span id="game-level">1</span></p>
    <p>Очки: <span id="game-score">0</span></p>
    <p>Патроны: <span id="ammo">30</span></p>
    <p>Оружие: <span id="weapon">Пистолет</span></p>
    <button id="start-button">Начать игру</button>
    <button id="restart-button" style="display: none;">Возродиться</button>
  </div>
  <canvas id="gameCanvas"></canvas>
  <div id="game-over-screen">
    <h2>Игра окончена!</h2>
    <p>Ваш счет: <span id="final-score"></span></p>
    <button id="play-again">Сыграть снова</button>
  </div>
  <div id="crosshair"></div>
  <audio id="shootSound" src="shoot.wav" preload="auto"></audio>
  <audio id="zombieDeathSound" src="zombie-death.wav" preload="auto"></audio>
  <audio id="grenadeExplosionSound" src="grenade-explosion.mp3" preload="auto"></audio>
  <script src="script.js"></script>
</body>

</html>

script.js:

const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
const playerHealthDisplay = document.getElementById("player-health");
const healthBarInner = document.getElementById("health-bar-inner");
const gameLevelDisplay = document.getElementById("game-level");
const gameScoreDisplay = document.getElementById("game-score");
const ammoDisplay = document.getElementById("ammo");
const weaponDisplay = document.getElementById("weapon");
const startButton = document.getElementById("start-button");
const restartButton = document.getElementById("restart-button");
const gameOverScreen = document.getElementById("game-over-screen");
const finalScoreDisplay = document.getElementById("final-score");
const playAgainButton = document.getElementById("play-again");
const shootSound = document.getElementById("shootSound");
const zombieDeathSound = document.getElementById("zombie-death.wav");
const grenadeExplosionSound = document.getElementById("grenade-explosion.mp3");

// Размеры канваса
canvas.width = 1600;
canvas.height = 1000;

// Объект игрока
const player = {
  x: 100,
  y: 100,
  width: 40,
  height: 40,
  color: "blue",
  speed: 5,
  dashSpeed: 10,
  health: 100,
  isAlive: false,
  isAttacking: false,
  isDashing: false,
  attackCooldown: 0,
  attackDuration: 20,
  attackRange: 80,
  ammo: 30,
  maxAmmo: 30,
  reloadTime: 1000
};

// Массив зомби
const zombies = [];
let gameLevel = 1;
let gameOver = false;
let score = 0;
let zombieSpawnInterval = 2000;
let lastZombieSpawnTime = Date.now();
let bloodSplatterTimer = null;
let bloodSplatterId = 0;

// Массив для пуль
const bullets = [];
const bulletSpeed = 10;

// Оружие
let weapon = 'pistol';
let hasShotgun = false;
let hasGrenade = false;

// Снаряды
let grenadeCooldown = 0;
const grenades = [];
const grenadeSpeed = 5;
const grenadeRadius = 30;

// Зоны безопасности
const safeZones = [];
const safeZoneRadius = 50;
let safeZoneSpawnInterval = 10000;
let lastSafeZoneSpawnTime = Date.now();

// Бонусы
const bonuses = [];
const bonusTypes = ['ammo', 'health', 'weapon'];
const bonusSpawnInterval = 15000;
let lastBonusSpawnTime = Date.now();

// Музыка
const backgroundMusic = new Audio('background.mp3');
backgroundMusic.loop = true;

// Карта
const map = {
  width: 3200,
  height: 2000
};

map.blocks = [
  { x: 0, y: 0, width: map.width, height: 50 }, // Верхняя
  { x: 0, y: map.height - 50, width: map.width, height: 50 }, // Нижняя
  { x: 0, y: 0, width: 50, height: map.height }, // Левая
  { x: map.width - 50, y: 0, width: 50, height: map.height }, // Правая
  // Препятствия на карте
  { x: 200, y: 200, width: 100, height: 500 },
  { x: 800, y: 400, width: 400, height: 100 },
  { x: 1400, y: 700, width: 200, height: 300 },
  { x: 2500, y: 1000, width: 500, height: 100 },
  { x: 1800, y: 200, width: 100, height: 600 }
];

// Флаг для проверки, началась ли игра
let gameStarted = false;

// Создание нового зомби
function createZombie() {
  const spawnPoint = getRandomSpawnPoint();
  const zombieType = Math.random() < 0.8 ? 'regular' : 'fast';

  return {
    x: spawnPoint.x,
    y: spawnPoint.y,
    width: 30,
    height: 30,
    color: zombieType === 'fast' ? 'yellow' : 'green',
    speed: zombieType === 'fast' ? 4 + gameLevel : 2 + gameLevel * 0.5,
    health: 50 + gameLevel * 10,
    isAlive: true,
    type: zombieType
  };
}

// Генерация случайной точки появления зомби
function getRandomSpawnPoint() {
  const padding = 100;
  const side = Math.floor(Math.random() * 4);
  switch (side) {
    case 0:
      return { x: Math.random() * canvas.width, y: -padding };
    case 1:
      return { x: canvas.width + padding, y: Math.random() * canvas.height };
    case 2:
      return { x: Math.random() * canvas.width, y: canvas.height + padding };
    case 3:
      return { x: -padding, y: Math.random() * canvas.height };
  }
}

// Объект для хранения спрайтов зомби
const zombieSprites = {};

// Функция для загрузки спрайта
function loadSprite(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

// Загрузка спрайтов зомби при загрузке страницы
window.addEventListener('load', async () => {
  zombieSprites.regular = await loadSprite('zombie.png'); // Замените на путь к изображению обычного зомби
  zombieSprites.fast = await loadSprite('fastZombie.png'); // Замените на путь к изображению быстрого зомби
});

// Рисование зомби
function drawZombies() {
  zombies.forEach(zombie => {
    if (zombie.isAlive) {
      // Выбираем спрайт в зависимости от типа зомби
      let sprite = zombieSprites[zombie.type] || zombieSprites.regular;

      // Вычисляем кадр анимации
      const frameCount = 4; // Количество кадров в анимации
      const frameWidth = sprite.width / frameCount;
      const frameHeight = sprite.height;
      const frameIndex = Math.floor((Date.now() / 200) % frameCount); // Меняем кадр каждые 200 мс

      // Рисуем спрайт зомби
      ctx.drawImage(
        sprite,
        frameIndex * frameWidth,
        0,
        frameWidth,
        frameHeight,
        zombie.x,
        zombie.y,
        zombie.width,
        zombie.height
      );

      // Рисуем полосу здоровья зомби
      drawZombieHealthBar(zombie);
    }
  });
}

// Функция для рисования полосы здоровья зомби
function drawZombieHealthBar(zombie) {
  const healthPercent = zombie.health / (50 + gameLevel * 10);
  ctx.fillStyle = healthPercent > 0.6 ? 'green' : healthPercent > 0.3 ? 'yellow' : 'red';
  ctx.fillRect(zombie.x, zombie.y - 10, zombie.width * healthPercent, 5);
}

// Движение зомби
function moveZombies() {
  zombies.forEach(zombie => {
    if (zombie.isAlive) {
      const dx = player.x - zombie.x;
      const dy = player.y - zombie.y;
      const distance = Math.sqrt(dx * dx + dy * dy);
      const speed = Math.min(zombie.speed, distance);
      zombie.x += dx / distance * speed;
      zombie.y += dy / distance * speed;
    }
  });
}

// Проверка столкновений игрока и зомби
function checkPlayerZombieCollisions() {
  zombies.forEach((zombie, index) => {
    if (zombie.isAlive) {
      if (
        player.x < zombie.x + zombie.width &&
        player.x + player.width > zombie.x &&
        player.y < zombie.y + zombie.height &&
        player.y + player.height > zombie.y
      ) {
        player.health -= 10;
        updateHealthBar();
        if (player.health <= 0 && player.isAlive) {
          player.isAlive = false;
          gameOver = true;
          endGame();
        }
      }
    }
  });
}

// Обработка нажатия клавиш
const keys = {};
document.addEventListener("keydown", function (e) {
  keys[e.code] = true;
  if (player.isAlive) {
    if (e.code === "Space" && player.attackCooldown <= 0 && player.ammo > 0) {
      player.isAttacking = true;
      player.attackCooldown = player.attackDuration;
      shootBullet();
      player.ammo--;
      updateAmmoDisplay();
      setTimeout(() => {
        player.isAttacking = false;
      }, player.attackDuration);
    }
    if (e.code === "ShiftLeft") {
      player.isDashing = true;
    }
    if (e.code === "KeyR" && player.ammo < player.maxAmmo && !player.isReloading) {
      reload();
    }
    if (e.code === "KeyG") {
      throwGrenade();
    }
  }
});

document.addEventListener("keyup", function (e) {
  keys[e.code] = false;
  if (e.code === "ShiftLeft") {
    player.isDashing = false;
  }
});

// Рисование игрока
function drawPlayer() {
  if (player.isAlive) {
    // Рисуем игрока
    ctx.fillStyle = player.color;
    ctx.fillRect(player.x, player.y, player.width, player.height);

    // Анимация атаки
    if (player.isAttacking) {
      ctx.fillStyle = "rgba(255, 255, 0, 0.5)";
      ctx.fillRect(player.x + player.width, player.y + player.height / 2 - 5, 10, 10);
    }

    // Отображение оружия
    let weaponImage;
    switch (weapon) {
      case 'pistol':
        weaponImage = new Image();
        weaponImage.src = 'pistol.png'; // Замените на путь к вашему изображению пистолета
        break;
      case 'Дробовик':
        weaponImage = new Image();
        weaponImage.src = 'shotgun.png'; // Замените на путь к вашему изображению дробовика
        break;
    }

    if (weaponImage) {
      // Рисуем изображение оружия
      ctx.drawImage(weaponImage, player.x + player.width / 2 - weaponImage.width / 2, player.y + player.height / 2 - weaponImage.height / 2);
    }
  }
}

// Обновление полосы здоровья
function updateHealthBar() {
  const healthPercent = Math.max(0, player.health / 100);
  healthBarInner.style.width = `${healthPercent * 100}%`;
}

function updateAmmoDisplay() {
  ammoDisplay.textContent = player.ammo;
}

function updateWeaponDisplay() {
  weaponDisplay.textContent = weapon;
}

// Функция для выстрела пули
function shootBullet() {
  const bullet = {
    x: player.x + player.width / 2,
    y: player.y + player.height / 2,
    dx: mouseX - (player.x + player.width / 2),
    dy: mouseY - (player.y + player.height / 2)
  };

  // Нормализация вектора направления пули
  const length = Math.sqrt(bullet.dx * bullet.dx + bullet.dy * bullet.dy);
  bullet.dx /= length;
  bullet.dy /= length;

  bullets.push(bullet);
  playShootSound();
}

function playShootSound() {
  shootSound.currentTime = 0;
  shootSound.play();
}

// Функция для рисования пуль
function drawBullets() {
  ctx.fillStyle = "yellow";
  bullets.forEach(bullet => {
    ctx.beginPath();
    ctx.arc(bullet.x, bullet.y, 3, 0, Math.PI * 2);
    ctx.fill();
  });
}

// Функция для обновления позиции пуль и проверки попаданий
function updateBullets() {
  bullets.forEach((bullet, index) => {
    bullet.x += bullet.dx * bulletSpeed;
    bullet.y += bullet.dy * bulletSpeed;

    // Проверка попадания в зомби
    zombies.forEach((zombie, zombieIndex) => {
      if (zombie.isAlive && checkCollision(bullet, zombie)) {
        bullets.splice(index, 1);
        zombie.isAlive = false;
        score += 10;
        playZombieDeathSound();

        // Создание брызг крови
        createBloodSplatter(zombie.x + zombie.width / 2, zombie.y + zombie.height / 2);

        if (score % 50 === 0) {
          gameLevel++;
          zombieSpawnInterval = Math.max(500, zombieSpawnInterval - 100);
        }
        return;
      }
    });

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

function playZombieDeathSound() {
  zombieDeathSound.currentTime = 0;
  zombieDeathSound.volume = 0.2;
  zombieDeathSound.play();
}

function createBloodSplatter(x, y) {
  const bloodSplatter = document.createElement('div');
  bloodSplatter.classList.add('blood-splatter');
  bloodSplatter.id = 'blood-splatter-' + bloodSplatterId++;
  bloodSplatter.style.left = (x - 15) + 'px';
  bloodSplatter.style.top = (y - 15) + 'px';
  document.body.appendChild(bloodSplatter);

  setTimeout(() => {
    document.body.removeChild(bloodSplatter);
  }, 1000);
}

// Рисование карты
function drawMap() {
  ctx.fillStyle = 'brown';
  map.blocks.forEach(block => {
    ctx.fillRect(block.x, block.y, block.width, block.height);
  });
}

// Проверка столкновений с блоками карты
function checkBlockCollisions() {
  if (player.isAlive) {
    map.blocks.forEach(block => {
      if (
        player.x < block.x + block.width &&
        player.x + player.width > block.x &&
        player.y < block.y + block.height &&
        player.y + player.height > block.y
      ) {
        // Отталкиваем игрока от блока
        if (keys["ArrowUp"] && player.y + player.height > block.y) {
          player.y = block.y - player.height;
        }
        if (keys["ArrowDown"] && player.y < block.y + block.height) {
          player.y = block.y + block.height;
        }
        if (keys["ArrowLeft"] && player.x + player.width > block.x) {
          player.x = block.x - player.width;
        }
        if (keys["ArrowRight"] && player.x < block.x + block.width) {
          player.x = block.x + block.width;
        }
      }
    });
  }
}

// Основной цикл игры
function gameLoop() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  if (!backgroundMusic.playing) {
    backgroundMusic.play();
  }

  if (player.isAlive) {
    // Движение игрока
    if (keys["ArrowUp"]) {
      player.y -= player.isDashing ? player.dashSpeed : player.speed;
    }
    if (keys["ArrowDown"]) {
      player.y += player.isDashing ? player.dashSpeed : player.speed;
    }
    if (keys["ArrowLeft"]) {
      player.x -= player.isDashing ? player.dashSpeed : player.speed;
    }
    if (keys["ArrowRight"]) {
      player.x += player.isDashing ? player.dashSpeed : player.speed;
    }

    // Проверка столкновений игрока с зомби
    checkPlayerZombieCollisions();

    // Проверка атаки на зомби
    checkAttack();
  }

  // Рисование игрока
  drawPlayer();

  // Рисование зомби
  drawZombies();

  // Рисование карты
  drawMap();

  // Проверка столкновений с блоками карты
  checkBlockCollisions();

  // Движение зомби
  if (player.isAlive) {
    moveZombies();
  }

  // Генерация новых зомби
  if (player.isAlive && Date.now() - lastZombieSpawnTime > zombieSpawnInterval) {
    zombies.push(createZombie());
    lastZombieSpawnTime = Date.now();
  }

  // Рисование и обновление пуль
  drawBullets();
  updateBullets();

  // Обновление информации
  playerHealthDisplay.textContent = player.health;
  gameLevelDisplay.textContent = gameLevel;
  gameScoreDisplay.textContent = score;
  updateAmmoDisplay();
  updateWeaponDisplay();

  // Обновление времени восстановления атаки
  if (player.attackCooldown > 0) {
    player.attackCooldown -= 16;
  }

  // Обновление времени перезарядки гранат
  if (grenadeCooldown > 0) {
    grenadeCooldown -= 16;
  }

  // Генерация зон безопасности
  if (Date.now() - lastSafeZoneSpawnTime > safeZoneSpawnInterval) {
    safeZones.push(createSafeZone());
    lastSafeZoneSpawnTime = Date.now();
  }

  // Генерация бонусов
  if (Date.now() - lastBonusSpawnTime > bonusSpawnInterval) {
    bonuses.push(createBonus());
    lastBonusSpawnTime = Date.now();
  }

  // Рисование зон безопасности
  drawSafeZones();

  // Рисование бонусов
  drawBonuses();

  // Проверка столкновений с зонами безопасности
  checkSafeZoneCollision();

  // Проверка столкновений с бонусами
  checkBonusCollision();

  // Проверка захвата оружия
  checkWeaponPickup();

  // Движение гранат
  updateGrenades();

  // Рисование гранат
  drawGrenades();

  requestAnimationFrame(gameLoop);
}

// Функция для проверки столкновений
function checkCollision(rect1, rect2) {
  return (
    rect1.x < rect2.x + rect2.width &&
    rect1.x + 3 > rect2.x &&
    rect1.y < rect2.y + rect2.height &&
    rect1.y + 3 > rect2.y
  );
}

// Проверка атаки на зомби
function checkAttack() {
  if (player.isAttacking) {
    zombies.forEach((zombie, index) => {
      if (zombie.isAlive) {
        const dx = zombie.x + zombie.width / 2 - (player.x + player.width / 2);
        const dy = zombie.y + zombie.height / 2 - (player.y + player.height / 2);
        const distance = Math.sqrt(dx * dx + dy * dy);

        if (distance < player.attackRange) {
          zombie.isAlive = false;
          score += 10;

          if (score % 50 === 0) {
            gameLevel++;
            zombieSpawnInterval = Math.max(500, zombieSpawnInterval - 100);
          }
        }
      }
    });
  }
}

// Функция для перезарядки
function reload() {
  if (!player.isReloading && player.ammo < player.maxAmmo) {
    player.isReloading = true;
    setTimeout(() => {
      player.ammo = player.maxAmmo;
      player.isReloading = false;
      updateAmmoDisplay();
    }, player.reloadTime);
  }
}

let mouseX = 0;
let mouseY = 0;
const crosshair = document.getElementById('crosshair');

canvas.addEventListener('mousemove', (event) => {
  mouseX = event.offsetX;
  mouseY = event.offsetY;

  // Обновление положения прицела
  crosshair.style.left = (mouseX - 10) + 'px';
  crosshair.style.top = (mouseY - 10) + 'px';
});

// Начало игры
startButton.addEventListener("click", () => {
  gameStarted = true;
  startGame();
});

// Возрождение
restartButton.addEventListener("click", () => {
  gameStarted = true;
  startGame();
});

function startGame() {
  player.isAlive = true;
  player.health = 100;
  player.x = 100;
  player.y = 100;
  player.ammo = player.maxAmmo;
  zombies.length = 0;
  bullets.length = 0;
  grenades.length = 0;
  gameLevel = 1;
  gameOver = false;
  score = 0;
  zombieSpawnInterval = 2000;
  lastZombieSpawnTime = Date.now();
  safeZones.length = 0;
  bonuses.length = 0;
  hasShotgun = false;
  hasGrenade = false;
  weapon = 'pistol';
  grenadeCooldown = 0;
  lastSafeZoneSpawnTime = Date.now();
  lastBonusSpawnTime = Date.now();
  startButton.style.display = "none";
  restartButton.style.display = "none";
  gameOverScreen.style.display = "none";
  updateHealthBar();
  updateAmmoDisplay();
  updateWeaponDisplay();
  // Установка начальной позиции игрока на карте
  player.x = 150;
  player.y = 150;
  gameLoop();
}

// Конец игры
function endGame() {
  gameOver = true;
  gameOverScreen.style.display = "block";
  finalScoreDisplay.textContent = score;
  restartButton.style.display = "block";
}

// Перезапуск игры
playAgainButton.addEventListener("click", startGame);

// Функция для создания новой зоны безопасности
function createSafeZone() {
  const x = Math.random() * canvas.width;
  const y = Math.random() * canvas.height;
  return { x, y };
}

// Функция для создания нового бонуса
function createBonus() {
  const x = Math.random() * canvas.width;
  const y = Math.random() * canvas.height;
  const type = bonusTypes[Math.floor(Math.random() * bonusTypes.length)];
  return { x, y, type };
}

// Функция для рисования зон безопасности
function drawSafeZones() {
  ctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
  safeZones.forEach(zone => {
    ctx.beginPath();
    ctx.arc(zone.x, zone.y, safeZoneRadius, 0, 2 * Math.PI);
    ctx.fill();
  });
}

// Функция для рисования бонусов
function drawBonuses() {
  bonuses.forEach(bonus => {
    switch (bonus.type) {
      case 'ammo':
        ctx.fillStyle = 'yellow';
        break;
      case 'health':
        ctx.fillStyle = 'green';
        break;
      case 'weapon':
        ctx.fillStyle = 'red';
        break;
    }
    ctx.fillRect(bonus.x, bonus.y, 10, 10);
  });
}

// Функция для проверки столкновений с зонами безопасности
function checkSafeZoneCollision() {
  safeZones.forEach((zone, index) => {
    if (
      player.x < zone.x + safeZoneRadius &&
      player.x + player.width > zone.x - safeZoneRadius &&
      player.y < zone.y + safeZoneRadius &&
      player.y + player.height > zone.y - safeZoneRadius
    ) {
      player.health = Math.min(player.health + 20, 100);
      updateHealthBar();
      safeZones.splice(index, 1);
    }
  });
}

// Функция для проверки столкновений с бонусами
function checkBonusCollision() {
  bonuses.forEach((bonus, index) => {
    if (
      player.x < bonus.x + 10 &&
      player.x + player.width > bonus.x &&
      player.y < bonus.y + 10 &&
      player.y + player.height > bonus.y
    ) {
      switch (bonus.type) {
        case 'ammo':
          player.ammo = Math.min(player.ammo + 10, player.maxAmmo);
          updateAmmoDisplay();
          break;
        case 'health':
          player.health = Math.min(player.health + 20, 100);
          updateHealthBar();
          break;
        case 'weapon':
          handleWeaponPickup(bonus.type);
          break;
      }
      bonuses.splice(index, 1);
    }
  });
}

// Функция для обработки захвата оружия
function handleWeaponPickup(weaponType) {
  switch (weaponType) {
    case 'weapon':
      hasShotgun = true;
      weapon = 'Дробовик';
      break;
  }
  updateWeaponDisplay();
}

// Функция для проверки захвата оружия
function checkWeaponPickup() {
  bonuses.forEach((bonus, index) => {
    if (bonus.type === 'weapon' &&
      player.x < bonus.x + 10 &&
      player.x + player.width > bonus.x &&
      player.y < bonus.y + 10 &&
      player.y + player.height > bonus.y
    ) {
      handleWeaponPickup(bonus.type);
      bonuses.splice(index, 1);
    }
  });
}

// Функция для выстрела гранатой
function throwGrenade() {
  if (grenadeCooldown <= 0 && hasGrenade) {
    grenadeCooldown = 1000; // Время перезарядки в миллисекундах

    const grenade = {
      x: player.x + player.width / 2,
      y: player.y + player.height / 2,
      dx: mouseX - (player.x + player.width / 2),
      dy: mouseY - (player.y + player.height / 2)
    };

    // Нормализация вектора направления гранаты
    const length = Math.sqrt(grenade.dx * grenade.dx + grenade.dy * grenade.dy);
    grenade.dx /= length;
    grenade.dy /= length;

    grenades.push(grenade);
  }
}

// Функция для обновления позиции гранат
function updateGrenades() {
  grenades.forEach((grenade, index) => {
    grenade.x += grenade.dx * grenadeSpeed;
    grenade.y += grenade.dy * grenadeSpeed;

    // Проверка взрыва гранаты
    if (checkGrenadeExplosion(grenade)) {
      grenades.splice(index, 1);
    }
  });
}

// Функция для проверки взрыва гранаты
function checkGrenadeExplosion(grenade) {
  zombies.forEach((zombie, zombieIndex) => {
    if (
      zombie.isAlive &&
      Math.sqrt(
        Math.pow(zombie.x - grenade.x, 2) + Math.pow(zombie.y - grenade.y, 2)
      ) <= grenadeRadius
    ) {
      zombie.isAlive = false;
      score += 20;
      playZombieDeathSound();

      // Создание брызг крови
      createBloodSplatter(zombie.x + zombie.width / 2, zombie.y + zombie.height / 2);

      if (score % 50 === 0) {
        gameLevel++;
        zombieSpawnInterval = Math.max(500, zombieSpawnInterval - 100);
      }
      return true; // Граната взорвалась
    }
  });

  return false; // Граната еще не взорвалась
}

// Функция для рисования гранат
function drawGrenades() {
  ctx.fillStyle = 'red';
  grenades.forEach(grenade => {
    ctx.beginPath();
    ctx.arc(grenade.x, grenade.y, 5, 0, 2 * Math.PI);
    ctx.fill();
  });
}

// Функция для воспроизведения звука взрыва гранаты
function playGrenadeExplosionSound() {
  grenadeExplosionSound.currentTime = 0;
  grenadeExplosionSound.play();
}

// ... (Все остальные функции)

// Добавление обработчика для звука взрыва гранаты
function checkGrenadeExplosion(grenade) {
  // ... (Предыдущий код)

  if (zombie.isAlive &&
    Math.sqrt(
      Math.pow(zombie.x - grenade.x, 2) + Math.pow(zombie.y - grenade.y, 2)
    ) <= grenadeRadius
  ) {
    // ... (Предыдущий код)
    playGrenadeExplosionSound(); // Проигрываем звук взрыва
    return true; // Граната взорвалась
  }
  // ... (Предыдущий код)
}

style.css

body {
  margin: 0;
  overflow: hidden;
  font-family: 'Press Start 2P', cursive;
  background-color: #222;
}

canvas {
  background-color: #333;
}

#game-info {
  position: absolute;
  top: 10px;
  left: 10px;
  color: white;
}

button {
  padding: 10px 20px;
  background-color: #4CAF50;
  color: white;
  border: none;
  cursor: pointer;
  font-family: 'Press Start 2P', cursive;
}

#game-over-screen {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 20px;
  border-radius: 5px;
  display: none;
  text-align: center;
}

#health-bar {
  width: 200px;
  height: 20px;
  background-color: #f00;
  border: 2px solid black;
  margin-bottom: 10px;
  overflow: hidden;
}

#health-bar-inner {
  height: 100%;
  background-color: #0f0;
  width: 100%;
}

.blood-splatter {
  position: absolute;
  width: 30px;
  height: 30px;
  background-image: url('blood.png');
  /* Замените на путь к вашей картинке брызг крови */
  background-size: cover;
  opacity: 0;
  animation: fadeOut 1s forwards;
}

@keyframes fadeOut {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

#crosshair {
  position: absolute;
  width: 20px;
  height: 20px;
  background-color: transparent;
  border: 2px solid white;
  border-radius: 50%;
  pointer-events: none; /* Не реагирует на клики */
}


Это полный код (без картинок и звуков) структуры моей браузерной игры. Тебе нужно решить следующую проблему: Когда пуля достигает спрайта зомби, игра намертво зависает. Проблема появилась после того, как один целостный код был разделён на три страницы, структура которых указана выше.