Рубрики
Схемы на Arduino

Игра Тетрис на Arduino и OLED дисплее

В данной статье мы рассмотрим создание всемирно известной игры «Тетрис» с помощью платы Arduino и OLED дисплее. На первый взгляд эта задача может показаться невообразимо сложной, но не волнуйтесь, я думаю с помощью нашего руководства ее решение не доставит вам особых хлопот.

Кратко принцип работы нашего проекта можно посмотреть в следующем видео.

https://youtube.com/shorts/sh8OwKF8QAw?si=fw5GKO9VtuhY5mcB

Необходимые компоненты

  1. Плата Arduino Nano (купить на AliExpress).
  2. OLED дисплей с интерфейсом I2C и диагональю экрана 1.3 дюйма (1.3 inch I2C OLED display) (купить на AliExpress — для данного проекта выбирайте вариант дисплея с 4 контактами).
  3. Кнопки — 4 шт.
  4. Зуммер (купить на AliExpress).
  5. Батарейка на 9 В.
  6. Макетная плата.
  7. Соединительные провода.

Распиновка OLED дисплея

Назначение контактов (распиновка) OLED дисплея с интерфейсом I2C приведена на следующем рисунке.

GND (Ground) — общий провод (земля).

VCC — контакт подачи питания на дисплей.

SCL (Serial Clock) — контакт для передачи синхросигналов в интерфейсе I2C.

SDA (Serial Data) — контакт для передачи данных в интерфейсе I2C.

Схема проекта

Схема проекта игры Тетрис на Arduino и OLED дисплее приведена на следующем рисунке.

OLED-дисплей в нашем проекте подключается к плате Arduino Nano с помощью 4-контактного интерфейса I2C. Что касается кнопок, то вам необходимо подключить их к четырем цифровым входам на Arduino Nano. Этот процесс достаточно прост — просто подключите один контакт каждой кнопки к цифровому входу платы, а другой контакт — к контакту GND платы.

Затем подключите зуммер к цифровому выходу плате Arduino Nano. Наконец, чтобы подать питание на проект, подключите батарею 9 В к контакту Vin платы Arduino Nano (положительный провод) и контакту GND платы (отрицательный провод).

Объяснение кода программы для Arduino

Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.

Первым делом в коде программы подключим необходимые библиотеки для работы с OLED дисплеем.

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

После этого инициализируем объект для работы с OLED дисплеем с помощью библиотеки Wire, подавая на контакт сброса (reset pin) -1, что значит что он не будет использоваться.

Шестнадцатеричное представление лого сайта circuit digest (откуда взят оригинал данной статьи):

3D массив, задающий форму деталей тетриса. Он представляет собой форму буквы «S» если ее повернуть влево. Его первый индекс (2) задает количество вращений фигуры, второй индекс (2) задает количество строк, а третий индекс (4) представляет собой количество столбцов. Значения в этом массиве представляют положение блоков внутри фрагмента.

3D массив, который задает форму буквы «S» повернутой вправо.

3D массив, который задает форму буквы «L» повернутой влево.

3D массив задающий квадрат 2×2, ему необходимо только одно вращение.

3D массив, который задает форму буквы «T» повернутой во всех возможных 4-х направлениях.

3D массив, который задает линию («line») размером 1×4. Он включает две ротации — одну горизонтальную и одну вертикальную.

Далее инициализируем необходимые переменные, среди которых четыре переменных целого типа: для движения влево, вправо, изменения и скорости. Зададим им начальные значения 11, 9, 12 и 10 соответственно.

Следующий фрагмент кода реализует функции checkLines() и breakLine(short line) для очистки заполненных строк в игре.

checkLines() перебирает каждую строку сетки снизу вверх, проверяя, заполнена ли строка (все ячейки заняты) или нет. Если это так, она вызывает функцию BreakLine(), передавая номер строки в качестве параметра, чтобы очистить эту строку.

breakLine(short line) сначала воспроизводит звук «стирания», используя функцию tone() на контакте SPEAKER_PIN, который будет указывать на то, что линия очищена. Затем он сдвигает все строки выше очищенной строки на одну ячейку и очищает верхнюю строку. Это также добавляет 10 к счету за очистку линии. Наконец, функция инвертирует отображение светодиодной матрицы на 50 мс, чтобы создать визуальный эффект очищенной линии, а затем возвращает отображение в нормальное состояние.

refresh(): эта функция очищает дисплей, рисует макет, рисует сетку и, наконец, рисует текущую фигуру на дисплее.

drawGrid(): эта функция перебирает всю сетку и, если ячейка заполнена, рисует белый прямоугольник размера ячейки.

nextHorizontalCollision(short piece[2][4], int amount): эта функция проверяет, есть ли какое-либо столкновение с сеткой в ​​горизонтальном направлении. Для этого он проверяет каждую ячейку текущей фигуры и добавляет сумму к ее позиции x. Если новая позиция x находится за пределами сетки или уже занята, происходит столкновение, и функция возвращает true.

nextCollision(): эта функция проверяет, есть ли какие-либо столкновения с сеткой в ​​вертикальном направлении. Она делает это, проверяя каждую ячейку в текущей фигуре и добавляя единицу к ее позиции y. Если новая позиция y находится за пределами сетки или уже занята, происходит столкновение, и функция возвращает true.

