Советский геймдев для друзей партии

Не так давно в мое распоряжение попал микрокомпьютер Электроника МК-90 1990 года выпуска. Я давно мечтал об этой машинке, но отталкивала средняя цена на Авито в 50 тыс. рублей.

Советский геймдев для друзей партии

Случилось чудо, и полностью рабочий калькулятор достался мне в подарок абсолютно бесплатно. К слову, МК-90 и при Советском союзе стоил недешево. Мой экземпляр по состоянию на дату выпуска (июнь 1990 года) можно было купить за баснословные 1 500 рублей (6 среднемесячных зарплат), а ранние модели стоили от 3 до 3.5 тысяч. За такие деньги можно было половину машины купить.

Советский геймдев для друзей партии

Неудивительно, что популярностью этот микрокалькулятор не пользовался, уступая место более дешевому МК-85 и еще более дешевому МК-61 (про него я кстати писал в предыдущей статье).

Сперва я назвал МК-90 микрокомпьютером, потом микрокалькулятором, и это неудивительно, ведь и у разработчиков из Минска были проблемы с его позиционированием. На лицевой стороне МК-90 написано «Микрокомпьютер», при этом на задней панели, да и в инструкции он значится как «Микрокалькулятор». Сейчас МК-90 скорее назвали бы планшетом или КПК.

Хвалим и ругаем

МК-90 превосходил по своим возможностям многие другие модели портативных электронных устройств. У меня сложилось впечатление, что разработчикам дали карт-бланш, и поэтому они сделали настолько дорогое, мощное, но не очень полезное для общественности устройство. Кажется, что доработать МК-90 напильником, пофиксить проблемы (коих при всех достоинствах хватало), и вышел бы флагман советской электронной промышленности, которым и на мировой арене не стыдно похвастаться. Но СССР закончился, а вместе с ним и мечты о светлом электронном будущем.

Чем же выделялся МК-90 на фоне других советских калькуляторов?

Во-первых, размерами. Сравнение с МК-61:

Советский геймдев для друзей партии

Во-вторых, в него встроен интерпретатор BASIC (в моем экземпляре BASIC 1.0, но в дальнейшем вышла версия 2.0). Да, и в МК-85 есть BASIC, но согласитесь, программировать на экране размером 120*64 пикселя гораздо удобнее, чем на однострочном, 12-ти символьном. И это третий плюс. МК-90 поддерживает и графический режим, который позволяет попиксельно выводить двухцветные спрайты, рисовать геометрические фигуры и даже графики.

В-четвертых, в комплекте шли два модуля оперативной памяти (МПО), которые позволяют сохранять записанные программы. Да, память энергозависимая, и батарейки в МПО нужно менять как минимум раз в полтора года. Да, при замене батареек память обнуляется. Но, согласитесь, это довольное удобное решение.

Советский геймдев для друзей партии

Но перейдем к недостаткам, первым из которых станет клавиатура. Мало того, что раскладка фонетическая и не соответствует общепринятой qwerty, так еще и клавиши нажимаются через раз (возможно, это от старости). Неудобно расположены стрелки и некоторые важные символы. Например, бесполезная точка с запятой выводится одной клавишей, а часто используемые знаки равно, сложения и умножения — двумя.

Еще я не очень понимаю, зачем в МК-90 понадобилась русская раскладка. Конечно, калькулятор советский, и русский язык должен быть, но он никак не используется при работе. Более того, использование русской раскладки может привести к ошибкам в работе программ.

Однако, это незначительные минусы, и при должной усидчивости к ним быстро привыкаешь.

Пишем игру

В дальнейшем я буду пользоваться точным эмулятором МК-90, который написал польский товарищ Piotr Piatek. На его сайте было много полезной информации, которая пригодилась мне для написания этой статьи. К сожалению, Piotr удалил все упоминания о советских калькуляторах в середине 2022 года (какое досадное совпадение). Хорошо, что интернет помнит все. Я скачал образы обоих версий BASICа: 1.0 и 2.0. Для чего мне это понадобилось, объясню ниже.

При включении эмулятора на экране отобразится название и три графы: бейсик, СМП0 и СМП1. При этом, калькулятор называется «Электроника — ПК100», и это рабочее название, что подтверждает проблемы с позиционированием. Во второй версии прошивки это поправили.

СМП0 и СМП1 отвечают за запуск программ с модулей памяти. При этом программы, написанные на BASIC, с помощью этих граф запустить нельзя. Только написанные на ассемблере.

Советский геймдев для друзей партии

