Геймдев будущего? Использование нейросетей в разработке игр. Часть 1

от автора

Ни для кого уже не секрет, что ​нейросети сегодня используются при разработке игр: создание концептов, арта, написание историй. А что насчет кодинга? Могут ли нейросети кодить вместо разработчиков и создавать полноценные игры? За ответом на этот вопрос я обратился к одной из лучших по мнению многих обзорщиков нейросетей для разработки – Qwen. Кстати, эта нейросеть бесплатна и без проблем работает в РФ.

Давайте начнем с простого. База для любого разработчика игр – аналог Pong! Но с небольшим усложнением. Мне захотелось, чтобы вместо шарика в игре летала снежинка. Я вбил самый простой промпт:

Напиши игру типа Pong! Вместо шарика сделай снежинку

В результате в течение 1 минуты нейросеть написала 475 строк кода и выдала мне саммари:

Это полноценная HTML-игра в стиле Pong с снежинкой! Основные особенности:

  • Вместо шарика используется анимированная снежинка с вращением

  • Стильный темный интерфейс с градиентами и свечением

  • Анимированный снег в фоне

  • Реалистичная физика отскоков

  • Подсчет очков до 5

  • Эффекты при отскоке (вращение снежинки)

  • Адаптивный дизайн

⌨️ Управление:

  • Игрок 1: W и S

  • Игрок 2: Стрелки вверх и вниз

🎉 Особенности дизайна:

  • Градиентный фон с зимней тематикой

  • Анимированные снежинки в фоне

  • Свечение элементов

  • Плавные переходы и анимации

  • Экран окончания игры с кнопкой перезапуска

Игра полностью автономна и работает в любом современном браузере!

Так выглядела самая первая версия игры

Так выглядела самая первая версия игры

То есть, Qwen в действительности сделала даже больше, чем я просил. Нейронка добавила в игру фон со стильными снежинками и добавила в игру полноценный PVP режим. Хотя, режима PVE в игре не было. Справедливости ради, в этом моя вина – я подразумевал его наличие у себя в голове, но не написал об этом в промпте. Что ж, давайте исправим:

Я: Можешь добавить в игру PVE режим?

Qwen: Конечно! Вот обновлённая версия игры Snow Pong с режимом PVE (игрок против компьютера). Я добавил:

  • Кнопку выбора режима: PVP или PVE

  • В режиме PVE правая ракетка управляется ИИ

  • ИИ отслеживает положение снежинки с небольшой задержкой для реалистичности

  • Сохраняется вся графика и анимации

Далее ещё минута, ещё 544 строчки кода и перевод мной новая версия игры: теперь я могу выбрать режим игры: PVP или PVE. Выбираем игру против компьютера и… понимаем, что это слишком легко. Qwen пожалел нас и сделал ракетку ИИ-противника очень медленной. С такой скоростью он отражал только один удар из пяти. Хорошо, что вместе с этим он предложил:

Qwen: Хочешь добавить уровни сложности (лёгкий/средний/сложный ИИ)?

Я: Да, давай добавим уровни сложности

Новое минутное ожидание и еще 536 строчек кода и передо мной новая игра с уровнями сложности. И вместе с этим, новое предложение от ИИ:

Qwen: Хочешь добавить звуки или счётчик очков за сессию? 😊
Я: Да, давай добавим звуки и счетчик очков за сессию, а так же переведем управление игрока в PVE режиме на стрелочки

Управление на W/S не всегда было удобно, так как эти символы не работали в верхнем регистре или в русской раскладке. Нейросеть в переписке со мной предлагала исправить эти проблемы, но я решил не зацикливаться на этом и просто перевести управление на стрелочки.

Итак, еще пару минут (по мере усложнения кода увеличивается и время ожидания) и 614 строчек кода спустя нас ждет новая версия игры. Новая версия оказалась проблемной – игра крашилась сразу же после запуска. После пары сообщений Qwen понял в чем была причина краша и исправил его. Но появилась новая проблема – теперь в PVE режиме ракетка игрока (на стрелочках) не двигалась. После недолгой переписки с ИИ мы решили проблему, на это ушло около пяти минут. Очень понравилась его решительность:

Я: Слушай, стрелочки все еще не работают Qwen: Спасибо, что держишь меня в курсе! Давай раз и навсегда решим проблему с управлением стрелочками

Раз и навсегда решить проблему со стрелочками не удалось, поэтому я предложил альтернативу – управление второго игрока на клавишах O и L. Таким образом в PVP режиме игрок 1 управлял на WS, а игрок 2 на OL, а в PVE режиме игрок управлял на WS.

