Загрузка данных
#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();
}