2D игра на чистом JavaScript
В этом пошаговом руководстве мы создадим простую игру MDN Breakout, написанную на чистом JavaScript и отрендеренную на HTML5 .
К каждому шагу прилагаются редактируемые live-примеры, с которыми можно поиграть, чтобы увидеть, как должна выглядеть игра на промежуточных этапах. Вы изучите основы использования элемента для реализации таких фундаментальных игровых механик, как рендеринг и перемещение изображений, обнаружение столкновений, механизмы управления, а также состояния выигрыша и проигрыша.
Для извлечения максимальной пользы из этой серии статей необходимо иметь средние (или хотя бы базовые) знания языка JavaScript. После прохождения этого урока вы сможете создавать собственные простые браузерные игры.
Детали к урокам
Все уроки и версии игры MDN Breakout доступны в GitHub:
Лучший способ получить надёжные знания в области разработки браузерных игр — это начать с чистого JavaScript. Затем можно выбрать любой фреймворк для использования в своих проектах. Фреймворки — это инструменты, созданные на языке JavaScript; поэтому, даже если вы планируете работать с ними, нелишним будет сначала изучить сам язык, чтобы понимать, что именно происходит внутри. Фреймворки ускоряют разработку и помогают справиться со скучными частями игры, но если что-то работает не так, как ожидалось, всегда можно попытаться отладить код или написать собственное решение на чистом JavaScript.
Примечание: Если вам интересно узнать о разработке двухмерных игр с помощью игровой библиотеки, ознакомьтесь с альтернативной серией статей 2D игра Breakout с использованием Phaser (en-US) .
Примечание: Эту серию статей можно использовать как материал для практических занятий по разработке игр. Также можно воспользоваться набором инструментов Gamedev Canvas Content Kit, основанном на этом уроке, если нужно сделать доклад о разработке игр в целом.
HTML5 Canvas — простая игра

До сих пор мы изучали, каким образом применить к холсту некоторые ключевые методы программирования, чтобы демонстрировать интерактивные рисунки и анимацию. Эти методы позволяют использовать холст не только для простого рисования, но и создавать на его основе завершенные, автономные приложения, такие как игры или мини-приложения в стиле Flash.
На рисунке ниже показан более амбициозный пример интерактивности и анимации, для реализации которого применяются все приобретенные нами на данный момент знания. Это простая игра, в которой пользователь должен провести значок через лабиринт. Значок начинает двигаться в определенном направлении после нажатия клавиши со стрелкой и продолжает перемещаться, пока не столкнется со «стеной» лабиринта. Для продолжения движения нужно нажать клавишу со стрелкой в направлении, в котором нет препятствий:

