8

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

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

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

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

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

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

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

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

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

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

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

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

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

Управление:

  • Игрок 1: W и S

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

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

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

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

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

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

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

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

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

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

То есть, 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

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

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

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

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

И окно победы/поражения

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

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

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

Полноценный симулятор 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>

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе

НЕ СТОИТ ПУБЛИКОВАТЬ:

- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры

- Посты, единственная цель которых - набор команды для разработки игры

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

Ссылка на сторонний ресурс, связанный с игрой, допускается только при следующих условиях:

- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"