Загрузка данных


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// --- ПИНЫ КНОПОК ---
const int BTN_PINS[4] = {2, 3, 4, 5};
#define BUZZER 8
#define EEPROM_ADDR 0

// --- ДОРОЖКИ ---
const int LANE_WIDTH = 30;
const int LANE_X[4] = {2, 33, 64, 95}; // X-координаты дорожек
const int HIT_LINE_Y = 55; // Y-координата линии попадания
const int NOTE_HEIGHT = 4;

// --- НОТЫ ---
struct Note {
  int lane;
  float y;
  bool active;
};
const int MAX_NOTES = 20;
Note notes[MAX_NOTES];

// --- СОСТОЯНИЯ ---
enum GameState { STATE_MENU, STATE_PLAYING, STATE_GAME_OVER };
GameState currentState = STATE_MENU;

// --- ПЕРЕМЕННЫЕ ---
int score = 0;
int lives = 3;
int combo = 0;
int maxCombo = 0;
int highScore = 0;
unsigned long lastNoteSpawn = 0;
unsigned long noteSpawnInterval = 700; // Интервал между нотами
unsigned long lastUpdate = 0;
int lastBtnState[4] = {HIGH, HIGH, HIGH, HIGH};

// --- МЕЛОДИЯ (частоты для дорожек) ---
const int LANE_TONES[4] = {262, 330, 392, 523};

void setup() {
  Serial.begin(9600);
  pinMode(BUZZER, OUTPUT);
  for (int i = 0; i < 4; i++) pinMode(BTN_PINS[i], INPUT_PULLUP);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
      for (;;);
    }
  }

  highScore = EEPROM.read(EEPROM_ADDR);
  if (highScore > 500) highScore = 0;

  drawMenu();
  Serial.println(F("=== RHYTHM GAME ==="));
}

void loop() {
  switch (currentState) {
    case STATE_MENU:
      if (digitalRead(BTN_PINS[0]) == LOW) {
        delay(300);
        startGame();
      }
      break;
    case STATE_PLAYING:
      handlePlaying();
      break;
    case STATE_GAME_OVER:
      if (digitalRead(BTN_PINS[0]) == LOW) {
        delay(300);
        drawMenu();
        currentState = STATE_MENU;
      }
      break;
  }
}

// ============================================================
//   МЕНЮ
// ============================================================
void drawMenu() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println(F("RHYTHM GAME"));
  display.println(F("Press btn 1"));
  display.println(F(""));
  display.print(F("Best: "));
  display.println(highScore);
  display.display();
}

void startGame() {
  score = 0;
  lives = 3;
  combo = 0;
  maxCombo = 0;
  for (int i = 0; i < MAX_NOTES; i++) notes[i].active = false;
  lastNoteSpawn = millis();
  lastUpdate = millis();
  currentState = STATE_PLAYING;
  Serial.println(F("GAME START"));
}

// ============================================================
//   ИГРА
// ============================================================
void handlePlaying() {
  if (millis() - lastUpdate < 20) return; // 50 FPS
  lastUpdate = millis();

  // --- СПАВН НОТ ---
  if (millis() - lastNoteSpawn > noteSpawnInterval) {
    lastNoteSpawn = millis();
    spawnNote();
  }

  // --- ДВИЖЕНИЕ НОТ ---
  for (int i = 0; i < MAX_NOTES; i++) {
    if (!notes[i].active) continue;
    notes[i].y += 1.5; // Скорость падения

    // Промах: нота ушла за экран
    if (notes[i].y > HIT_LINE_Y + 5) {
      notes[i].active = false;
      lives--;
      combo = 0;
      tone(BUZZER, 200, 200);
      Serial.print(F("MISS! Lives: "));
      Serial.println(lives);

      if (lives <= 0) {
        endGame();
        return;
      }
    }
  }

  // --- ОБРАБОТКА КНОПОК ---
  for (int lane = 0; lane < 4; lane++) {
    int btnReading = digitalRead(BTN_PINS[lane]);
    if (btnReading == LOW && lastBtnState[lane] == HIGH) {
      handleLaneHit(lane);
    }
    lastBtnState[lane] = btnReading;
  }

  // --- ОТРИСОВКА ---
  render();
}