С точки зрения посетителя веб-страницы это забавная игра, а с точки зрения разработчика это эффективное использование возможностей холста HTML5 и искусного программирования на JavaScript. Посмотреть этот пример вживую вы можете на странице — «HTML5 Canvas — лабиринт». Оттуда же можно скачать необходимые файлы изображений для этого примера — face.png, maze.png и easy_maze.png.
Использование холста для создания сложного приложения требует от разработчика перелопатить значительный объем кода. В следующих разделах мы изучим основные моменты создания этого приложения, но будьте готовы хорошенько попотеть с программированием JavaScript.
Подготовительные работы
Прежде чем анимировать что-либо, нам нужно создать для этого соответствующие условия, подготовив должным образом холст. Хотя весь лабиринт можно было бы нарисовать на холсте линиями и прямоугольниками, для этого потребовался бы внушительный объем кода, написание которого вручную будет чрезвычайно долгим и утомительным процессом. Для этого нужно составить логическую модель всего лабиринта, а потом вырисовывать каждую его часть отдельной операцией.
Для этого подхода почти наверняка потребуется инструмент для автоматического создания кода рисования. Например, лабиринт можно будет нарисовать в Adobe Illustrator, а потом преобразовать его в код для холста с помощью какого-либо модуля расширения.
Другой вариант — взять готовую графику лабиринта и прорисовать ее на холсте. Этот подход будет особенно легким, т.к. Интернет изобилует бесплатными страницами для создания лабиринтов. Найти такие страницы очень легко — просто выполните поиск в Google по словам «maze generator», и вы получите буквально тысячи ссылок. Выбрав понравившийся вам генератор, укажите несколько параметров (например, размер, форму, цвета, плотность и сложность лабиринта), нажмите кнопку Создать, и в считанные секунды вы получите рисунок лабиринта, который можно сохранить на своем компьютере.
Исходная разметка примера выглядит следующим образом:
HTML5 Canvas - Лабиринт canvas < border: 6px double black; background: white; >img < display: none; >button
В нашем примере используется готовый рисунок лабиринта. При загрузке страницы код отображает этот рисунок (хранящийся в файле maze.png) на холсте. Далее приводится код, который запускает этот процесс при загрузке страницы:
// Определяем глобальные переменные для холста и контекста var canvas; var context; window.onload = function() < // Подготавливаем холст canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); // Рисуем фон лабиринта drawMaze("maze.png", 268, 5); // При нажатии клавиши вызываем функцию processKey() window.onkeydown = processKey; >;
В действительности этот код не рисует фон лабиринта, а вызывает для этого другую функцию — drawMaze(). Использование в этом примере отдельной функции рисования лабиринта означает, что он не ограничен одним видом лабиринта, а позволяет загрузить любую картинку лабиринта. Для этого нужно просто вызвать функцию drawMaze() и передать ей название файла изображения лабиринта и координаты начала его прохождения. В следующем листинге приведен код этой функции:
// Отслеживаем текущую позицию значка var x = 0; var y = 0; // Таймер, включающий и отключающий новый лабиринт в любое время var timer; function drawMaze(mazeFile, startingX, startingY) < // Остановить таймер (если запущен) clearTimeout(timer); // Остановить перемещение значка dx = 0; dy = 0; // Загружаем изображение лабиринта var imgMaze = new Image(); imgMaze.onload = function() < // Изменяем размер холста в соответствии // с размером изображения лабиринта canvas.width = imgMaze.width; canvas.height = imgMaze.height; // Рисуем лабиринт context.drawImage(imgMaze, 0,0); // Рисуем значок x = startingX; y = startingY; var imgFace = document.getElementById("face"); context.drawImage(imgFace, x, y); context.stroke(); // Рисуем следующий кадр через 10 миллисекунд timer = setTimeout("drawFrame()", 10); >; imgMaze.src = mazeFile; >
В коде используется двухэтапный метод рисования изображения на холсте. Сначала определяется функция для обработки события изображения onload и последующего отображения загруженного изображения на холсте. Потом устанавливается атрибут src объекта изображения, что загружает изображение и активирует код. Этот двухэтапный процесс немного посложнее, чем просто получение изображения из скрытого элемента на странице, но он необходим для создания функции, позволяющей загружать любое изображение лабиринта.
После загрузки изображения лабиринта код подгоняет размер холста к размеру изображения, устанавливает значок в исходную позицию, а потом прорисовывает ее на холсте. Наконец, вызывается метод setTimeout(), чтобы начать показ кадров анимации.
Анимация значка
Процесс прохождения лабиринта начинается, когда пользователь нажмет одну из клавиш со стрелками. Например, при нажатии клавиши значок начинает двигаться вниз, пока не натолкнется на препятствие или не будет нажата другая клавиша.
Для этого в коде используются две глобальные переменные для отслеживания скорости значка, иными словами, количества пикселов, на которое он смещается по оси x или y в каждом кадре. Эти переменные называются dx и dy.
Когда пользователь нажимает какую-либо клавишу, холст вызывает функцию processKey(). Эта функция проверяет, не была ли нажата одна из клавиш со стрелкой, и если была, изменяет направление движения значка. Чтобы определить, какая именно клавиша со стрелкой была нажата, проверяется код нажатой клавиши. Например, код 38 соответствует клавише . Функция processKey() игнорирует все клавиши, за исключением клавиш со стрелками:
// Скорость перемещения значка var dx = 0; var dy = 0; function processKey(e) < // Если значок находится в движении, останавливаем его dx = 0; dy = 0; // Если нажата стрелка вверх, начинаем двигаться вверх if (e.keyCode == 38) < dy = -1; >// Если нажата стрелка вниз, начинаем двигаться вниз if (e.keyCode == 40) < dy = 1; >// Если нажата стрелка влево, начинаем двигаться влево if (e.keyCode == 37) < dx = -1; >// Если нажата стрелка вправо, начинаем двигаться вправо if (e.keyCode == 39) < dx = 1; >>
Функция processKey() не меняет текущую позицию значка и не пытается обновить ее отображение на холсте. Эта задача осуществляется функцией drawFrame(), которая вызывается каждые 10 мс:
function drawFrame() < // Обновляем кадр только если значок движется if (dx != 0 || dy != 0) < // Закрашиваем перемещение значка желтым цветом context.beginPath(); context.fillStyle = "rgb(254,244,207)"; context.rect(x, y, 15, 15); context.fill() // Обновляем координаты значка, создавая перемещение x += dx; y += dy; // Проверка столкновения со стенками лабиринта // (вызывается доп. функция) if (checkForCollision()) < x -= dx; y -= dy; dx = 0; dy = 0; >// Перерисовываем значок var imgFace = document.getElementById("face"); context.drawImage(imgFace, x, y); // Проверяем дошел ли пользователь до финиша. // Если дошел, то выводим сообщение if (y > (canvas.height - 17)) < alert("Ты победил!"); return; >> // Рисуем следующий кадр через 10 миллисекунд timer = setTimeout("drawFrame()", 10); >
Код функции drawFrame() довольно простой, но всеохватывающий, решающий несколько задач. Прежде всего, он проверяет, движется ли значок в каком-либо направлении. Если не движется, то функция, по сути, ничего не делает.
Если же значок движется, то функция drawFrame() закрашивает желтым цветом текущую позицию значка, создавая, таким образом, след после продвижения. Потом значок перемещается в новую позицию. Затем код вызывает функцию checkForCollision(), чтобы проверить новую позицию. (Код этой функции проверки попадания рассматривается в следующем разделе.) Если новая позиция не верна, это означает, что значок столкнулся с преградой, и его нужно возвратить назад в старую позицию и прекратить движение.
Потом проверяется, не вышел ли значок за пределы лабиринта, т.е. прошел его. Если вышел, то выводится соответствующее сообщение. В противном случае код устанавливает время ожидания для вызова метода drawFrame() опять 10 мс.
На данном этапе мы рассмотрели весь код для игры в лабиринт, за исключением логики функции checkForCollision(), которая выполняет проверку столкновения значка с преградой.
Проверка попадания с использованием цвета пикселов
В предыдущей статье вы увидели, как можно выполнять проверку попадания посредством математических вычислений. Но это можно сделать и другим способом. Вместо того чтобы просматривать коллекцию нарисованных объектов, можно исследовать цвет блока пикселов. В некоторых отношениях этот подход более простой, т.к. для него не требуется обрабатывать все объекты и код для отслеживания позиции объекта. Но он будет работать только в случае четко определенных предположений об исследуемых цветах.
Проверка попадания посредством тестирования пикселов является идеальным способом для применения в игре «Лабиринт». С помощью этого метода можно определить, когда значок столкнулся со стеной лабиринта черного цвета. Если бы не этот метод, то нужно было бы сохранить всю информацию о лабиринте в памяти, а потом определять, не перекрывают ли координаты значка линии стен лабиринта.
Метод проверки на столкновения посредством анализа цвета пикселов возможен благодаря предоставляемой холстом возможности манипулировать отдельными пикселами, из которых состоит любое изображение. Контекст рисования имеет три метода для манипулирования пикселами: getImageData(), putImageData() и createImageData().
Метод getImageData() применяется для захвата пикселов прямоугольной области холста. Захваченные пикселы можно изменить и вставить обратно в холст с помощью метода putImageData(). А метод createImageData() позволяет создать в памяти новый, пустой блок пикселов, которые можно изменить, а потом вставить в холст посредством метода putImageData().
Если вы ожидаете, что каждый пиксел представляется одним числом, то ошибаетесь. Каждый пиксел представляется четырьмя числами — тремя для красной, зеленой и синей цветовых составляющих и одним для прозрачности. Поэтому, чтобы исследовать каждый пиксел, нам требуется цикл, который проходит через массив с шагом в четыре элемента. Ниже показана структура метода checkForCollision(), выполняющего проверку столкновения с преградой:
function checkForCollision() < // Перебираем все пикселы и инвертируем их цвет var imgData = context.getImageData(x-1, y-1, 15+2, 15+2); var pixels = imgData.data; // Получаем данные для одного пиксела for (var i = 0; n = pixels.length, i < n; i += 4) < var red = pixels[i]; var green = pixels[i+1]; var blue = pixels[i+2]; var alpha = pixels[i+3]; // Смотрим на наличие черного цвета стены, что указывает на столкновение if (red == 0 && green == 0 && blue == 0) < return true; >// Смотрим на наличие серого цвета краев, что указывает на столкновение if (red == 169 && green == 169 && blue == 169) < return true; >> // Столкновения не было return false; >
Итак, обсуждение программы «Лабиринт» завершено.
Примеры игр на HTML5 Canvas
Диапазон творческих возможностей холста практически неограничен. Интернет содержит огромное множество еще более амбициозных примеров работы с холстом, которые демонстрируют высшее мастерство кодирования на HTML5. Далее приводится список некоторых веб-сайтов, демонстрирующих потрясающие примеры:
Этот сайт содержит столько увлекательных примеров разработок на основе холста, что вы не сможете оторваться от экрана. Можно порекомендовать начать знакомство с этим сайтом с игры Mutant Zombie Monsters или инструмента для построения графиков биржевых котировок TickerPlot.
Это впечатляющее приложение на основе холста графически представляет статьи английской Википедии, где связанные темы соединяются тонкими линиями, похожими на паутину. При выборе новой темы соответствующая часть карты знаний помещается в центр страницы посредством плавной анимации.
В этой игре вы ходите с автоматом наперевес по простому трехмерному лабиринту, наподобие древней 3D-игры Wolfenstein, которая открыла повальное увлечение стрелялками в далеком 1992 г.
Игра на голом HTML / CSS
Сегодня речь пойдет о том, как создать игру не используя js и прочих скриптов. Только HTML / CSS, только хейткор.