generate() — эта функция устанавливает переменные для следующей фигуры тетриса, которая будет введена на игровое поле. Она устанавливает currentType таким же, как nextType, а затем генерирует новый nextType с помощью функции random().

Если currentType не является частью «O» (представленной значением типа 5), то для значения PieceX устанавливается случайное число от 0 до 8 (включительно), поскольку часть «O» всегда центрируется. В противном случае для фрагмента «O» значение PieceX устанавливается в случайное число от 0 до 6 (включительно).

Значение PieceY установлавивается равным 0, что указывает на то, что фигура будет начинаться с верха игрового поля. Значение поворота (rotation value) устанавливается в 0, что указывает на то, что фигура не была повернута.

Наконец, вызывается функция copyPiece() для копирования соответствующего фрагмента в массив фрагментов.

Функция drawPiece() принимает тип фигуры тетриса, ее вращение и текущие координаты x и y на игровом поле. Затем она использует цикл for для перебора четырех блоков фигуры и рисования каждого блока на игровом поле с помощью функции display.fillRect().

Функция drawNextPiece() рисует следующий фрагмент тетриса в поле предварительного просмотра в правой части игрового поля. Сначала она копирует следующий фрагмент в массив nPiece с помощью функции copyPiece(). Затем она проходит через четыре блока фрагмента и рисует каждый блок в поле предварительного просмотра с помощью функции display.fillRect().

refresh(): эта функция вызывается для обновления изображения на дисплее. Сначала она очищает дисплей, а затем рисует макет, сетку и текущию фигуру на экране.

drawGrid(): эта функция вызывается функцией Refresh() для рисования сетки на экране, представляющей уже упавшие блоки.

nextHorizontalCollision(): This function checks if the current piece will collide with any block in the next horizontal move. It does this by checking each of the 4 blocks that make up the piece, and determining whether moving it by amount spaces would make it overlap with any blocks that have already fallen.

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

generate(): эта функция генерирует новую фигуру для управления игроком. Она устанавливает для currentType тип следующей фигуры, выбирает случайную позицию для начала фигуры (pieceX), устанавливает для PieceY значение 0 (верхняя часть игровой области), устанавливает вращение на 0 и вызывает copyPiece() для заполнения в массиве частей с соответствующими блоками для новой фигуры.

drawPiece(): эта функция вызывается для рисования текущей фигуры на экране. Для этого она перебирает каждый из 4 блоков, составляющих фигуру, и рисует для каждого из них белый квадрат в соответствующей позиции на экране.

drawNextPiece(): эта функция вызывается для рисования следующей фигуры на экране. Для этого она копирует позиции блоков для следующей фигуры в новый массив nPiece, а затем рисует небольшой предварительный просмотр фигуры в правом верхнем углу экрана.

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

getMaxRotation(): эта функция возвращает максимальное количество вращений, которое может иметь данный тип фигуры. Она возвращает 2 для типов 1, 2 и 5, 4 для типов 0 и 4, 1 для типа 3 и 0 для любого другого типа.

canRotate(): эта функция проверяет, можно ли повернуть текущую фигуру на величину поворота. Это делается путем вызова функции copyPiece() для создания нового массива частей с повернутыми блоками, а затем вызова nextHorizontalCollision(), чтобы проверить, будет ли повернутая часть перекрываться с какими-либо упавшими блоками.

drawLayout() — эта функция отвечает за отрисовку основного макета игрового экрана, включая границу, разделительную линию, счет и следующую фигуру. Она вызывает функции drawNextPiece() и drawText() для рисования следующей фигуры и счета соответственно.

getNumberLength(int n) — вспомогательная функция, которая принимает на вход целое число и возвращает количество его цифр. Это полезно для определения длины счета, который необходимо вывести на экран.

drawText(char text[], short length, int x, int y) — рисует строку текста на экране по заданным координатам x и y. В качестве параметров она принимает текст, который нужно отрисовать, его длину и координаты x и y. Эта функция устанавливает размер, цвет, положение курсора и шрифт текста перед рисованием текста.

В функции setup() мы инициализируем входные и выходные контакты, устанавливаем связь через последовательный порт, инициализируем и очищаем OLED-дисплей и устанавливаем вращение дисплея. Затем на две секунды отображается логотип, а затем очищается дисплей и вызывается функция drawLayout() для рисования макета игры. Наконец, устанавливается случайное начальное число, генерируется первая фигура и запускается таймер.

В функции loop() мы проверяем, прошел ли заданный интервал времени, и выполняем необходимые действия, такие как проверка и очистка строк или генерация новой фигуры. Также мы считываем состояния кнопок и обрабатываем движение текущей фигуры, включая вращение и горизонтальное перемещение. Наконец, мы регулируем скорость игры в зависимости от нажатия определенной кнопки. Также мы генерируем звуковые эффекты с помощью функций tone() и noTone().

Заключение

В этом проекте мы рассмотрели как создать игру «Тетрис», используя плату Arduino и OLED-дисплей 128×64. Для программирования игры мы использовали язык C++ и Arduino IDE.

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

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

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

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

Код программы (скетча)

Источник статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *