Тамагочи «Space Invaders»

Проекты на Arduino и Slot Shield

Дети девяностых и их родители могут не читать описание, а сразу переходить к сборке девайса.

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

В порыве ностальгии мы решили сделать своего тамагочи, а в качестве виртуального друга выбрали милых космических захватчиков из классической аркады Space Invaders.

Наш питомец будет жить на монохромном LED-дисплее. Мы начнём с простейшего двухклеточного организма, к концу игры он займёт собой почти все 64 пикселя матрицы. На втором экране будут появляться его просьбы: пришельца нужно кормить, поить, говорить приятные слова и вовремя укладывать спать. Каждому из этих действий соответствует кнопка на четырёхкнопочной клавиатуре. Не забывайте о питомце, иначе он может погибнуть.

Что потребуется

Видеоинструкция

Как собрать

Установите Troyka Slot Shield на Iskra Neo

Вставьте две LED матрицы 8×8 в левый и средний слоты верхнего ряда. Чтобы у матриц были разные адреса, капните припой на любую контактную площадку любого из модулей. Как это сделать — смотрите на странице описания матриц.

Поверните четырёхкнопочную клавиатуру на 90 градусов против часовой стрелки и вставьте в оставшийся слот верхнего ряда.

Переверните пьезопищалку вверх ногами и вставьте в нижний правый слот.

Скетч

Прошейте контроллер скетчем через Arduino IDE.

tamagotchi.ino
// библиотека для работы I²C
#include "Wire.h"
// библиотека для работы со светодиодной матрицей
#include "TroykaLedMatrix.h"
// библиотека для работы с кнопками
#include "TroykaButton.h"
 
// создаём объекты matrix для работы с матрицами
// для каждой матрицы передаём свой адрес
// подробнее читайте на:
// http://wiki.amperka.ru/продукты:troyka-led-matrix
TroykaLedMatrix matrix1;
TroykaLedMatrix matrix2(0b01100001);
 
// создаём объекты для работы с кнопками и передаём им номера пинов
TroykaButton button1(0);
TroykaButton button2(1);
TroykaButton button3(4);
TroykaButton button4(5);
 
// пин пищалки
#define BUZZER_PIN  10
// интервал времени до сметри
// если инопланетянин не получил потребность
#define INTERVAL_DEATH  10000
// первое состояние инопланиянина
const uint8_t stateAlienOne[][8] {
  { 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00},
};
 
// второе состояние инопланиянина
const uint8_t stateAlienTwo[][8] {
  { 0x00, 0x00, 0x00, 0x18, 0x3C, 0x00, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x00, 0x30, 0x78, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x00, 0x0C, 0x1E, 0x00, 0x00}
};
 
// третье состояние инопланиянина
const uint8_t stateAlienThree[][8] {
  { 0x00, 0x00, 0x00, 0x38, 0x54, 0x7C, 0x00, 0x00},
  { 0x00, 0x00, 0x1C, 0x2A, 0x3E, 0x00, 0x00, 0x00},
  { 0x00, 0x00, 0x38, 0x54, 0x7C, 0x00, 0x00, 0x00},
  { 0x00, 0x00, 0x00, 0x1C, 0x2A, 0x3E, 0x00, 0x00}
};
 
// четвёртое состояние инопланиянина
const uint8_t stateAlienFour[][8] {
  { 0x00, 0x00, 0x1C, 0x2A, 0x3E, 0x2A, 0x00, 0x00},
  { 0x00, 0x00, 0x38, 0x54, 0x7C, 0x2A, 0x00, 0x00},
  { 0x00, 0x00, 0x38, 0x54, 0x7C, 0x54, 0x00, 0x00},
  { 0x00, 0x00, 0x1C, 0x2A, 0x3E, 0x54, 0x00, 0x00},
};
 
// пятое состояние инопланиянина
const uint8_t stateAlienFive[][8] {
  { 0x00, 0x18, 0x3C, 0x5A, 0x7E, 0x3C, 0x00, 0x00},
  { 0x00, 0x00, 0x18, 0x3C, 0x5A, 0x7E, 0x5A, 0x00},
  { 0x00, 0x00, 0x18, 0x3C, 0x5A, 0x7E, 0x99, 0x00},
  { 0x00, 0x18, 0x3C, 0x5A, 0x7E, 0x5A, 0x00, 0x00}
};
 
// шестое состояние инопланиянина
const uint8_t stateAlienSix[][8] {
  { 0x00, 0x38, 0x7C, 0xD6, 0xFE, 0xBA, 0x92, 0x10},
  { 0x00, 0x1C, 0x3E, 0x6B, 0x7F, 0x59, 0x92, 0x20},
  { 0x00, 0x1C, 0x3E, 0x6B, 0x7F, 0x5D, 0x49, 0x08},
  { 0x00, 0x38, 0x7C, 0xD6, 0xFE, 0x9A, 0x49, 0x04}
};
 
// отображение «поесть»
const uint8_t food[][8] {
  { 0x00, 0x20, 0x44, 0x28, 0x04, 0x00, 0x7E, 0x3C},
  { 0x40, 0x28, 0x44, 0x08, 0x00, 0x00, 0x7E, 0x3C},
};
 
// отображение «попить»
const uint8_t drink[][8] {
  { 0x00, 0x24, 0x24, 0x3C, 0x34, 0x2C, 0x34, 0x3C},
  { 0x00, 0x24, 0x24, 0x3C, 0x2C, 0x34, 0x2C, 0x3C},
};
 
// отображение «приласкать»
const uint8_t love[][8] {
  { 0x00, 0x66, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00},
  { 0x00, 0x24, 0x7E, 0x7E, 0x3C, 0x18, 0x00, 0x00},
};
 
// отображение «поспать»
const uint8_t sleep[][8] {
  { 0x00, 0x00, 0x00, 0x80, 0x80, 0xFE, 0xFF, 0x81},
  { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
};
 
// отображение «OK»
const uint8_t ok[] {
 0x00, 0x64, 0x94, 0x95, 0x96, 0x95, 0x65, 0x00
};
 
// отображение «смерти»
const uint8_t death[] {
  0x00, 0x40, 0xE0, 0x40, 0x40, 0x7C, 0xFE, 0xFF
};
 
// перечесления
enum {ONE, TWO, THREE, FOUR, FIVE, SIX};
enum {NOT, FOOD, DRINK, LOVE, SLEEP};
 
// размер инопланетянина
int alienSize;
// желание инопланетянина
int alienDesire;
// номер кадра анимации инопланитянина
int frameAnimationAlien = 0;
// номер кадра анимации желания инопланитянина
int frameAnimationDesire = 0;
 
// переменные запоминания времени
unsigned long desireMillis;
unsigned long animationMillis;
unsigned long deathMillis;
// переменные включения таймеров состояния
bool timerEnableDesire = false;
bool timerEnableDeath = false;
 
// случайный диапазон времени
// через который у инопланитянина появится новая потребность
int intervalDesire;
 
void setup()
{
  // начало работы с матрицами
  matrix1.begin();
  matrix2.begin();
  // очищаем матрицы
  matrix1.clear();
  matrix2.clear();
  // начало работы с кнопками
  button1.begin();
  button2.begin();
  button3.begin();
  button4.begin();
  // начальный размер инопланитянина
  alienSize = ONE;
  // желаний нет
  alienDesire = NOT;
  // запоминаем текущее время
  animationMillis = millis();
  // для генерации разных значений
  randomSeed(analogRead(A0));
}
 
void loop()
{
  // считываем данные с кнопок
  button1.read();
  button2.read();
  button3.read();
  button4.read();
 
  // отображение элементов на матрице
  if (millis() - animationMillis > 500) {
    animationMillis = millis();
    updateBitmapAnimationAlien();
    updateBitmapAnimationDesire();
  }
 
  // если у инопланитянина нет желаний и таймер не запущен
  if (alienDesire == NOT && !timerEnableDesire) {
    // выбираем случайный период времени
    // через который инопланетянин выявит в чём то потребность
    intervalDesire = random(1000, 5000);
    // запоминаем текущее время
    desireMillis = millis();
    // включаем таймер ожидание появление потребности
    timerEnableDesire = true;
  }
  // если таймер включён и период выявлеения желания прошёл
  if (timerEnableDesire && millis() - desireMillis > intervalDesire) {
    // выбираем случайное желание
    alienDesire = random(1, 5);
    // выключаем таймер ожидание появление потребности
    timerEnableDesire = false;
    // запоминаем текущее время
    deathMillis = millis();
    // выключаем таймер ожидания смерти
    timerEnableDeath = true;
  }
  // если таймер смерти включён и прошёл свой интервал
  if(timerEnableDeath && millis() - deathMillis > INTERVAL_DEATH) {
    // отображаем могилку
    matrix1.drawBitmap(death);
    // стираем потребность в чём то
    // так как умершей в ней уже не нуждается
    matrix2.clear();
    for (int y = 0; y <= 2; y++) {
      // издаём звуковой сигнал
      tone(BUZZER_PIN, 200, 300);
      delay(200);
      // издаём звуковой сигнал
      tone(BUZZER_PIN, 131, 300);
      delay(200);
    }
    // котроллер отправляем тоже в вечный сон
    while(1);
  }
 
  // проверяем состояние потребности инопланетянин 
  switch(alienDesire) {
    case FOOD:
    // если нажата соответсвующая кнопка
    if (button1.justPressed()) {
      // выводим «ОК» на матрицу
      matrix2.drawBitmap(ok);
      // подаём звуковой сигнал
      tone(BUZZER_PIN, 2000, 300);
      delay(1000);
      // увеличиваем размер инопланетянина
      alienSize++;
      // убираем потребность
      alienDesire = NOT;
    } else if (button2.justPressed() 
            || button3.justPressed()
            || button4.justPressed()) {
      // если нажата не соответсвующая кнопка
      // подаём соответсвующий звуковой сигнал
      tone(BUZZER_PIN, 500, 500);     
    }
    break;
    case DRINK:
    // если нажата соответсвующая кнопка
    if (button2.justPressed()) {
      // выводим «ОК» на матрицу
      matrix2.drawBitmap(ok);
      // подаём звуковой сигнал
      tone(BUZZER_PIN, 2000, 300);
      delay(1000);
      // увеличиваем размер инопланетянина
      alienSize++;
      // убираем потребность
      alienDesire = NOT;
    } else if (button1.justPressed() 
            || button3.justPressed()
            || button4.justPressed()) {
      // если нажата не соответсвующая кнопка
      // подаём соответсвующий звуковой сигнал
      tone(BUZZER_PIN, 500, 500);     
    }
    break;
    case LOVE:
    // если нажата соответсвующая кнопка
    if (button3.justPressed()) {
      // выводим «ОК» на матрицу
      matrix2.drawBitmap(ok);
      // подаём звуковой сигнал
      tone(BUZZER_PIN, 2000, 300);
      delay(1000);
      // увеличиваем размер инопланетянина
      alienSize++;
      // убираем потребность
      alienDesire = NOT;
    } else if (button1.justPressed() 
            || button2.justPressed()
            || button4.justPressed()) {
      // если нажата не соответсвующая кнопка
      // подаём соответсвующий звуковой сигнал
      tone(BUZZER_PIN, 500, 500);     
    }
    break;
    case SLEEP:
    if (button4.justPressed()) {
      // выводим «ОК» на матрицу
      matrix2.drawBitmap(ok);
      // подаём звуковой сигнал
      tone(BUZZER_PIN, 2000, 300);
      delay(1000);
      // увеличиваем размер инопланетянина
      alienSize++;
      // убираем потребность
      alienDesire = NOT;
    } else if (button1.justPressed() 
            || button2.justPressed()
            || button3.justPressed()) {
      // если нажата не соответсвующая кнопка
      // подаём соответсвующий звуковой сигнал
      tone(BUZZER_PIN, 500, 500);     
    }
    break;
  }
}
 
// функция обновления анимации инопланитянина
void updateBitmapAnimationAlien() {
  // ощичаем матрицу
  matrix1.clear();
  if (frameAnimationAlien == 4) {
    frameAnimationAlien = 0;
  }
  // отображаем двигающегося инопланетянина по кадрово
  switch (alienSize) {
    case ONE:
    matrix1.drawBitmap(stateAlienOne[frameAnimationAlien++]);
    break;
    case TWO:
    matrix1.drawBitmap(stateAlienTwo[frameAnimationAlien++]);
    break;
    case THREE:
    matrix1.drawBitmap(stateAlienThree[frameAnimationAlien++]);
    break;
    case FOUR:
    matrix1.drawBitmap(stateAlienFour[frameAnimationAlien++]);
    break;
    case FIVE:
    matrix1.drawBitmap(stateAlienFive[frameAnimationAlien++]);
    break;
    case SIX:
    matrix1.drawBitmap(stateAlienSix[frameAnimationAlien++]);
    break;
  }
}
 
// функция обновления анимации желаний инопланитянина
void updateBitmapAnimationDesire() {
  // ощичаем матрицу
  matrix2.clear();
  if (frameAnimationDesire == 2) {
    frameAnimationDesire = 0;
  }
  // отображаем потребность по кадрово
  switch (alienDesire) {
    case NOT:
    matrix2.clear();
    break;
    case FOOD:
    matrix2.drawBitmap(food[frameAnimationDesire++]);
    break;
    case DRINK:
    matrix2.drawBitmap(drink[frameAnimationDesire++]);
    break;
    case LOVE:
    matrix2.drawBitmap(love[frameAnimationDesire++]);
    break;
    case SLEEP:
    matrix2.drawBitmap(sleep[frameAnimationDesire++]);
    break;
  }
}

Что дальше?

Хотите собрать другой девайс? Выберите своё будущее устройство из списка проектов на Slot Shield.

Если вы захотите изменить графику, рекомендуем воспользоваться удобным редактором для LED матриц.

Часто задаваемые вопросы

Где скачать необходимые библиотеки и как их установить?