Тетрис своими руками

Добавил пользователь Алексей Ф.
Обновлено: 17.09.2024

В предыдущих статьях этой серии мы уже успели написать сапёра, змейку и десктопный клон игры 2048. Попробуем теперь написать свой Тетрис.

Нам, как обычно, понадобятся:

  • 30 минут свободного времени;
  • Настроенная рабочая среда, т.е. JDK и IDE (например, Eclipse);
  • Библиотека LWJGL (версии 2.x.x) для работы с графикой (опционально). Обратите внимание, что для LWJGL версий выше 3 потребуется написать код, отличающийся от того, что приведён в статье;
  • Спрайты, т.е. картинки плиток всех возможных состояний (пустая, и со степенями двойки до 2048). Можно нарисовать самому, или скачать использовавшиеся при написании статьи.

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

С чего начать?

Начать стоит с главного управляющего класса, который в нашем проекте находится выше остальных по уровню абстракции. Вообще отличный совет — в начале работы всегда пишите код вида if (getKeyPressed()) doSomething() , так вы быстро определите фронт работ.

Это наш main() . Он ничем принципиально не отличается от тех, что мы писали в предыдущих статьях — мы всё так же инициализируем поля и, пока игра не закончится, осуществляем по очереди: ввод пользовательских данных ( input() ), основные игровые действия ( logic() ) и вызов метода отрисовки у графического модуля ( graphicsModule.draw() ), в который передаём текущее игровое поле ( gameField ). Из нового разве что метод sync — метод, который должен будет гарантировать нам определённую частоту выполнения итераций. С его помощью мы сможем задать скорость падения фигуры в клетках-в-секунду.

Вы могли заметить, что в коде использована константа FPS . Все константы удобно определять в классе с public static final полями. Полный список констант, который нам потребуется в ходе разработки, можно посмотреть в классе Constants на GitHub.

Оставим пока инициализацию полей на потом (мы же ещё не знаем, какие нам вообще понадобятся поля). Разберёмся сначала с input() и logic() .

Получение данных от пользователя

Код, честно говоря, достаточно капитанский:

Все данные от ввода мы просто сохраняем в соответствующие поля, действия на основе них будет выполнять метод logic() .

Теперь уже потихоньку становится понятно, что нам необходимо. Во-первых, нам нужны клавиатурный и графический модули. Во-вторых, нужно как-то хранить направление, которое игрок выбрал для сдвига. Вторая задача решается просто — создадим enum с тремя состояниями: AWAITING, LEFT, RIGHT . Зачем нужен AWAITING ? Чтобы хранить информацию о том, что сдвиг не требуется (использования в программе null следует всеми силами избегать). Перейдём к интерфейсам.

Интерфейсы для клавиатурного и графического модулей

Так как многим не нравится, что я пишу эти модули на LWJGL, я решил в статье уделить время только интерфейсам этих классов. Каждый может написать их с помощью той GUI-библиотеки, которая ему нравится (или вообще сделать консольный вариант). Я же по старинке реализовал их на LWJGL, код можно посмотреть здесь в папках graphics/lwjglmodule и keyboard/lwjglmodule.

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

Отлично, мы получили от пользователя данные. Что дальше?

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

Сюда же добавим проверку на переполнение поля (в Тетрисе игра завершается, когда фигурам некуда падать):

Так, а теперь мы напишем класс для того магического gameField, в который мы всё это передаём, да?

Не совсем. Сначала мы пропишем поля класса Main и метод initFields() , чтобы совсем с ним закончить. Вот все поля, которые мы использовали:

А инициализировать мы их будем так:

Если вы решили не использовать LWJGL и написали свои классы, реализующие GraphicsModule и KeyboardHandleModule , то здесь нужно указать их конструкторы вместо, соответственно new LwjglGraphicsModule() и new LwjglKeyboardHandleModule() .

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

Класс GameField

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

Начнём по порядку.

Хранить информацию о поле…

…и о падающей фигуре

TpReadableColor — простой enum, содержащий элементы с говорящими названиями (RED, ORANGE и т.п.) и метод, позволяющий получить случайным образом один из этих элементов. Ничего особенного в нём нет, код можно посмотреть тут.

Это все поля, которые нам понадобятся. Как известно, поля любят быть инициализированными.
Сделать это следует в конструкторе.

Конструктор и инициализация полей

Далее в конструкторе стоит заполнить массив theField значениями константы EMPTINESS_COLOR , а countFilledCellsInLine — нулями (второе в Java не требуется, при инициализации массива все int‘ы равны 0). Или можно создать несколько слоёв уже заполненных ячейкам — на GitHub вы можете увидеть реализацию именно второго варианта.

А что это там за spawnNewFigure()? Почему инициализация фигуры вынесена в отдельный метод?

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

На этом с хранением данных мы закончили. Переходим к методам, которые отдают информацию о поле другим классам.

Методы, передающие информацию об игровом поле

Таких метода всего два. Первый возвращает цвет ячейки (для графического модуля):

А второй сообщает, переполнено ли поле (как это происходит, мы разобрали выше):

Методы, обновляющие фигуру и игровое поле

Начнём реализовывать методы, которые мы вызывали из Main.logic() .

Сдвиг фигуры

Что мы сделали в этом методе? Мы запросили у фигуры ячейки, которые бы она заняла в случае сдвига. А затем для каждой из этих ячеек мы проверяем, не выходит ли она за пределы поля, и не находится ли по её координатам в сетке статичный блок. Если хоть одна ячейка фигуры выходит за пределы или пытается встать на место блока — сдвига не происходит. Coord здесь — класс-оболочка с двумя публичными числовыми полями (x и y координаты).

Поворот фигуры

Логика аналогична сдвигу:

Падение фигуры

Сначала код в точности повторяет предыдущие два метода:

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

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

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

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

Класс фигуры

Rotation мод здесь будет выглядеть таким образом:

Соответственно, от самого класса Figure нам нужен только конструктор, инициализирующий поля:

И методы, которыми мы пользовались в GameField следующего вида:

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

Форма фигуры и маски координат

Чтобы не занимать лишнее место, здесь я приведу реализацию только для двух форм: I-образной и J-образной. Код для остальных фигур принципиально не отличается и выложен на GitHub.

Реализуем методы, которые использовали выше:

Ну а сами маски координат я предлагаю просто захардкодить следующим образом:

Т.е. для каждого объекта enum‘а мы передаём с помощью импровизированных (других в Java нет) делегатов метод, в котором в зависимости от переданного состояния поворота возвращаем разные реальные координаты блоков. В общем-то, можно обойтись и без делегатов, если хранить в каждом элементе отсупы для каждого из режимов поворота.


Один из наборов для самостоятельной сборки jolliFactory поставляется с модулем управления двухцветной светодиодной матрицей. Данный модуль позволяет соединять последовательно большое количество модулей в соответствии с требованиями вашего проекта.

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

Видеоигра тетрис была выпущена в 1984 году и имела огромный успех для портативной ручной приставки Game Boy, выпущенной в 1989 году, которая сделала ее популярной до настоящего времени.

Просто ради развлечения, мы решили создать простой тетрис с использованием двух последовательно соединенных двухцветных светодиодных модулей, которые управляются микроконтроллером Arduino. Мы просмотрели много сайтов в интернете, но не нашли ничего подобного.

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

Для выполнения данного проекта необходимы базовые знания электроники, некоторые умения по пайке компонентов и знание микроконтроллера Arduino.

Ниже показано видео работы готового устройства.

Шаг 1: Изготовления драйвера управления двухцветным светодиодным модулем


В данном проекте используются две светодиодные матрицы, управляемые Arduino Nano. Нам понадобится два набора Bi-color (Red/Green) LED Matrix Driver Module Kits от jolliFactory. Каждый из этих модулей использует две микросхемы-драйвера MAX7219 для управления двухцветной светодиодной матрицей. Данные микросхемы идеально пригодны для нашего проекта, поскольку снимают основную работу с микроконтроллера и облегчают разводку логических элементов устройства.

Двухцветные светодиодные матрицы можно приобрести здесь.

Данный набор имеет все необходимые компоненты. Для сборки нужны некоторые навыки пайки.

Ниже показано видео по сборке светодиодного модуля:

Шаг 2: Подключение





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

Для навигации и вращения блоков игры тетрис понадобится SPST панель с четырьмя нажимными кнопками.

За исключением двух двухцветных светодиодных модулей и четырех нажимных кнопок, мы выполнили монтажные соединения всей схемы на небольшой перфорированной плате размером около 60 мм x 60 мм.

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

Шаг 3: Программирование платы Arduino

В плату Arduino необходимо загрузить скетч для управления дисплеем.

Для этого мы использовали Arduino IDE V1.03.

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

Шаг 4: Корпус и окончательная сборка







Поскольку данный проект задумывался для сайта FUN factor без намерения использовать устройство длительное время, мы не уделили большое внимание созданию надежного корпуса. Однако изготовленный корпус позволяет удобно держать устройство и комфортно играть.

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

Прикрепленные файлы:

topa_biser Опубликована: 08.07.2014 0 0


Вознаградить Я собрал 0 0

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

Как сделать тетрис классический

Избалованные i2c шиной скажут "фу", ведь у нас 4 провода управления SCLK, DIN, DC, CE и RTS которые соответственно подключаются к Arduino, 3, 4, 5, 6, 7. Не забываем подключить питание 3.3 вольта, и землю.

Для рисования будем использовать библиотеку Adafruit GFX Library. В архиве с ней есть примеры использования.
Для управления игрой используем ИК пульт от телевизора. Сигнал будет принимать датчик TL1838.

Как сделать тетрис классический

Я использовал Arduino Uno, т.к. она уже имеет стабилизатор на 3.3 вольт.
Подключение IR приемника:
pin 8 — IR (управляющий). Питание на +5V и GND.
Подключение пьезодинамика:
pin 9 — speaker, Земля на GND.

Как сделать тетрис классический


Создадим на этой матрице игру Тетрис. Управление с помощью уже существующей аналоговой клавиатуры.

Переход в игру и обратно по кнопке red.
Создаем простенько
Назначение кнопок:
green левая, правая — фигура влево/вправо
green вверх — поворот фигуры но 90 градусов
white — увеличение скорости на 25 пунктов
red — выход из игры

Для реализации игры создадим массив со всеми изображениями фигур для Тетриса arrFigure[][], массив указателей на новую фигуру при повороте, transFigure[], а также структуру FIGURA для сохранения информации о текущей фигуре(положении на экране, )

arrFigure[][5] — первые четыре байта —
построчно для четырех(max высота фигуры) строк значение строки буфера экрана displayArray[24]
для пустой строки, занятой только фигурой
пятый байт — ширина фигуры (при движении влево-вправо проще вычислениями следить за границей экрана, при повороте это делать сложнее,
поэтому введен этот параметр)
Новая фигура помещается в верхнюю невидимую часть буфера экрана (байты 20-23)

Добавление фигуры на поле выполняет процедура add_figura(). Фигура выбирается с помощью функции random(). В структуру FIGURA заносятся следующие начальные значения:
— offsetY – номер фигуры, выдаваемый функцией random();
— offsetX=3
— int offsetY=20 (верх буфера экрана);
— с — номер фигуры;
— length — ширина;
— FIGURA1.driving=1 – фигура движется.
и вывести изображение фигуры в буфер эграна. Данные при этом заносятся в верхнюю невидимую часть буфера экрана (байты 20-23 массива displayArray).

Рассмотрим как осуществляется движение фигуры вниз.
Через время установленное в переменной speed2 (меняется нажатием на кнопку white) происходит стирание из буфера экрана фигуры (процедура figura_clear()), вычисление нового положения фигуры, прорисовка (занесение в буфер экрана) фигуры в измененных коорднатах (процедура figura_driving()).

Перед изменением положения фигуры (вниз, влево, вправо, поворот) необходимо производить проверку на столкновение с другими (уже неподвижными фигурами) и выход из границ. Для движения влево, вправо, поворот изменение положения производится до движения вниз. Для движения вниз при положительной проверке на столкновение происходит останов фигуры (FIGURA1.driwing=0). Процедуры проверки на столкновение collision1() и collision2()

Читайте также: