Курс Arduino с нуля · Большие проекты

Урок 30. Проект: игра «Повтори за мной»

Плата становится ведущим: сама сочиняет мелодию, показывает её нотами и цифрами, а вы повторяете кнопками. Уровни растут, темп ускоряется, рекорд — на экране.

Урок 30 из 31⏱ примерно 50 минутОбновлено: 30 июля 2026

Что понадобится

  • Плата Uno из набора
  • Кабель USB
  • Макетная плата
  • Экран LCD1602
  • Потенциометр 10 кОм
  • 3 тактовые кнопки
  • Зуммер
  • 24 провода «папа-папа»

Все детали есть в обучающем наборе, по которому идёт курс.

Полвека назад по миру разошлась игрушка «Саймон»: четыре цветные кнопки, которые загораются в случайном порядке, а игрок повторяет последовательность, пока хватает памяти. Сегодня соберём свою версию — «Повтори за мной». Плата впервые побудет не исполнителем, а ведущим: сама придумает задание, сама покажет его и сама рассудит, справились вы или нет.

В брошюре набора эта игра тоже есть — глава 17, там она собрана на трёх светодиодах. Нам светодиоды ставить некуда, да и незачем: на макетке уже стоит экран, он и «подсветит» кнопки, и заодно покажет уровень и рекорд. Из нового железа — только зуммер из урока 19.

Что построим

Uroven5Smotri...Tvoyhod!2Oshibkana5Rekord:7мелодиясыгранавсё верно —шаг добавилсяне такнопкановая мелодия — и снова уровень 1Плата показываетноты и цифры по очередиИгрок повторяеткаждое нажатие проверяется сразуОшибкасчёт и рекорд на экране
Круг игры: плата играет мелодию и показывает цифры, игрок повторяет её кнопками. Верный ответ удлиняет мелодию на шаг, ошибка — счёт, рекорд и всё сначала.

Правила простые:

  • у каждой кнопки — своя нота и своё место на экране: левая, середина, правая;
  • плата проигрывает мелодию, а экран подсказывает глазами: на каком месте цифра — ту кнопку и жать;
  • повторили без ошибок — мелодия становится на один шаг длиннее и чуть быстрее;
  • ошиблись — грустный звук, счёт и рекорд на экране, новая мелодия и снова первый уровень;
  • дожали до шестнадцатого уровня — победные фанфары.

Как плата запоминает мелодию

вся мелодия в памяти213312…и так до 16игрок её не видит — она спрятана в массивеуровень 1 играет2уровень 2 играет21уровень 3 играет213Мелодия не перепридумывается: каждый раунд открывает из неё на один шаг больше.
Мелодия рождается целиком при старте и лежит в массиве. Раунды не придумывают новых нот — они открывают из готовой мелодии на один шаг больше.

Это главный секрет программы. Кажется, что игра «добавляет ноту» на каждом уровне, но на самом деле вся мелодия из шестнадцати шагов придумана заранее и записана в массив — как секретный код в сейфе из урока 29. Уровень — это просто число: сколько первых клеток массива играем и спрашиваем.

Придумывает мелодию команда random — мы звали её в уроке 21. У неё есть подвох, о котором честно пишет и брошюра набора: случайные числа не случайны. После каждого включения плата выдаёт одну и ту же «случайную» цепочку — запомнил мелодию один раз и выигрывай вечно. Лекарство то же, что в уроке 21: перед игрой «завести» случайность шумом свободного входа — randomSeed(analogRead(A0)).

Кто на каком выводе

Сейф из урока 29 не разбираем — из него уйдёт только сервопривод: выньте три его провода, засов игре не нужен. Кнопки остаются на выводах 7, 8 и 10, экран — на своих шести. Зуммер садится на вывод 6 — если вы делали задание со звёздочкой в уроке 29, у вас всё уже стоит.

Полный список: экран — 12, 11, 5, 4, 3, 2; кнопки — 7, 8, 10; зуммер — 6. Свободны 9 и 13. Вывод A0 остаётся пустым нарочно — его шум кормит генератор случайностей.

Собираем схему

