
Создаём электронную игру Simon Says на Arduino с четырьмя светодиодами, кнопками и пьезодинамиком. Полный код, список компонентов и пошаговое объяснение алгоритма.
Электронная игра на запоминание последовательностей (известная как Simon Says) -- классика, придуманная в 1978 году. Устройство показывает случайную последовательность цветовых сигналов, а игрок должен повторить её в правильном порядке. С каждым раундом последовательность удлиняется на один элемент, и задача становится всё сложнее.
Этот проект идеален для изучения работы с несколькими кнопками одновременно, массивами, генерацией случайных чисел и таймерами на Arduino. Пьезодинамик добавляет звуковую обратную связь -- каждому цвету соответствует своя нота, что помогает запоминать последовательность не только визуально, но и на слух.
Уровень сложности: средний, подходит для 6-8 классов.

В этом проекте каждая кнопка и светодиод делят один и тот же пин Arduino. Это возможно благодаря тому, что в каждый момент времени пин работает либо как выход (для светодиода), либо как вход (для чтения кнопки). Такой приём экономит пины.
Распределение пинов:
| Компонент | Пин Arduino |
|---|---|
| Кнопка/LED 1 (красный) | 2 |
| Кнопка/LED 2 (зелёный) | 13 |
| Кнопка/LED 3 (жёлтый) | 10 |
| Кнопка/LED 4 (белый) | 8 |
| Пьезодинамик | 5 |
Каждый светодиод подключается через резистор 220 Ом к соответствующему пину. Кнопка одним контактом соединяется с тем же пином, другим -- с GND. Резистор 220 Ом на кнопке служит для защиты. Пьезодинамик подключается между пином 5 и GND.
Важно: расположите каждую кнопку рядом со «своим» светодиодом на макетной плате. Это критично для удобства игры -- игрок должен интуитивно понимать, какую кнопку нажимать для повтора конкретного цвета.

