понедельник, 27 ноября 2017 г.

Саймон говорит - игра на Ардуино

Саймон говорит - игра для развития памяти на Ардуино
Саймон говорит - детская игра, популярная прежде всего в англоговорящих странах. В ней игроки должны выполнять приказы, которые отдает ведущий. Приказы начинаются со слов "Саймон говорит ...", как правило, это просты активные действия, например, "подпрыгни", "похлопай в ладоши" и т.п. Идея этой игры легла в основу электронной игрушки, весьма популярной в конце 1970-х и начала 1980-х годов. Задачей игрока было запомнить продемонстрированную ему последовательность вспышек и звуков и затем воспроизвести ее. Эта игрушка развивает зрительную и слуховую память, скорость реакции. Ее до сих пор можно найти в магазинах. Но, чем покупать, гораздо интереснее собрать такую игрушку самому. Этим мы сегодня и займемся.

Для сборки игры потребуются:
  • Ардуино. Я использую Arduino Nano;
  • 4 светодиода разных цветов;
  • 4 кнопки;
  • Динамик на 8 Ом. Можно обойтись и без него, но со звуком играть гораздо интереснее;
  • Резисторы: 4 по 10 кОм для кнопок, 4 по 220 Ом для светодиодов и 1 резистор на 100 Ом для динамика;
  • Ну и провода, чтобы всё это соединить.

Собираем устройство по следующей схеме:

Саймон говорит - схема на Ардуино

В классической игрушке использовались именно 4 кнопки и светодиода, но я при сборке своей решил отступить от этого правила и сократил их количество до трех, чтобы пятилетнему ребенку было попроще играть. В качестве корпуса можно использовать любую подходящую коробку или контейнер, мне, например, попалась под руку коробка от виртуального осциллографа. Для этого я проделал в ней отверстия для кнопок, светодиодов и выключателя. Светодиоды закрепил термоклеем, остальное отлично зафиксировалось штатными гайками. В итоге получилось вот что:

Самодельная игра на Ардуино Саймон говорит

Внутри коробки

Когда устройство собрано остается только прошить Ардуино. Ниже приведен код, я снабдил его достаточным количеством комментариев. Думаю, кто захочет, сможет без труда в нем разобраться. Скачать данный скетч можно по ссылке.

#define SequenceMaxLen 50      // Длина последовательности
#define ShowingTime 300        // Длительность отображения элемента последовательности (мс)
#define DelayBetweenLeds 200   // Задержка между отображением элементов (мс)
#define WaitingTime 10000      // Время ожидания ввода (мс)
#define RepeatRightSequence 1  // Нужно ли повторять правильную последовательность при проигрыше
byte Sequence[SequenceMaxLen]; // Последовательность включения светодиодов
byte CurrentLen;               // Текущая длина последовательности
int CurrentIndex;              // Номер обрабатываемого элемента последовательности
byte ButtonsNLeds[] = {2, 3, 4, 5}; // Номера выводов, к которым подключены кнопки и светодиоды
byte ButtonCount = sizeof(ButtonsNLeds) / sizeof(ButtonsNLeds[0]);
bool ButtonStillPressed;
enum eMode {mStartNewGame, mShowingSequence, mRepeatSequence, mWaitingForButtons};
eMode Mode;                    // Текущий режим: отображение последовательности/ожидание ввода
unsigned long tm;              // Переменная для отслеживания временных интервалов

#define pin_Speaker 12         // Пин к которому подключен динамик
int Notes[] = {880, 988, 1047, 1175}; // Частоты нот для каждой из кнопок (A5, B5, C6, D6)

void SetPinMode(byte NewPinMode) {
  for (byte i = 0; i < ButtonCount; i++)
    pinMode(ButtonsNLeds[i], NewPinMode);
}

void Blink(int Count, int DelayTime) {
  SetPinMode(OUTPUT);
  for (byte t = 0; t < Count; t++) {
    for (byte i = 0; i < ButtonCount; i++)
      digitalWrite(ButtonsNLeds[i], 1);
    delay(DelayTime);
    for (byte i = 0; i < ButtonCount; i++)
      digitalWrite(ButtonsNLeds[i], 0);
    delay(DelayTime);
  }
}

void Win() {
  tone(pin_Speaker, 2637, 125);
  delay(162);
  tone(pin_Speaker, 2093, 500);
  SetPinMode(OUTPUT);
  for (byte t = 0; t < 5; t++) {
    for (byte i = 0; i < ButtonCount; i++) {
      digitalWrite(ButtonsNLeds[i], 1);
      delay(50);
      digitalWrite(ButtonsNLeds[i], 0);
      delay(20);
    }
  }
}

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(0));
  Mode = mStartNewGame;
}

void loop() {
  switch (Mode) {
    case mStartNewGame: { // Новая игра
        Serial.println("Mode = mStartNewGame");
        for (byte i = 0; i < SequenceMaxLen; i++) // Создаем последовательность
          Sequence[i] = random(ButtonCount);
        SetPinMode(OUTPUT);
        Blink(3, 800);
        delay(DelayBetweenLeds); // Пауза перед включением первого светодиода
        Mode = mShowingSequence;
        Serial.println("Mode = mShowingSequence");
        CurrentIndex = -1;
        CurrentLen = 1;
        return;
      }
    case mRepeatSequence:
      if (RepeatRightSequence == 0) {
        Mode = mStartNewGame;
        return;
      }
    case mShowingSequence: { // Показываем последовательность
        if (CurrentIndex == -1) {
          CurrentIndex = 0;
          Serial.print("Sequence: ");
          Serial.print(Sequence[CurrentIndex]);
          digitalWrite(ButtonsNLeds[Sequence[0]], HIGH);
          tone(pin_Speaker, Notes[Sequence[0]], ShowingTime);
          tm = millis() + ShowingTime;
          return;
        }
        if (millis() >= tm) {
          digitalWrite(ButtonsNLeds[Sequence[CurrentIndex]], LOW); // Гасим светодиод
          CurrentIndex++;
          if (CurrentIndex < CurrentLen) { // Если показана еще не вся последовательность
            delay(DelayBetweenLeds);
            Serial.print(", ");
            Serial.print(Sequence[CurrentIndex]);
            digitalWrite(ButtonsNLeds[Sequence[CurrentIndex]], HIGH); // то включаем следующий светодиод
            tone(pin_Speaker, Notes[Sequence[CurrentIndex]], ShowingTime);
            tm = millis() + ShowingTime;
          }
          else {
            Serial.println();
            if (Mode == mRepeatSequence) {
              Mode = mStartNewGame; // Начинаем новую игру
              delay(1000);
            }
            else {
              Mode = mWaitingForButtons; // Иначе переключаемся в режим ввода
              Serial.println("Mode = mWaitingForButtons");
              SetPinMode(INPUT);
              CurrentIndex = 0;
              tm = millis() + WaitingTime;
            }
          }
        }
        return;
      }
    case mWaitingForButtons: { // Ожидаем ввода последовательности
        if (millis() > tm) { // Время вышло. Game over
          tone(pin_Speaker, 110, 800);
          Blink(10, 50);
          Mode = mRepeatSequence; // Показываем правильную последовательность
          CurrentIndex = -1;
          return;
        }
        for (byte i = 0; i < ButtonCount; i++) {
          if (digitalRead(ButtonsNLeds[i]) == HIGH) { // Если кнопка нажата
            delay(50);
            if (ButtonStillPressed) return; // Кнопка все еще нажата, прерываем обработку
            ButtonStillPressed = 1;
            Serial.print("Button pressed: ");
            Serial.print(i);
            tone(12, Notes[i], 300);
            if (i == Sequence[CurrentIndex]) { // Нажата верная кнопка
              Serial.println(" - Right!");
              CurrentIndex++;
              if (CurrentIndex < CurrentLen) // Если введена еще не вся последовательность
                tm = millis() + WaitingTime; // то ожидаем следующую кнопку
              else { // Иначе (введена вся последовательность)
                delay(500);
                if (CurrentLen == SequenceMaxLen) { // Конец игры
                  Win();
                  delay(500);
                  Mode = mStartNewGame; // Начинаем новую игру
                  return;
                }
                delay(800);
                CurrentLen++; // увеличиваем ее длину
                Mode = mShowingSequence; // и переключаем режим
                SetPinMode(OUTPUT);
                CurrentIndex = -1;
                Serial.println("Mode = mShowingSequence");
                return;
              }
            }
            else { // Если нажата неверная кнопка
              Serial.println(" - Wrong!");
              tone(pin_Speaker, 110, 800);
              SetPinMode(OUTPUT);
              for (int t = 0; t < 10; t++) {
                digitalWrite(ButtonsNLeds[i], HIGH);
                delay(50);
                digitalWrite(ButtonsNLeds[i], LOW);
                delay(50);
              }
              Mode = mRepeatSequence; // Показываем правильную последовательность
              CurrentIndex = -1;
              Serial.println("Mode = mRepeatSequence");
              return;
            }
            return;
          }
        }
        ButtonStillPressed = 0;
        return;
      }
  }
}

И в заключение видео получившейся игрушки. Ребенку "коробочка" понравилась:)


15 комментариев:

  1. Здравствуйте, возникли вопросы по вашему проекту. горят первые два диода как надо , остальные два очень тускло и с случайной периодичностью мигают. в чем может быть проблема?

    ОтветитьУдалить
    Ответы
    1. Добрый день!
      Соединили все как на схеме или что-то меняли?

      Удалить
    2. все по схеме. можно ли с вами связаться(почта,вк)?

      Удалить
    3. у меня точно такая-же проблема, можете помочь пожалуйста?
      почта: danya134@yandex.ru

      Удалить
  2. вы не могли бы фотографию схемы добавить,никак не грузит

    ОтветитьУдалить
    Ответы
    1. Добрый день!
      Ссылка на схему: https://yadi.sk/i/lO_zrTZnSYcrmA
      и внутренности коробки: https://yadi.sk/i/6yONcvaiMcBrPQ

      Удалить
  3. Здравствуйте, очень понравился ваш проект, но вы не могли бы объяснить, как сделать все без макетной платы с пайкой,если вам не сложно, я лишь начинающий и хотел попробовать так

    ОтветитьУдалить
    Ответы
    1. Добрый вечер.
      Могу нарисовать электрическую принципиальную схему, с ней будет понятнее? А в чем сложность чтения схемы, сделанной в fritzing?

      Удалить
    2. В сборке написана ардуино нано, а по факту на рисунке уно, извините уж за беспокойство, просто я собрал 2 проекта и все я паял без плат, так было бы более ясно, если вам не сложно конечно.
      Или нужно использовать уно, просто ни одна картинка на сайте не открывается у меня к союалению

      Удалить
    3. Fritzing это отлично, просто насторожило uno и макетная плата,если бы без макетной и с нано была бы в zritzing я был ооооочень благодарен

      Удалить
    4. Нано, уно, про мини - нет никакой разницы. В них используется один и тот же микроконтроллер.
      Проблему отображения картинок буду решать. Видимо, придется менять платформу...

      Удалить
  4. Скажите пожалуйста ,я хочу подключить дисплей мэлт 8 на 2 ,когда пишешь lcd.begin..... и другое выдает ошибку

    ОтветитьУдалить
    Ответы
    1. А к чему вы подключаете дисплей? Вот к этому проекту и этому скетчу? Библиотеку в скетч добавили? Идущие с ней примеры пробовали?

      Удалить
  5. Этот комментарий был удален автором.

    ОтветитьУдалить