Содержание статьи

Игра «Змейка» является классическим примером для изучения взаимодействия пользователя с кодом через клавиатуру и обработки динамических данных в реальном времени. В этой статье рассматривается подход к созданию полноценной игры без использования Canvas, опираясь исключительно на DOM-элементы для визуализации игрового поля.
Использование HTML-таблицы или сетки из div позволяет отображать состояние игры, где каждая клетка может менять цвет или содержимое в зависимости от позиции змейки и еды. Такой подход упрощает отслеживание координат элементов и делает логику игры более наглядной.
Особое внимание уделено управлению: движение змейки реализуется через обработку событий keydown, что позволяет фиксировать направления и предотвращать обратный ход. Логика столкновений и проверки границ поля реализуется через массивы, которые хранят координаты сегментов змейки, а рост после поедания еды управляется динамическим добавлением новых элементов в массив.
В статье также рассматривается организация игрового цикла с использованием setInterval для обновления состояния поля и движения змейки с фиксированной скоростью. Такой подход обеспечивает плавность анимации и стабильное реагирование на ввод пользователя, позволяя сосредоточиться на оптимизации логики без привлечения графических библиотек.
Настройка игрового поля и сетки для змейки

Игровое поле создается с помощью HTML-элемента div, который будет контейнером для всех ячеек сетки. Рекомендуется фиксировать размеры поля через атрибуты или inline-стили, чтобы сетка оставалась квадратной и элементы змейки правильно отображались.
Сетка формируется путем генерации ряда div-элементов внутри контейнера. Каждая ячейка представляет собой отдельный блок, идентифицируемый классом и координатами через dataset, например data-x и data-y. Это упрощает управление положением змейки и еды на поле.
Для точного контроля размера сетки определите количество рядов и колонок. Например, поле 20×20 создается через двойной цикл: внешний цикл по строкам, внутренний по колонкам. Каждая ячейка получает фиксированную ширину и высоту, чтобы сетка оставалась равномерной.
Позиционирование змейки осуществляется путем изменения классов ячеек. Каждая часть змейки получает отдельный класс, что позволяет визуально выделять голову и тело. При этом координаты ячеек остаются привязанными к логике игры для проверки столкновений.
При генерации поля стоит заранее создать все ячейки, чтобы минимизировать изменение DOM во время игры. Это ускоряет рендеринг и предотвращает мерцания при перемещении змейки. Ячейки можно хранить в массиве для быстрого доступа по координатам.
Для управления размерами сетки учитывайте размер окна браузера. Оптимально, если общая ширина поля не превышает 80% ширины экрана, чтобы игра оставалась удобной на разных устройствах. Высоту можно подгонять под ширину, сохраняя квадратное соотношение ячеек.
Создание змейки: структура данных и начальные позиции
Для реализации змейки оптимально использовать массив объектов, где каждый объект хранит координаты сегмента змейки на игровом поле. Например, структура сегмента может выглядеть так:
let segment = { x: 5, y: 10 };
Змейка представляется массивом таких сегментов, начиная с головы:
let snake = [
{ x: 5, y: 10 },
{ x: 4, y: 10 },
{ x: 3, y: 10 }
];
Рекомендации по начальной позиции и длине змейки:
- Начальная длина обычно 3–5 сегментов, чтобы дать игроку время освоиться.
- Начальные координаты выбираются внутри границ поля, чтобы змейка сразу не сталкивалась со стеной.
- Голова змейки – первый элемент массива, движение начинается с нее.
Для динамического расширения змейки при сборе еды добавляется новый сегмент в конец массива, копируя координаты последнего сегмента до обновления позиции:
let lastSegment = snake[snake.length - 1];
snake.push({ x: lastSegment.x, y: lastSegment.y });
Смещение змейки осуществляется путем переброса координат каждого сегмента к позиции предыдущего:
- Начать с конца массива, обновляя координаты каждого сегмента на координаты сегмента перед ним.
- Голове присвоить новые координаты в зависимости от направления движения.
Такая структура обеспечивает простое управление, проверку столкновений и масштабируемость для увеличения длины змейки.
Обработка клавиш для управления направлением змейки