Конечно же речь не идет (пока) о каких-то восхитительной сложности играх, но вы только вдумайтесь: никаких скриптов.
План
Создадим подобие всем известной игры Duck hunter, что там будет:
- Летающие утки
- В них можно стрелять
- За это начисляются очки
Основная задумка как раз в том, что утки — это летающие чекбоксы, которые имеют два состояния: checked и не checked. Да и стилизовать чекбоксы мы не можем, поэтому в разработку пойдут псевдоэлементы :before и :after.
background: url(../img/duck_light.png) no-repeat left center;
Заставим летать их с помощью css анимаций
Банальная анимация
@keyframes utkaFly < 0%< left: 150%; top:10%; >25% < left: 80%; top:50%; >50% < left: 50%; top:30%; >75% < left: 30%; top:40%; >100% < left: -50%; top:10%; >>
На этом первый пункт закончен, утки есть и они летают, ничего сложного.
Убей утку
Тк псевдоэлементы находятся внутри блоков-родителей, то клик по ним (псевдоэлементам) вызовет событие клика по чекбоксам. Таким образом, нам даже не понадобятся label’ы, чекбоксы изменят свое состояние на checked по клику на [type=checkbox]:before.
А когда мы кликаем по уткам? Правильно, когда стреляем в них. Соответственно, при попадании утка должна умереть, очки начислиться. Смерть утки обыграем селектором .duck:checked. Правда есть тут один подводный камушек — полет утки — это анимация, а стили анимации имеют преимущество, поэтому просто задать, например:
left: -50%; opacity: 0; z-index: -5;
Что может быть сильнее анимации (а плане приоритета)? Только другая анимация. Поэтому умершая утка будет позиционироваться с помощью анимации:
@keyframes utkaDie < 0%< left: -50%; >>
Собственно отрицательное позиционирование утки нужно только для того, чтобы она больше не засоряла игровое пространство и нельзя было на нее случайно кликнуть, убрав у чекбокса состояние checked.
Считаем очки
К сожалению, css не позволяет напрямую вычислять количество элементов с определенным селектором на странице. Напомню, что нам нужно узнать количество «уток» в состоянии checked. Но есть функция counter-increment. Свойство counter-increment увеличивает значение счетчика на единицу, имя которого мы укажем.
.duck:checked
Запись выше означает, что каждый элемент, подходящий под селектор .duck:checked увеличивает наш счетчик score (название произвольное). Отметили чекбокс — счетчик увеличился, сняли отметку — тут же уменьшился.
Хорошо, некий внутренний счетчик есть, осталось отобразить его в качестве результата игры. Сделаем это, конечно же, с помощью css-свойства content:
.score:after
Итог
- Современные технологии удивляют
- Если на хорошем уровне владеешь хоть одной технологией, уже можешь творить что-то интересное (так что хватит ныть, что ты всего лишь верстальщик)
- Сверстать игру можно
Ресурсы
- GitHub
- Демо
Своя игра: создаём собственную «Змейку»
Не так давно мы разбирали, как искусственный интеллект учится играть в змейку. А теперь мы сами сделаем такую игру, чтобы ей могли насладиться обычные люди. Что нам понадобится:
- HTML, чтобы можно было играть прямо в браузере;
- CSS для украшений;
- JavaScript для самой игры.
Логика игры
У классической змейки правила простые:
- есть поле из клеточек, где случайным образом появляется еда;
- есть змейка, которая всё время двигается и которой мы можем управлять;
- если змейка на своём пути встречает еду — еда исчезает, появляется в новом месте, а сама змейка удлиняется на одну клеточку;
- если змейка врежется в стену или в саму себя, игра заканчивается.
Чтобы играть было проще, мы сделаем так, чтобы змейка не врезалась в стенки, а проходила сквозь них. Если что — сможете это сами потом настроить в коде, когда захотите посложнее.
Последовательность наших действий будет такой:
- Делаем пустую HTML-страницу.
- Настраиваем внешний вид с помощью CSS.
- Рисуем игровое поле.
- Пишем скрипт, который и будет отвечать за всю игру.
Делаем HTML-страницу
С этим всё просто: берём стандартный код и сохраняем его как файл snake.html .
Змейка
Это даст нам пустую страницу, которую мы сейчас немного настроим стилями.
Настраиваем внешний вид
За внешний вид на странице у нас отвечает раздел , поэтому мы просто добавим в него CSS-код:
html, body < height: 100%; margin: 0; >/*Задаём глобальные параметры*/ body < background: black; display: flex; align-items: center; justify-content: center; >/*Делаем границу вокруг игрового поля*/ canvas
Теперь у нас на странице нет лишних отступов, зато всё по центру, есть чёрный фон и граница вокруг игрового поля. Самое время создать само игровое поле.
Рисуем игровое поле
Поле делается очень просто:
400 пикселей в ширину, столько же в высоту, название поля — game. Этого достаточно, чтобы браузер отобразил холст с такими размерами и позволил нам на нём рисовать.
Пишем скрипт
1. Зададим все переменные, которые нам понадобятся.
// Поле, на котором всё будет происходить, — тоже как бы переменная var canvas = document.getElementById('game'); // Классическая змейка — двухмерная, сделаем такую же var context = canvas.getContext('2d'); // Размер одной клеточки на поле — 16 пикселей var grid = 16; // Служебная переменная, которая отвечает за скорость змейки var count = 0; // А вот и сама змейка var snake = < // Начальные координаты x: 160, y: 160, // Скорость змейки — в каждом новом кадре змейка смещается по оси Х или У. На старте будет двигаться горизонтально, поэтому скорость по игреку равна нулю. dx: grid, dy: 0, // Тащим за собой хвост, который пока пустой cells: [], // Стартовая длина змейки — 4 клеточки maxCells: 4 >; // А это — еда. Представим, что это красные яблоки. var apple = < // Начальные координаты яблока x: 320, y: 320 >;
2. Сделаем генератор случайных чисел. Он нам понадобится, чтобы размещать еду на поле случайным образом.
// Делаем генератор случайных чисел в заданном диапазоне
function getRandomInt(min, max) < return Math.floor(Math.random() * (max - min)) + min;
3. Напишем основной игровой цикл, который будет работать бесконечно.
// Игровой цикл — основной процесс, внутри которого будет всё происходить function loop() < // Дальше будет хитрая функция, которая замедляет скорость игры с 60 кадров в секунду до 15. Для этого она пропускает три кадра из четырёх, то есть срабатывает каждый четвёртый кадр игры. Было 60 кадров в секунду, станет 15. requestAnimationFrame(loop); // Игровой код выполнится только один раз из четырёх, в этом и суть замедления кадров, а пока переменная count меньше четырёх, код выполняться не будет. if (++count < 4) < return; >// Обнуляем переменную скорости count = 0; // Очищаем игровое поле context.clearRect(0, 0, canvas.width, canvas.height); // Двигаем змейку с нужной скоростью snake.x += snake.dx; snake.y += snake.dy; // Если змейка достигла края поля по горизонтали — продолжаем её движение с противоположной стороны if (snake.x < 0) < snake.x = canvas.width - grid; >else if (snake.x >= canvas.width) < snake.x = 0; >// Делаем то же самое для движения по вертикали if (snake.y < 0) < snake.y = canvas.height - grid; >else if (snake.y >= canvas.height) < snake.y = 0; >// Продолжаем двигаться в выбранном направлении. Голова всегда впереди, поэтому добавляем её координаты в начало массива, который отвечает за всю змейку. snake.cells.unshift(< x: snake.x, y: snake.y >); // Сразу после этого удаляем последний элемент из массива змейки, потому что она движется и постоянно особождает клетки после себя if (snake.cells.length > snake.maxCells) < snake.cells.pop(); >// Рисуем еду — красное яблоко context.fillStyle = 'red'; context.fillRect(apple.x, apple.y, grid - 1, grid - 1); // Одно движение змейки — один новый нарисованный квадратик context.fillStyle = 'green'; // Обрабатываем каждый элемент змейки snake.cells.forEach(function (cell, index) < // Чтобы создать эффект клеточек, делаем зелёные квадратики меньше на один пиксель, чтобы вокруг них образовалась чёрная граница context.fillRect(cell.x, cell.y, grid - 1, grid - 1); // Если змейка добралась до яблока. if (cell.x === apple.x && cell.y === apple.y) < // увеличиваем длину змейки snake.maxCells++; // Рисуем новое яблочко // Помним, что размер холста у нас 400x400, при этом он разбит на ячейки — 25 в каждую сторону apple.x = getRandomInt(0, 25) * grid; apple.y = getRandomInt(0, 25) * grid; >// Проверяем, не столкнулась ли змея сама с собой // Для этого перебираем весь массив и смотрим, есть ли у нас в массиве змейки две клетки с одинаковыми координатами for (var i = index + 1; i < snake.cells.length; i++) < // Если такие клетки есть — начинаем игру заново if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) < // Задаём стартовые параметры основным переменным snake.x = 160; snake.y = 160; snake.cells = []; snake.maxCells = 4; snake.dx = grid; snake.dy = 0; // Ставим яблочко в случайное место apple.x = getRandomInt(0, 25) * grid; apple.y = getRandomInt(0, 25) * grid; >> >); >
4. Сделаем управление стрелочками на клавиатуре.
// Смотрим, какие нажимаются клавиши, и реагируем на них нужным образом document.addEventListener('keydown', function (e) < // Дополнительно проверяем такой момент: если змейка движется, например, влево, то ещё одно нажатие влево или вправо ничего не поменяет — змейка продолжит двигаться в ту же сторону, что и раньше. Это сделано для того, чтобы не разворачивать весь массив со змейкой на лету и не усложнять код игры. // Стрелка влево // Если нажата стрелка влево, и при этом змейка никуда не движется по горизонтали… if (e.which === 37 && snake.dx === 0) < // то даём ей движение по горизонтали, влево, а вертикальное — останавливаем // Та же самая логика будет и в остальных кнопках snake.dx = -grid; snake.dy = 0; >// Стрелка вверх else if (e.which === 38 && snake.dy === 0) < snake.dy = -grid; snake.dx = 0; >// Стрелка вправо else if (e.which === 39 && snake.dx === 0) < snake.dx = grid; snake.dy = 0; >// Стрелка вниз else if (e.which === 40 && snake.dy === 0) < snake.dy = grid; snake.dx = 0; >>);
5. Запускаем игру. Для этого достаточно запустить предыдущий бесконечный цикл, поэтому пишем:
6. Наслаждаемся результатом:
Чтобы у вас тоже получилось такое, просто скопируйте готовый код, сохраните его как HTML-файл и откройте в браузере.
Готовый код
Змейка html, body < height: 100%; margin: 0; >/*Задаём глобальные параметры*/ body < background: black; display: flex; align-items: center; justify-content: center; >/*Делаем границу вокруг игрового поля*/ canvas
Как улучшить
Этот код — самая простая реализация змейки, и игру можно сделать ещё лучше:
- выводить количество набранных очков;
- сделать так, чтобы нельзя было проходить сквозь стены;
- добавить препятствия;
- поставить таймер — кто больше соберёт еды за 5 минут;
- добавить вторую змейку и играть вдвоём.
Проголосуйте за тот вариант, который вам больше всего нравится, в комментариях, или сделайте свою змейку, где всё это будет одновременно.
Апскиллинг, как говорится
Апскиллинг — это, например, переход с уровня junior на уровень middle, а потом — senior. У «Яндекс Практикума» есть курсы ровно для этого: от алгоритмов и типов данных до модных фреймворков.

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
