В прошлом году мы в летней компьютерной школе проводили кружок по Arduino. Там поучаствовали и преподаватели, в результате чего появилась 8-битная игровая консоль с экраном 64x64.

Теперь же мы решили сделать кружок, в котором ребята смогут создать собственные игры для этой консоли. Благодаря её минимализму, кода должно получиться немного. Всего на кружок было запланировано около 14 часов в течение смены, так что низкий порог входа в программирование таких игр был важной особенностью.


4095 светодиодов и все-все-все

API для создания игр


Железка у нас была только одна, поэтому сначала надо было сделать эмулятор. Ведь над играми будет работать несколько команд. Кроме того, исходно весь код был в одном *.ino файле, поэтому надо было разбить его на части, чтобы добавление новых игр сводилось к программированию их поведения и отрисовки. Среду сборки мы оставили Arduino IDE, чтобы упростить настройку рабочих машин.

Для вывода изображения на экран, процессор постоянно сканирует его и загружает в сдвиговые регистры цвета пикселей. Поэтому правильная процедура обновления экрана должна отдавать только одну линию пикселей. Но такой интерфейс был бы слишком неудобным, поэтому мы сделали набор функций типа game_draw_sprite, game_draw_text, а они уже внутри проверяют какую линию надо выводить (и надо ли вообще). Из-за этого возникает некоторый оверхед, так как все вызовы будут делаться для каждой строки, а не только для нужных.



Эмулятор предоставляет те же функции, но он отрисовывает всё во фреймбуфере, к тому же работает намного быстрее, чем консоль. Поэтому не всегда можно оценить то, что получится на железе. Зато он решает большинство проблем с отладкой, потому что можно запустить Visual Studio (или gdb) и посмотреть как работает программа. Все скриншоты здесь именно с этого эмулятора.

Разработчик должен реализовать две функции, чтобы получилась игра: отрисовка экрана и обновление внутреннего состояния. Обновление происходит с заранее заданной периодичностью. Функция отрисовки вызывается для каждой строки экрана (на самом деле для групп по 4 строки сразу из-за особенностей адресации).

Функция обновления реагирует на нажатые на джойстике клавиши и меняет внутренние данные игры (например, координаты объектов, чтобы их переместить). Так как в каждый момент времени выполняется только одна игра, нет смысла в каждой из них объявлять статические переменные. Ведь всего у нас 2 килобайта памяти. Поэтому работа с памятью у нас слегка неудобная — все данные игры хранятся в структуре, к которой приходится обращаться через указатель.

Чтобы проверить работоспособность API, а также меню для выбора игр, мы реализовали игру «Змейка». Делали слегка в спешке, поэтому несколько багов осталось. Дети их потом с радостью обнаруживали.



Совместная разработка


Для начала мы разместили наш проект с гитхаба на внутреннем сервере с gitlab. Ребята работали с форками этого репозитория, а затем присылали пулл реквесты, чтобы собрать всё в кучу. В целом всё прошло удачно, но глубоко в объяснение принципов работы git мы не погружались.

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

Сапёр


Первая команда решила делать классический сапёр. Мины размещались очень хитро — по одной в столбце — поэтому им понадобились только спрайты с цифрами от 1 до 3. Программа состоит из 475 строк, но там есть мусор и оставленные комментарии из шаблона. Придираться к коду в пулл реквестах уже не оставалось времени.



Самая большая сложность в реализации сапёра — это открывание всех свободных клеток, смежных с той свободной, на которую нажал пользователь. Обход в ширину было объяснять долго. Да и память он под очередь занимает. Поэтому ребята просто несколько раз сканировали массив и открывали соседние и открытыми клетки. Это нечастая операция, поэтому всё сработало нормально.

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

Breakout


Игра называется Breakout, потому что ребята сначала хотели делать именно его. Но потом решили, что всё сложно и у них получился Pong. В игре несколько режимов — игра двух игроков, игра с компьютером, демонстрация (игра компьютера против себя). Правда вот компьютер проигрывать не умеет — ему всегда удаётся отбить мяч. Более хитрый алгоритм ребята придумать не успели. Всего файл с игрой занимает 272 строки.



Flappy submarine


Параллельно с детьми я сделал «Flappy submarine», чтобы показывать им как работать со спрайтами и управлением.



Всех этих игр хватило, чтобы забить 32 килобайта программной памяти. Но после апргейда до ATmega2560 памяти будет 256 килобайт, так что в следующем году мы планируем повторить этот кружок не выкидывая уже созданные игры.

> Исходники проекта на гитхабе

Комментарии (6)


  1. Snusmumrick97
    31.08.2017 10:07
    -1

    А можно увидеть код симулятора?


    1. Snusmumrick97
      31.08.2017 10:11

      Уже нашёл на githab


  1. zabidon
    31.08.2017 18:30
    +1

    А почему светодиодов 4095 а не 4096


    1. Dovgaluk Автор
      31.08.2017 18:31

      Приз самому внимательному игроку!
      Один в углу отвалился (и в коде даже есть хак, который сбрасывает значение яркости для этого светодиода).


  1. Armleo
    31.08.2017 18:55

    STM32 берите. STM32F4 нормальный такой с 1MB флеша, они дешевле чем ардуино.


    1. Dovgaluk Автор
      31.08.2017 19:15

      Кстати, мы уже попробовали. Даже выводили видео.
      Но именно этот экземпляр хочется оставить 8-битным. Да и избыточен ARM для простеньких игрушек.