Общие сведения
«Бегущий огонёк» - игра, собранная на основе NeoPixel-кольца, Piranha UNO, и тактовой кнопки. Суть игры: необходимо с помощью нажатия кнопки останавливать бегущий огонёк внутри светящегося интервала. Трудность в том, что сложность игры постоянно растёт, а при ошибке происходит откат назад.
Видео
Нам понадобится
- Piranha UNO (или другой контроллер)
- NeoPixel-кольцо
- Кнопка
- Breadboard mini (для более удобного подключения)
- Макетные провода
- Источник питания
Также понадобится библиотека iarduino_NeoPixel
При необходимости, ознакомьтесь с нашей инструкцией по установке библиотек в Arduino IDE.
Подключение
Мы предполагаем, что Вы уже спаяли NeoPixel-кольцо. Если нет, сперва соберите его по этой инструкции, а затем вернитесь к данному проекту.
Соедините все компоненты при помощи проводов «папа-мама», также можете для удобства воспользоваться макетной платой Breadboard mini.
К 3 выводу контроллера подключена кнопка. Вторым контактом она подключена к GND. NeoPixel-кольцо подключено к 5 выводу контроллера.
Минус питания подаётся на GND контроллера и кольца, а плюс - на 5V.
Для питания рекомендуем использовать
отдельный блок питания с напряжением
5В и током не менее 3А.
Для проекта можно использовать любой контроллер. Не забудьте выбрать используемую плату в меню Инструменты -> Плата
Скетч проекта
#include <iarduino_NeoPixel.h> // подключаем библиотеку iarduino_NeoPixel для работы со светодиодами NeoPixel iarduino_NeoPixel led(5,60); // указываем, что к 5 выводу подключено 60 светодиодов #define buttonNextPin 3 // Пин кнопки "Далее" bool btnNextFlag = false; // флаг нажатия кнопки uint32_t btnNextTimer = 0; // время с момента нажатия (необходимо для устранения дребезга контактов int8_t pointPosition=0; // позиция бегущей точки bool clickFlag = false; // флаг нажатия кнопки uint8_t startInterval=0; // начало интервала (генерируется случайным образом) uint8_t endInterval; // конец интервала (расчитывается исходя из начала интервала и его длины) uint8_t interval = 10; // "окно" зоны для ловли точки uint8_t speedTime = 50; // "скорость" двидения точки (мс). Чем меньше значение, тем быстрее скорость int diff=0; // не поместившиеся точки интервала bool newGameFlag = true; // флаг новой игры (для единоразовой генерации начала интервала) uint8_t currentLedInterval; // растущее от startInterval до endInterval число (инжекс светодиода) uint8_t ledInterval[60]; // массив номеров светодиодов интервала uint8_t currentLedDiff = 0; // растущее от 0 до diff число - непоместившаяся часть интервала int32_t colorInterval = 0x0000ff; // цвет интервала (изначально синий) uint32_t timeNewLevel; // время старта нового уровня uint32_t timeMinLimit = 3000; // мс, 0...65535. Минимальное время до "убегания" интервала uint32_t timeMaxLimit = 7000; // мс, 0...65535. Максимальное время до "убегания" интервала uint32_t timeRunning; // время "убегания" интервала между минимальным и максимальным, вичисленное случайным образом uint8_t randomStepsRun; // число шагов, на которое будет "убегать" интервал uint8_t n = 0; // число пикселей, на которые уже "убежал" интервал uint32_t timeStartRunning; // время начала "убегания" интервала void setup(){ Serial.begin(9600); // инициируем передачу данных в монитор последовательного порта if(led.begin()) {Serial.println("Ok");} // инициируем работу со светодиодами и выводим сообщение об успешной инициализации else {Serial.println("Err");} // если инициализация невозможна (не хватает памяти для хранения цветов всех светодиодов), // то выводим сообщение об ошибке pinMode(buttonNextPin, INPUT_PULLUP); // пин, к которому подключена кнопка - вход, подтиягивающий резистор полюкчен програмно } void loop(){ randomSeed(analogRead(A0)); // новая опорная точка для генератора случайных чисел (на "висящем в воздухе" выводе А0 присутствует случайный сигнал) bool buttonNextState = !digitalRead(buttonNextPin); // состояние кнопки - сигнал с пина кнопки (инвертируем для удобства) if (buttonNextState && !btnNextFlag && millis() - btnNextTimer > 100) { // если кнорка нажата И до этого нажата не была И она нажата уже 100мс (задержка от дребезга контактов) btnNextTimer = millis(); // сбрасываем таймер Serial.println("press"); // выводим инфомацию о том что кнопка нажата btnNextFlag = true; // флаг нажатия кнопки - true clickFlag=true; // флаг нажатия кнопки - true } if (!buttonNextState && btnNextFlag) { // если кнопку отпустили btnNextFlag = false; // флаг нажатия кнопки false - теперь она отпущена btnNextTimer = millis(); // сбрасываем таймер } if (newGameFlag){ // если начался новый уровень newGameFlag=false; // меняем флаг, чтобы не заходить в цикл до новой смены уровней currentLedDiff = 0; // текущая разница "непоместившихся" пикселей startInterval = random(led.count()); // начало интервала - случайное число от 0 до последнего светодиода в круге currentLedInterval = startInterval; // начало интервала на старте endInterval = startInterval+interval; // конец интервала timeNewLevel = millis(); // время старта нового уровня timeRunning = random(timeMinLimit, timeMaxLimit); // случайно определяем время, через которое интервал начнёт "убегать" if (endInterval > led.count()){ // если интервал вышел за пределы кольца diff = endInterval-led.count(); // вычисляем количесво светодиодов, которые "не поместились" в конце endInterval = led.count()-1; // устанавливаем конец интервала последнему пикселю в кольце } else diff = 0; // иначе не переносим "не поместившиеся" значения (если их нет) for (uint8_t i=0; i < interval; i++){ // формируем массив из номеров светодиодов интервала if (currentLedInterval <= endInterval){ // если при заполнении интервала не дошли до конца ledInterval[i] = currentLedInterval; // i-й элемент массива равен текцщему индексу светодиода currentLedInterval++; // увеличиваем текущий индекс светодиода на 1 } else if (diff != 0){ // иначе, если требуется "перенос" непоместившейся части интервала ledInterval[i] = currentLedDiff; // i-й элемент массива равен первому светодиоду currentLedDiff++; // увеличиваем текущий индекс "непоместившихся" светодиодов на 1 } } colorInterval = random(0x777777); // генерируем случайный цвет } led.setColor(NeoPixelAll, 0,0,0); // гасим все светодиоды for (uint8_t i=0; i < interval; i++){ // зажигаем светодиоды интервала led.setColor(ledInterval[i], colorInterval); } led.setColor(pointPosition, 255,255,255); // зажигаем бегущую точку led.write(); // записываем delay(speedTime); // задержка в перемещении точки (определяет её скорость) if (pointPosition >= led.count()-1) pointPosition = -1; // если точка дошла до конца, начинем сначала; // -1, потому что дальше значение увеличится на 1 и позиция станет равно 0 if (clickFlag && isIncludePoint(interval, ledInterval, pointPosition)){ // если клик кнопки в момент, когда точка в зоне интервала clickFlag = false; // флаг для того, чтобы больше не заходить в этот цикл и не обрабатывать клик повторно for (uint8_t i=0; i < interval; i++){ // зажигаем интервал зеленым led.setColor(ledInterval[i], 0,255,0); } interval--; // уменьшаем интервал на 1 пиксель if (interval < 10) speedTime -= speedTime*30/100; // увеличиваем скорость точки на 30% led.write(); // записываем delay(1000); // "победа" горит 1 секунду diff=0; // сбрасываем разницу "непоместившихся" пикселей (вдруг она была) newGameFlag=true; // начинаем новую игру } else if (clickFlag && !isIncludePoint(interval, ledInterval, pointPosition)){ // если клик кнопки в момент, когда точка НЕ в зоне интервала clickFlag = false; // флаг для того, чтобы больше не заходить в этот цикл и не обрабатывать клик повторно for (uint8_t i=0; i < interval; i++){ // зажигаем интервал красным led.setColor(ledInterval[i], 255,0,0); } led.write(); // записываем delay(1000); // "проигрыш" горит 1 секунду interval++; // увеличиваем интервал на 1 пиксель if (speedTime < 50) speedTime += speedTime*42.865/100; // уменьшаем скорость точки до предыдущего значения diff=0; // сбрасываем разницу "непоместившихся" пикселей (вдруг она была) newGameFlag=true; // начинаем новую игру } pointPosition++; // перемещаем точку на новую позицию if (interval <= 1) { // если интервал равен одной точке for (uint8_t i=0; i < led.count(); i++){ // зажигаем весь круг зеленым - победа led.setColor(i, 0,255,0); } led.write(); // записываем delay(2000); // ждём две секунды newGameFlag = true; // начинаем новую игру (новый уровень) pointPosition=0; // устанавливаем точку в 0 положение interval = 10; // начинаем новую игру - интервал свновь равен 10 speedTime = 50; // начинаем новую игру - скорость точки (задержка перемещения) снова 50 diff=0; // сбрасываем разницу "непоместившихся" пикселей (вдруг она была) currentLedDiff = 0; // сбрасываем разницу "непоместившихся" пикселей (вдруг она была) } if ((millis()-timeNewLevel) > timeRunning){ // если слишком долго целимся if(n==0) { // если пока не начинали перемещать интервал timeStartRunning = millis(); // отмечаем время начала движения интервала n++; // прибавляем шаг смещения randomStepsRun = random(5, 50); // случайно определяем число пикселей, на которые сдвинется интервал } if(n < randomStepsRun && (timeStartRunning+speedTime) < millis()){ // если интервал еще не "доехал" до конца, и пришло время сдвинуть его на шаг timeStartRunning = millis(); // "сбрасываем" время начала движения интервала n++; // прибавляем шаг смещения for (uint8_t j=0; j < interval; j++){ // для каждого номера светодиода в интервале, if (ledInterval[j] > 0) ledInterval[j]--; // если его значение больше 0, уменьшаем на 1, else ledInterval[j]=led.count()-1; // иначе "перепрыгиваем" в конец - приравниваем последнему номеру светодиода в кольце } } else if(n >= randomStepsRun){ // если интервал "доехал" до конца n=0; // обнуляем число шагов timeNewLevel = millis(); // "обнуляем" время начала нового уровня (формально, новый уровень не начинается) timeRunning = random(timeMinLimit, timeMaxLimit); // определяем случайным образом новое время, через которое интревал начнёт "убегать" } } } bool isIncludePoint(uint8_t interval, uint8_t *ledInterval, int8_t pointPosition){ // функиця, определяющая, находится ли бегущий светодиод внутри интервала for (uint8_t i=0; i < interval; i++){ // для каждого значения в интервале, проверить: if (ledInterval[i] == pointPosition){ // если это значение равно позиции бегущего светодиода (позиции - номера светодиодов в кольце) return true; // вернуть "true" } } return false; // иначе вернуть "false" }
Обсуждение