Для управления движением змейки необходимо обработать нажатия клавиш стрелок. Оптимально использовать объект, где хранится текущее направление движения, чтобы избежать одновременного изменения на противоположное направление.
Пример структуры для направления:
| Свойство | Описание |
|---|---|
| dx | изменение позиции по горизонтали (-1 влево, 1 вправо, 0 без движения) |
| dy | изменение позиции по вертикали (-1 вверх, 1 вниз, 0 без движения) |
Добавьте обработчик события keydown к документу:
| Клавиша | dx | dy |
|---|---|---|
| ArrowUp | 0 | -1 |
| ArrowDown | 0 | 1 |
| ArrowLeft | -1 | 0 |
| ArrowRight | 1 | 0 |
Важно проверять, чтобы новое направление не совпадало с противоположным текущему. Например, при движении вправо нельзя сразу повернуть влево. Для этого используется условие:
| Текущее направление | Запрещённое изменение |
|---|---|
| dx = 1, dy = 0 | dx = -1, dy = 0 |
| dx = -1, dy = 0 | dx = 1, dy = 0 |
| dx = 0, dy = 1 | dx = 0, dy = -1 |
| dx = 0, dy = -1 | dx = 0, dy = 1 |
Пример функции обработчика:
document.addEventListener('keydown', function(event) {
switch(event.key) {
case 'ArrowUp':
if(currentDirection.dy !== 1) currentDirection = {dx:0, dy:-1};
break;
case 'ArrowDown':
if(currentDirection.dy !== -1) currentDirection = {dx:0, dy:1};
break;
case 'ArrowLeft':
if(currentDirection.dx !== 1) currentDirection = {dx:-1, dy:0};
break;
case 'ArrowRight':
if(currentDirection.dx !== -1) currentDirection = {dx:1, dy:0};
break;
}
});
Для плавного управления рекомендуется обновлять положение змейки через фиксированный интервал времени, например с помощью setInterval, используя текущие значения dx и dy для изменения координат головы змейки.
Логика движения змейки и обновление позиции
Движение змейки реализуется через массив сегментов, где каждый сегмент хранит координаты X и Y на сетке. Основной принцип: голова змейки получает новое направление, а остальные сегменты повторяют положение предыдущего сегмента.
При обновлении позиции выполняется последовательность действий: сначала вычисляется новая координата головы на основе текущего направления (вверх, вниз, влево, вправо). Затем каждый последующий сегмент принимает координаты сегмента, стоящего перед ним. Это позволяет змейке плавно перемещаться без разрывов.
Для предотвращения обратного хода (например, движение вправо сразу влево) проверяется направление ввода. Новое направление применяется только если оно не противоположно текущему. Это исключает мгновенное столкновение головы с хвостом.
Если змейка съедает пищу, при обновлении позиции добавляется новый сегмент в конец массива с координатами последнего сегмента. Таким образом, длина змейки увеличивается на один блок без нарушения текущего движения.
Обновление состояния игры должно происходить через таймер или интервал, чтобы движение было равномерным и предсказуемым. Интервал выбирается в миллисекундах, например, 200–300 мс для стандартной скорости. Ускорение можно реализовать уменьшением интервала после достижения определённых очков.
После вычисления новых координат проверяется столкновение головы с границами поля или с собственным телом. При столкновении игра завершается, что обеспечивает корректную обработку логики столкновений и поддерживает целостность игрового процесса.
Генерация и размещение еды на игровом поле
Для создания еды на игровом поле используется случайная генерация координат в пределах размеров сетки. Каждая ячейка поля имеет уникальные координаты, и еда должна появляться только в свободных ячейках, чтобы не совпадать с телом змейки.
Алгоритм генерации:
- Определяем размеры игрового поля: ширину и высоту в количестве ячеек.
- Создаем массив свободных ячеек, исключая координаты, занятые змейкой.
- Случайным образом выбираем одну ячейку из массива свободных для размещения еды.
- Сохраняем координаты еды для последующей проверки столкновения со змейкой.
Для визуального отображения на странице создается элемент div или span с уникальным идентификатором, соответствующим координатам ячейки:
- Устанавливаем абсолютное позиционирование относительно игрового контейнера.
- Присваиваем координаты через свойства
topиleft, умноженные на размер одной ячейки. - Используем отдельный класс для еды, чтобы можно было легко обновлять или удалять элемент при съедании.
При съедании еды змейкой алгоритм выполняет следующие шаги:
- Удаляет текущий элемент еды с поля.
- Увеличивает длину змейки.
- Генерирует новые координаты для следующей еды с соблюдением условий свободных ячеек.
Для предотвращения повторного появления еды на тех же координатах рекомендуется хранить список всех занятых ячеек и обновлять его после каждого перемещения змейки.
Обнаружение столкновений с границами и телом змейки
Для определения столкновений с границами игрового поля проверяется координата головы змейки. Если значение X меньше 0 или больше ширины поля минус единица, либо Y меньше 0 или больше высоты поля минус единица, это фиксируется как столкновение. В коде проверка выполняется до обновления позиции головы.
Столкновение с телом реализуется через сравнение позиции головы с координатами всех сегментов тела. Используется массив объектов, где каждый объект хранит X и Y сегмента. При каждом движении головы выполняется итерация по массиву начиная с первого сегмента после головы. Если координаты совпадают, фиксируется столкновение.
Для оптимизации проверок при увеличении длины змейки рекомендуется использовать объект для быстрого поиска существующих позиций тела. Ключ формируется как строка вида «X:Y», что позволяет проверять наличие сегмента за константное время вместо итерации всего массива.
Система очков и увеличение длины змейки
Увеличение длины змейки реализуется добавлением нового сегмента в массив, представляющий тело змейки. После того как змейка съела еду, в конец массива добавляется копия последнего сегмента. В следующем обновлении движения новый сегмент займёт позицию, где ранее находился последний элемент.
Важно синхронизировать добавление сегмента с движением змейки, чтобы новые части корректно следовали за головой. Для этого лучше использовать буферное значение роста, например growSegments = 1;, и уменьшать его на 1 после каждого перемещения, пока он не достигнет нуля.
Обновление счета и длины должно происходить до перерисовки игрового поля, чтобы игрок видел актуальное состояние змейки и текущие очки в реальном времени. Это обеспечивает точное соответствие между действиями игрока и результатами на экране.
Организация игрового цикла и перерисовки экрана
Игровой цикл строится на функции, которая регулярно обновляет состояние змейки и отображение поля. Для этого используется метод setInterval, задающий интервал обновлений в миллисекундах. Оптимальное значение для старта – 200 мс, его можно уменьшать по мере увеличения скорости змейки.
В каждом вызове функции цикла выполняются последовательные действия: проверка столкновений, обновление позиции головы змейки, сдвиг сегментов тела, проверка съедения еды и увеличение длины змейки. После обновления логики вызывается функция перерисовки поля.
Перерисовка реализуется через очистку контейнера игрового поля, обычно через innerHTML = '', и последовательное добавление новых элементов для головы, тела и еды. Каждому сегменту присваиваются координаты data-x и data-y, что упрощает контроль позиции и последующую проверку столкновений.
Для повышения плавности движения рекомендуется минимизировать количество операций внутри цикла и обновлять только измененные элементы. Также полезно выделять функции для логики движения, проверки столкновений и генерации еды, чтобы цикл оставался компактным и читабельным.
При изменении скорости игры можно динамически пересоздавать интервал setInterval, удаляя старый через clearInterval и создавая новый с меньшим временем между вызовами. Это позволяет ускорять змейку без нарушения структуры игрового цикла.
Вопрос-ответ:
Как правильно задать размеры игрового поля для змейки в JavaScript?
Размер игрового поля определяется числом ячеек по горизонтали и вертикали. Обычно создают сетку с одинаковыми квадратами, например 20×20 пикселей. Размер поля в пикселях вычисляется как количество ячеек умноженное на размер одной ячейки. В коде это используется для отрисовки змейки и еды, а также для проверки столкновений с границами.
Каким образом реализовать движение змейки без задержек и с плавной анимацией?
Движение змейки организуется через цикл обновления, который периодически изменяет координаты каждого сегмента. В JavaScript для этого используют функцию requestAnimationFrame или setInterval. Важно обновлять положение змейки до отрисовки экрана и удалять или добавлять сегменты в массив, представляющий тело змейки. Плавность зависит от выбранного интервала и постоянного обновления позиции перед перерисовкой.
Как обработать нажатия клавиш, чтобы змейка не могла двигаться в обратном направлении?
Для этого при обработке события keydown нужно проверять текущее направление движения змейки. Например, если она движется вправо, запретить смену направления на левое. В коде это реализуется сравнением нового направления с текущим и изменением его только если оно не противоположно. Такой подход предотвращает мгновенные столкновения с собственным телом.
Каким образом генерировать еду так, чтобы она не появлялась на теле змейки?
Генерация еды выполняется случайным образом по координатам сетки. После выбора случайной позиции необходимо проверить, что эти координаты не совпадают с любой частью тела змейки. Если совпадение найдено, генерируется новая позиция до тех пор, пока не будет свободной ячейки. Это позволяет избежать ситуаций, когда еда появляется внутри змейки и делает игру некорректной.