+abcdefghi+Uroven5Smotri...12345678910111213141516кнопка 1кнопка 2кнопка 3151520911132426283012341Экран, потенциометр контраста и три кнопки — в точности как в уроке 29, сейф не разбираем.2Зуммер висит над шинами: ножки в ряд a, столбики 10 и 13, знаком «+» вправо. Отверстия шин под его корпусом не используем.3Сигнал зуммера: из столбика 13 вниз на дорожку, затем направо, наверх и на вывод 6.4Минус зуммера: из столбика 10 по дорожке налево и в самое первое отверстие шины «−».Сервопривод из урока 29 больше не нужен: выньте три его провода — вывод 9 освободился.Провод соединён только там, где на нём светлая шляпка; шины он пересекает между отверстиями.
Всё из урока 29, только без сервопривода. Зуммер висит над шинами: ножки в столбиках 10 и 13, сигнал уходит на вывод 6.
  1. Отключите сервопривод: выньте его оранжевый, красный и коричневый провода. Больше ничего из урока 29 не трогайте.
  2. Вставьте зуммер между кнопкой 1 и проводами экрана: ножка со знаком «+» на крышке — в столбик 13 ряда a, вторая — в столбик 10. Корпус повиснет над шинами — их отверстия под ним просто не используем.
  3. Сигнальный провод: из столбика 13 (любое свободное отверстие рядов b–e) на вывод 6 платы.
  4. Минус: из столбика 10 на верхнюю шину «−» — по дорожке налево, в самое первое отверстие шины у левого края макетки.

Проверьте перед включением: знак «+» зуммера смотрит в сторону столбика 13, и именно из этого столбика провод идёт на вывод 6; минус из столбика 10 сидит в шине «−»; кнопки и экран не тронуты — при сомнении сверьтесь со схемой урока 29.

Шаг 1. Каждой кнопке — голос и место

Начинаем, как всегда, с проверки железа. Соберём мини-пианино из трёх клавиш: нажатие играет ноту и показывает цифру на «своём» краю экрана:

// Урок 30. Шаг 1: у каждой кнопки — свой голос и своё место на экране.

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int BUTTONS[3] = {7, 8, 10};
const int NOTES[3] = {523, 659, 784};   // до, ми и соль второй октавы
const int BUZZER_PIN = 6;
// Где на экране живёт каждая кнопка: левая, середина, правая.
const int SPOTS[3] = {2, 7, 12};

void setup() {
  lcd.begin(16, 2);
  for (int i = 0; i < 3; i++) {
    pinMode(BUTTONS[i], INPUT_PULLUP);
  }
  lcd.print("Tri knopki -");
  lcd.setCursor(0, 1);
  lcd.print("tri noty");
}

void loop() {
  for (int i = 0; i < 3; i++) {
    if (digitalRead(BUTTONS[i]) == LOW) {
      playKey(i + 1);
    }
  }
}

// Сыграть кнопку: цифра на своём месте экрана плюс её нота.
void playKey(int number) {
  lcd.clear();
  lcd.setCursor(SPOTS[number - 1], 0);
  lcd.print(number);
  tone(BUZZER_PIN, NOTES[number - 1], 300);
  delay(360);   // нота 300 мс и короткая тишина после неё
  lcd.clear();
}
  • частоты 523, 659 и 784 — это до, ми и соль из таблицы нот урока 19: три ноты, которые звучат дружно в любом порядке;
  • массив с позициями решает, где рисовать цифру: кнопка 1 — слева, 2 — в середине, 3 — справа. Экран повторяет географию кнопок на макетке;
  • нота длится 300 миллисекунд, и после неё выдержана короткая тишина — правило из урока 19, чтобы звуки не слипались.

Прошейте и понажимайте: каждая кнопка должна отвечать своей нотой, а цифра — появляться на своей стороне. Если что-то молчит — раздел «Не работает?» ниже.

Шаг 2. Плата придумывает мелодию

Теперь пусть ведущий сочинит задание. Заводим массив на шестнадцать шагов и заполняем его случайными номерами кнопок:

// Урок 30. Шаг 2: плата придумывает мелодию и показывает её.

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int BUTTONS[3] = {7, 8, 10};
const int NOTES[3] = {523, 659, 784};
const int BUZZER_PIN = 6;
const int SPOTS[3] = {2, 7, 12};