До идеальной игры осталась последняя деталь: боты в PVE режиме были слишком слабы – даже сильный бот не создавал никаких проблем. Я попросил его повысить уровень сложности и увеличить скорость передвижения ракетки у ботов – и все сработало. Теперь чтобы победить сложного бота надо постараться. Игра полностью готова и любой желающий может в неё поиграть:

https://chat.qwen.ai/s/deploy/386f16fb-096d-4fe2-b706-a8c72374825c

Не знаю, как долго она будет храниться на серверах, поэтому не могу ручаться за работоспособность ссылки для тех, кто увидит статью сильно позже её выхода.

Я добавил меню игры

Я добавил меню игры
И окно победы/поражения

И окно победы/поражения
А так стал выглядеть сам геймплей

А так стал выглядеть сам геймплей

Что я получил в итоге?

Полноценный симулятор Pong! со снежинкой вместо мячика. На фоне падал снег, добавляя игре атмосферы. В игре присутствовал счетчик очков. Удары ракетки по мячику и победа озвучивались. В игре было два режима: PVP и PVE и три уровня сложности для PVE режима. Режим и сложность выбирались в главном меню игры. Нейросеть отлично запоминает что мы с ней делали и по моей просьбе легко достает нужную мне версию игры. Итоговый код игры составил 701 строчку. Его я приложу ниже. Отмечу также, что мне не пришлось применить никаких знаний программирования для создания этой игры. Все что я делал – это ставил задачи нейросети.

Что я потратил?

Примерно полтора часа времени на диалог с ИИ с учетом времени ожидания ответа. Нейросеть бесплатна и ничего от меня не потребовала.

Получается, что нейросети уже сейчас могут успешно генерировать простые игры типа Pong!, даже с небольшими усложнениями. Но, что будет, если попросить их создать что-то более сложное? В следующий раз мы с Qwen попробуем создать что-то посложнее понга. Например, платформер. И я обязательно напишу об этом.

