/*
  Игра на адресной светодиодной ленте "Понг"
  - Нажимай кнопку, когда шарик попал в твою зону
  - Чем ближе к краю ленты, тем сильнее будет отскок
  - Нажал не в своей зоне - проиграл
  - Пропустил - проиграл
*/

#define LED_PIN 8     // пин ленты
#define LED_NUM 90   // кол-во светодиодов
#define LED_BR 250     // яркость ленты

#define B1_PIN 9      // пин кнопки 1
#define B2_PIN 10      // пин кнопки 2
#define BUZZ_PIN 11    // пин пищалки

#define ZONE_SIZE 15  // размер зоны
#define MIN_SPEED 2   // минимальная скорость
#define MAX_SPEED 10  // максимальная скорость
#define WIN_SCORE 5   // победный счёт

// =============================================
#include "FastLED.h"
CRGB leds[LED_NUM];

// ========== КНОПКА ==========
// мини класс кнопки
#define BTN_DEB 50    // дебаунс, мс
struct Button {
  public:
    Button (byte pin) {
      _pin = pin;
      pinMode(_pin, INPUT_PULLUP);
    }
    bool click() {
      bool btnState = digitalRead(_pin);
      if (!btnState && !_flag && millis() - _tmr >= BTN_DEB) {
        _flag = true;
        _tmr = millis();
        return true;
      }
      if (btnState && _flag && millis() - _tmr >= BTN_DEB) {
        _flag = false;
        _tmr = millis();
      }
      return false;
    }
    uint32_t _tmr;
    byte _pin;
    bool _flag;
};

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

// ========== ПЕРЕМЕННЫЕ ==========
int pos = 0;
int spd;
byte score1 = 0, score2 = 0;
uint32_t tmr;

// ============= LOOP =============
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);
    }
  }
}

// ========= ЗАЛИВКА ЗОН =========
void fillZones(CRGB color1, CRGB color2) {
  // заливаем концы ленты переданными цветами
  for (int i = 0; i < ZONE_SIZE; i++) {
    leds[i] = color1;
    leds[LED_NUM - i - 1] = color2;
  }
}

// ========= МИГАТЬ И ПИЩАТЬ =========
// (цвет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);                   // ждать
}

// =========== ПРОИГРЫШ ===========
// (номер игрока, 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);
  }
}

// ============== НОВАЯ ИГРА ==============
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();
}

// ============== НОВЫЙ РАУНД ==============
void newRound() {
  spd = random(0, 2) ? MIN_SPEED : -MIN_SPEED;  // случайное направление
  pos = (LED_NUM * 10) / 2;     // в центр ленты
}

// ============== ИГРА ==============
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;                   // выходим
    }

    FastLED.clear();
    fillZones(CRGB::Green, CRGB::Green);  // показываем зоны
    leds[pos / 10] = CRGB::Cyan;          // рисуем мячик
    FastLED.show();
  }
}