const int SEQ_MAX = 16;   // длиннее мелодия не вырастет
int seq[SEQ_MAX];         // сама мелодия: номера кнопок по порядку

void setup() {
  lcd.begin(16, 2);
  // Уберите эту строку — и после каждого включения мелодия
  // будет одна и та же. Случайность надо «завести» шумом.
  randomSeed(analogRead(A0));
  for (int i = 0; i < SEQ_MAX; i++) {
    seq[i] = random(1, 4);   // случайное 1, 2 или 3
  }
}

void loop() {
  lcd.clear();
  lcd.print("Smotri:");
  delay(800);
  for (int i = 0; i < 5; i++) {   // пока показываем первые пять шагов
    playKey(seq[i]);
  }
  delay(2000);                    // передышка — и снова с начала
}

void playKey(int number) {
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(number);
  tone(BUZZER_PIN, NOTES[number - 1], 300);
  delay(360);
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(" ");   // стереть цифру, чтобы шаги не сливались
}
  • random(1, 4) выдаёт 1, 2 или 3 — правая граница, как всегда у random, не входит;
  • цифры показа рисуются во второй строке и стираются после каждой ноты — иначе два одинаковых шага подряд слились бы в один;
  • пока программа просто крутит первые пять шагов по кругу — послушайте, как звучит её «почерк».

Ради опыта закомментируйте строку с randomSeed двумя косыми чертами, прошейте и перезапустите плату несколько раз кнопкой RESET: мелодия будет одна и та же. Верните строку — теперь каждый запуск звучит по-новому.

Шаг 3. Повтори и проверь — сразу

Подключаем игрока. Плата показывает четыре шага, потом ждёт четыре нажатия. И вот отличие от сейфа из урока 29: там мы копили весь код и сравнивали в конце, а здесь каждое нажатие проверяется немедленно — зачем слушать дальше, если игрок уже ошибся?

// Урок 30. Шаг 3: четыре шага — и проверка каждого нажатия сразу.

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int BUTTONS[3] = {7, 8, 10};
const int NOTES[3] = {523, 659, 784};
const int BUZZER_PIN = 6;
const int SPOTS[3] = {2, 7, 12};

const int SEQ_MAX = 16;
int seq[SEQ_MAX];

void setup() {
  lcd.begin(16, 2);
  for (int i = 0; i < 3; i++) {
    pinMode(BUTTONS[i], INPUT_PULLUP);
  }
  randomSeed(analogRead(A0));
  for (int i = 0; i < SEQ_MAX; i++) {
    seq[i] = random(1, 4);
  }
}

void loop() {
  // Показ задания.
  lcd.clear();
  lcd.print("Smotri:");
  delay(800);
  for (int i = 0; i < 4; i++) {
    playKey(seq[i]);
  }

  // Ответ игрока.
  lcd.clear();
  lcd.print("Tvoy hod!");
  bool allRight = true;
  for (int i = 0; i < 4; i++) {
    int pressed = waitKey();
    playKey(pressed);          // игра отзывается нотой на нажатие
    if (pressed != seq[i]) {   // сверяем сразу, не дожидаясь конца
      allRight = false;
      break;                   // дальше слушать незачем
    }
  }

  lcd.clear();
  if (allRight) {
    lcd.print("Verno!");
    tone(BUZZER_PIN, 659, 120);
    delay(150);
    tone(BUZZER_PIN, 784, 250);
  } else {
    lcd.print("Oshibka!");
    tone(BUZZER_PIN, 300, 250);
    delay(280);
    tone(BUZZER_PIN, 200, 500);
  }
  delay(2000);
}

// Стоять на месте, пока игрок не нажмёт кнопку. Вся работа — ждать,
// поэтому блокирующее ожидание здесь уместно, как в уроке 21.
int waitKey() {
  while (true) {
    for (int i = 0; i < 3; i++) {
      if (digitalRead(BUTTONS[i]) == LOW) {
        delay(30);                                    // дребезг нажатия
        while (digitalRead(BUTTONS[i]) == LOW) {}     // ждём отпускания
        delay(30);                                    // дребезг отпускания
        return i + 1;
      }
    }
  }
}

void playKey(int number) {
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(number);
  tone(BUZZER_PIN, NOTES[number - 1], 300);
  delay(360);
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(" ");
}
  • новая функция ждёт нажатия и возвращает номер кнопки; внутри — вечный цикл, и это законно: пока игрок думает, у платы правда нет других дел (урок 21);
  • после нажатия программа дожидается, пока кнопку отпустят, — иначе одно долгое нажатие засчиталось бы за несколько;
  • на каждое нажатие игра отзывается нотой и цифрой — как настоящий «Саймон»;
  • break обрывает проверку на первой же ошибке — приём из урока 29, только там он жил внутри функции-судьи.

Шаг 4. Настоящая игра: уровни, темп, рекорд

Осталось превратить упражнение в игру. Уровень — это сколько шагов мелодии играем; после каждого успеха он растёт на один. А чтобы было веселее, с ростом уровня ноты становятся короче — игра ускоряется:

// Урок 30. Шаг 4: настоящая игра — мелодия растёт, темп ускоряется.

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int BUTTONS[3] = {7, 8, 10};
const int NOTES[3] = {523, 659, 784};
const int BUZZER_PIN = 6;
const int SPOTS[3] = {2, 7, 12};

const int SEQ_MAX = 16;   // столько уровней до полной победы
int seq[SEQ_MAX];
int level = 1;            // сколько шагов играем сейчас
int record = 0;           // лучший уровень с момента включения

void setup() {
  lcd.begin(16, 2);
  for (int i = 0; i < 3; i++) {
    pinMode(BUTTONS[i], INPUT_PULLUP);
  }
  randomSeed(analogRead(A0));
  newMelody();
}

void loop() {
  // Показ: первые level шагов мелодии.
  lcd.clear();
  lcd.print("Uroven ");
  lcd.print(level);
  lcd.setCursor(0, 1);
  lcd.print("Smotri...");
  delay(900);
  lcd.setCursor(0, 1);
  lcd.print("         ");
  for (int i = 0; i < level; i++) {
    playKey(seq[i]);
  }

  // Ответ: столько же нажатий, проверка на лету.
  lcd.setCursor(0, 0);
  lcd.print("Tvoy hod!    ");
  for (int i = 0; i < level; i++) {
    int pressed = waitKey();
    playKey(pressed);
    if (pressed != seq[i]) {
      gameOver();
      return;   // начать loop заново — уже с первого уровня
    }
  }

  // Уровень пройден.
  if (level == SEQ_MAX) {
    lcd.clear();
    lcd.print("POBEDA!");
    for (int f = 400; f <= 1200; f = f + 200) {   // фанфары вверх
      tone(BUZZER_PIN, f, 120);
      delay(140);
    }
    delay(2000);
    if (level > record) {
      record = level;
    }
    level = 1;
    newMelody();
  } else {
    level = level + 1;
    delay(600);
  }
}

void gameOver() {
  tone(BUZZER_PIN, 300, 250);
  delay(280);
  tone(BUZZER_PIN, 200, 500);
  if (level > record) {
    record = level;   // запомнили, докуда добрались
  }
  lcd.clear();
  lcd.print("Oshibka na ");
  lcd.print(level);
  lcd.setCursor(0, 1);
  lcd.print("Rekord: ");
  lcd.print(record);
  delay(3000);
  level = 1;
  newMelody();   // новая мелодия: прежнюю подсмотреть не выйдет
}

void newMelody() {
  for (int i = 0; i < SEQ_MAX; i++) {
    seq[i] = random(1, 4);
  }
}

// Чем выше уровень, тем короче ноты — игра ускоряется.
int noteTime() {
  int len = 320 - level * 12;
  if (len < 140) {
    len = 140;
  }
  return len;
}

int waitKey() {
  while (true) {
    for (int i = 0; i < 3; i++) {
      if (digitalRead(BUTTONS[i]) == LOW) {
        delay(30);
        while (digitalRead(BUTTONS[i]) == LOW) {}
        delay(30);
        return i + 1;
      }
    }
  }
}

void playKey(int number) {
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(number);
  tone(BUZZER_PIN, NOTES[number - 1], noteTime());
  delay(noteTime() + noteTime() / 5);
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(" ");
}

Что здесь важного:

  • показ и опрос используют одну и ту же переменную-уровень — сколько показали, столько и спросили;
  • длину ноты считает отдельная функция: триста двадцать миллисекунд минус двенадцать за каждый уровень, но не короче ста сорока — на высоких уровнях игра заметно злеет;
  • после проигрыша мелодия придумывается заново — иначе можно было бы выучить старую по кусочку за попытку;
  • рекорд живёт до выключения питания, как в игре на реакцию из урока 21.

Прошейте и сыграйте всей семьёй. Уровень седьмой-восьмой — уже серьёзный результат: у обычного человека в «оперативной памяти» помещается около семи вещей.

Не работает? Разбираемся

  • Нет звука. Проверьте зуммер: «+» должен смотреть к столбику 13, сигнал — уходить со столбика 13 на вывод 6, минус — со столбика 10 в шину «−». Перепутанные столбики — самая частая причина тишины.
  • Мелодия после каждого включения одна и та же. Потерялась строка с randomSeed — верните её в начало программы.
  • Одно нажатие засчитывается за два. Дребезг сильнее обычного: увеличьте обе паузы в функции ожидания с 30 до 50–80 миллисекунд.
  • Экран пустой или квадраты. Контраст: медленно покрутите потенциометр, как в уроке 26.
  • Цифры появляются не там, где нажимаете. Кнопки перепутаны местами: левая должна сидеть на выводе 7, средняя на 8, правая на 10.
  • Игра «нажимает» кнопки сама. Какая-то кнопка повёрнута на 90 градусов и замкнута всегда — пересадите её ножками вверх-вниз, как в уроке 10.
  • Звук есть, но хриплый и тихий. Зуммер сел ножками не до конца — прижмите его, чтобы ножки ушли в отверстия полностью.

Задания для самостоятельной работы

  • Задание 1. Поменяйте голоса: возьмите частоты нот из другой октавы — таблица была в уроке 19. Игра сразу зазвучит иначе.
  • Задание 2. Настройте сложность под игрока: для младших сделайте победу на восьмом уровне, для чемпионов — на двадцатом. Меняется одна константа.
  • Задание со звёздочкой ⭐. Сейчас плата ждёт нажатия вечно — можно уйти пить чай и вернуться. Сделайте по-турнирному: на каждое нажатие даётся пять секунд, не успели — проигрыш с надписью «Ne uspel!». Пригодятся часы millis из урока 11.
Разбор задания со звёздочкой

Вся хитрость — в функции ожидания. Раньше её цикл крутился вечно, теперь он смотрит на часы: прошло больше пяти секунд — возвращаем ноль. Ноль не может быть номером кнопки, поэтому он отлично работает «особым сигналом» — как минус один в пианино из урока 20. Осталось поймать этот ноль в игре и показать «Ne uspel!».

// Урок 30. Разбор задания: думать можно не дольше пяти секунд.

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int BUTTONS[3] = {7, 8, 10};
const int NOTES[3] = {523, 659, 784};
const int BUZZER_PIN = 6;
const int SPOTS[3] = {2, 7, 12};

const int SEQ_MAX = 16;
const unsigned long ANSWER_LIMIT = 5000;   // пять секунд на одно нажатие
int seq[SEQ_MAX];
int level = 1;
int record = 0;

void setup() {
  lcd.begin(16, 2);
  for (int i = 0; i < 3; i++) {
    pinMode(BUTTONS[i], INPUT_PULLUP);
  }
  randomSeed(analogRead(A0));
  newMelody();
}

void loop() {
  lcd.clear();
  lcd.print("Uroven ");
  lcd.print(level);
  lcd.setCursor(0, 1);
  lcd.print("Smotri...");
  delay(900);
  lcd.setCursor(0, 1);
  lcd.print("         ");
  for (int i = 0; i < level; i++) {
    playKey(seq[i]);
  }

  lcd.setCursor(0, 0);
  lcd.print("Tvoy hod!    ");
  for (int i = 0; i < level; i++) {
    int pressed = waitKey();
    if (pressed == 0) {          // ноль — значит, время вышло
      lcd.clear();
      lcd.print("Ne uspel!");
      delay(1200);
      gameOver();
      return;
    }
    playKey(pressed);
    if (pressed != seq[i]) {
      gameOver();
      return;
    }
  }

  if (level == SEQ_MAX) {
    lcd.clear();
    lcd.print("POBEDA!");
    for (int f = 400; f <= 1200; f = f + 200) {
      tone(BUZZER_PIN, f, 120);
      delay(140);
    }
    delay(2000);
    if (level > record) {
      record = level;
    }
    level = 1;
    newMelody();
  } else {
    level = level + 1;
    delay(600);
  }
}

// Ждём нажатия, но не вечно: часы millis отсчитывают лимит.
int waitKey() {
  unsigned long start = millis();
  while (millis() - start < ANSWER_LIMIT) {
    for (int i = 0; i < 3; i++) {
      if (digitalRead(BUTTONS[i]) == LOW) {
        delay(30);
        while (digitalRead(BUTTONS[i]) == LOW) {}
        delay(30);
        return i + 1;
      }
    }
  }
  return 0;   // особое значение: «никто не нажал»
}

void gameOver() {
  tone(BUZZER_PIN, 300, 250);
  delay(280);
  tone(BUZZER_PIN, 200, 500);
  if (level > record) {
    record = level;
  }
  lcd.clear();
  lcd.print("Oshibka na ");
  lcd.print(level);
  lcd.setCursor(0, 1);
  lcd.print("Rekord: ");
  lcd.print(record);
  delay(3000);
  level = 1;
  newMelody();
}

void newMelody() {
  for (int i = 0; i < SEQ_MAX; i++) {
    seq[i] = random(1, 4);
  }
}

int noteTime() {
  int len = 320 - level * 12;
  if (len < 140) {
    len = 140;
  }
  return len;
}

void playKey(int number) {
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(number);
  tone(BUZZER_PIN, NOTES[number - 1], noteTime());
  delay(noteTime() + noteTime() / 5);
  lcd.setCursor(SPOTS[number - 1], 1);
  lcd.print(" ");
}

Обратите внимание: лимит объявлен константой с типом unsigned long — с миллисекундами мы всегда работаем в этом типе, привычка из урока 11.

Что мы узнали

  • плата может быть ведущим: сама придумывает задание и сама судит ответ;
  • случайные числа не случайны — генератор надо заводить шумом свободного входа;
  • растущая сложность — это не новые данные, а число «сколько шагов открыто»;
  • проверять ответ можно на лету, не дожидаясь конца, — в отличие от сейфа;
  • ожидание с лимитом времени строится на millis и «особом» возвращаемом значении;
  • один и тот же набор деталей — сейф вчера, игра сегодня: разницу делает программа.

Остался последний урок курса. В нём не будет готовой схемы и готовой программы — вы сами придумаете устройство и пройдёте весь путь: от идеи на бумажке до работающего прибора. Все инструменты для этого у вас уже есть.

Частые вопросы

Как сделать игру Саймон на Arduino без светодиодов?

Подсветку кнопок заменяет экран LCD1602: у каждой кнопки своё место на экране — левая, середина, правая, — и во время показа мелодии цифра появляется на нужной стороне. Заодно экран показывает уровень и рекорд, чего светодиоды не умеют.

Почему random выдаёт одну и ту же последовательность?

Генератор случайных чисел Arduino после включения всегда начинает с одного места — числа выходят «случайные», но одинаковые. Лекарство — строка randomSeed(analogRead(A0)) в начале программы: шум свободного входа A0 каждый раз заводит генератор по-новому.

Сколько уровней в игре и как это поменять?

В нашей версии полная победа — шестнадцать уровней, длина мелодии задана одной константой. Поставьте восемь для младших игроков или двадцать для чемпионов — программа пересчитает всё сама.

Чем проверка ответа отличается от сейфа из урока 29?

Сейф сначала копил весь код и сравнивал его с секретом в конце. Игра проверяет каждое нажатие сразу: как только игрок ошибся, слушать дальше незачем — звучит грустный сигнал и раунд заканчивается.

Можно ли добавить в игру светодиоды, как в брошюре набора?

Да: после урока 29 свободны выводы 9 и 13. На два светодиода хватит прямо так, а если снять экран, освободятся ещё шесть выводов — тогда получится собрать игру ровно по брошюре, с подсветкой возле каждой кнопки.

← Все уроки курса