```cpp
// Игра на запоминание последовательностей (Simon Says)
// 4 светодиода, 4 кнопки, пьезодинамик
// Пины для кнопок и светодиодов (общие)
const int pins[] = {2, 13, 10, 8};
// Пин пьезодинамика
const int BUZZER_PIN = 5;
// Массив для хранения последовательности (до 100 элементов)
int sequence[100];
// Текущая длина последовательности
int curLen = 0;
// Время ожидания нажатия кнопки (мс)
const int PLAYER_WAIT_TIME = 2000;
void setup() {
Serial.begin(9600);
// Инициализация случайных чисел от шума на аналоговом пине
randomSeed(analogRead(A0));
// Пауза 3 секунды для стабилизации
delay(3000);
// Генерируем первый элемент последовательности
curLen = 0;
resetGame();
}
// Сброс игры -- начало новой последовательности
void resetGame() {
curLen = 1;
sequence[0] = random(0, 4); // случайный цвет (0-3)
}
// Включить светодиод на указанном пине
void lightOn(int pin) {
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
}
// Выключить светодиод
void lightOff(int pin) {
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
}
// Звуковой сигнал заданной частоты и длительности
void beep(int freq, int duration) {
tone(BUZZER_PIN, freq, duration);
delay(duration);
noTone(BUZZER_PIN);
}
// Показать текущую последовательность игроку
void playSequence() {
// Короткий сигнал -- внимание, смотри!
beep(200, 100);
delay(500);
for (int i = 0; i < curLen; i++) {
int pin = pins[sequence[i]];
lightOn(pin);
delay(500); // светодиод горит полсекунды
lightOff(pin);
delay(250); // пауза между элементами
}
}
// Быстрое мигание всех светодиодов (эффект проигрыша)
void flashAll() {
for (int k = 0; k < 4; k++) {
// Зажигаем все
for (int i = 0; i < 4; i++) lightOn(pins[i]);
delay(150);
// Гасим все
for (int i = 0; i < 4; i++) lightOff(pins[i]);
delay(150);
}
}
// Считать нажатую кнопку (возвращает индекс 0-3 или -1)
int readButton() {
// Переключаем все пины в режим входа
for (int i = 0; i < 4; i++) {
pinMode(pins[i], INPUT_PULLUP);
}
unsigned long startTime = millis();
while (millis() - startTime < PLAYER_WAIT_TIME) {
for (int i = 0; i < 4; i++) {
if (digitalRead(pins[i]) == LOW) {
// Кнопка нажата -- подсвечиваем
delay(50); // антидребезг
if (digitalRead(pins[i]) == LOW) {
// Подтверждаем нажатие светом и звуком
lightOn(pins[i]);
beep(300 + i * 200, 200);
// Ждём отпускания
while (digitalRead(pins[i]) == LOW) {
delay(10);
}
lightOff(pins[i]);
delay(100);
return i;
}
}
}
delay(10);
}
return -1; // таймаут -- игрок не ответил
}
// Получить ответы игрока и проверить правильность
bool checkPlayerInput() {
for (int i = 0; i < curLen; i++) {
int expected = sequence[i];
int pressed = readButton();
// Вывод в Serial Monitor для отладки
Serial.print("Ожидается: ");
Serial.print(expected);
Serial.print(" | Нажато: ");
Serial.println(pressed);
if (pressed != expected) {
return false; // ошибка или таймаут
}
}
return true; // всё правильно
}
void loop() {
// Показываем последовательность
playSequence();
// Ждём ответ игрока
if (checkPlayerInput()) {
// Верно! Звук победы и переход на следующий уровень
beep(1000, 100);
delay(100);
beep(1500, 200);
delay(800);
// Добавляем новый элемент
sequence[curLen] = random(0, 4);
curLen++;
Serial.print("Уровень: ");
Serial.println(curLen);
} else {
// Ошибка! Эффект проигрыша
Serial.println("Проигрыш!");
beep(100, 500);
flashAll();
delay(1000);
// Начинаем заново
resetGame();
}
}
`
Общие пины для кнопок и светодиодов. Главная особенность этой схемы -- кнопка и светодиод подключены к одному пину. Когда нужно зажечь светодиод, пин переключается в OUTPUT. Когда нужно прочитать кнопку -- в INPUT_PULLUP. Функции lightOn() и readButton() управляют этим переключением автоматически.
Массив `sequence[]`. Хранит до 100 случайных чисел от 0 до 3. Каждое число соответствует одному из четырёх цветов. При каждом успешном раунде к массиву добавляется новый элемент функцией random(0, 4).
Показ последовательности. Функция playSequence() проходит по массиву и для каждого элемента зажигает соответствующий светодиод на 500 мс с паузой 250 мс между элементами. Перед началом показа раздаётся короткий звуковой сигнал -- он сообщает игроку: «Внимание, запоминай!»
Чтение кнопок с антидребезгом. Механические кнопки при нажатии дают серию быстрых контактов (дребезг). Функция readButton() решает эту проблему двойной проверкой с задержкой 50 мс. Если после паузы кнопка всё ещё нажата -- значит, это реальное нажатие, а не шум.
Таймер ожидания. У игрока есть 2 секунды (PLAYER_WAIT_TIME) на каждое нажатие. Если время истекло, функция возвращает -1, что засчитывается как ошибка. Таймер реализован через millis() -- более надёжный способ, чем delay(), потому что позволяет проверять кнопки непрерывно.
Отладка через Serial Monitor. Код выводит ожидаемое и фактическое значение каждого нажатия в Serial Monitor. Это незаменимо при отладке -- если игра ведёт себя странно, открыв монитор порта, можно точно увидеть, что идёт не так.
Начните без кода. Перед сборкой поиграйте с учениками в устную версию Simon Says: один ученик показывает последовательность цветов (используя цветные карточки), другие повторяют. Это поможет понять логику игры до погружения в код.
Настройте сложность. Измените PLAYER_WAIT_TIME для разных возрастов: 3000 мс для младших классов, 1500 мс для продвинутых. Можно также уменьшать время показа каждого элемента с ростом уровня.
Соревнование в классе. Используйте Serial Monitor для отслеживания максимального уровня. Устройте турнир -- кто запомнит самую длинную последовательность. Рекорд можно сохранять в EEPROM Arduino, чтобы он не сбрасывался при выключении.
Расширение проекта. Предложите ученикам добавить LCD-дисплей для отображения текущего уровня и рекорда. Можно реализовать режим для двух игроков с поочерёдными раундами.
В каталоге Alashed Hardware есть готовые наборы со светодиодами, кнопками, пьезодинамиком и Arduino Uno -- всё подобрано для немедленного начала сборки. Среда Alashed CodeStudio позволяет ученикам писать код в браузере, видеть Serial Monitor онлайн и делиться проектами друг с другом, что идеально подходит для групповой работы в классе.
Подключите школу к пилоту. Генерируйте КМЖ за 2 минуты, ведите CodeStudio уроки, заказывайте оборудование — всё в одном месте.