Общие сведения
«Бегущий огонёк» - игра, собранная на основе 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"
}

Обсуждение