Как сделать тетрис на ардуино
В предыдущих статьях этой серии мы уже успели написать сапёра, змейку и десктопный клон игры 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 нет) делегатов метод, в котором в зависимости от переданного состояния поворота возвращаем разные реальные координаты блоков. В общем-то, можно обойтись и без делегатов, если хранить в каждом элементе отсупы для каждого из режимов поворота.
После первого эксперимента с ардуино, джойстиком и сервомотором я начал думать, как еще можно использовать стик. Так как у меня был дисплей 128x64 для arduino, было решено сделать что-нибудь с ним. Наиболее простой вариант - передвигать фигуру по дисплею вслед за манипуляциями джойстика. Итак, у нас в наличии: аналог arduino uno, джойстик и дисплей 128x64 точек.
Теперь собираем схему. Подключаем джойстик к arduino:
- gnd - gnd;
- 5v - 5v;
- VRX (сигнал для координат по оси X) - A1;
- VRY (сигнал для координат по оси Y) - A0;
- SW (режим кнопки - нажата или отпущена) - 2 пин.
Подключаем дисплей к arduino:
- gnd - gnd;
- vcc - 3.3v;
- scl - A5;
- sda - A4.
Открываем Arduino IDE, запускаем следующий скетч:
После загрузки скетча в центре дисплея отрисуется прямоугольник, который можно передвигать по осям X и Y манипулируя джойстиком. Вот видео с результатами работы:
Итак, в данной статье рассмотрен процесс подключения дисплея 128x64 точек и джойстика к Arduino Uno, приведен пример скетча, позволяющего двигать прямоугольник на дисплее манипулируя джойстиком.
В дальнейшем планирую написать простую игру наподобие тетриса или арканоида используя arduino uno, джойстик и дисплей.
Установка webmin для операционной системы Armbian (Orange Pi / Raspberry Pi). Install webmin for armbian.
В заметке описан способ доступа к фронтэнд (frontend) кэшу (cache) advanced приложения из бэкэнда (backend) для php фреймворка Yii2.
Приехал мне из китая вот такой удобный ЛСД шилд для ардуино Уно , но так же он подходит и для версии MEGA .
Шил по сути удобная платформа с дисплеем 1602 и 6ю кнопками для подключения к ардуино - Бутербродом .
Все это легко монтируется , как я раньше без него то жил ?
Купить платы можно на Алиэкспресс :
Для начала нужно залить скетч игры в вашу плату ардуино и настроить контрастность дисплея , а для игры необходимо нажать кнопку SELECT.
Функционал кнопок:
• UP - движение вверх
• DOWN - движение вниз
• RIGHT - ускорение
• LEFT - пауза
• SELECT - продолжить игру (пауза), старт новой игры
Для динамики игры, помимо ручного ускорения, каждые 5 секунд происходит программное увеличение скорости звездолета. На 25 секунде включается максимальная сложность "UNREAL" и скорость больше не растет.
После каждой игры на дисплее высвечивается время текущей игры, а также время лучшей попытки.
Сам код , работает на любой версии Ардуино ИДЕ
Подписывайся на Geek каналы :
★ Моя партнёрка с Aliexpress ★
★ Получай 10.5% скидку с любой покупки на Aliexpress! ★
★ Полезное браузерное приложение для кэшбэка ★
Похожие статьи
Сенсорная кнопка и Arduino
Подобные кнопки/датчики используют ваше тело как часть электрической цепи. Когда вы касаетесь чувствительной поверхности сенсорной кнопки, емкость цепи изменяется и фиксируется. Изменение емкости приводит к изменению выходного сигнала.
Вероятно, многим может показаться, что использовать подобный модуль непросто и периодически придется иметь дело с неожиданным результатом. Что ж, оказывается, что все не так. Сенсорная кнопка отлично справляется с поставленными задачами и не преподносит никаких сюрпризов, хотя стоимость и настораживает.
Подключение OLED I2C дисплея с размером экрана 0,96″ и разрешением 128*64 точек к плате Arduino UNO.
Сегодня мы рассмотрим подключение OLED I2C дисплея с размером экрана 0,96″ и разрешением 128*64 точек к плате Arduino UNO.
Делаем весы на ардуино Arduino , работа с тензонометрическим датчиком и платой HX711
В данной статье я соберу весы на базе ардуино ,подробно разобрав все тонкости подключения датчика к плате преобразователя HX711 и так же программной части .
Подключение геймпада \ джойстика к Arduino , беспроводной джойстик 2 4 dualshock от PS2
В данной статье я продемонстрирую простой способ подключения беспроводного геймпада 2.4 от плейстейшен к плате Arduino UNO
Выставка электроники Hong Kong Electronics Fair 2019 которую стоит посетить
Почему стоит посещать выставки? На хорошей Экспо всегда можно увидеть, что нас ждёт в ближайшее время, какие веяния и тенденции будут актуальными в ближайшие полгода. Hong Kong Electronics Fair – как раз одна из таких выставок, где экспоненты демонстрируют на что они способны, а мы – гости мероприятия знакомимся и активно тестируем продукты, оцениваем их и решаем, что станет хитом, что просто заслуживает интереса, а что обречено лежать без внимания на стенде. Напомним, что все это проводится под крышей красивейшего выставочного центра Гонконга – Hong Kong Convention & Exhibition Centre .
- Опубліковано 22 лис 2018
- Сегодня будем разбирать игру Tetris и её реализацию на Arduino.
Качай Vikings: War of Clans и получи 200 💰голды и 🏥 бесплатный щит:
➤ Android: bit.ly/2qLTtwU ➤ IOS: bit.ly/2K0MEA3
Конкурс MacbookPro - club168701342
КОМЕНТАРІ • 1 088
Ошибочка вышла! Всё таки в памяти один светодиод занимает 3 байта, просто хранится это число другим способом, не в одной большой переменной :)
Саш давай уже большую матрицу и игру марио со звуком. Я бы с удовольствием сделал такую по твоей технологии)
Примечание: не подключайте Arduino к компьютеру без внешнего питания 5V, так как лента потребляет большой ток!
Библиотеки
Для работы с кнопкой используется “мини библиотека” кнопки, которая просто обрабатывает клики с гашением дребезга:
Программа
Зададим константами все настройки и пины проекта:
Подключим библиотеку ленты и создадим ленту:
Также создадим две кнопки (структура описана выше в главе Библиотеки):
В блоке setup() настроим ленту, пин пищалки, а также запустим новую игру:
Функция новой игры мигает зонами игроков тремя цветами и пищит, после чего задаёт начальные условия:
Где newRound() и задаёт стартовые значения скорости и позиции шарика:
Выше использовалась функция blinkTone() , давайте посмотрим что у неё внутри:
Данная функция моргает и пищит с заданной частотой и временем. Моргает здесь функция fillZones() , которая просто красит начальный и конечный отрезки ленты указанными цветами. Она выглядит так:
В основном цикле программы у нас вызываются две функции: опрос кнопок и процесс самой игры:
Опрос кнопок – проверяем клик. Если игрок кликнул вне своей зоны – проиграл раунд. Кликнул внутри зоны – мяч отбивается и происходит пересчёт скорости: чем ближе к краю ленты, тем сильнее отскок.
Тут же используется функция gameOver() , которая сигнализирует о проигрыше раунда у конкретного игрока:
Функция реализована не очень красиво, можно сократить её вдвое. Это ваше домашнее задание =)
Процесс игры – тут нас встречает таймер на 10мс, в котором происходит движение “шарика” и его отрисовка:
Внутри таймера двигаем шарик и проверяем, не вылетел ли он за ленту:
Если вылетел – выходим из функции, чтобы не отрисовывать шарик в несуществующем месте ленты!
Дальше очищаем ленту, заново рисуем зоны и поверх – текущею позицию шарика. Координаты у нас десятикратные, поэтому делим на 10:
Видео
Читайте также: