100kitov.ru

Интересные факты — события, биографии людей, психология
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Создание игры «Тетрис»

Тетрис на JavaScript

Отменяйте все дела, переносите встречи. Сегодня мы делаем тетрис, в который можно играть в браузере и на который можно потратить весь день.

В чём идея

Правила игры все знают: сверху в двумерный игровой стакан падают фигуры разной формы, составленные из модульных блоков. Внизу блоки соединяются. Если собрать целую горизонтальную линию из блоков, она исчезает, все остальные блоки сдвигаются на ряд ниже.

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

Если вдруг не знаете, как это работает, вот фрагмент с чемпионата мира по тетрису:

Код не мой

Код, который мы разбираем в этом проекте, написал американский разработчик Стивен Ламберт:

В этой статье мы объясним, как этот код работает.

Неожиданная сложность

Самое главное при программировании такой игры — это как-то хранить содержимое игрового экрана и учитывать движение фигур.

Если бы мы писали эту игру на Unreal Engine или Unity, первым интуитивным решением было бы сделать для блоков какую-то сущность типа объекта. У него были бы свойства — например, конфигурация. Может быть, мы бы захотели потом сделать взрывающиеся объекты или объекты с заморозкой, объекты с повышенной скоростью, отравленные объекты или что-то ещё в таком духе.

Но есть нюанс: смысл объекта в том, что он неделимый. А в «Тетрисе» все объекты запросто делятся, когда мы «закрываем линию». У какой-нибудь Т-образной фигуры может запросто пропасть хвостик, а у Z-образной фигуры — нижняя перекладина.

Получается, что фигура в тетрисе выглядит как объект, иногда ведёт себя как объект, но не обладает свойствами объекта. Поэтому объектный подход нам здесь не подходит.

Решение — представить игровое поле в виде двумерного массива нулей и единиц. Ноль означает, что клетка свободна, а единица — что занята какой-то частью фигуры. Хранить и обрабатывать двумерный массив довольно просто, поэтому решение кажется логичным.

Сами фигуры тоже представим в виде двумерного массива из нолей и единиц, но особым образом — в виде квадрата, где единицы отвечают за части фигуры, а ноли — за пустое место:

Если вместо квадрата просто взять фактические размеры фигуры и загнать их в массив, то при вращении они не влезут в исходный массив. А внутри квадрата их можно вращать как угодно — размер массива от этого не изменится:

Получается, что если мы добавим в общий массив с игровым цветом параметр, который отвечает за цвет, то можем рисовать каждую фигуру своим цветом. Так и сделаем.

Подготовка страницы

Игра будет работать на HTML-странице с помощью элемента Canvas — это холст, на котором мы можем рисовать произвольные фигуры через JavaScript.

Возьмём пустую страницу и сразу нарисуем на ней игровое поле. Сразу сделаем чёрный фон, игровое поле поставим по центру, а его рамки сделаем белыми:

Всё остальное сделаем скриптом. Добавим тэг <script>..</script> сразу после того, как нарисовали холст, и начнём писать содержимое скрипта.

Заводим переменные и константы

Пока что всё просто:

  • делаем массив для игрового поля и заполняем его;
  • делаем массивы, которые хранят наши фигуры и их цвета;
  • в отдельном массиве будем хранить новые фигуры, которые появятся следующими;
  • делаем флаг остановки игры. Пока он не сработает — можно играть.

Генерируем выпадающие фигуры

Первое, что нам понадобится для этого, — функция, которая выдаёт случайное число в заданном диапазоне. По этому числу мы будем выбирать фигуры.

Теперь мы можем создать последовательность из выпадающих фигур. Логика будет такая:

  1. Задаём обычную последовательность доступных фигур.
  2. Случайным образом забираем оттуда фигуру и помещаем в игровую последовательность.
  3. Так делаем до тех пор, пока от обычной последовательности ничего не останется.
  4. У нас получилась случайная игровая последовательность фигур, с которыми мы будем работать дальше.

Последний этап в этом блоке — получить из игровой последовательности, которую мы только что сделали, следующую фигуру, которая у нас появится. Мы должны знать, что это за фигура; как она рисуется; откуда она начинает движение. Обратите внимание: на выходе мы получаем не только двумерный массив с фигурой, а ещё и название и её координаты. Название нам нужно для того, чтобы знать, каким цветом рисовать фигуру.

Движение, вращение и установка фигуры на место

В тетрисе мы можем вращать каждую фигуру на 90 градусов по часовой стрелке сколько угодно раз. А так как у нас фигура — это двумерный массив из чисел, то быстро найдём в интернете готовый код для поворота числовой матрицы:

После каждого поворота и при каждой смене позиции нам нужно проверить, а может ли в принципе фигура так двигаться? Если движению или вращению мешают стенки поля или другие фигуры, то нужно сообщить программе, что такое движение делать нельзя. Дальше мы будем делать эту проверку перед тем, как что-то отрисовывать на экране.

Если проверка не прошла, то мы не делаем последнее движение, и фигура просто продолжает падать вниз. Если ей некуда падать и она упёрлась в другие, то нам нужно зафиксировать это в игровом поле. Это значит, что мы записываем в массив, который отвечает за поле, нашу матрицу фигуры, пропуская ноли и записывая только единицы.

Как только фигура встала, нам нужно проверить, получился целый ряд или нет. Если получился — сдвигаем на один ряд вниз всё, что сверху. Такую проверку делаем каждый раз при установке фигуры и начинаем с нижнего ряда, поднимаясь наверх.

Читайте так же:
Ученые используют наблюдения ассирийских астрономов для исследования солнечной активности

Что будет, когда мы проиграем

Когда фигура при окончательной установке вылезает за границы игрового поля, это значит, что мы проиграли. За это у нас отвечает флаг gameOver, и его задача — остановить анимацию игры.

Чтобы было понятно, что игра закончена, выведем надпись GAME OVER! прямо поверх игрового поля:

Обрабатываем нажатия на клавиши

Всё как в обычном тетрисе: стрелки влево и вправо двигают фигуру, стрелка вверх поворачивает её на 90 градусов, а стрелка вниз ускоряет падение.

Единственное, о чём нужно не забыть — после каждого нажатия вызвать проверку, можно ли так двигать фигуру или нет.

Запускаем движения и анимацию

Смысл главного цикла игры такой:

  • на каждом кадре мы очищаем игровое поле и отрисовываем его заново с учётом упавших фигур;
  • рисуем текущую фигуру в том месте, где она находится в данный момент.

Так как кадры меняются быстро, мы не заметим постоянного очищения и отрисовки. Нам будет казаться, что фигура просто движется вниз и реагирует на наши действия.

Последнее, что нам осталось сделать, — запустить игру:

// старт игры
rAF = requestAnimationFrame(loop);

Готовый результат можно посмотреть на странице с игрой.

Что дальше

У нас есть игра, но нет важных элементов:

  • подсчёта очков и статистики;
  • записи имён, чтобы понять, кто набрал больше очков;
  • звуковых эффектов;
  • ускорения падения после каждых, например, 10 собранных рядов.

Сделаем это в другой версии игры, а пока отменяйте планы, сегодня мы играем в бесконечный тетрис.

Часть №1: Создание игры «Тетрис» на С++/SFML

На написание данной статьи меня побудил ролик на YouTube, в котором автор за 4 минуты с нуля, используя графическую библиотеку SFML, создает рабочий прототип Тетриса. Всё это происходило в ускоренном воспроизведении под веселую вариацию оригинальной тетрис-мелодии «Коробейники». За всё это время автор не произнес ни слова, а сам ролик содержал несколько довольно неочевидных моментов, которые требовали пояснений. В связи с этим я решил взять на себя ответственность и восполнить данный пробел.

Первые шаги

Как вы уже знаете, практически любое SFML-приложение начинается с минимального каркаса:

Далее мы создаем спрайт для представления различных элементов фигур в нашей игре и присваиваем ему файл текстуры — tiles.png. Сам файл текстуры у меня располагается по пути C:devSFML_Tutorialimages . Текстура являет собой 8 разноцветных квадратов, горизонтально стоящих друг за другом. Размер каждого такого квадрата составляет 18×18 пикселей.

Ниже представлен код, который подгружает эту текстуру в программу:

Если мы скомпилируем и запустим наш проект, то увидим следующее:

Теперь нужно выделить из всего спрайта отдельный квадратик, чтобы можно было его использовать для построения нужных нам фигур Тетриса. Для этого воспользуемся методом setTextureRect():

В результате мы получим следующее:

Работа с фигурками Тетриса

Следует вопрос: «А как мы можем программным образом представить наши фигурки?». Давайте рассмотрим наши фигурки более детально. Из Википедии можно узнать, что наши фигурки называются тетрамино, т.к. каждая такая фигура состоит из 4 квадратов. Всего же в Тетрисе используется 7 таких фигурок-тетрамино:

Поэтому вполне логично описать их в виде целочисленного двумерного массива с 7-ю строками (по числу фигур) и 4-мя столбцами, задающими форму каждой конкретной фигуры. Возникает еще один вопрос: «А каким образом мы будем задавать форму тетрамино?».

Ответ вы найдете ниже:

Стоит отметить, что нам не важен порядок следования закрашенных квадратов. Важен лишь сам факт: закрашен квадрат или нет. Поэтому первой фигуре с множеством «точек» (3, 5, 4, 6) в соответствие можно поставить как набор (5, 3, 4, 6) , так и набор (3, 5, 6, 4) .

Игровое поле также можно представить в виде целочисленного массива field[M][N] , где M — это высота поля, а N — его ширина. Добавим эти элементы в наш код:

Привязка тетрамино к координатам игрового поля

Итак, у нас есть массив, в котором содержатся все тетрамино. Следующим шагом будет их отображение на игровом поле. А для этого нужно каким-то образом связать локальные координаты, которыми задаются фигурки-тетратимино, с глобальными координатами игрового поля. Для решения данной задачи в качестве примера рассмотрим Z-тетрамино. Напомню, что отсчет координат игрового поля начинается с верхнего левого угла:

Видно, что, например, квадрат №5 будет иметь координаты (1;2) , квадрат №6 будет иметь координаты (0;3) и т.д. Но как описать данные факты математически? Чтобы разобраться с этим вопросом, сделаем небольшое лирическое отступление. Для этого возьмите листик и начните выписывать в строку числа от 0 до 40 так, чтобы в каждой строке было ровно 10 чисел.

У вас должно получиться следующее:

1 2 3 9
1011121319
2021222329
3031323339

Сразу бросается в глаза, что в числах каждой отдельно взятой строки содержится одинаковое количество десятков. Например, в первой строке (числа 0, 1, 2, 3…) у нас 0 десятков, во второй строке (числа 10, 11, 12, 13…) у нас по одному десятку в каждом числе, в третьей строке (числа 20, 21, 22, 23…) у нас уже по 2 десятка в каждом числе и т.д.

Если теперь рассмотреть столбцы, то номера столбцов соответствуют остатку от деления на 10, содержащемуся в числе из этого столбца. Например, возьмем столбец №1 и число 21. Если мы 21 разделим на 10, то получим в результате 2 с остатком 1 ( 21 = 2 * 10 + 1 ), что соответствует номеру столбца. Если возьмем столбец №9 и число 39, также разделим его на 10, то получим в результате 3 с остатком 9 ( 39 = 3 * 10 + 9 ), что также соответствует номеру столбца. В итоге, если продолжать этот процесс, то наша исходная таблица примет следующий вид:

Читайте так же:
Как голуби пьют воду, не поднимая головы? Описание, фото и видео
1 2 3 9
0=*0+1=*0+12=*0+23=*0+39=*0+9
1010=1*10+11=1*10+112=1*10+213=1*10+319=1*10+9
2020=2*10+21=2*10+122=2*10+223=2*10+329=2*10+9
3030=3*10+31=3*10+132=3*10+233=3*10+339=3*10+9

Здесь уже становится понятно, что остаток от деления на 10 задает расположение числа по горизонтали, а количество десятков (частное от деления на 10) — задает расположение по вертикали. Если вспомнить, что в программировании оператор % — это остаток от деления, а оператор / — частное от деления (деление нацело), то, например, число 32 в нашей таблице будет иметь координаты (2;3) = (32 % 10; 32 / 10) .

Вернемся к нашей исходной задаче:

Т.к. здесь в каждой строке не 10 чисел, а 2 числа, то делить нужно на 2, а не на 10. И тогда квадратик №5 будет иметь координаты (1;2) = (5 % 2; 5 / 2) .

Отображение тетрамино на игровом поле

А сейчас попробуем отобразить наши фигурки на игровом поле. Для этого, прежде всего, нужно создать структуру Point , которая будет представлять точку с целыми координатами + два вспомогательных массива a[] и b[] :

Затем с помощью первого цикла for мы переведем «локальные» координаты каждого отдельного кусочка тетрамино в «глобальные», а затем с помощью второго цикла for отобразим это всё на игровом поле. При этом стоит учитывать, что размеры отдельного кусочка составляют 18×18 пикселей:

Результат выполнения программы:

Заключение

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

Поделиться в социальных сетях:

Спрайты и текстуры в C++/SFML

Комментариев: 18

как можно сделать чтобы фигуры падали с разных частей игрового поля, а не только из левого угла как на примере?

sprite.setPosition(a[i].x * 18, a[i].y * 18) — это координаты, в которых появляется наша фигура. Т.е. что-бы наша фигура появилась правее всё, что нужно сделать — добавить к значению х число, на которое мы хотим изменить положение
sprite.setPosition(a[i].x * 18 + 18, a[i].y * 18), к примеру, сдвинет фигуру на 18 вправо

Неточность — квадрат №6 будет иметь координаты (3;0)
6%2=0
6/2 =3

квадрат №6 будет иметь координаты (0;3)

А материал крайне интересный хотя и сложно пока иметь дело с координатами.

все верно написано,
когда делишь с остатком (%) получаешь координату Х
когда делишь что бы узнать частное ( / ) получаешь координату Y

2D система координат выглядит вот так:

а как убрать тетрамино (O) маленький один квадрат, чтоб фигур было не 7 а шесть,

вроде убрано но все равно квадрат появляется первым

/>Дмитрий Бушуев :

>>вроде убрано но все равно квадрат появляется первым
Не может такого быть.
Загрузите свой код, например, сюда — https://ideone.com/
и выложите ссылку. Посмотрим, что там за магия

У меня тоже получился похожий результат, но стиль кода из примера местами режет глаз. Я бы даже сказала, противоречит тому, чему нас тут учили в уроках по С++:
1) используются глобальные константы и даже переменные
2) не "говорящие" названия используемых констант и переменных, один массив a[4] чего стоит!
3) куча используемых литералов (2, 18, 4), которые тоже просятся в константы
4) поголовно используется тип int, хотя для перечисления четырёх кубиков вполне хватило бы какого-то менее "тяжёлого" типа.
И, наконец, зачем нужен массив b[4]? по крайней мере для этого урока он лишний.
Я осознаю, что автор руководствовался вдохновившим видеороликом, но мне кажется, что перечисленные мной моменты желательно было бы адаптировать.

/>Дмитрий Бушуев :

Спасибо за конструктивную критику.
Действительно, в коде есть места, которые требуют доработки. Но на это (отчасти) и был расчет. Что пользователь, который уже освоил бОльшую часть статей на данном сайте, сначала попробует выполнить проект в первозданном виде, убедится в его работоспособности и дальше уже будет изменять его так, как ему захочется.

Еще одна причина, по которой не стал переделывать код, это то, что статья выходила частями. Я подумал, что могут найтись пользователи (обычно, так и бывает), которым будет невтерпеж увидеть полностью завершенный вариант проекта, а не ждать окончания всего цикла уроков. Из-за того, что код данных статей полностью соответствует видеоролику, то пользователи смогут без проблем самостоятельно продолжить написания игры.

Теперь, касаемо непосредственно ваших замечаний:
— Всё верно, имена переменных (и других объектов) всегда должны быть осмысленными. Иначе потом можно в них легко запутаться;
— Это же относится и к т.н. "магическим числам" по типу (2, 18, 4). Их желательно выносить в константы, чтобы потом в случае чего (например, изменились размеры текстур/спрайтов/ширины поля/и т.д.) не рыться по всему коду, выискивая вхождения каждого такого числа.
-Насчет массива b[4] — действительно, он объявлен заранее, чтобы пригодиться нам в дальнейшем. Чуть позже я попрошу Администратора сделать необходимые коррективы в статье.

Читайте так же:
Почему Ленин взял себе такой псевдоним? Причины, фото и видео

Насчет адаптации Ваших замечания — скорее всего это будет сделано в виде предисловия/упоминания в статье. Данный пример создания игры не ставит перед собой задачу создать идеальный код, а только лишь показывает возможности языка C++ в связке с SFML. Статья чем-то схожа со школьным учителем, который на доске от руки рисует немного косые линии и окружности (больше похожие на овалы) или объясняет про векторы в стиле — "ну это такие стрелочки…".

P.S.: Еще раз спасибо за замечания и проявленный к статье интерес. Всегда буду рад ответить на ваши вопросы

Для меня одной не очевидно, где взять картинку с кубиками, чтобы каждый был по 18 пикселей?

6 фактов о Пажитнове и «Тетрисе», которые вы наверняка не знали

Меня зовут Денис и я пишу книгу об истории русскоязычного геймдева. Для книги я беру интервью с разработчиками знаковых игр, первое взял у Алексея Пажитнова, создателя «Тетриса». Ниже подборка неожиданных фактов об Алексее Пажитнове, собранная из трёх интервью.

Первая игра Алексея Пажитнова – не «Тетрис», а Muddled Casino, которую он разработал в 1982:

В этой коллекции [имеется в виду Microsoft Entertainment Pack Puzzle Collection] есть игра Muddled Casino, что можно примерно перевести как «запутанное казино». И это была моя первая, самая первая игра, которую я когда-либо сделал — головоломка. Я придумал её ещё до «Тетриса» для «Электроники-60». Потом ребята портировали её на PC, существенно исправив, но сохранив идею. Хоть и тяжеловата она, у меня к ней тёплые чувства, потому что это первое, что я придумал.

Алексей пошёл работать в Вычислительный центр Академии наук из-за доступа к компьютерам. Вначале его взяли на должность «эксперта-патентоведа». Работа была несложной:

Как только у кого-нибудь возникал вопрос по поводу патентов, я просто говорил: программное обеспечение объектом патентного права не является, поэтому ничем не могу помочь.

Таким образом у Алексея было достаточно времени для разработки игр.

Первые деньги от разработки игр Алексей получил от центра научно-технического творчества молодежи «Дока». После развала СССР ЦННТМ «Дока» стал акционерным обществом, но игра Алексея Пажитнова Welltris «подкармливала» компанию как минимум до 1996 года:

Как-то мы с Алексеем обсуждали развитие «Тетриса». У нас возникла идея: трёхмерный «Тетрис». Вернее, квазитрёхмерный стакан, в который падают двухмерные элементы. Алексей разработал эту игру, которая получила название Welltris, и мы продали её от своего имени компании Spectrum HoloByte. […] Деньги мы заработали приличные, закупили на них компьютеры и, наверное, стали самой компьютеризированной компанией в Советском Союзе: количество компьютеров у нас почти равнялось количеству работников.

С помощью специальной версии «Тетриса» лечат «посттравматическое стрессовое расстройство (ПТСР)»:

Было известно, что одна из главных причин этого синдрома состоит в так называемых флешбэках, то есть в навязчивых воспоминаниях о травмирующих сценах. У учёных возникла идея: вытеснив флешбэки, можно вылечить постравматический синдром. Так вот, оказалось, что «Тетрис» — это лучшая «вытеснялка», и учёные сделали медицинское устройство со специальной версией «Тетриса».

Алексей уже более 7 лет каждый день минут 15-20 играет в Empires and Puzzles. Также он регулярно просматривает AppStore в поисках новых интересных головоломок и иногда находит что-то интересное:

Или вот The Witness — «Cвидетель». Это сборник головоломок, совмещённый с трёхмерной ходилкой. Вы ходите по какому-то пустому, но очень интересно организованному пространству и встречаете головоломные экраны, на которых должны решить головоломки. Причём головоломки в очень широком диапазоне, от предельно простых до очень сложных. Я в неё поиграл дня три-четыре и отложил, чтобы доиграть, когда у меня будет настроение получше.

Тетрис на JavaScript + изучение современных возможностей языка

Лучший способ узнавать новое и закреплять полученные знания – практика. Лучшая практика в программировании – создание игр. Лучшая игра в мире – Тетрис. Сегодня мы будем узнавать новое в процессе написания тетриса на javaScript.

В конце руководства у нас будет полностью функционирующая игра с уровнями сложности и системой очков. По ходу дела разберемся с важными игровыми концепциями, вроде графики и игрового цикла, а также научимся определять коллизии блоков и изучим возможности современного JavaScript (ES6):

Весь код проекта вы можете найти в github-репозитории.

Тетрис

