Скидки, ограниченное предложение! Полный список акционных товаров

Урок 34. Игра змейка (питон) на матрице из светодиодов

Необходимые детали
Видео уроки

В этом уроке мы создадим игру змейка (питон) на матрице из 48 светодиодов и 4 кнопок. Обычно, светодиодные матрицы состоят из светодиодов, аноды и катоды которых соединены по столбцам и строкам. В таких матрицах, светодиоды зажигаются поочерёдно. В нашем уроке, все светодиоды будут гореть постоянно, так как они, как и кнопки, подключены к Arduino через расширители выводов.

Нам понадобится:

Для реализации проекта нам необходимо установить библиотеку:

  • iarduino_I2C_IO для работы с расширителями выводов.

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Видео:


Схема подключения:

Схема подключения цифрового расширителя выводов к Arduino

Алгоритм работы:

При старте:
  • Подключение библиотеки и создание объектов для каждого модуля расширителя выводов. Объекты можно создавать по отдельности, но в нашем уроке, мы создали объекты в виде элементов одного массива, так проще выводить информацию на матрицу.
  • Конфигурация выводов, для светодиодов OUTPUT, для кнопок INPUT. Выводы конфигурируются вызовом функции pinMode, для каждого используемого вывода, каждого объекта.
  • Создание переменных, для хранения координат «съедобной» точки, которую должен съесть питон. Создание двух массивов для хранения координат всех точек питона (в одном массиве координаты по оси X, а в другом, по оси Y). В этих массивах должны быть установлены координаты первых двух точек питона. Создание переменной для хранения длины питона. Создание переменной для хранения направления питона. Прорисовка питона (включение двух светодиодов в соответствии с координатами в массивах).
В коде loop:
  • Продвигаем питона на один шаг вперёд с интервалом в 500 мс, для этого:
    • Сдвигаем элементы массивов, хранящие координаты точек питона (ось X и ось Y), на один элемент к концу массива. После сдвига, значение нулевого элемента, будет совпадать со значением первого элемента, в обоих массивах. А длина питона, хранящаяся в отдельной переменной, останется неизменной, значит питон не вырос.
    • Меняем значение 0 элемента (голова питона) на значение соседней координаты (сверху, снизу, справа, слева), в зависимости от направления питона.
    • Проверяем условия игры:
      • Если координата головы питона выходит за пределы поля, значит игра проиграна
      • Если координата головы питона совпадает с координатой любой из точек тела питона, значит игра проиграна
      • Если координата головы питона совпадает с координатой «съедобной» точки, значит увеличиваем значение длины питона и создаём координату новой точки
      • Если длина питона >= 15, значит игра выиграна
    • Выключаем светодиод, координата которого соответствует концу питона (элементы массивов с индексом = длина питона + 1)
    • Включаем светодиод, координата которого соответствует голове питона (элементы массивов с индексом 0)
  • Мигаем «съедобной точкой» с интервалом в 100 мс.
  • Читаем состояние выводов к которым подключены кнопки, меняем направление питона при их нажатии

Если игра выиграна, то выводим улыбающийся смайлик, а если проиграна, то грустный смайлик..

Код программы:

#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));}}}

Ссылки:

Обсуждение

Присоединяйся

Другие уроки

На главную