Игра Космические Пришельцы на Piranha ULTRA

Описание:

Игра Space Invader – одна из первых супер-популярных аркадных игр, выпущенная компанией Taito в 1978 году. Игра проложила путь для жанра шутем ап (shoot'em up), и упоминания о ней повсеместны в массовой культуре, начиная от ТВ-сериалов и журналов и заканчивая самодельными футболками с иконическим изображением пришельца. Многие публикации используют этот символ как обозначение видеоигр в общем. И это неудивительно — Space Invader считается первым блокбастером среди видеоигр. Только к концу 1978г было изготовлено и продано сто тысяч аркадных машин! И это не пластиковые диски с видеоигрой, это огромные шкафы с ЭЛТ экраном.

Наши «космические пришельцы» основаны на скетче, написанным Элвином Ли в 2012 году и являются своеобразной пародией на Space Invaders. Мы сделали наших «пришельцев» больше и лучше. Теперь вместо одной матрицы восемь на восемь, мы используем четыре. Можем мы себе это позволить благодаря тому, что нам не надо развертывать каждую матрицу выводами Arduino, мы используем Trema-модули светодиодных матриц, которые работают по шине I2C (айтуси). Используя нашу библиотеку для этих матриц мы быстро адаптировали и расширили оригинальный скетч, и даже добавили звуков.

Логика игры использует двух-мерные и трех-мерные массивы и 2-х килобайт оперативной памяти Arduino UNO для их хранения уже не хватает, поэтому мы взяли за основу наш сверх-секретный прототип Piranha ULTRA, которая вот-вот должна появиться в продаже и идеально подойдет, если нужен форм-фактор UNO. С ULTRA можно четырёх-кратно расширить возможности UNO по статической памяти и восьми-кратно по динамической.

Вернёмся к игре: у игры 3 уровня, 3-й уровень — это босс, пройдя которого игра начинается сначала, но уже на более высокой скорости. Так называемый 2-й луп. С каждым лупом сложность игры растёт за счёт увеличения скорости всей игры (переменная loopDelay). Данный проект идеально подойдёт, если вы всегда хотели научиться делать игры, но не знали с чего начать.

Видео:

Нам понадобится:

Для реализации проекта нам понадобится установить библиотеки:

О том, как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Подключение:

Для удобства подключения мы воспользуемся Trema Shield для Arduino и i2C Hub (Trema-модуль) для Arduino. Так же нам понадобятся: Trema-модуль КнопкаTrema-модуль СлайдерTrema-модуль Зуммер, 3 штуки 3-проводных шлейфов «мама-мама» (в комплекте с зуммером, слайдером, кнопкой), 5 штук 4-проводных шлейфов «мама-мама» (4 в комплекте, по одному с каждой LED Матрицей), 4 штуки Trema-модуль LED Матрица 8x8 и Battery Shield.

Перед подключением необходимо настроить адреса матриц. Это можно сделать по этой инструкции.

Для начала подключим Trema Shield к Battery Shield:

Теперь подключим все провода и модули к Trema Shield:

Прежде чем загружать скетч, необходимо скачать и установить библиотеку для матриц I2C.

Код игры:

// основано на *Space Vaders*, Elwin Lee, elwinlee.com
#include <Wire.h>
#include <Battery_Shield.h>
#include <iarduino_I2C_Matrix_8x8.h>
iarduino_I2C_Matrix_8x8 disp[] = {0x10, 0x11, 0x12, 0x13};

#define NOTE_C4 262
#define NOTE_A4 440
#define NOTE_F5 698
// матрица для временного хранения массива экрана:
byte tempMatrix[16][16] = {  
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};

// заставка первого уровня:
#define level1_ani { \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
}
// первый уровень:
#define level1 { \
  {0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
  {0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  \
}

// заставка второго уровня:
#define level2_ani { \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
}

// второй уровень:
#define level2 { \
  {0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}, \
  {0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, \
  {0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
  {0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, \
  {0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  \
}

// заставка третьего уровня:
#define level3_ani { \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
}

// третий уровень:
#define level3 { \
  {0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0},  \
  {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0},  \
  {0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0},  \
  {0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},  \
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}   \
  }

// экран выигрыша:
#define win { \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1}, \
  {1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1}, \
  {1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1}, \
  {1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1}, \
  {1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, \
  {1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, \
  {1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}  \
}


// 

const uint8_t sizeX = 16;                                              //размер экрана
const uint8_t sizeY = 16;                                              //размер экрана
uint8_t pixels[sizeY][sizeX];                                          //массив пикселей для матриц

uint8_t loopDelay = 32;

bool levelAni = true;
bool levelStart = false;
bool levelComplete = false;
bool fireSoundPlayed = false;
bool speedIncreasedFlag = false;

int levelsArrayIndex = 0;

const uint8_t _lvls = 7;
byte levels[_lvls][sizeY][sizeX] = { 
  level1_ani, level1, level2_ani, level2, level3_ani, level3, win
};

//copy of levels array for resetting purposes
byte initial[_lvls][sizeY][sizeX] = { 
  level1_ani, level1, level2_ani, level2, level3_ani, level3, win
};

// звуки
int boom = NOTE_C4;

boolean loseSound = true;
int lose[] = { NOTE_F5, NOTE_A4, NOTE_C4 };
int loseNoteDurations[] = { 2, 4, 4};

uint16_t muteTimer = 3000;
bool mute = false;


// таймеры
unsigned long aniTime;
unsigned long completeTime;
unsigned long enemyTime;
unsigned long gameOverTime;
unsigned long muteMillis;
unsigned long bossTimer;

// игрок
uint8_t bulletRow = sizeY - 3;
int bulletCol;
byte bulletArray[6];

int bulletDelayCount = 0;
boolean fired = false;


// выводы управления
const uint8_t btnPin = 3;
const uint8_t potPin = A3;
const uint8_t spkPin = 6;
int potVal;

boolean btnDown = false;

// враги
boolean enemyWon = false;
boolean enemyWaiting = false;
boolean enemyFired = false;
boolean enemyBulletCollision = false;

int enemyAttackSpeed[7] = {0, 150, 0, 100, 0, 25, 0};
int enemyBulletSpeed[7] = {0, 12, 0, 8, 0, 2, 0};
int enemyBulletRow;
int enemyBulletDelayCount = 0;
int enemyBulletArray[8][2] = { {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0} };
int enemyBottomCount = 0;
int randomBullet;

// флаги сдвига врагов
boolean shiftLevel = false;
boolean shiftLeft = true;
boolean shiftDown = false;
boolean shiftRight = false;
boolean shiftUp = false;

int shiftCount = 0;
int shiftSpeed[2] = {70, 35};

// переменные для вывода в матрицы 8х8 
byte matrix1[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte matrix2[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte matrix3[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte matrix4[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

void setup() {
  delay(500);
  // создаём объекты LED матриц:
  uint8_t k = sizeof(disp)/sizeof(iarduino_I2C_Matrix_8x8);
  // инициируем матрицы:
  for(int i = 0; i < k; i++){
    disp[i].begin();
    disp[i].clrScr();
    disp[i].angle(270);
    disp[i].bright(255);
    disp[i].fps(120);
  }
  // устанавливаем вывод кнопки и слайдера на ввод:
  pinMode(btnPin, INPUT);
  pinMode(potPin, INPUT);
  //устанавливаем таймер анимации уровня:
  aniTime = millis();
  
}
void loop() {   
  // заполнение первой матрицы:
 for (int row = 0; row < 8; row++) {
  for (int col = 0; col < 8; col++) {
    if (pixels[row][col] == 1) {
      bitSet(matrix1[row], col);
    } else {
      bitClear(matrix1[row], col);
    }
  }
 }
   // заполнение второй матрицы:
 for (int row = 8; row < 16; row++) {
  for (int col = 0; col < 8; col++) {
    if (pixels[row][col] == 1) {
      bitSet(matrix2[row-8], col);
    } else {
      bitClear(matrix2[row-8], col);
    }
  }
 }
   // заполнение третьей матрицы:
 for (int row = 0; row < 8; row++) {
  for (int col = 8; col < 16; col++) {
    if (pixels[row][col] == 1) {
      bitSet(matrix3[row], col-8);
    } else {
      bitClear(matrix3[row], col-8);
    }
  }
 }
    //заполнение четвёртой матрицы:
 for (int row = 8; row < 16; row++) {
  for (int col = 8; col < 16; col++) {
    if (pixels[row][col] == 1) {
      bitSet(matrix4[row-8], col-8);
    } else {
      bitClear(matrix4[row-8], col-8);
    }
  }
 }
setPattern(levelsArrayIndex);                              // создаём уровни
  if (levelAni) playLevelAni();                            // проигрываем заставку
  if (levelStart) readPot();                               // считываем слайдер
  if (levelStart) refreshPlayer();                         // обновляем положение игрока, опираясь на слайдер
  if (levelStart) readBtn();                               // считываем кнопку
  if (levelStart && !enemyWon) readEnemy();                // узнаём в каком ряду есть ближайший к игроку враг, и стреляем в игрока
  if (levelStart && !levelComplete) checkLevelState();     // проверяем наличие врагов
  if (levelComplete) levelFinished();                      // уровень выигран!
  if (levelStart && shiftLevel)  shiftRow();               // сдвигаем пиксели уровня
  if (enemyWon) gameOver();                                // проигрываем, если в игрока попали
  updatePixels();                                          // обновляем пиксели игровой матрицы
  disp[0].drawImage(matrix3);                              // выводим игровое поле по четвертям на матрицы 8х8
  disp[1].drawImage(matrix2);                              //
  disp[2].drawImage(matrix1);                              //
  disp[3].drawImage(matrix4);                              //
  delay(loopDelay);                                        // задержка, чтоб игра не была слишком быстрой
  if(levelsArrayIndex == 7 && !speedIncreasedFlag){        // если игра пройдена (луп),
    increaseSpeed();                                       // увеличиваем скорость 
  }
  if(levelsArrayIndex == 0){                               // при переходе на новый луп
    speedIncreasedFlag = false;                            // сбрасываем флаг увеличения скорости
  }
}
void increaseSpeed(){
  if (loopDelay > 0) loopDelay -= 16;
  speedIncreasedFlag = true;
}
// копирование пикселей из временной матрицы:
void setPattern(int pattern) {
  
  if (levelsArrayIndex < 7) {  //if not last level
    for (int i = 0; i < sizeY; i++) {
      for (int j = 0; j < sizeX; j++) {
        if ( levels[levelsArrayIndex][i][j] == 1 ) {  //copy only "ones" to temporary matrix
          tempMatrix[i][j] = levels[levelsArrayIndex][i][j];  
        }
      }
    }
  } else {
    restart();  //restart if end of the game has been reached
  }
  
}
// функция считывания слайдера:
void readPot() {
  potVal = analogRead(potPin);                     // считываем слайдер
  delay(1);                                        // ждём одну миллисекунду
  int potVal2 = analogRead(potPin);                // считываем опять
  potVal = (potVal + potVal2)/2;                   // среднее арифметическое, для уменьшения "джитера"
  potVal = map(potVal, 1023, 0, 0, sizeX-3);       // маппим значение слайдера на положение игрока 
}
// функция обновления пикселей игрока:
void refreshPlayer() {

  byte _playerRows[2][sizeX] = {                                   //временная матрица для хранения положения игрока
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  };
  
  //draw player pixels
  _playerRows[0][potVal+1] = 1;
  _playerRows[1][potVal] = 1;
  _playerRows[1][potVal+1] = 1;
  _playerRows[1][potVal+2] = 1;

  for (int i = sizeY-2; i < sizeY; i++) {
    for (int j = sizeX-1; j >= 0; j--) {
       if(_playerRows[i-(sizeY-2)][j] == 1) {
         tempMatrix[i][j] = _playerRows[i-(sizeY-2)][j];
       }
    }
  } 
}
// функция считывания кнопки:
void readBtn() {
  int btnPress = digitalRead(btnPin);                            // считываем кнопку
  if (btnPress == 1 && !fired && !btnDown){                      // если кнопка нажата  
    bulletCol = potVal+1;                                        // записываем из какой колонки стрелять
    btnDown = true;
    fired = true;
    if (!mute) playFireSound();
  } else if (!btnPress) {
    muteMillis = millis();
  }
  if (btnDown) {
    if (btnPress == 0) {
      btnDown = false;
    }  
  }
  if (millis() - muteMillis > muteTimer){
    mute = !mute;
    muteMillis = millis();
  }
  if (fired) {   
    shoot(bulletCol);                                            //стреляем
  } 
}
// фунуция проигрывания звука выстрела:
void playFireSound(){
  for (int i = 1200; i > 250; i--){
    tone(spkPin, i, 1);
  }
  noTone(spkPin);
}
// функция стрельбы игрока:
void shoot(int _bulletCol) {
  
    //if (bulletDelayCount == 1) {                                     //раскомментировать, если нужно увеличить сложность
    bulletRow--;                                                       //                                                 ^                        
    if (bulletRow < 0) {                                               //если снаряд достиг конца матрицы                 |
      bulletRow = sizeY-3;                                             //сбрасываем положение выстрела                    |
      fired = false;                                                   //                                                 |
    } else {                                                           //                                                 |
      if (tempMatrix[bulletRow][_bulletCol] == 0) {                    //если следующая позиция свободна                   |
        tempMatrix[bulletRow][_bulletCol] = 1;                         //снаряд на переходит на эту позицию               |
      } else {                                                         //если нет,                                        |
        collisionBullet(bulletRow, _bulletCol);                        //снаряд попал                                     |
      }                                                                //                                                 |
     }                                                                 //                                                 |
    //}-------------------------------------------------------------------------------------------------------------------|
}
// функция обнаружения попадания:
void collisionBullet(int _row, int _col) {
  if (!mute){
  for (int i = 0; i < 1000; i++){
  tone(spkPin, random(2000, 10000), 0.25);                                                              // звук удара снаряда о врага
  }
  }
  noTone(spkPin);
  levels[levelsArrayIndex][_row][_col] = 0;                                                                //удаляем убитый пиксель из матрицы
  tempMatrix[_row][_col] = 0;                                                                              // обновляем временную матрицу
  
  bulletRow = sizeY-3;                                                                                    //устанавливаем положение выстрела над игроком
  fired = false;                                                                                        //обнуляем флаг выстрела
}
// функция проигрывания звука смерти игрока:
void playDeathSound() {
  for (int i = 0; i < 2000; i++){
    tone(spkPin, random(2000, 10000), 0.25);
  }
  for (int i = 0; i < 1000; i++){
    tone(spkPin, random(2000, 10000), 0.5);
  }
  for (int i = 0; i < 1000; i++){
    tone(spkPin, random(1000, 5000), 0.5);
  }
  for (int i = 0; i < 2000; i++){
    tone(spkPin, random(5000), 0.5);
  }
  noTone(spkPin);
}
// функция анимации врагов:
void readEnemy() {
 // в этой функции все комментарии оставлены авторские 
  if (!enemyWaiting && !enemyFired && !enemyBulletCollision){                                              //if enemy isn't doing anything
    enemyTime = millis();                                                                                  //start timer
    enemyWaiting = true;
  }
  
  if (enemyWaiting) {
    if (millis() - enemyTime > enemyAttackSpeed[levelsArrayIndex]) {                                      //attack speed for each level according to array 
      
      int _row = 9;                                                                                       //temp row value
      boolean dobreak = false;                                                                            //for double break;
      
      //loop to get the enemy's pixels of highest row (closest to the player) 
      for (int i = 9; !dobreak && i >= 0; i--) {                                                          //backwards loop to get highest row
        for (int j = 0; j < sizeX; j++) {
            if(levels[levelsArrayIndex][i][j] == 1) {                                                      //if a "one" is found in the row
              _row = i;                                                                                  //save row index
              enemyBulletRow = _row + 1;                                                                  //value to put enemy bullet 1 row below enemy
              dobreak = true;                                                                            //double break;
              break;
            }
         }  
      }
      
      enemyBottomCount = 0;                                                                             //counter for number of "one" pixels in highest row (closest to the player) 
      for (int c = 0; c < sizeX; c++) {                                                                  //loop through highest row
        if(levels[levelsArrayIndex][_row][c] == 1) {                                                       //check if column contains "one" 
          enemyBulletArray[enemyBottomCount][0] = _row;  
          enemyBulletArray[enemyBottomCount][1] = c;
          enemyBottomCount++;  
        }
      }
      fireSoundPlayed = false; 
      enemyWaiting = false;
      enemyFired = true;                                                                                  //fire enemy bullet
      
      randomBullet = random(enemyBottomCount);                                                          //randomly select one of the column pixels
    }
  }
  
  if (enemyFired) {
    if (enemyBulletDelayCount == (enemyBulletSpeed[levelsArrayIndex]-1)) {                              //enemy bullet speed (higher value = slower)
      enemyBulletArray[randomBullet][0]++;                                                              //row + 1
      if (!fireSoundPlayed && !mute){ 
        playEnemyFireSound();
        fireSoundPlayed = true;
      } else if(!mute) playEnemyBulletSound();
      if  (enemyBulletArray[randomBullet][0] > sizeY - 1) {                                              //if bullet reaches bottom
        enemyBulletDelayCount = 0;                                                                      //reset and remove bullet
        enemyFired = false;
      } else {
        if (tempMatrix[enemyBulletArray[randomBullet][0]] [enemyBulletArray[randomBullet][1]] == 0) {      //if next row is empty
          tempMatrix[enemyBulletArray[randomBullet][0]] [enemyBulletArray[randomBullet][1]] = 1;          //bullet claims row
        } else {                                                                                          //bullet hit something!!
          if (!levelComplete) {
            enemyBulletDelayCount = 0;  
            enemyFired = false;
            enemyBulletCollision = true;
          }
        }
      }
    } else {
      tempMatrix[enemyBulletArray[randomBullet][0]][enemyBulletArray[randomBullet][1]] = 1;
    }
    enemyBulletDelayCount = (enemyBulletDelayCount+1) % enemyBulletSpeed[levelsArrayIndex];  //enemy bullet speed
  }
  
  if (enemyBulletCollision) {
    if (enemyBulletArray[randomBullet][0] == sizeY-2 || enemyBulletArray[randomBullet][0] == sizeY-1) {  //check if enemy's bullet hit player
      enemyWon = true;
      gameOverTime = millis();
    }
    enemyBulletCollision = false;
  }
}
// функция проигрывания звука выстрелов врага:
void playEnemyFireSound(){
  for(int i = 1500; i > 750; i--){
      tone(spkPin, i, 0.5);
  }
  noTone(spkPin);
}
// функция проигрывания звука полёта пули врагов:
void playEnemyBulletSound(){
  for(int i = 1000; i > 900; i--){
      tone(spkPin, i, 0.1);
  }
  noTone(spkPin);
}
// функция сдвига босса:
void shiftBoss(){
  int j,k;
   byte _levelTemp[9][sizeX] = {                               //временная матрица для хранения пикселей босса
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  };
   for (int i = 0; i < 9; i++) {                            // загружаем пиксели в матрицу
    for (int j = 0; j < sizeX; j++) {
        _levelTemp[i][j] = levels[levelsArrayIndex][i][j];
    }
  }
   for (int i = 0; i < 9; i++){                                // переворачиваем положение босса
    for (j = (sizeX - 1), k = 0; j >= 0; k++, j--){
      levels[levelsArrayIndex][i][k] = _levelTemp[i][j];
    }
   } 
}
// функция сдвига врагов (2-й и 3-й уровни):
void shiftRow() {
  int _speed;
  if (levelsArrayIndex == 3) {
      _speed = shiftSpeed[0];
  } else if (levelsArrayIndex == 5) {
      _speed = shiftSpeed[1];
  }
  byte _levelTemp[9][sizeX] = {                               // создаём временную матрицу для хранения положения врагов
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
  };
  for (int i = 0; i < 9; i++) {                                // загружаем в неё врагов
    for (int j = 0; j < sizeX; j++) {
        _levelTemp[i][j] = levels[levelsArrayIndex][i][j];
    }
  }
  
  if (shiftCount == (_speed-1)) {                              //интервал сдвига сдвигом
    if (shiftLeft == true) {                                  //сдвиг влево
      for (int i = 0; i < 9; i++) {
        for (int j = 0; j < sizeX; j++) {
          if (j == sizeX-1) {
            levels[levelsArrayIndex][i][sizeX-1] = _levelTemp[i][0];
          } else {
            levels[levelsArrayIndex][i][j] = _levelTemp[i][j+1];
          }
        }
      }
      shiftCount = 0;
      shiftLeft = false;
      shiftDown = true;
    } else if (shiftDown == true) {                          //сдвиг вниз
      for (int i = 0; i < 9; i++) {
        for (int j = 0; j < sizeX; j++) {
          if ( i == 0 ) {   
            levels[levelsArrayIndex][i][j] = 0;
          } else {
            levels[levelsArrayIndex][i][j] = _levelTemp[i-1][j]; 
          }
        }
      }
      shiftDown = false;
      shiftRight = true;
     } else if (shiftRight == true) {                          //сдвиг вправо
        for (int i = 0; i < 9; i++) {
          for (int j = sizeX-1; j >= 0; j--) {
            if (j == 0) {
              levels[levelsArrayIndex][i][0] = _levelTemp[i][sizeX-1];
            } else {
              levels[levelsArrayIndex][i][j] = _levelTemp[i][j-1];
            }
          }
        }
        shiftCount = 0;
        shiftRight = false;
        shiftUp = true;
      
      } else if (shiftUp == true) {                          //сдвиг вверх
        for (int i = 0; i < 9; i++) {
          for (int j = 0; j < sizeX; j++) {
            if ( i == 8 ) {
              levels[levelsArrayIndex][i][j] = 0;
            } else {
              levels[levelsArrayIndex][i][j] = _levelTemp[i+1][j];
            }
          }
        }
        shiftUp = false;
        shiftLeft = true;
      }
    }
    shiftCount = (shiftCount+1) % _speed;  
  if ((millis() - bossTimer > 8000) && levelsArrayIndex == 5){
    
    shiftBoss();
    bossTimer = millis();
  }
}
// функция включения сдвига врагов и показа заставки уровня:
void playLevelAni() { 
  if (millis() - aniTime < 3000) {                             // время перехода от номера уровня к игре 

  } else {
    levelAni = false;
    levelStart = true; 
    levelsArrayIndex++;
    if (levelsArrayIndex == 3 || levelsArrayIndex == 5) {      // если второй или третий уровень
      shiftLeft = true;                                        // включаем анимацию
      shiftDown = false;                                       //
      shiftRight = false;                                      //
      shiftUp = false;                                         //
      shiftLevel = true;                                       //
    } else {
      shiftLevel = false;
    }  
  }
}
// функция проверки завершённости уровня (есть ли кто живой?):
void checkLevelState() {
  int _countLevelPixels = 0;
  for (int i = 0; i < 5; i++) {
    for (int j = 0; j < sizeX; j++) {
      if(levels[levelsArrayIndex][i][j] == 1){
        _countLevelPixels++;                     // считаем сколько осталось "единиц"
      }
    }  
  }
  if((_countLevelPixels) == 0){                  // если не осталось - мы выиграли!
    completeTime = millis();
    levelComplete = true;
  }  
}
// функция завершения уровня:
void levelFinished() { 
  if (!mute) winSound();
  if (millis() - completeTime > 2000) {         // время перед запуском следующего уровня
  
    //reset all booleans
    enemyWaiting = false;
    enemyFired = false;
    enemyBulletCollision = false;

    levelStart = false;  
    levelComplete = false;
    levelAni = true;
    aniTime = millis();
    levelsArrayIndex++;
  }
}
// функция обновления пикселей
void updatePixels() {
  for (int i = 0; i < sizeY; i++) {
    for (int j = 0; j < sizeX; j++) {
      pixels[i][j] = tempMatrix[i][j]; 
    }
  }
  
  //reset all values for temp matrix
  for (int i = 0; i < sizeY; i++) {
    for (int j = 0; j < sizeX; j++) {
      tempMatrix[i][j] = 0;
    }
  }
}
// функция проигрывания мелодии выигрыша
void winSound(){
  for (int thisNote = 0; thisNote < 3; thisNote++) {
      int noteDuration = 250/loseNoteDurations[thisNote];
      tone(spkPin, lose[thisNote], noteDuration);
      int pauseBetweenNotes = noteDuration * 1.30;
      delay(pauseBetweenNotes);
      noTone(spkPin);
    }
}
// функция конца игры
void gameOver() {  
  if( loseSound) {
    loseSound = false;
    if (!mute) playDeathSound();
    loopDelay = 32; 
  }  
  if (millis() - gameOverTime < 3000) {               // время до перезапуска
    delay(250);
  } else {
    restart();  
  }
}
// функция перезапуска игры
void restart() {
  // сбрасываем флаги:
  enemyWon = false;
  enemyWaiting = false;
  enemyFired = false;
  enemyBulletCollision = false;
  
  levelAni = true;
  levelStart = false;  
  levelComplete = false;
  levelsArrayIndex = 0;
  
  loseSound = true;
  
  // сбрасываем уровни:
  for (int level = 0; level<_lvls; level++) {
     for (int x = 0; x < sizeY; x++) {
         for (int y = 0; y < sizeX; y++) {
            levels[level][x][y] = initial[level][x][y];
          }
       }
    }
aniTime = millis();
}

Ссылки:

    Обсуждение