Эта всемирно известная игра появилась в далеком 1984 году. Придумал ее русский программист Алексей Пажитнов. Правила очень просты и известны каждому. Сверху вниз падают фигурки разной формы, которые можно вращать и перемещать. Игрок должен складывать их внизу игрового поля. Если получается заполнить целый ряд, он пропадает. Игра заканчивается, когда башня из фигурок достигает верха игрового поля.

Тетрис – великолепный выбор для первого знакомства с гейм-разработкой. Он достаточно прост для программирования, но в то же время содержит все принципиальные игровые элементы. К тому же в нем максимально простая графика.

Структура проекта

Для удобства разобьем весь проект на отдельные файлы:

  1. constants.js – конфигурация и правила игры;
  2. board.js — логика игрового поля:
  3. piece.js – управление фрагментами-тетрамино;
  4. main.js – инициализация и управление всей игрой;
  5. index.html – веб-страница с html-кодом проекта и подключением ресурсов;
  6. styles.css — стили для оформления игры;
  7. README.md .

Сразу же подключим все нужное в index.html:

Создание каркаса

Игровое поле состоит из 10 колонок и 20 рядов. Эти значения будут часто использоваться, поэтому мы сделаем их константами и вынесем в отдельный файл constants.js . В этом файле также укажем размер одного блока.

Читайте так же:
Как астрономы определяют расстояние до звезд и галактик?

Для отрисовки графики будем использовать холст – элемент HTML5 canvas. Добавим его в html-файл с инфраструктурой будущей игры:

Теперь в главном скрипте проекта main.js нужно найти элемент холста и получить контекст 2D для рисования:

Здесь мы используем установленные ранее константные значения.

Метод scale используется, чтобы избежать постоянного умножения всех значений на BLOCK_SIZE и упростить код.

Оформление

Для оформления такой ретро-игры идеально подходит пиксельный стиль, поэтому мы будем использовать шрифт Press Start 2P. Подключите его в секции head :

Теперь добавим основные стили в style.css :

Для разметки используются системы CSS Grid и Flexbox.

Вот, что у нас получилось:

Пустое игровое поле Пустое игровое поле

Игровое поле

Поле состоит из клеточек, у которых есть два состояния: занята и свободна. Можно было бы просто представить клетку булевым значением, но мы собираемся раскрасить каждую фигурку в свой цвет. Лучше использовать числа: пустая клетка – , а занятая – от 1 до 7, в зависимости от цвета.

Само поле будет представлено в виде двумерного массива (матрицы). Каждый ряд – массив клеток, а массив рядов – это, собственно, поле.

Создадим отдельный класс для представления игрового поля – Board и разместим его в файле board.js . Сразу же добавим пару важных методов:

Для создания пустой матрицы поля и заполнения ее нулями используются методы массивов: Array.from() и Array.fill().

Теперь создадим экземпляр класса Board в основном файле игры.

Функция play будет вызвана при нажатии на кнопку Play. Она очистит игровое поле с помощью метода reset :

Для наглядного представления матрицы удобно использовать метод console.table:

Тетрамино

Каждая фигурка в тетрисе состоит из четырех блоков и называется тетрамино. Всего комбинаций семь – дадим каждой из них имя (I, J, L, O, S, T, Z) и свой цвет:

Для удобства вращения каждое тетрамино будет представлено в виде квадратной матрицы 3х3. Например, J-тетрамино выглядит так:

Для представления I-тетрамино потребуется матрица 4×4.

Заведем отдельный класс Piece для фигурок, чтобы отслеживать их положение на доске, а также хранить цвет и форму. Чтобы фигурки могли отрисовывать себя на поле, нужно передать им контекст рисования:

Блок фигурки занимает несколько клеток на поле, но раскрашены должны быть только те, значение которых больше 0 . Для отрисовки нужно перебрать все ячейки блока в двойном цикле:

Итак, нарисуем первое тетрамино на поле:

Активная фигурка сохраняется в свойстве board.piece для удобного доступа.

Первое тетрамино на полеПервое тетрамино на поле

Управление с клавиатуры

Передвигать фигурки по полю (влево, вправо и вниз) можно с помощью клавиш-стрелок.

Добавим в класс Piece метод move , которые будет изменять текущие координаты тетрамино на поле.

Перечисления

