Ну вы блин даёте! Студентам с их задачками тут делать нечего. Алисы ии вполне хватает, разжуёт в скетче и по просьбе текстом всё опишет…для тугодумов
Вот пример игры 1D.
«Бильярдный огонёк»: светодиодная игра на Arduino, где важна не только точность, но и тактика
________________________________________
Описание игры
«Бильярдный огонёк» — это компактная интерактивная игра, собранная на базе Arduino и адресной светодиодной ленты WS2812 (NeoPixel). Вместо привычного экрана здесь выступает сама лента: на ней в реальном времени разворачивается динамичное действие, а управление происходит с помощью одной кнопки.
Суть игры — не просто попасть «шариком» (движущимся огоньком) в мишень (неподвижный зелёный светодиод), а сделать это по особым правилам: перед попаданием огонёк обязан хотя бы раз отскочить от края ленты. Механика напоминает упрощённый бильярд или пинбол: игрок должен рассчитать силу «удара» (длительность нажатия кнопки) так, чтобы шарик долетел до стенки, отразился и уже после этого остановился точно на цели.
Визуальная обратная связь реализована через цветовые сигналы:
• жёлтый — успех: игрок победил, лента мигает столько раз, сколько побед он уже одержал в текущем цикле;
• красный — ошибка: огонёк попал в мишень, но без отскока; лента мигает красным, а позиции шарика и цели случайно перемешиваются.
Игра отлично подходит для демонстрации возможностей адресной ленты, тренировки интуиции и как стартовый проект для знакомства с Arduino: код понятен, логика прозрачна, а эффект — сразу заметен.
________________________________________
Правила игры
1. Старт раунда. В начале раунда на ленте загорается неподвижная зелёная мишень в случайной позиции. Рядом располагается красный «шарик» — движущийся огонёк.
2. Управление. Игрок нажимает и удерживает кнопку. Длительность нажатия определяет силу «броска»: чем дольше удержание, тем больше шагов сделает огонёк. При этом движение постепенно замедляется — как будто шарик теряет скорость.
3. Обязательный отскок. Чтобы победа была засчитана, огонёк должен хотя бы один раз коснуться края ленты (левого или правого) и изменить направление. Без этого попадание в мишень не считается успехом.
4. Победа. Если огонёк остановился ровно на мишени и при этом был хотя бы один отскок — засчитывается победа. Лента мигает жёлтым цветом столько раз, сколько побед уже одержано (1–5). После этого мишень перемещается на новую случайную позицию.
5. Ошибка («накат без отскока»). Если огонёк оказался на мишени, но ни разу не ударился о край ленты, срабатывает «штраф»: лента мигает красным один раз, а затем и шарик, и мишень случайным образом перемещаются на новые позиции. Направление движения шарика сбрасывается.
6. Промах. Если огонёк не попал в мишень, он остаётся на той позиции, где остановился. Игрок может попробовать снова, рассчитав новую силу броска.
7. Цикл побед. После пяти побед подряд счётчик сбрасывается, игра возвращается к начальным настройкам, и цикл начинается заново.
________________________________________
Особенности механики
• Расчёт силы броска. Сила напрямую зависит от времени удержания кнопки: система переводит миллисекунды в количество шагов. При этом всегда гарантируется хотя бы один шаг, даже при очень коротком нажатии.
• Замедление движения. Огонёк стартует быстро и постепенно замедляется к концу траектории — это добавляет элемент реалистичности и усложняет расчёт.
• Тактическая глубина. Из-за обязательного отскока игрок не может «аккуратно докатить» шарик до цели: нужно давать запас хода, чтобы он успел долететь до стенки и вернуться.
• Мгновенная обратная связь. Цветовые сигналы (жёлтый и красный) и случайная перестановка позиций делают ошибки и успехи очевидными с первого взгляда.
________________________________________
Техническая реализация
Игра построена на следующих компонентах:
• Arduino (например, Uno или Nano) — центральный контроллер.
• Адресная светодиодная лента WS2812/NeoPixel (16 светодиодов) — поле игры и основной интерфейс.
• Кнопка — единственный элемент управления.
• Библиотека Adafruit_NeoPixel — для управления лентой.
Логика игры реализована в одном скетче:
• Отдельный флаг (hasBounced) отслеживает, был ли отскок от края за время текущего броска.
• Проверка победы происходит только при совпадении двух условий: позиция огонька равна позиции мишени и был хотя бы один отскок.
• В случае ошибки вызывается функция, которая случайно меняет позиции и шарика, и мишени, исключая их совпадение.
________________________________________
Для кого эта игра
«Бильярдный огонёк» будет интересен:
• Начинающим электронщикам и программистам на Arduino — как понятный пример работы с адресной лентой, обработкой нажатий и логикой состояний.
• Участникам мастер классов и кружков — игра даёт быстрый и наглядный результат, мотивируя разбираться в коде дальше.
• Любителям DIY проектов — это компактный, эффектный и легко модифицируемый проект: в него можно добавить звук, усложнить правила или сделать несколько уровней.
Благодаря простой, но увлекательной механике «Бильярдный огонёк» становится не просто учебным примером, а настоящей мини игрой, в которую интересно играть и совершенствовать свои навыки расчёта и реакции.
и скетч на дофига строк.
// ============================================================
// ПОДКЛЮЧЕНИЕ БИБЛИОТЕКИ ДЛЯ РАБОТЫ С АДРЕСНОЙ ЛЕНТОЙ WS2812
// ============================================================
// Подключаем библиотеку Adafruit_NeoPixel — она умеет управлять
// адресными светодиодами WS2812 (NeoPixel), позволяя задавать
// цвет каждого отдельного светодиода на ленте.
#include <Adafruit_NeoPixel.h>
// ============================================================
// НАЗНАЧЕНИЕ ПИНОВ
// ============================================================
// Пин, к которому подключён контакт DIN ленты WS2812.
// Лента получает данные по этому проводу.
#define LED_PIN 6
// Количество светодиодов в ленте.
// В данной игре используется 16 штук — это и есть «игровое поле».
#define LED_COUNT 16
// Пин, к которому подключена кнопка.
// Второй контакт кнопки замкнут на GND (землю).
// Пин работает в режиме INPUT_PULLUP, то есть:
// кнопка не нажата → на пине HIGH (подтяжка к питанию),
// кнопка нажата → на пине LOW (замкнут на землю).
#define BUTTON_PIN 7
// ============================================================
// МЕХАНИКА ШАГОВ ОТ ДЛИТЕЛЬНОСТИ НАЖАТИЯ КНОПКИ
// ============================================================
// Сколько шагов по ленте огонёк делает за один «тик».
// Один тик = 50 мс. За 50 мс удержания — 2 шага.
// То есть 1 секунда удержания = 20 тиков = 40 шагов.
#define STEPS_PER_TICK 2
// Длительность одного тика в миллисекундах.
// Используется как единица измерения силы броска:
// система считает, сколько тиков прошло, и умножает на STEPS_PER_TICK.
#define TICK_MS 50.0
// Минимальное число шагов, даже если кнопку нажали на долю секунды.
// Гарантирует, что огонёк всегда сдвинется хотя бы на 1 позицию.
#define MIN_STEPS 1
// ============================================================
// ЗАМЕДЛЕНИЕ ОГОНЬКА (ПАРАМЕТРЫ АНИМАЦИИ)
// ============================================================
// Задержка (в миллисекундах) между шагами в начале движения.
// Чем меньше — тем быстрее огонёк стартует.
// 5 мс — почти мгновенно, шар «вылетает» с большой скоростью.
#define DELAY_MIN 10
// Задержка (в миллисекундах) между шагами в конце движения.
// Чем больше — тем медленнее огонёк ползёт перед остановкой.
// 250 мс — шар почти замирает, еле двигается.
#define DELAY_MAX 200
// ============================================================
// ЦВЕТ ДВИЖУЩЕГОСЯ ОГОНЬКА (ТЁПЛЫЙ КРАСНЫЙ)
// ============================================================
// Каждый компонент (R, G, B) — от 0 до 255.
// Красный = 25, зелёный = 10, синий = 10.
// Получается тёплый приглушённый красно-оранжевый оттенок.
// Яркость намеренно небольшая, чтобы не слепило глаза.
#define RED 25
#define GREEN 10
#define BLUE 7
// ============================================================
// ЦВЕТ НЕПОДВИЖНОЙ ЦЕЛИ (ЗЕЛЁНЫЙ)
// ============================================================
// Мишень горит чистым зелёным: красный = 0, зелёный = 25, синий = 0.
// Зелёный хорошо контрастирует с красным огоньком и жёлтой вспышкой.
#define TARGET_R 0
#define TARGET_G 25
#define TARGET_B 0
// ============================================================
// ЦВЕТ ВСПЫШКИ ПРИ ПОБЕДЕ (СЛАБЫЙ ЖЁЛТЫЙ)
// ============================================================
// При победе вся лента мигает жёлтым.
// Красный = 30, зелёный = 25, синий = 0 — тёплый медовый оттенок.
#define FLASH_R 30
#define FLASH_G 25
#define FLASH_B 0
// ============================================================
// ЦВЕТ ВСПЫШКИ ПРИ НАКАТЕ БЕЗ ОТСКОКА (КРАСНЫЙ)
// ============================================================
// Если игрок попал в мишень без отскока — лента мигает красным.
// Это штрафной сигнал: «ты сделал неправильно».
// Красный = 30, зелёный = 0, синий = 0 — чистый красный.
#define MISS_FLASH_R 30
#define MISS_FLASH_G 0
#define MISS_FLASH_B 0
// ============================================================
// СКОЛЬКО ПОБЕД ДО ПОЛНОГО СБРОСА
// ============================================================
// После 5 побед подряд счётчик обнуляется, и игра начинается заново.
// Это один полный «цикл» или «серия».
#define MAX_WINS 5
// ============================================================
// СОЗДАНИЕ ОБЪЕКТА ЛЕНТЫ
// ============================================================
// Создаём объект strip класса Adafruit_NeoPixel.
// Параметры:
// LED_COUNT — количество светодиодов (16),
// LED_PIN — пин данных (6),
// NEO_GRB + NEO_KHZ800 — формат цвета (GRB) и частота (800 кГц).
// Это стандартные настройки для большинства лент WS2812.
Adafruit_NeoPixel strip(
LED_COUNT,
LED_PIN,
NEO_GRB + NEO_KHZ800
);
// ============================================================
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
// ============================================================
// Текущая позиция подвижного огонька («шарика») на ленте.
// Изменяется от 0 (крайний левый светодиод) до LED_COUNT-1 (крайний правый).
// В начале игры шарик стоит на позиции 0.
int position = 0;
// Направление движения огонька:
// 1 — вправо (позиция увеличивается),
// -1 — влево (позиция уменьшается).
// В начале игры шарик начинает движение вправо.
int direction = 1;
// Позиция неподвижной мишени на ленте.
// Задаётся случайно функцией newTarget() при старте и после каждой побед.
int targetPosition = 0;
// Счётчик побед в текущей серии.
// Растёт от 0 до MAX_WINS (5). После 5-й победы сбрасывается в 0.
// Также используется для определения количества жёлтых миганий.
int winCount = 0;
// ============================================================
// ФУНКЦИЯ setup() — ВЫПОЛНЯЕТСЯ ОДИН РАЗ ПРИ ВКЛЮЧЕНИИ
// ============================================================
void setup() {
// Инициализация ленты: подготовка внутренних структур библиотеки.
strip.begin();
// Установка максимальной яркости (0–255).
// 255 — максимум, но реальные значения цветов заданы небольшими (10–30),
// поэтому свечение будет мягким.
strip.setBrightness(255);
// Первая отправка данных на ленту — все светодиоды выключены.
// На случай, если при включении на ленте остался «мусор».
strip.show();
// Настройка пина кнопки в режим INPUT_PULLUP.
// Это значит, что внутри микроконтроллера включён подтягивающий резистор:
// кнопка не нажата → на пине HIGH (логическая единица),
// кнопка нажата → на пине LOW (логический ноль, замкнут на GND).
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Инициализация генератора случайных чисел.
// Читаем «шум» с неподключённого аналогового пина A0 —
// это даёт случайное значение, которое используется как «зерно» (seed)
// для функции random(). Без этого random() выдавал бы одну и ту же
// последовательность чисел при каждом включении.
randomSeed(analogRead(A0));
// Генерация первой случайной мишени на ленте.
// Вызывается один раз при старте игры.
newTarget();
}
// ============================================================
// ФУНКЦИЯ loop() — ВЫПОЛНЯЕТСЯ БЕСКОНЕЧНО, РАЗ ЗА РАЗОМ
// ============================================================
void loop() {
// --- ОТРИСОВКА ТЕКУЩЕГО КАДРА ---
// Очистка ленты: все светодиоды выключаются (чёрный цвет).
// Делается перед каждым новым кадром, чтобы старые пиксели не «остались».
strip.clear();
// Зажигаем мишень зелёным цветом в её текущей позиции.
// strip.Color() собирает RGB-компоненты в формат ленты (GRB).
strip.setPixelColor(targetPosition, strip.Color(TARGET_R, TARGET_G, TARGET_B));
// Зажигаем движущийся огонёк красным цветом в его текущей позиции.
strip.setPixelColor(position, strip.Color(RED, GREEN, BLUE));
// Отправляем собранные данные на ленту — светодиоды загораются.
strip.show();
// --- ОЖИДАНИЕ НАЖАТИЯ КНОПКИ ---
// Ждём, пока кнопка не будет нажата.
// digitalRead() возвращает HIGH, пока кнопка не нажата (INPUT_PULLUP).
// Как только игрок нажмёт кнопку — пин станет LOW, и цикл while прервётся.
// delay(10) внутри — чтобы не «крутить» процессор впустую.
while (digitalRead(BUTTON_PIN) == HIGH) {
delay(10);
}
// Небольшая пауза 20 мс для устранения «дребезга» контактов.
// Кнопка при нажатии может «дрожать» на несколько миллисекунд,
// и без этой задержки микроконтроллер может засчитать одно нажатие как несколько.
delay(20);
// Проверка: кнопка действительно ещё нажата?
// Если после дребезга пин снова HIGH — это было ложное срабатывание.
// Возвращаемся из loop() — он запустится заново и начнёт ждать снова.
if (digitalRead(BUTTON_PIN) != LOW) {
return;
}
// --- ЗАПУСК БРОСКА ---
// Вызываем функцию, которая считает силу броска (количество шагов)
// по времени удержания кнопки. Функция ждёт отпускания кнопки,
// затем возвращает количество шагов.
int steps = countStepsByHoldTime();
// Если шагов больше нуля (всегда минимум 1 из-за MIN_STEPS),
// запускаем анимацию движения огонька.
if (steps > 0) {
runLightSequence(steps);
}
// После завершения броска loop() запускается заново:
// лента перерисовывается с новыми позициями, ждём следующего нажатия.
}
// ============================================================
// ФУНКЦИЯ: countStepsByHoldTime()
// ------------------------------------------------------------
// Считает, сколько шагов должен сделать огонёк, в зависимости
// от того, как долго игрок удерживал кнопку.
//
// Логика:
// 1. Запоминаем момент нажатия.
// 2. Ждём, пока игрок отпустит кнопку.
// 3. Считаем, сколько миллисекунд прошло.
// 4. Переводим время в тики (1 тик = 50 мс).
// 5. Переводим тики в шаги (1 тик = 2 шага).
// 6. Если шагов меньше 1 — ставим 1 (MIN_STEPS).
//
// Возвращает: количество шагов (int).
// ============================================================
int countStepsByHoldTime() {
// Запоминаем время начала нажатия (миллисекунды с запуска Arduino).
unsigned long pressStart = millis();
// Ждём, пока игрок держит кнопку (пин в состоянии LOW).
// delay(5) — небольшая пауза между проверками, чтобы не нагружать процессор.
while (digitalRead(BUTTON_PIN) == LOW) {
delay(5);
}
// Вычисляем, сколько миллисекунд прошло от нажатия до отпускания.
unsigned long heldMs = millis() - pressStart;
// Переводим миллисекунды в тики: делим на длительность одного тика.
// Например, 350 мс / 50 = 7 тиков.
int ticks = heldMs / TICK_MS;
// Переводим тики в шаги: умножаем на количество шагов за тик.
// Например, 7 тиков * 2 = 14 шагов.
int steps = ticks * STEPS_PER_TICK;
// Гарантируем минимум: даже при самом коротком нажатии (например, 10 мс)
// шагов будет хотя бы 1 — огонёк сдвинется.
if (steps < MIN_STEPS) {
steps = MIN_STEPS;
}
// Возвращаем посчитанное количество шагов.
return steps;
}
// ============================================================
// ФУНКЦИЯ: runLightSequence(int steps)
// ------------------------------------------------------------
// Запускает огонёк на заданное число шагов с постепенным
// замедлением (easing). После остановки проверяет результат:
//
// — попал на цель С отскоком → победа (жёлтая вспышка, новая мишень)
// — попал на цель БЕЗ отскока → штраф (красная вспышка, всё переставляется)
// — не попал → ничего не происходит, шар остаётся
//
// Параметр: steps — количество шагов (от 1 и больше).
// ============================================================
void runLightSequence(int steps) {
// Флаг: был ли отскок от стенки во время этого броска?
// Сбрасывается в false в начале каждого броска.
// Устанавливается в true, если огонёк коснулся края ленты.
bool hasBounced = false;
// --- ЦИКЛ АНИМАЦИИ ДВИЖЕНИЯ ---
// Проходим по каждому шагу от 0 до steps-1.
for (int step = 0; step < steps; step++) {
// Очищаем ленту перед отрисовкой текущего кадра.
strip.clear();
// Рисуем мишень (зелёная) — она стоит на месте весь бросок.
strip.setPixelColor(targetPosition, strip.Color(TARGET_R, TARGET_G, TARGET_B));
// Рисуем огонёк (красный) в его текущей позиции.
strip.setPixelColor(position, strip.Color(RED, GREEN, BLUE));
// Отправляем кадр на ленту — светодиоды обновляются.
strip.show();
// --- РАСЧЁТ ЗАДЕРЖКИ (ЗАМЕДЛЕНИЯ) ДЛЯ ЭТОГО ШАГА ---
// Прогресс: насколько мы близки к концу движения.
// На первом шаге step=0, progress=0.0 (начало).
// На последнем шаге step=steps-1, progress=1.0 (конец).
// Если шаг всего один — прогресс сразу 1.0 (нет замедления, один шаг).
float progress = (steps > 1)
? (float)step / (steps - 1)
: 1.0;
// Применяем квадратичное замедление: умножаем прогресс сам на себя.
// Это значит, что огонёк начинает быстро, а к концу всё сильнее тормозит.
// Пример: progress=0.5 → eased=0.25 (задержка выросла только на 25%),
// progress=0.9 → eased=0.81 (задержка выросла на 81%).
float eased = progress * progress;
// Вычисляем фактическую задержку для этого шага в миллисекундах.
// Формула: начало (DELAY_MIN) + разброс * замедление.
// Пример: при eased=0.0 → задержка 5 мс (быстро),
// при eased=1.0 → задержка 250 мс (медленно).
int currentDelay = DELAY_MIN + (int)((DELAY_MAX - DELAY_MIN) * eased);
// Ждём вычисленное время — это создаёт эффект замедления.
delay(currentDelay);
// --- ДВИЖЕНИЕ ОГОНЬКА ---
// Если это не последний шаг — двигаем огонёк.
// На последнем шаге движение не нужно: шар «останавливается».
if (step < steps - 1) {
// Сдвигаем позицию на 1 в текущем направлении
// (вправо = +1, влево = -1).
position += direction;
// Проверка: достигли ли правого края ленты?
// LED_COUNT-1 — индекс последнего светодиода (15 для ленты из 16).
if (position >= LED_COUNT - 1) {
// Прижимаем к правому краю, чтобы не вылететь за пределы.
position = LED_COUNT - 1;
// Меняем направление на влево (отскок от правой стенки).
direction = -1;
// Отмечаем, что был отскок — это важно для проверки победы.
hasBounced = true;
}
// Проверка: достигли ли левого края ленты?
if (position <= 0) {
// Прижимаем к левому краю.
position = 0;
// Меняем направление на вправо (отскок от левой стенки).
direction = 1;
// Отмечаем, что был отскок.
hasBounced = true;
}
}
} // --- КОНЕЦ ЦИКЛА АНИМАЦИИ ---
// Пауза 500 мс после остановки, чтобы игрок увидел,
// где остановился шар.
delay(500);
// --- ПРОВЕРКА РЕЗУЛЬТАТА БРОСКА ---
// Вариант 1: ПОБЕДА — шар на цели И был хотя бы один отскок.
if (position == targetPosition && hasBounced) {
// Увеличиваем счётчик побед.
winCount++;
// Мигаем жёлтым столько раз, сколько побед уже одержано.
// Первая победа → 1 мигание, вторая → 2, и так до 5.
flashYellow(winCount);
// Если достигли максимума побед (5) — полный сброс игры.
if (winCount >= MAX_WINS) {
winCount = 0; // Обнуляем счётчик
position = 0; // Шар — в начало
direction = 1; // Направление — вправо
}
// Генерируем новую случайную мишень.
newTarget();
// Вариант 2: НАКАТ БЕЗ ОТСКОКА — шар на цели, но не было отскока.
} else if (position == targetPosition && !hasBounced) {
// Мигаем лентой красным один раз — штрафной сигнал.
flashRed();
// Случайно переставляем И шар, И мишень на новые позиции.
// Игра «начинается заново» с новой расстановкой.
newTargetAndBall();
}
// Вариант 3: ПРОМАХ — шар не на цели.
// Ничего не делаем. Шар остаётся там, где остановился.
// Игрок может попробовать ещё раз, рассчитав новую силу броска.
}
// ============================================================
// ФУНКЦИЯ: flashYellow(int times)
// ------------------------------------------------------------
// Мигает всей лентой жёлтым цветом заданное количество раз.
// Используется при победе.
//
// Параметр: times — сколько раз мигнуть (1–5).
// ============================================================
void flashYellow(int times) {
// Цикл: повторяем «вспышку-паузу» заданное число раз.
for (int i = 0; i < times; i++) {
// Зажигаем ВСЕ светодиоды жёлтым цветом.
for (int j = 0; j < LED_COUNT; j++) {
strip.setPixelColor(j, strip.Color(FLASH_R, FLASH_G, FLASH_B));
}
// Отправляем на ленту — она загорается жёлтым.
strip.show();
// Держим свет 300 мс — достаточно, чтобы заметить вспышку.
delay(300);
// Гасим всю ленту.
strip.clear();
strip.show();
// Пауза между вспышками — 500 мс.
// Чуть длиннее, чем сама вспышка, чтобы мигания были чёткими,
// а не сливались в сплошное свечение.
delay(500);
}
}
// ============================================================
// ФУНКЦИЯ: flashRed()
// ------------------------------------------------------------
// Мигает всей лентой красным цветом ОДИН раз.
// Используется как штрафной сигнал при накате без отскока.
// ============================================================
void flashRed() {
// Зажигаем все светодиоды красным.
for (int j = 0; j < LED_COUNT; j++) {
strip.setPixelColor(j, strip.Color(MISS_FLASH_R, MISS_FLASH_G, MISS_FLASH_B));
}
// Отправляем на ленту.
strip.show();
// Держим красный свет 300 мс.
delay(300);
// Гасим ленту.
strip.clear();
strip.show();
// Пауза после вспышки — 300 мс.
// Это даёт игроку время осознать «ошибку» перед перестановкой.
delay(300);
}
// ============================================================
// ФУНКЦИЯ: newTarget()
// ------------------------------------------------------------
// Генерирует новую случайную позицию для мишени.
// Гарантирует, что новая мишень не совпадёт с текущим
// положением огонька (чтобы шар не «стоял на цели»).
// Вызывается при старте и после каждой победы.
// ============================================================
void newTarget() {
// Переменная для новой позиции мишени.
int newPos;
// Цикл: генерируем случайную позицию, пока она не совпадёт
// с текущей позицией огонька.
// random(LED_COUNT) возвращает число от 0 до LED_COUNT-1 (0..15).
do {
newPos = random(LED_COUNT);
} while (newPos == position);
// Запоминаем новую позицию мишени.
targetPosition = newPos;
}
// ============================================================
// ФУНКЦИЯ: newTargetAndBall()
// ------------------------------------------------------------
// Случайно переставляет И мишень, И шарик на новые позиции.
// Вызывается при штрафе (накат без отскока).
//
// Логика:
// 1. Случайная новая позиция для шара (не совпадает с текущей).
// 2. Случайная новая позиция для мишени (не совпадает с шаром).
// 3. Сброс направления движения шара — вправо.
//
// Гарантирует, что шар и мишень окажутся в разных местах
// и ни один из них не «останется на старом месте».
// ============================================================
void newTargetAndBall() {
// --- НОВАЯ ПОЗИЦИЯ ШАРА ---
// Переменная для новой позиции шара.
int newBallPos;
// Генерируем случайную позицию, пока она не совпадёт с текущей.
// Это гарантирует, что шар реально переместится.
do {
newBallPos = random(LED_COUNT);
} while (newBallPos == position);
// Запоминаем новую позицию шара.
position = newBallPos;
// --- НОВАЯ ПОЗИЦИЯ МИШЕНИ ---
// Переменная для новой позиции мишени.
int newTargetPos;
// Генерируем случайную позицию, пока она не совпадёт с новым шаром.
// Это гарантирует, что мишень не «накроет» шар.
do {
newTargetPos = random(LED_COUNT);
} while (newTargetPos == position);
// Запоминаем новую позицию мишени.
targetPosition = newTargetPos;
// --- СБРОС НАПРАВЛЕНИЯ ---
// После перестановки шар начинает движение вправо (по умолчанию).
// Это даёт игроку привычную стартовую точку.
direction = 1;
}
…и делов - описал концепт, раз семь исправила скетч и сидю-играю ![]()

