В этом уроке мы создадим игру змейка (питон) на матрице из 48 светодиодов и 4 кнопок. Обычно, светодиодные матрицы состоят из светодиодов, аноды и катоды которых соединены по столбцам и строкам. В таких матрицах, светодиоды зажигаются поочерёдно. В нашем уроке, все светодиоды будут гореть постоянно, так как они, как и кнопки, подключены к Arduino через расширители выводов.
Нам понадобится:
- Arduino Uno х 1шт.
- Trema модуль - расширитель на 8 Входов/Выходов. x 8шт.
- Trema модуль - светодиод (синий) x 48шт.
- Trema модуль - кнопка (синяя) x 4шт.
- Trema Shield x 1шт.
- Конструктор для создания каркаса.
Для реализации проекта нам необходимо установить библиотеку:
- iarduino_I2C_IO для работы с расширителями выводов.
О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Видео:
Схема подключения:
- Расширители выводов подключаются к Arduino по аппаратной шине I2C. На одной шине можно разместить до 8 расширителей, адреса которым задаются при помощи переключателей на модуле, от 0x20 до 0x27.
- Светодиоды подключаются к первым 6 выводам каждого расширителя (если подключить к другим выводам, то нужно изменить их номера в скетче).
- Кнопки подключаются к 7му выводу первых 4х расширителей (если подключить к другим выводам, или модулям расширителей, то их номера и адреса, нужно изменить в скетче).
Алгоритм работы:
При старте:
- Подключение библиотеки и создание объектов для каждого модуля расширителя выводов. Объекты можно создавать по отдельности, но в нашем уроке, мы создали объекты в виде элементов одного массива, так проще выводить информацию на матрицу.
- Конфигурация выводов, для светодиодов OUTPUT, для кнопок INPUT. Выводы конфигурируются вызовом функции pinMode, для каждого используемого вывода, каждого объекта.
- Создание переменных, для хранения координат «съедобной» точки, которую должен съесть питон. Создание двух массивов для хранения координат всех точек питона (в одном массиве координаты по оси X, а в другом, по оси Y). В этих массивах должны быть установлены координаты первых двух точек питона. Создание переменной для хранения длины питона. Создание переменной для хранения направления питона. Прорисовка питона (включение двух светодиодов в соответствии с координатами в массивах).
В коде loop:
- Продвигаем питона на один шаг вперёд с интервалом в 500 мс, для этого:
- Сдвигаем элементы массивов, хранящие координаты точек питона (ось X и ось Y), на один элемент к концу массива. После сдвига, значение нулевого элемента, будет совпадать со значением первого элемента, в обоих массивах. А длина питона, хранящаяся в отдельной переменной, останется неизменной, значит питон не вырос.
- Меняем значение 0 элемента (голова питона) на значение соседней координаты (сверху, снизу, справа, слева), в зависимости от направления питона.
- Проверяем условия игры:
- Если координата головы питона выходит за пределы поля, значит игра проиграна
- Если координата головы питона совпадает с координатой любой из точек тела питона, значит игра проиграна
- Если координата головы питона совпадает с координатой «съедобной» точки, значит увеличиваем значение длины питона и создаём координату новой точки
- Если длина питона >= 15, значит игра выиграна
- Выключаем светодиод, координата которого соответствует концу питона (элементы массивов с индексом = длина питона + 1)
- Включаем светодиод, координата которого соответствует голове питона (элементы массивов с индексом 0)
- Мигаем «съедобной точкой» с интервалом в 100 мс.
- Читаем состояние выводов к которым подключены кнопки, меняем направление питона при их нажатии
Если игра выиграна, то выводим улыбающийся смайлик, а если проиграна, то грустный смайлик..
Код программы:
#include <Wire.h> // Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотеки iarduino_I2C_IO. #include <iarduino_I2C_IO.h> // Подключаем библиотеку iarduino_I2C_IO для работы с модулями iarduino_I2C_IO Md[8 ]={0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27}; // Создаём массив из 8 объектов, для работы с модулями, указывая адрес каждого модуля на шине I2C int8_t Ms[3][8]; // Создаём массив для прорисовки смайликов в конце игры int8_t Px[16]={2,2}; // Создаём массив с кооржинатами точек питона по оси X int8_t Py[16]={6,7}; // Создаём массив с кооржинатами точек питона по оси Y int8_t Sx = 0; // Создаём переменную для хранения координаты «съедобной» точки по оси X int8_t Sy = 0; // Создаём переменную для хранения координаты «съедобной» точки по оси Y uint8_t Pl = 2; // Создаём переменную для хранения длины питона (количество точек) uint8_t Pn = 0; // Создаём переменную для хранения направления питона (0-вверх, 1-вниз, 2-влево, 3-вправо) uint32_t Tm = 0; // Создаём переменную для хранения времени, прошедшего с начала старта скетча (в миллисекундах) uint32_t Pm = 500; // Создаём переменную для хранения интервала времени, с которым ползет питон (в миллисекундах) uint32_t Sm = 100; // Создаём переменную для хранения интервала времени, с которым мигает «съедобная» точка (в миллисекундах) bool Fp(uint8_t x, uint8_t y, uint8_t z = 0); // Объявляем функцию которая будет искать совпадения координат xy с координатами точек в теле питона, начиная с точки z void Fs(uint8_t); // Объявляем функцию которая будет выводить смайлик на экран void (*Fr)() = 0; // Создаём указатель на функцию, но вместо адреса функции, указываем адрес 0. Если обратиться к указателю Fr(), то вместо вызова функции, программа начнёт выполняться с 0 адреса (перезагрузка) void setup(){ // Заполняем массив со смайликами Ms[0][0]=B00000000; Ms[1][0]=B00000000; Ms[2][0]=B00000000; Ms[0][1]=B00000000; Ms[1][1]=B00110011; Ms[2][1]=B00110011; Ms[0][2]=B00000000; Ms[1][2]=B00110011; Ms[2][2]=B00110011; Ms[0][3]=B00000000; Ms[1][3]=B00000000; Ms[2][3]=B00000000; Ms[0][4]=B00000000; Ms[1][4]=B00000000; Ms[2][4]=B00000000; Ms[0][5]=B00000000; Ms[1][5]=B00100001; Ms[2][5]=B00011110; Ms[0][6]=B00000000; Ms[1][6]=B00011110; Ms[2][6]=B00100001; Ms[0][7]=B00000000; Ms[1][7]=B00000000; Ms[2][7]=B00000000; // Конфигурируем выводы for(int i=0; i<8; i++){Md[i].begin(); // Проходим по всем модулям for(int j=0; j<6; j++){Md[i].pinMode(j, OUTPUT); // Конфигурируем очередной вывод, со светодиодом, в режим выхода (к ним подключены светодиоды) }} Md[0].pinMode(7, INPUT); // Конфигурируем 7 вывод 0 модуля в режим входа (к нему подключена кнопка «вверх») Md[1].pinMode(7, INPUT); // Конфигурируем 7 вывод 1 модуля в режим входа (к нему подключена кнопка «вниз») Md[2].pinMode(7, INPUT); // Конфигурируем 7 вывод 1 модуля в режим входа (к нему подключена кнопка «влево») Md[3].pinMode(7, INPUT); // Конфигурируем 7 вывод 1 модуля в режим входа (к нему подключена кнопка «вправо») // Чистим дисплей Fs(0); // Создаём координаты «съедобной точки» randomSeed(analogRead(0)); // Выбираем случайную начальную позицию в последовательности чисел выводимых функцией random do{Sx=random(6); Sy=random(8);} while(Fp(Sx,Sy)); // Создаём координату новой «съедобной» точки, но так, чтобы она не попала на тело питона // Прорисовываем питона for(int i=0; i<Pl; i++){Md[Py[i]].digitalWrite(Px[i], HIGH);} // Прорисовываем начальные точки питона } void loop(){ Tm = millis(); // Сдвигаем питона на одну точку, через каждые 500 миллисекунд if(Tm%Pm<5){ // Если прошло Pm мс, то ... // Сдвигаем массивы точек питона memmove(&Px[1], Px, Pl); // Cдвигаем элементы массива (координаты точек питона по оси X), на 1 элемент назад memmove(&Py[1], Py, Pl); // Cдвигаем элементы массива (координаты точек питона по оси Y), на 1 элемент назад // Создаём координаты первой точки в массивах точек питона switch(Pn){ // Cоздаём новую первую точку питона (его голову), в зависимости от его направления (Pn) case 0: Py[0]--; break; // - cверху case 1: Py[0]++; break; // - снизу case 2: Px[0]++; break; // - слева case 3: Px[0]--; break; // - справа } // Проверяем условия игры if (Fp(Px[0], Py[0], 1)) {Fs(0); Fs(2); delay(1000); Fr();} // Если голова питона (Px[0], Py[0]) столкнулась с его телом, то начинаем игру заново if (Px[0]<0||Px[0]>5||Py[0]<0||Py[0]>7){Fs(0); Fs(2); delay(1000); Fr();} // Если голова питона вышла за пределы поля, то начинаем игру заново if (Px[0]==Sx && Py[0]==Sy) {do{Sx=random(6); Sy=random(8);}while(Fp(Sx,Sy)); Pl++;} // Если точка съедена, то создаём координату новой «съедобной» точки, но так, чтобы она не попала на тело питона и увеличиваем длину питона if (Pl>=15) {Fs(0); Fs(1); delay(1000); Fr();} // Если длина питона >= 15 точек, то выводим смайлик // Гасим старую и зажигаем новую точку питона Md[Py[Pl]].digitalWrite(Px[Pl], LOW ); // Гасим старую точку (хвост питона) Md[Py[0] ].digitalWrite(Px[0] , HIGH); // Зажигаем новую точку (голова питона) } // Мигаем «съедобной» точкой if (Tm%Sm<5){Md[Sy].digitalWrite(Sx, !Md[Sy].digitalRead(Sx)); delay(5);} // Если прошло Sm мс, то читаем состояние на выходе «съедобной» точки и меняем его на противоположное // Меняем направление (значение переменной Pn) if (Md[0].digitalRead(7) && Pn!=1){Pn=0;} // Если нажата кнопка «вверх» и питон не двигался вниз, то меняем его направление if (Md[1].digitalRead(7) && Pn!=0){Pn=1;} // Если нажата кнопка «вниз» и питон не двигался вверх, то меняем его направление if (Md[2].digitalRead(7) && Pn!=3){Pn=2;} // Если нажата кнопка «влево» и питон не двигался вправо, то меняем его направление if (Md[3].digitalRead(7) && Pn!=2){Pn=3;} // Если нажата кнопка «вправо» и питон не двигался влево, то меняем его направление } bool Fp(uint8_t x, uint8_t y, uint8_t z){for(int i=z; i<Pl; i++){if(Px[i]==x && Py[i]==y){return true;}}return false;} void Fs(uint8_t a){for(int i=0; i<8; i++){for(int j=0; j<6; j++){Md[i].digitalWrite(j,Ms[a][i]&bit(j));}}}
Обсуждение