Заходим в режим программирования BASIC и видим командную строку.

Советский геймдев для друзей партии

Я не буду углубляться в тонкости программирования, тем более, что в интернете можно найти подробную инструкцию, пусть и не всегда понятно написанную. Поэтому сразу перейду к разработке игры.

Пользователю доступны 11 824 байта памяти первой версии и 12 248 байт во второй. При этом память занимают не только данные, но и сам текст кода. Поэтому придется экономить не только на переменных, но и на количестве символов в коде, например обходиться без пробелов (а BASIC это позволяет) и по максимуму использовать каждую строку.

Советский геймдев для друзей партии

Сперва я хотел сделать игру с открытым, случайно-генерируемым миром, размером 64*64 тайла. Когда я понял, что МК-90 не позволяет создавать массивы таких размеров, а производительность просто ужасная, то снизил количество объектов на карте до 20-ти. Но и это не помогло. Карта генерировалась не меньше трех минут, а рендеринг одного кадра занимал от минуты и дольше. Для разработки и отладки мне пришлось даже ускорить работу эмулятора в 5-10 раз. От этой идеи я решил отказаться.

Поэтому я решил пойти другим путем, и создать мир размером в один экран калькулятора, где каждый тайл занимает 8*8 пикселей. То есть общее количество тайлов на карте (15*8) = 120 штук. Кажется, что 120 объектов больше, чем 20, но на самом деле рендеринг всех тайлов занимает от силы секунду.

МК-90 позволяет рисовать графику: точки, отрезки, линии, круги и прямоугольники, но с их помощью графику сделать сложно, да и память жалко. Хорошо, что помимо графических примитивов, МК-90 умеет рисовать спрайты. Для этого используется функция DRAW M, которая принимает как аргумент последовательность байт.

49 CLS 50 DRAW M40E81C3E7FFCFA0740E81C3E7FFCFA0740E81C3E7FFCFA0740E81C3E7FFCFA07000000FFFF00000018183C3C7E7E180018183C3C7E7E18003C7E7EFFE7C3C3C3:LOCATE 8,0 51 DRAW M40E81C3E7FFCFA0740E81C3E7FFCFA07005A7E7E5A7E5A7E0000000F1F181818181818F8F000000018183C3C7E7E180018183C3C7E7E18000000FFAA55FF0000:LOCATE 16,0 52 DRAW M40E81C3E7FFCFA0740E81C3E7FFCFA070C0CFFAA55FF0C0C0C0C0CFCFC0C0C0C0C0C0C0C0C0C0C0C0C0C0C0F0700000018183C3C7E7E18000000FFAA55FF0000:LOCATE 24,0 53 DRAW M40E81C3E7FFCFA073C7E7EFFE7C3C3C30000FFAA55EF342C342C342C342C342C342C342C342C342C342C34EFF72C342C342C342C342C342C342CF4AC54F80000:LOCATE 32,0 54 DRAW M66990066990066990000F8AC54EC342C342CF7AA55FF000040E81C3E7FFCFA0740E81C3E7FFCFA07000000F0F818181818181818181818181818181818181818:LOCATE 40,0 55 DRAW M669900669900669918183C3C7E7E18000000FFAA55EF342C10081008100810081008100A1500000040E81C3E7FFCFA0740E81C3E7FFCFA0740E81C3E7FFCFA07:LOCATE 48,0 56 DRAW M669900669900669918183C3C7E7E18000000FFAA55FF000040E81C3E7FFCFA0700000000000000000000000000000000000000000000000040E81C3E7FFCFA07:LOCATE 56,0 62 DRAW M669900669900669918183C3C7E7E18000000FFAA55FF000040E81C3E7FFCFA07000000000000000000000000000000003C7E7EFFE7C3C3C340E81C3E7FFCFA07:LOCATE 64,0 64 DRAW M669900669900669918183C3C7E7E18000000FFAA55FF000040E81C3E7FFCFA0740E81C3E7FFCFA0740E81C3E7FFCFA0740E81C3E7FFCFA0740E81C3E7FFCFA07:LOCATE 72,0 65 DRAW M669900669900669918183C3C7E7E18000000FFAA55EF342C342C342C342C342C342C342C342C342C342C342C342C342C342C372A351F0000187EFFFF7E181800:LOCATE 80,0 66 DRAW M669900669900669918183C3C7E7E18000000FFAA55FF0000187EFFFF7E181800187EFFFF7E181800187EFFFF7E1818000000FFAA55FF0000187EFFFF7E181800:LOCATE 88,0 67 DRAW M66990066990066993C7E7EFFE7C3C3C3342CF4AC54F80000187EFFFF7E1818003C7E7EFFE7C3C3C3342C342C342C342C342CF4AC54EC342C342C372A351F0000:LOCATE 96,0 68 DRAW M6699006699006699187EFFFF7E181800187EFFFF7E181800187EFFFF7E181800187EFFFF7E181800187EFFFF7E181800187EFFFF7E1818000000FFAA55FF0000:LOCATE 104,0 69 DRAW M669900669900669900001F2A352F342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342C342CF4AC54F80000:LOCATE 112,0 70 DRAW M1A3E66FFFFE7E7FF342CF4AC54F80000924949929249499292494992924949929249499292494992924949929249499292494992924949929249499292494992:LOCATE X,Y:DRAW M7EFFFFFFFFFFFF7E

Вот эти 16 строк рисуют игровой мир и персонажа (он представляет собой простой черный квадрат с усеченными углами) .

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

Советский геймдев для друзей партии

Черный пиксель равен единице, прозрачный — нулю. Создаем битовую маску:

0001 1000 0001 1100 0001 1000 0011 1100 0101 1010 0001 1000 0010 1000 0010 1100

И переводим эти биты в шестнадцатеричную систему счисления.

1 8 1 С 1 8 3 С 5 А 1 8 2 8 2 С

Вот эту последовательность байт (181C183C5A18282C) и принимает функция DRAW M.

Советский геймдев для друзей партии

Эта функция и стала причиной, из-за которой я перешел с аутентичного моему устройству BASIC 1.0 на BASIC 2.0. Оказалось, что в первой версии функция DRAW M почему-то пожирает и так ограниченную память. Во второй версии такого нет, но есть свои приколы, о которых я расскажу позже.

А сперва мне нужно было разобраться, как перенести в память калькулятора последовательность из 1 920 байт (15 столбцов по 64 строки пикселей, каждая из которых представляет собой два символа), чтобы заполнить весь экран нужным мне изображением.

Для начала это изображение нужно было нарисовать, и опять в этом мне помог старый добрый Aseprite. Получился такой вот открытый мини-мир:

Советский геймдев для друзей партии

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

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

Сразу возникла идея создать битовую маску на основе получившегося изображения, а потом написать программу, которая преобразит ее в нужную мне последовательность байт. Для первой цели я использовал графический редактор GIMP. Он позволяет сохранить индексированные изображения в формате заголовочного файла для языков C/C++. Вот, что вышло (я не буду приводить весь текст):

static unsigned char header_data[] = { 1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1, 1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1, 1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1, 1,1,1,0,0,1,0,1, 0,0,0,1,0,1,1,1,0,0,0,1,0,1,1,1, 0,0,0,1,0,1,1,1,0,0,0,1,0,1,1,1, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 1,1,0,0,0,0,0,1... };

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

Осталось решить вопрос, как перенести все это в память МК-90. Сперва казалось, что это не так сложно. В папке эмулятора лежат два файла: smp0.bin и smp1.bin, которые отвечают за нулевой и первый модули памяти, соответственно. Первая версия BASIC сохраняет код на модуль в чистом текстовом виде, а вот вторая версия все ключевые слова кодирует определенными байтами (хорошо, что не весь текст). Например код FOR I = 0 to 10 будет выглядеть так:

0x8E I = 0 TO 10

Вторая проблема заключалась в том, что текстовый редактор кодирует переход на следующую строку как 0x0D, а калькулятор принимает исключительно 0x0A, иначе возникают ошибки. Пришлось лезть в HEX-редактор и менять эти байты вручную. Тут же выяснилось, что программа (а точнее конкретный файл с программой) хранится на модуле памяти блоками по 512 байт, и если вручную не отредактировать количество таких блоков, то калькулятор выдаст очередную ошибку. Зато я разобрался, как модуль памяти хранит наименования файлов и прочие их параметры.

Итак, мир игры готов:

Советский геймдев для друзей партии

Теперь нужно завести переменные для определения позиции игрока, временные переменные для проверки свободных зон для движения:

20 X=112:Y=8:X1=0:Y1=0

Долго мучился над вопросом, как определять, в какие зоны персонаж может попасть, ведь создавать массив размером 8*15 элементов, а потом заполнять его нулями и единицами слишком затратно в части памяти. По моим подсчетам, такой способ занимает как минимум 1.3 Кб. Я опробовал несколько вариантов, в том числе с использованием операторов DATA и READ, созданием отдельных массивов с индексами и т.д. В одном случае обработка одного кадра занимала 15 секунд, в другом — 6 секунд. В итоге я вернулся к первоначальному варианту и пожертвовал памятью ради производительности. Обработка одного кадра теперь занимает не более секунды.

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

10 DIM M1(7,14) 11 FOR I=0 TO 7 12 FOR J=0 TO 14 13 LET M1(I,J) = 0 14 NEXT I 15 NEXT J 16 M1(7,1)=1:M1(2,2)=1:M1(7,2)=1:M1(5,3)=1:M1(2,3)=1:M1(6,3)=1:M1(3,3)=1:M1(7,3)=1:M1(4,3)=1:M1(1,4)=1:M1(2,4)=1:M1(2,5)=1:M1(3,5)=1:M1(4,9)=1:M1(5,9)=1 17 M1(4,5)=1:M1(5,6)=1:M1(2,6)=1:M1(6,6)=1:M1(4,6)=1:M1(4,7)=1:M1(5,7)=1:M1(2,7)=1:M1(2,8)=1:M1(5,11)=1:M1(2,11)=1:M1(6,11)=1:M1(7,13)=1:M1(1,14)=1 18 M1(7,11)=1:M1(7,12)=1:M1(1,13)=1:M1(4,13)=1:M1(5,13)=1:M1(2,13)=1:M1(6,13)=1:M1(3,13)=1:M1(2,9)=1:M1(6,9)=1:M1(3,9)=1:M1(2,10)=1:M1(6,10)=1

Данные для определения свободных тайлов внесены. Пришло время перейти к считыванию клавиш и проверке возможности движения.

Клавиши считываются с помощью оператора INC, и тут же временные переменные меняются в сторону увеличения или уменьшения для последующих проверок. Если клавиши не были нажаты, то с помощью всеми ненавистного оператора GOTO программа возвращается в начало.

100 K=INC 101 IF K=28 THEN LET X1=X:Y1=Y-8:GOTO 150 102 IF K=29 THEN LET X1=X:Y1=Y+8:GOTO 150 103 IF K=26 THEN LET X1=X-8:Y1=Y:GOTO 150 104 IF K=25 THEN LET X1=X+8:Y1=Y:GOTO 150 105 GOTO 100

Строки со 150-ой по 153-ю проверяют, находится ли новое положение в пределах индексов массива. Да, пришлось использовать целых четыре строки, потому что BASIC ругался, если все проверки находились в одной.

150 IF X1>14 THEN GOTO 100 151 IF X1<0 THEN GOTO 100 152 IF Y1>7 THEN GOTO 100 153 IF Y1<0 THEN GOTO 100

Если все проверки пройдены, то определяем, свободен ли тайл в следующей позиции. Если нет, то возвращаемся в начало, иначе стираем персонажа в старой позиции и рисуем в следующей (DRAW M инвертирует пиксели, поэтому можно не заморачиваться с позиционной отрисовкой).

154 IF M1(Y1,X1) = 0 THEN GOTO 100 155 DRAW M7EFFFFFFFFFFFF7E:X=X1:Y=Y1:Y1=0:X1=0:LOCATE X*8,Y*8:DRAW M7EFFFFFFFFFFFF7E:GOTO 100

Для калькулятора 30-ти летней давности и программы на BASICе вышло довольно неплохо:

Первый этап завершен. Дальше я приступлю к описанию геймплея, а пока на этом все. Ждите продолжение.

5959
21 комментарий

В школе щупал этот девайс, казался чем-то из далекого будущего.
Piotr удалил все упоминания о советских калькуляторах в середине 2022 годаХоспаде, как же все ебанулись уже.

16
Ответить

Да уж, обидно, что без этой информации и статья бы не вышла. Самый полезный источник по МК-90 и МК-85

3
Ответить
9
Ответить

О, а как накатил туда Basic 2.0?

Ответить

браво, это не фотографический шоп?

Ответить

Припадок ностальгии, BASIC - мой первый ЯП, в детстве писал текстовые квесты и игры с пиксельным примитивным графоном и тысячами строк на СЮБОР-е, благо книжка была на русском. Все эти 10 CLS, 20 INPUT X, 30 PRINT X, 40 GOTO 20... Эхх

4
Ответить

Та же фигня. Жаль карты памяти не было. В итоге дольше печатаешь буквоциферную абракадабру, чем играешь. Но всё равно было интересно, что же шайтан машина выдаст. Счастью не было предела, когда по экрану бегает и прыгает "созданный" тобой Марио.

1
Ответить