Коды клавиш будут храниться в файле constants.js . Для этого удобно применять специальный тип данных – перечисление (enum). В JavaScript нет встроенных перечислений, поэтому мы воспользуемся обычным объектом:

Ключевое слово const не работает с полями объектов, а лишь запрещает переприсваивание данных в переменную KEY . Чтобы сделать константы в объекте неизменяемыми, мы используем метод Object.freeze(). При этом важно помнить две вещи:

  1. Для правильной работы этого метода нужен строгий режим выполнения;
  2. Метод работает только на один уровень вложенности.

Вычисляемые имена свойств

Теперь нужно сопоставить коды клавиш и действия, которые следует выполнить при их нажатии.

ES6 позволяет добавлять в объекты свойства с вычисляемыми именами. Другими словами, в имени свойства можно использовать переменные и даже выражения.

Для установки такого свойства нужны квадратные скобки:

Для перемещения тетрамино мы будем стирать старое отображение и копировать его в новых координатах. Чтобы получить эти новые координаты, сначала скопируем текущие, а затем изменим нужную ( x или y ) на единицу.

Так как координаты являются примитивными значениями, мы можем использовать spread-оператор, чтобы перенести их в новый объект. В ES6 существует еще один механизм копирования: Object.assign().

В объекте moves теперь хранятся функции вычисления новых координат для каждой клавиши. Получить их можно так:

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

Теперь добавим обработчик для события keydown:

Метод board.valid() будет реализован в следующем разделе. Его задача – определять допустимость новых координат на игровом поле.

board.js Управление с клавиатуры

Обнаружение столкновений

Если бы фигурки тетриса могли проходить сквозь друг друга, а также сквозь пол и стены игрового поля, игра не имела бы смысла. Важно проверить возможные столкновения элементов перед изменением их положения.

Возможные столкновения одного тетрамино:

  1. с полом при движении вниз;
  2. со стенками игрового поля при движении вправо или влево;
  3. с другими тетрамино, уже размещенными на поле.

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

Мы уже умеем вычислять новую позицию фигурки на поле при нажатии клавиш-стрелок. Теперь нужно добавить проверку на ее допустимость. Для этого мы должны проверить все клетки, которые будет занимать тетрамино в новом положении.

Для такой проверки удобно использовать метод массива every(). Для каждой клетки в матрице тетрамино нужно определить абсолютные координаты на игровом поле, а затем проверить, свободно ли это место и не выходит ли оно за границы поля.

Пустые клетки матрицы тетрамино при этом не учитываются.

Если проверка прошла удачно, передвигаем фигурку в новое место.

Читайте так же:
10 способов найти общий язык с любым человеком — это надо знать

Теперь мы можем добавить возможность ускоренного падения (hard drop) фигурок при нажатии на пробел. Тетрамино при этом будет падать пока не столкнется с чем-нибудь.

Вращение

Фигурки можно вращать относительно их «центра масс»:

Вращение тетрамино относительно центраВращение тетрамино относительно центра

Чтобы реализовать такую возможность, нам понадобятся базовые знания линейной алгебры. Мы должны транспонировать матрицу, а затем умножить ее на матрицу преобразования, которая изменит порядок столбцов.

Вращение тетрамино в двумерном пространствеВращение тетрамино в двумерном пространстве

На JavaScript это выглядит так:

Эту функцию можно использовать для вращения фигурок, но перед началом манипуляций с матрицей, ее нужно скопировать, чтобы не допускать мутаций. Вместо spread-оператора, который работает лишь на один уровень в глубину, мы используем трюк с сериализацией – превратим матрицу в JSON-строку, а затем распарсим ее.

Теперь при нажатии на клавишу Вверх, активная фигурка будет вращаться:

constants.js main.js Вращение фигурки при нажатии на клавишу Вверх

Случайный выбор фигурок

Чтобы каждый раз появлялись разные фигурки, придется реализовать рандомизацию, следуя стандарту SRS (Super Rotation System).

Добавим цвета и формы фигурок в файл constants.js:

Теперь нужно случайным образом выбрать порядковый номер тетрамино:

На этом этапе мы можем выбирать тип фигурки случайным образом при создании.

Добавим в класс Piece метод spawn :

Игровой цикл

Почти во всех играх есть одна главная функция, которая постоянно делает что-то, даже если игрок пассивен – это игровой цикл. Нам он тоже понадобится, чтобы фигурки постоянно генерировались и падали сверху вниз на игровом поле.

RequestAnimationFrame

Для совершения циклических действий удобно использовать метод requestAnimationFrame. Он сообщает браузеру о том, что нужно сделать, а браузер выполняет это во время следующей перерисовки экрана.

Мы создадим функцию animate , которая будет производить все необходимые перерисовки для одного цикла анимации, а затем рекурсивно вызовет сама себя для выполнения перерисовок следующего цикла. Самый первый вызов функции animate будет происходить внутри функции play .

Таймер

Нам также потребуется таймер, чтобы в каждом фрейме анимации «ронять» активное тетрамино вниз. Возьмем готовый пример с MDN и немного модифицируем его.

Для начала создадим объект для хранения нужной информации:

В цикле мы будем обновлять это состояние и отрисовывать текущее отображение:

Заморозка состояния

При достижении активной фигуркой низа игрового поля, ее нужно «заморозить» в текущем положении и создать новое активное тетрамино.

Для этого добавим классу Board метод freeze . Он будет сохранять положение фигурки в матрице игрового поля:

Теперь при достижении фигуркой низа поля, мы увидим в консоли, что матрица самого поля изменилась:

Добавим метод для отрисовки целого поля (с уже «замороженными» тетрамино):

Обратите внимание, что теперь объекту игрового поля тоже нужен контекст рисования, не забудьте передать его:

main.js Отрисовка уже размещенных тетраминоОтрисовка уже размещенных тетрамино

Очистка линий

Главная задача игры – собирать из блоков целые ряды, которые должны пропадать с поля, освобождая место для новых фигурок.

Добавим в класс Board метод для проверки, не собрана ли целая линия, которую можно удалить, и удаления всех таких линий:

Его нужно вызывать каждый раз после «заморозки» активного тетрамино при достижении низа игрового поля:

board.js Удаление собранных рядов

Система баллов

Чтобы сделать игру еще интереснее, нужно добавить баллы за сбор целых рядов.

Чем больше рядов собрано за один цикл, тем больше будет начислено очков.

Для хранения информации создадим новый объект accountValues .

При каждом изменении счета нужно обновлять данные на экране. Для этого мы обратимся к возможностям метапрограммирования в JavaScript – Proxy.

Прокси позволяет отслеживать обращение к свойствам объекта, например, для их чтения (get) или обновления (set) и реализовывать собственную логику:

Теперь при каждом изменении свойств объекта account будет вызываться функция updateAccount .

Добавим логику начисления очков в обработчик события keydown :

и в метод очистки собранных рядов:

Уровни

Чем лучше вы играете в тетрис, тем быстрее должны падать фигурки, чтобы вам не стало скучно. Придется добавить уровни сложности в нашу игру, постепенно увеличивая частоту фреймов игрового цикла.

Информация на экране о количестве собранных линий и текущем уровне должна обновляться при каждом изменении. Для этого добавим два новых поля ( lines и levels ) в объект accountValues .

Они будут обновляться по той же схеме, что и score , с помощью прокси-объекта.

Напишем отдельную функцию resetGame, в которую поместим всю логику для начала новой игры:

Теперь нужно немного обновить логику начисления очков за собранные линии. С каждым уровнем очков должно быть больше.

При сборке каждых десяти рядов, уровень будет повышаться, а скорость – увеличиваться.

Завершение игры

Игра завершается, когда пирамида фигурок достигает самого верха игрового поля.

Мы должны добавить проверку внутри метода board.drop . Если y-координата тетрамино равна нулю, значит, ему уже некуда падать.

Теперь метод drop возвращает false , если игру пора останавливать, и true – в остальных случаях.

Чтобы прервать игровой цикл, нужно отменить последний вызов requestAnimationFrame . Также нужно показать пользователю сообщение.

main.js Сообщение об окончании игрыСообщение об окончании игры

Следующая фигура

Для удобства игрока мы можем добавить подсказку – какая фигурка будет следующей. Для этого используем еще один холст меньшего размера:

Получим его контекст для рисования и установим размеры:

Осталось внести изменения в метод board.drop :

Теперь игрок знает, какое тетрамино будет следующим, и может выстраивать стратегию игры.

Подсказка о следующей фигуркеПодсказка о следующей фигурке

голоса
Рейтинг статьи
Ссылка на основную публикацию