Игра “1D Pong” на Ардуино
Задача
- Сделать одномерную версию классической игры “Понг” для двух игроков
Базовые уроки
Подключение
- Вторую кнопку удобнее расположить на отдельной макетке
- Остальное как в базовых уроках
Библиотеки
- FastLED – можно установить через менеджер библиотек
Для работы с кнопкой используется “мини библиотека” кнопки, которая просто обрабатывает клики с гашением дребезга:
Программа
Зададим константами все настройки и пины проекта:
#define LED_PIN 2 // пин ленты #define LED_NUM 90 // кол-во светодиодов #define LED_BR 250 // яркость ленты #define B1_PIN 3 // пин кнопки 1 #define B2_PIN 4 // пин кнопки 2 #define BUZZ_PIN 5 // пин пищалки #define ZONE_SIZE 10 // размер зоны #define MIN_SPEED 5 // минимальная скорость #define MAX_SPEED 20 // максимальная скорость #define WIN_SCORE 5 // победный счёт
Подключим библиотеку ленты и создадим ленту:
#include "FastLED.h" CRGB leds[LED_NUM];
Также создадим две кнопки (структура описана выше в главе Библиотеки):
Button b1(B1_PIN); Button b2(B2_PIN);
В блоке setup()
настроим ленту, пин пищалки, а также запустим новую игру:
void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, LED_NUM); FastLED.setBrightness(LED_BR); pinMode(BUZZ_PIN, OUTPUT); newGame(); }
Функция новой игры мигает зонами игроков тремя цветами и пищит, после чего задаёт начальные условия:
void newGame() { blinkTone(CRGB::Red, CRGB::Red, 300, 300); blinkTone(CRGB::Yellow, CRGB::Yellow, 300, 300); blinkTone(CRGB::Green, CRGB::Green, 600, 300); fillZones(CRGB::Green, CRGB::Green); FastLED.show(); randomSeed(millis()); // делаем случайные числа более случайными newRound(); }
Где newRound()
и задаёт стартовые значения скорости и позиции шарика:
void newRound() { spd = random(0, 2) ? MIN_SPEED : -MIN_SPEED; // случайное направление pos = (LED_NUM * 10) / 2; // в центр ленты }
Выше использовалась функция blinkTone()
, давайте посмотрим что у неё внутри:
// (цвет1, цвет2, частота, время задержки) void blinkTone(CRGB color1, CRGB color2, int freq, int del) { fillZones(color1, color2); // залить зоны FastLED.show(); // показать tone(BUZZ_PIN, freq); // пищать delay(del); // ждём noTone(BUZZ_PIN); // не пищать fillZones(0, 0); // выключить зоны FastLED.show(); // показать delay(del); // ждать }
Данная функция моргает и пищит с заданной частотой и временем. Моргает здесь функция fillZones()
, которая просто красит начальный и конечный отрезки ленты указанными цветами. Она выглядит так:
void fillZones(CRGB color1, CRGB color2) { // заливаем концы ленты переданными цветами for (int i = 0; i < ZONE_SIZE; i++) { leds[i] = color1; leds[LED_NUM - i - 1] = color2; } }
В основном цикле программы у нас вызываются две функции: опрос кнопок и процесс самой игры:
void loop() { poolButtons(); gameRoutine(); }
Опрос кнопок – проверяем клик. Если игрок кликнул вне своей зоны – проиграл раунд. Кликнул внутри зоны – мяч отбивается и происходит пересчёт скорости: чем ближе к краю ленты, тем сильнее отскок.
void poolButtons() { if (b1.click()) { // произошёл клик игрока 1 if (pos >= ZONE_SIZE * 10) gameOver(0); // мячик вне зоны 1 игрока - проиграл else { // мячик в зоне - отбил tone(BUZZ_PIN, 1200, 60); spd = map(pos, ZONE_SIZE * 10, 0, MIN_SPEED, MAX_SPEED); // меняем скорость } } // аналогично для игрока 2 if (b2.click()) { if (pos < (LED_NUM - ZONE_SIZE) * 10) gameOver(1); else { tone(BUZZ_PIN, 1200, 60); spd = map(pos, (LED_NUM - ZONE_SIZE) * 10, LED_NUM * 10, -MIN_SPEED, -MAX_SPEED); } } }
Тут же используется функция gameOver()
, которая сигнализирует о проигрыше раунда у конкретного игрока:
// (номер игрока, 0 или 1) void gameOver(byte player) { newRound(); // новый раунд if (player == 0) { score2++; if (score2 == WIN_SCORE) { // победил игрок 2 score1 = score2 = 0; // обнуляем счёт // победный бип бип бип игрока 2 blinkTone(CRGB::Black, CRGB::Green, 600, 200); blinkTone(CRGB::Black, CRGB::Green, 600, 200); blinkTone(CRGB::Black, CRGB::Green, 600, 200); delay(500); newGame(); // новая игра } else blinkTone(CRGB::Red, CRGB::Green, 200, 500); // красный бииип игрока 1 } else { score1++; if (score1 == WIN_SCORE) { // победил игрок 1 score1 = score2 = 0; blinkTone(CRGB::Green, CRGB::Black, 600, 200); blinkTone(CRGB::Green, CRGB::Black, 600, 200); blinkTone(CRGB::Green, CRGB::Black, 600, 200); delay(500); newGame(); // новая игра } else blinkTone(CRGB::Green, CRGB::Red, 200, 500); } }
Функция реализована не очень красиво, можно сократить её вдвое. Это ваше домашнее задание =)
Процесс игры – тут нас встречает таймер на 10мс, в котором происходит движение “шарика” и его отрисовка:
void gameRoutine() { if (millis() - tmr >= 10) { // каждые 10 мс tmr = millis(); // ... } }
Внутри таймера двигаем шарик и проверяем, не вылетел ли он за ленту:
pos += spd; // двигаем мячик if (pos < 0) { // вылетел слева gameOver(0); // игрок 1 проиграл return; // выходим } if (pos >= LED_NUM * 10) { // вылетел справа gameOver(1); // игрок 2 проиграл return; // выходим }
Если вылетел – выходим из функции, чтобы не отрисовывать шарик в несуществующем месте ленты!
Дальше очищаем ленту, заново рисуем зоны и поверх – текущею позицию шарика. Координаты у нас десятикратные, поэтому делим на 10:
FastLED.clear(); fillZones(CRGB::Green, CRGB::Green); // показываем зоны leds[pos / 10] = CRGB::Cyan; // рисуем мячик FastLED.show();