void spawnNote() {
  for (int i = 0; i < MAX_NOTES; i++) {
    if (!notes[i].active) {
      notes[i].lane = random(0, 4);
      notes[i].y = 0;
      notes[i].active = true;
      return;
    }
  }
}

void handleLaneHit(int lane) {
  // Ищем ближайшую ноту в этой дорожке
  int bestIndex = -1;
  float bestDistance = 999;

  for (int i = 0; i < MAX_NOTES; i++) {
    if (!notes[i].active) continue;
    if (notes[i].lane != lane) continue;

    float distance = abs(notes[i].y - HIT_LINE_Y);
    if (distance < bestDistance) {
      bestDistance = distance;
      bestIndex = i;
    }
  }

  if (bestIndex >= 0 && bestDistance < 8) {
    // Попадание!
    notes[bestIndex].active = false;
    combo++;
    if (combo > maxCombo) maxCombo = combo;

    int points = 10 + (combo / 5) * 5; // Больше комбо — больше очков
    score += points;

    tone(BUZZER, LANE_TONES[lane], 50);
    Serial.print(F("HIT! Lane "));
    Serial.print(lane);
    Serial.print(F(" Combo "));
    Serial.print(combo);
    Serial.print(F(" Score "));
    Serial.println(score);

    // Ускорение игры каждые 10 попаданий
    if (score % 100 == 0) {
      noteSpawnInterval = max(300, noteSpawnInterval - 100);
    }
  } else {
    // Промах — нажал, когда ноты нет
    combo = 0;
    lives--;
    tone(BUZZER, 150, 200);
    Serial.print(F("WRONG PRESS! Lives: "));
    Serial.println(lives);

    if (lives <= 0) {
      endGame();
    }
  }
}

void endGame() {
  currentState = STATE_GAME_OVER;
  tone(BUZZER, 100, 800);
  delay(800);

  if (score > highScore) {
    highScore = score;
    EEPROM.write(EEPROM_ADDR, highScore);
    Serial.print(F("NEW RECORD: "));
    Serial.println(highScore);
  }

  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(10, 5);
  display.print(F("GAME"));
  display.setCursor(10, 25);
  display.print(F("OVER"));
  display.setTextSize(1);
  display.setCursor(0, 50);
  display.print(F("Score: "));
  display.print(score);
  display.print(F(" Best: "));
  display.print(highScore);
  display.display();

  Serial.print(F("GAME OVER. Score: "));
  Serial.print(score);
  Serial.print(F(" Max Combo: "));
  Serial.println(maxCombo);
}

// ============================================================
//   ОТРИСОВКА
// ============================================================
void render() {
  display.clearDisplay();

  // --- Дорожки ---
  for (int i = 0; i < 4; i++) {
    display.drawLine(LANE_X[i] + LANE_WIDTH / 2, 0,
                     LANE_X[i] + LANE_WIDTH / 2, SCREEN_HEIGHT, SSD1306_WHITE);
  }

  // --- Линия попадания ---
  display.drawLine(0, HIT_LINE_Y, SCREEN_WIDTH, HIT_LINE_Y, SSD1306_WHITE);

  // --- Зоны попадания (маленькие квадраты) ---
  for (int i = 0; i < 4; i++) {
    display.drawRect(LANE_X[i] + 5, HIT_LINE_Y - 4, LANE_WIDTH - 10, 8, SSD1306_WHITE);
  }

  // --- Ноты ---
  for (int i = 0; i < MAX_NOTES; i++) {
    if (!notes[i].active) continue;
    int x = LANE_X[notes[i].lane] + 5;
    int y = (int)notes[i].y;
    if (y < -10 || y > SCREEN_HEIGHT) continue;
    display.fillRect(x, y, LANE_WIDTH - 10, NOTE_HEIGHT, SSD1306_WHITE);
  }

  // --- HUD ---
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print(F("S:"));
  display.print(score);
  display.setCursor(50, 0);
  display.print(F("C:"));
  display.print(combo);
  display.setCursor(95, 0);
  display.print(F("L:"));
  display.print(lives);

  display.display();
}