<!DOCTYPE html> <html lang="ru"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>Snow Pong</title>     <style>         * {             margin: 0;             padding: 0;             box-sizing: border-box;             font-family: 'Arial', sans-serif;         }                  body {             background: linear-gradient(135deg, #1e3c72, #2a5298);             display: flex;             justify-content: center;             align-items: center;             min-height: 100vh;             overflow: hidden;         }                  .game-container {             position: relative;             width: 800px;             height: 600px;             background: rgba(10, 20, 40, 0.8);             border-radius: 20px;             box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);             overflow: hidden;             border: 2px solid rgba(255, 255, 255, 0.2);         }                  .game-title {             text-align: center;             color: white;             padding: 20px;             font-size: 28px;             font-weight: bold;             text-shadow: 0 0 10px rgba(135, 206, 250, 0.8);         }                  .score-board {             display: flex;             justify-content: space-around;             background: rgba(0, 0, 0, 0.3);             padding: 15px;             color: white;             font-size: 24px;             font-weight: bold;         }                  .score {             text-shadow: 0 0 5px rgba(135, 206, 250, 0.8);         }                  .session-score {             font-size: 16px;             color: rgba(255, 255, 255, 0.7);             margin-top: 5px;             text-align: center;         }                  .paddle {             position: absolute;             width: 15px;             height: 100px;             background: linear-gradient(to right, #4facfe, #00f2fe);             border-radius: 10px;             box-shadow: 0 0 15px rgba(79, 172, 254, 0.6);         }                  .left-paddle {             left: 30px;         }                  .right-paddle {             right: 30px;         }                  /* --- ДВОЙНОЙ КРЕСТ — СНЕЖИНКА (настоящая!) --- */         .snowflake {             position: absolute;             width: 6px;             height: 6px;             background: white;             border-radius: 50%;             transform: translate(-50%, -50%);             box-shadow:                  /* Горизонтальные лучи (← и →) */                 -15px 0 0 rgba(255, 255, 255, 0.9),                 15px 0 0 rgba(255, 255, 255, 0.9),                                  /* Вертикальные лучи (↑ и ↓) */                 0 -15px 0 rgba(255, 255, 255, 0.9),                 0 15px 0 rgba(255, 255, 255, 0.9),                                  /* Диагональ ↖ и ↘ */                 -11px -11px 0 rgba(255, 255, 255, 0.8),                 11px 11px 0 rgba(255, 255, 255, 0.8),                                  /* Диагональ ↗ и ↙ */                 11px -11px 0 rgba(255, 255, 255, 0.8),                 -11px 11px 0 rgba(255, 255, 255, 0.8);             animation: rotate 8s linear infinite;             filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));         }          @keyframes rotate {             0% { transform: translate(-50%, -50%) rotate(0deg); }             100% { transform: translate(-50%, -50%) rotate(360deg); }         }                  .center-line {             position: absolute;             left: 50%;             top: 0;             bottom: 0;             width: 2px;             background: rgba(255, 255, 255, 0.2);             transform: translateX(-50%);         }                  .center-line::before {             content: '';             position: absolute;             top: 50%;             left: 50%;             width: 10px;             height: 10px;             background: rgba(255, 255, 255, 0.2);             border-radius: 50%;             transform: translate(-50%, -50%);         }                  .controls {             text-align: center;             color: rgba(255, 255, 255, 0.7);             margin-top: 10px;             font-size: 14px;         }                  .game-over {             position: absolute;             top: 0;             left: 0;             width: 100%;             height: 100%;             background: rgba(0, 0, 0, 0.8);             display: flex;             flex-direction: column;             justify-content: center;             align-items: center;             z-index: 10;             opacity: 0;             pointer-events: none;             transition: opacity 0.5s;         }                  .game-over.active {             opacity: 1;             pointer-events: all;         }                  .game-over h2 {             color: white;             font-size: 48px;             margin-bottom: 20px;             text-shadow: 0 0 20px rgba(135, 206, 250, 0.8);         }                  .game-over p {             color: #4facfe;             font-size: 24px;             margin-bottom: 30px;         }                  .restart-btn {             background: linear-gradient(45deg, #4facfe, #00f2fe);             color: white;             border: none;             padding: 15px 30px;             font-size: 18px;             border-radius: 25px;             cursor: pointer;             box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);             transition: all 0.3s;         }                  .restart-btn:hover {             transform: translateY(-2px);             box-shadow: 0 8px 20px rgba(79, 172, 254, 0.6);         }                  .sound-toggle {             position: absolute;             top: 10px;             right: 10px;             background: transparent;             border: none;             color: rgba(255, 255, 255, 0.7);             font-size: 20px;             cursor: pointer;             z-index: 5;         }                  .snow-effect {             position: absolute;             top: 0;             left: 0;             width: 100%;             height: 100%;             pointer-events: none;             z-index: 1;         }                  .snow {             position: absolute;             background: white;             border-radius: 50%;             opacity: 0.8;             pointer-events: none;         }          .start-screen {             position: absolute;             top: 0;             left: 0;             width: 100%;             height: 100%;             background: rgba(0, 0, 0, 0.9);             display: flex;             flex-direction: column;             justify-content: center;             align-items: center;             z-index: 20;             color: white;             text-align: center;             padding: 20px;         }          .start-screen h2 {             font-size: 36px;             margin-bottom: 20px;             text-shadow: 0 0 10px rgba(135, 206, 250, 0.8);         }          .start-option {             margin: 15px 0;             font-size: 18px;             color: rgba(255, 255, 255, 0.9);         }          .mode-btn, .difficulty-btn {             display: inline-block;             padding: 8px 16px;             margin: 0 10px;             border: 2px solid rgba(255, 255, 255, 0.3);             background: transparent;             color: white;             border-radius: 20px;             cursor: pointer;             font-size: 14px;             transition: all 0.3s;         }                  .mode-btn.active, .difficulty-btn.active {             background: rgba(79, 172, 254, 0.4);             border-color: #4facfe;             box-shadow: 0 0 10px rgba(79, 172, 254, 0.5);         }                  .mode-btn:hover, .difficulty-btn:hover {             background: rgba(255, 255, 255, 0.1);         }          .start-btn {             margin-top: 30px;             padding: 15px 40px;             background: linear-gradient(45deg, #4facfe, #00f2fe);             color: white;             border: none;             border-radius: 30px;             font-size: 18px;             cursor: pointer;             box-shadow: 0 5px 15px rgba(79, 172, 254, 0.4);             transition: all 0.3s;         }          .start-btn:hover {             transform: translateY(-2px);             box-shadow: 0 8px 20px rgba(79, 172, 254, 0.6);         }          .focus-hint {             margin-top: 20px;             padding: 10px 20px;             background: rgba(255, 255, 255, 0.1);             border-radius: 10px;             color: #4facfe;             font-size: 16px;             cursor: pointer;             border: 1px solid rgba(255, 255, 255, 0.3);         }          .debug-info {             position: absolute;             top: 10px;             left: 10px;             color: #00ff00;             font-size: 12px;             z-index: 10;             background: rgba(0, 0, 0, 0.7);             padding: 5px;             border-radius: 5px;         }     </style> </head> <body>     <div class="game-container">         <div class="game-title">❄️ Snow Pong ❄️</div>                  <button class="sound-toggle" id="sound-btn">🔊</button>                  <div class="score-board">             <div class="score" id="left-score">0</div>             <div class="score" id="right-score">0</div>         </div>                  <div class="session-score">             Всего голов за сессию: <span id="total-goals">0</span>         </div>                  <div class="center-line"></div>         <div class="paddle left-paddle" id="left-paddle"></div>         <div class="paddle right-paddle" id="right-paddle"></div>         <div class="snowflake" id="snowflake"></div>         <div class="snow-effect" id="snow-effect"></div>                  <div class="game-over" id="game-over">             <h2>Игра окончена!</h2>             <p id="winner-text">Победитель: Игрок 1</p>             <button class="restart-btn" id="restart-btn">Новая игра</button>         </div>                  <div class="controls">             <div id="control-hint">Управление: W/S</div>         </div>          <div class="start-screen" id="start-screen">             <h2>Добро пожаловать в Snow Pong! ❄️</h2>                          <div class="start-option">                 <strong>Режим игры:</strong><br>                 <button class="mode-btn active" data-mode="pvp">PVP</button>                 <button class="mode-btn" data-mode="pve">PVE</button>             </div>                          <div class="start-option" id="difficulty-section">                 <strong>Сложность (для PVE):</strong><br>                 <button class="difficulty-btn active" data-level="easy">Лёгкий</button>                 <button class="difficulty-btn" data-level="medium">Средний</button>                 <button class="difficulty-btn" data-level="hard">Сложный</button>             </div>                          <button class="start-btn" id="start-game-btn">Начать игру</button>                          <div class="focus-hint" id="focus-btn">👉 Кликните сюда для фокуса</div>         </div>          <div class="debug-info" id="debug">Последняя клавиша: —</div>     </div>      <script>         // Элементы         const gameContainer = document.querySelector('.game-container');         const leftPaddle = document.getElementById('left-paddle');         const rightPaddle = document.getElementById('right-paddle');         const snowflake = document.getElementById('snowflake');         const leftScoreElement = document.getElementById('left-score');         const rightScoreElement = document.getElementById('right-score');         const totalGoalsElement = document.getElementById('total-goals');         const gameOverScreen = document.getElementById('game-over');         const winnerText = document.getElementById('winner-text');         const restartBtn = document.getElementById('restart-btn');         const snowEffect = document.getElementById('snow-effect');         const soundBtn = document.getElementById('sound-btn');         const controlHint = document.getElementById('control-hint');         const startScreen = document.getElementById('start-screen');         const debug = document.getElementById('debug');          // Настройки         let gameMode = 'pvp';         let difficulty = 'medium';         let soundEnabled = true;         let audioContext = null;          // Параметры         const gameWidth = 800;         const gameHeight = 600;         const paddleHeight = 100;         const paddleWidth = 15;         const snowflakeSize = 30;         const paddleSpeed = 8;         const maxScore = 5;                  // Игровые переменные         let leftPaddleY = gameHeight / 2 - paddleHeight / 2;         let rightPaddleY = gameHeight / 2 - paddleHeight / 2;         let snowflakeX = gameWidth / 2;         let snowflakeY = gameHeight / 2;         let snowflakeVelX = 5;         let snowflakeVelY = 3;         let leftScore = 0;         let rightScore = 0;         let totalGoals = 0;         let gameActive = false;          // Управление         const keys = { w: false, s: false, o: false, l: false };          // --- НАСТРОЙКИ СЛОЖНОСТИ ---         const difficultySettings = {             easy: { speed: 0.1, error: 30, multiplier: 1 },             medium: { speed: 0.15, error: 10, multiplier: 2 },             hard: { speed: 0.2, error: 2, multiplier: 3 }         };          // --- УПРОЩЁННЫЙ ИИ (без застревания) ---         function moveAiPaddle() {             if (gameMode !== 'pve') return;              const settings = difficultySettings[difficulty];             const rightPaddleCenter = rightPaddleY + paddleHeight / 2;             let targetY = snowflakeY;             const error = difficulty === 'hard' ? 0 : (Math.random() * settings.error - settings.error / 2);             const finalTarget = targetY + error;             const diff = finalTarget - rightPaddleCenter;             const threshold = difficulty === 'hard' ? 2 : 5;              if (Math.abs(diff) > threshold) {                 const moveSpeed = paddleSpeed * settings.speed * settings.multiplier;                 if (diff > 0) {                     rightPaddleY += moveSpeed;                 } else {                     rightPaddleY -= moveSpeed;                 }             }              if (rightPaddleY < 0) rightPaddleY = 0;             if (rightPaddleY > gameHeight - paddleHeight) rightPaddleY = gameHeight - paddleHeight;         }          // --- АУДИО ---         function initAudio() {             if (audioContext) return;             try {                 audioContext = new (window.AudioContext || window.webkitAudioContext)();             } catch (e) {                 console.warn('Audio not available');             }         }          function playSound(freq, dur = 0.1) {             if (!soundEnabled || !audioContext) return;             const osc = audioContext.createOscillator();             const gain = audioContext.createGain();             osc.connect(gain);             gain.connect(audioContext.destination);             osc.frequency.value = freq;             osc.type = 'square';             gain.gain.setValueAtTime(0.1, audioContext.currentTime);             gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + dur);             osc.start();             osc.stop(audioContext.currentTime + dur);         }          function playBounce() { playSound(300); }         function playScore() { playSound(150, 0.2); }         function playWin() { playSound(800, 0.3); }          soundBtn.addEventListener('click', () => {             soundEnabled = !soundEnabled;             soundBtn.textContent = soundEnabled ? '🔊' : '🔇';         });          // --- СНЕГ ---         function createSnowflakes() {             for (let i = 0; i < 20; i++) {                 const snow = document.createElement('div');                 snow.classList.add('snow');                 const size = Math.random() * 6 + 2;                 snow.style.width = `${size}px`;                 snow.style.height = `${size}px`;                 snow.style.left = `${Math.random() * 100}%`;                 snow.style.top = `${Math.random() * 100}%`;                 snow.style.opacity = Math.random() * 0.7 + 0.3;                 snow.dataset.speed = Math.random() * 2 + 1;                 snowEffect.appendChild(snow);             }         }                  function animateSnowflakes() {             document.querySelectorAll('.snow').forEach(snow => {                 let top = parseFloat(snow.style.top) + parseFloat(snow.dataset.speed);                 if (top > 100) top = -5;                 snow.style.top = `${top}%`;             });         }                  // --- ВЫБОР НА СТАРТЕ ---         const modeButtons = document.querySelectorAll('.mode-btn');         const difficultyButtons = document.querySelectorAll('.difficulty-btn');         const difficultySection = document.getElementById('difficulty-section');         const startGameBtn = document.getElementById('start-game-btn');         const focusBtn = document.getElementById('focus-btn');          modeButtons.forEach(btn => {             btn.addEventListener('click', () => {                 modeButtons.forEach(b => b.classList.remove('active'));                 btn.classList.add('active');                 gameMode = btn.dataset.mode;                 difficultySection.style.display = gameMode === 'pve' ? 'block' : 'none';                 updateControlHint();             });         });          difficultyButtons.forEach(btn => {             btn.addEventListener('click', () => {                 difficultyButtons.forEach(b => b.classList.remove('active'));                 btn.classList.add('active');                 difficulty = btn.dataset.level;             });         });          // --- ФОКУС ---         function setFocus() {             document.body.focus();             debug.textContent = 'Фокус установлен';             setTimeout(() => debug.textContent = 'Последняя клавиша: —', 1000);         }          focusBtn.addEventListener('click', setFocus);         startGameBtn.addEventListener('click', setFocus);          // --- УПРАВЛЕНИЕ ---         document.addEventListener('keydown', (e) => {             const key = e.key.toLowerCase();             debug.textContent = `Key down: ${e.key}`;             if (keys.hasOwnProperty(key)) {                 keys[key] = true;                 e.preventDefault();             }         });                  document.addEventListener('keyup', (e) => {             const key = e.key.toLowerCase();             if (keys.hasOwnProperty(key)) {                 keys[key] = false;             }         });          // --- ИГРОВАЯ ЛОГИКА ---         function updateControlHint() {             controlHint.textContent = gameMode === 'pvp'                 ? 'Игрок 1: W/S | Игрок 2: O/L'                 : 'Вы играете за левую ракетку. Управление: W/S';         }          function checkCollision(el1, el2) {             const r1 = el1.getBoundingClientRect();             const r2 = el2.getBoundingClientRect();             return !(r1.right < r2.left || r1.left > r2.right || r1.bottom < r2.top || r1.top > r2.bottom);         }          function resetSnowflake() {             snowflakeX = gameWidth / 2;             snowflakeY = gameHeight / 2;             snowflakeVelX = 5 * (Math.random() > 0.5 ? 1 : -1);             snowflakeVelY = 3 * (Math.random() > 0.5 ? 1 : -1);             updateSnowflakePosition();         }          function updateSnowflakePosition() {             snowflake.style.left = `${snowflakeX}px`;             snowflake.style.top = `${snowflakeY}px`;         }          function updatePaddlePositions() {             leftPaddle.style.top = `${leftPaddleY}px`;             rightPaddle.style.top = `${rightPaddleY}px`;         }          function resetGame() {             leftScore = 0;             rightScore = 0;             leftScoreElement.textContent = '0';             rightScoreElement.textContent = '0';             leftPaddleY = gameHeight / 2 - paddleHeight / 2;             rightPaddleY = gameHeight / 2 - paddleHeight / 2;             resetSnowflake();             updatePaddlePositions();         }          // --- ИГРОВОЙ ЦИКЛ ---         function gameLoop() {             if (gameActive) {                 // Управление                 if (gameMode === 'pvp') {                     if (keys.w) leftPaddleY = Math.max(0, leftPaddleY - paddleSpeed);                     if (keys.s) leftPaddleY = Math.min(gameHeight - paddleHeight, leftPaddleY + paddleSpeed);                     if (keys.o) rightPaddleY = Math.max(0, rightPaddleY - paddleSpeed);                     if (keys.l) rightPaddleY = Math.min(gameHeight - paddleHeight, rightPaddleY + paddleSpeed);                 } else if (gameMode === 'pve') {                     if (keys.w) leftPaddleY = Math.max(0, leftPaddleY - paddleSpeed);                     if (keys.s) leftPaddleY = Math.min(gameHeight - paddleHeight, leftPaddleY + paddleSpeed);                     moveAiPaddle();                 }                  // Движение снежинки                 snowflakeX += snowflakeVelX;                 snowflakeY += snowflakeVelY;                 updateSnowflakePosition();                  // Отскок от стен                 if (snowflakeY <= 0 || snowflakeY >= gameHeight - 6) {                     snowflakeVelY = -snowflakeVelY;                     playBounce();                 }                  // Столкновение                 if (checkCollision(snowflake, leftPaddle) || checkCollision(snowflake, rightPaddle)) {                     snowflakeVelX = -snowflakeVelX * 1.05;                     if (checkCollision(snowflake, rightPaddle)) {                         snowflakeVelX *= 1.03;                     }                     playBounce();                 }                  // Гол                 if (snowflakeX < 0) {                     rightScore++;                     totalGoals++;                     playScore();                     resetSnowflake();                 } else if (snowflakeX > gameWidth) {                     leftScore++;                     totalGoals++;                     playScore();                     resetSnowflake();                 }                  // Победа                 if (leftScore >= maxScore || rightScore >= maxScore) {                     gameActive = false;                     playWin();                     gameOverScreen.classList.add('active');                     winnerText.textContent = leftScore >= maxScore                          ? (gameMode === 'pve' ? '🎉 Вы победили!' : 'Победитель: Игрок 1!')                         : (gameMode === 'pve' ? '🤖 Компьютер победил!' : 'Победитель: Игрок 2!');                 }                  // Обновление                 leftScoreElement.textContent = leftScore;                 rightScoreElement.textContent = rightScore;                 totalGoalsElement.textContent = totalGoals;                 updatePaddlePositions();                 animateSnowflakes();             }             requestAnimationFrame(gameLoop);         }          // --- ЗАПУСК ИГРЫ ---         startGameBtn.addEventListener('click', () => {             startScreen.style.display = 'none';             initAudio();             setFocus();             gameActive = true;             updateControlHint();             requestAnimationFrame(gameLoop);         });          restartBtn.addEventListener('click', () => {             resetGame();             gameOverScreen.classList.remove('active');             gameActive = true;             setFocus();         });          // --- ИНИЦИАЛИЗАЦИЯ ---         window.onload = () => {             createSnowflakes();             difficultySection.style.display = 'none';             updateControlHint();             document.body.tabIndex = 0;             document.body.focus();         };     </script> </body> </html>


ссылка на оригинал статьи https://habr.com/ru/articles/933814/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *