Как я пытался засунуть Bitsy в ZX Spectrum и почему не вышло

Когда я делал игру на предыдущий битси-джем, словил себя на мысли, что довольно простое внутреннее устройство игровых данных Bitsy, которое буквально состоит из названий ресурсов, а также единиц и нулей, которые представляют расположение пикселей в графике, позволяет с лёгкостью считывать данные. Это значит, что можно относительно просто портировать игры с Bitsy почти на любую платформу.

Я решил попробовать самостоятельно портировать игры с Bitsy на ZX Spectrum. Основных причин тому было несколько:

  • Gamin Bitsy Jam.
  • Мой интерес к Spectrum Basic как едва ли не самому простому инструменту разработки для старых компьютеров.
  • Совпадение в отрисовываемых стандартных ресурсах — и графические ресурсы битси, и знакоместо на Спектруме является монохромным изображением 8×8.
  • Дискретность движения (т.е. движение по клеточкам) в битси, которая точь-в-точь движение по знакоместам на спеке.

Идея меня заняла и где-то за неделю я сделал такое:

d5AqLmo

Над считыванием палитры я не работал, решил, что для прототипа будет достаточно монохромной палитры. В играх на Bitsy отношение сторон 1 к 1 и разрешение ниже, чем у спектрума, поэтому в качестве рабочего решения я решил замостить неиспользуемую область экрана «звёздочками». Человечек движется и в зависимости от кнопок движения WASD идёт в соответствующую сторону. Чашку можно «поднять», и я пишу это слово в кавычках, потому что в моём прототипе игра не запоминает, что именно игрок поднял. В общем, у меня довольно быстро опустились руки, а ведь как всё хорошо начиналось.

qkrQVFp

Вряд ли бы я довёл свой проект до полного завершения, но сделал бы намного больше, если бы не некоторые особенности Spectrum Basic. Для того, чтобы было куда подставлять ресурсы из Bitsy, нужно было собрать базовую игру с архитектурой, под которую будет генерироваться код. Разработка велась добавлением фич — сначала сделал движение, потом добавил обработку непроходимых тайлов, затем загрузку ресурсов. Звучит тривиально, но когда я что-то исправлял в коде, мне приходилось иметь дело с числами строк, которые выполняются.

Строки выполнения — классика доисторического бейсика. Для удобства можно именовать строки от 1 до 9999, они выполняются последовательно, если какая-то из них не выполняет команду go to, которая отправляет прямо на указанную вместе с этим оператором строку. Как видно из скриншота выше, я не очень грамотно распределил ресурсы, фактически логика игры начинается со строки 400 и заканчивается где-то на 700. И если мне понадобится перенести какие-то строки, внутренним копированием не обойтись — оно скопирует и номера строк, а не только код. Ненужные строчки удаляются не Backspace, а впечатыванием номера этой строки в пустую строку. И всё это на относительно современном редакторе-кода/эмуляторе BASin, я уж молчу об оригинальном спектруме с его специфическими командами, которые для специалиста ускоряют введение команд, а для новичка превращают это в ад. Кто не в курсе — для каждой команды бейсика в спеке используется шорткат, который был хотя бы подписан на оригинальной клавиатуре, с моим ЙЦУКЕНГом там делать нечего. К тому же я не привык думать строками, вся работа по редактированию превращалась в сущий ад и тем страшнее было по поводу введения новых фич.

kFxI44j

Другой проблемой была ограниченность используемых символов. Бейсик на спектруме для нестандартных символов, т. е. тех, которые не являются буквами латиницы или знаками препинания использует UDG (User Defined Graphics). У пользователя есть возможность вставить 21 знак. Если пытаться конвертировать игры на русском, едва ли не все эти знаки уйдут на поддержку локализации. Значит, вырезаем для прототипа эту фичу, но и для графики 21 знак недостаточен, особенно если использовать конвертеры изображений для битси, которые переводят картинки в тайлы. Впрочем, это небольшая проблема, а если каждый переход комнаты пересобирать ресурсы, можно вполне уложиться в лимит. Я знаю, что для профессиональных спектрумистов это не проблема и часть графики можно загнать в другие участки памяти, но нужно было шерстить документацию, бесконечные туториалы, а у меня были проблемы со средой программирования и эта штука стала ещё одной преградой для выполнения задуманного.

Но не всё в разработке этого прототипа было плохо и неудобно. По другую сторону от бейсика у меня был Python, который обрабатывал ресурсы Bitsy и переводил их в спектрумный формат. Поскольку игра на бейсике не была завершена, этот код лишь парсил данные файла с данными из Bitsy и переводил их в привычный для ZX Spectrum формат. Но даже имея самые базовые познания в регулярных выражениях, довольно просто адаптировать ресурсы Bitsy для других платформ.

Например, данные спрайтов для спектрума необходимо подгружать оператором DATA. Для знакоместа 8×8 он должен принимать 8 аргументов, где каждый из них будет строкой из восьми пикселей, где положение закрашенных пикселей будет определяться простой формулой перевода двоичного кода, где единицы закрашены, а нули — нет, в десятичный. Я написал следующий, может и кривой (когда пересматривал, у меня к нему появились вопросы), но работающий код (кстати, в редакторе кода на гамине есть Lua, но нет Python, так что подсветка не всегда соответствует):

def get_sprites():
    sprites = []
    names = []
    sprites = re.findall(r'SPR.{,3}sd{8}sd{8}sd{8}sd{8}sd{8}sd{8}sd{8}sd{8}', txt)#ищем код спрайта с названием
    print("REM sprite data")
    for i in range(len(sprites)):
        spr_name = re.search(r'.{,5}', sprites[i])#s - любой пробельный символ, точка - любой непробельный, ищем название спрайта
        name = spr_name.group(0)
        print(name)
        names.append(name)#список индексов спрайтов
        text = 'DATA '
        raw_data = re.findall(r'd{8}', sprites[i])#вычленяем строчки с цифрами
        for k in raw_data:#и затем переводим двоичный код в десятичный
            num = 0
            for j in range(8):#прогоняем каждую букву каждого элемента списка
                num = num + int(k[j]) * 2**(7-j)
                if j == 7:
                    text += str(num) + ', '
        #убираем последних два символа в строке, т.к. разделитель в конце строки не нужен
        text = text[:-2]
        print(text)
        #далее возвращаем количество созданных ресурсов для контроля индексов спрайтов
        return i

Скорее всего, этому проекту суждено остаться нерабочим прототипом, я взялся за задачу, которая мне была не по зубам. Но делать это, особенно поначалу, было весьма интересно. Да и кто знает — если первый джем меня привёл к такой разработке, то к чему может привести второй? Всем удачи на джеме!