Commodore 64, или как я перестал бояться и полюбил оперативную память
Не так давно я приобрел Commodore 64. Он довольно долго стоял и пылился, ждал своего часа. И вот наконец-то я подключил к нему старенький ламповый монитор и купил аудиокассеты.
Без этих кассет у меня не было другой возможности сохранить написанный код.
Да, можно скачать эмулятор, и люди даже написали полноценную IDE с возможностью творить на BASIC, ассемблере, и даже редактировать спрайты, текстовые экраны и наборы символов. Этими возможностями я и воспользуюсь, но все таки окончательный результат хочу видеть на настоящем железе.
Сразу скажу, что эта статья — попытка разобраться в том, что же за зверь такой — Commodore 64, со своими указателями, регистрами памяти, восемью битами и двоичной системой счисления. Поэтому я могу делать ошибки, поступать нелогично и вообще писать откровенный бред. Может быть, у меня вообще ничего не получится, но статья все равно выйдет в свет, дабы я мог поучиться на своих же ошибках.
Содержание:
Чем пользуюсь
Железо. Commodore 64C. Произведен в Гонконге. К сожалению, я не смог найти в сети информацию о серийных номерах, поэтому даже не знаю год выпуска (Возможно, 84-ый).
Магнитофон. Commodore Datassette 1530. Произведен в Сингапуре. Дата выпуска — сентябрь 1988 года.
Среда разработки. CBM prg Studio. Позволяет писать код на BASIC, ассемблере, редактировать спрайты, символы и текстовые экраны, сохранять проекты в форматах TAP (кассеты), D64 (дискеты). Можно связать IDE и VICE, что позволяет быстро проверять работоспособность программ.
Basic
И вот мы включаем Commodore 64 — самый популярный в США 8-ми битный компьютер из 80-х годов. Встречает нас ламповое сине-голубое текстовое окно, которое с ходу предлагает написать что-нибудь на бейсике.
38911 BASIC BYTES FREE, заманивает нас компьютер. Не будем сопротивляться. На бейсике я уже писал, например, на Apple II и советском калькуляторе Электроника МК-90. Здесь тоже все довольно стандартно. Пишем, по классике, программу, которая выводит 256 раз Hello World! на экран.
В Commodore Basic V2 стандартный для бейсика набор команд. Практически вся логика осуществляется последовательностями FOR-IF-GOTO. Приятный бонус — не обязательно перед объявлением переменных писать ключевое слово LET, что экономит место в памяти. Не обязательно разделять слова пробелами, а несколько строк можно объединить в одну через двоеточие.
Есть свои особенности и баги. Например, функция ASC, которая возвращает цифровое значение символа, не принимает пустую строку (пофиксили в следующих релизах), а функция FRE, которая показывает количество оставшихся байт в памяти бейсика, выводит отрицательные значения (приводится в порядок небольшой формулой).
Самый главный плюс, и этого мне очень не хватало на Apple II — команда IF в случае положительной проверки выполняет все действия после THEN, прописанные через двоеточие. В других версиях старого бейсика, которые я пробовал, код после двоеточия воспринимался как следующая строка, и выполнялся в любом случае.
Но больше всего я удивился, когда не увидел в списке команд бейсика функций для работы с графикой. В компьютерах от Commodore такая возможность появилась только в следующей версии Basic v3.5.
Но, покопавшись, я нашел две прекрасные команды: Poke и Peek, на которые раньше не обращал внимание. И все заверте…
Немного про оперативную память
В Commodore 64, неожиданно, 64 килобайта доступной оперативной памяти, а точнее 65 535 байт или регистров. И все эти регистры объединяются в определенные блоки, которые отвечают за ту или иную работу процессора, видеочипа, звукового чипа и устройств ввода-вывода. Да, в Commodore есть очень продвинутые для своего времени видео- и звуковая карты.
Сразу скажу, что без книг и гайдов с Commodore 64 делать нечего. Как минимум, нужно найти книги «Commodore 64 Programmer’s Reference Guide», «Mapping the Commodore 64», «Commodore 64 User Manual», а также активно пользоваться сайтом С64-Wiki.
Вернемся к программированию. Функция Poke помещает в один байт памяти определенное значение, а функция Peek считывает значения из памяти. Каждый регистр памяти (байт) занимает 8 бит, то есть от 0 до 255 возможных значений в десятичной системе счисления. Ох уж эти системы счисления, и как сложно понять концепцию человеку, который кроме как на Python и VBA ничего особо не писал. Но, пора пришла.
Двоичная система счисления.
Вот живете вы, и всю жизнь считаете десятками. 0-1-2-3-4…-10-11-12-13…20, и так далее. А что, если бы у человечества было только две цифры: 0 и 1? Тогда числа были бы невероятно длинные, это я вам точно могу сказать.
Идете вы пиво покупать, и надо взять восемь бутылок. Вы начинаете считать: 1-2-3…8. Но в двоичном мире есть только 0 и 1, вот и приходится мучиться. 1-10-11-100-101-110-111. Посчитали семь бутылок. А восьмая? Ну, допустим она будет нулевой.
В Commodore 64 один байт (регистр) памяти равен 8 бит: «0 0 0 0 0 0 0 0». Если считать их также как пиво, то «0 0 0 0 0 0 0 0» будет равно нулю, «1 1 1 0 1 0 1 0» — 234-ем, а «1 1 1 1 1 1 1 1» — 255-ти.
Кстати, позиции битов начинаются с конца числа, и это пригодится в дальнейшем.
7 6 5 4 3 2 1 0: позиция.
0 0 0 0 0 0 0 0: бит.
Собственно, на этом вся система и строится.
Команды Poke и Peek принимают только десятичные значения. Не умеет бейсик работать с двоичными и шестнадцатеричными числами, поэтому стоит вооружиться либо навыками устного счета, либо конвертером (вот это мой вариант).
Приведу пару примеров использования Poke и Peek. Допустим, я хочу поменять цвет рамки, цвет основного экрана и цвет текста. Сделаю банальный, но эффектный стиль матрицы.
Регистры 53281 и 53280 отвечают за цвет рамки и экрана. Мы говорим, что эти регистры теперь будут равны нулю, что соответствует черному цвету. Регистр 646 отвечает за цвет курсора, а значение 5 соответствует зеленому.
А теперь что-нибудь посложнее. Commodore поддерживает всего 16 цветов, поэтому вышеупомянутые регистры могут принимать только значения от 0 до 15. Значения от 16 до 255 будут просто повторять уже пройденные цвета.
Допустим, мы хотим поменять цвет курсора, но не совсем стандартным образом. Мы не просто запишем новое значение в регистр, а используем побитовое И (AND) и побитовое ИЛИ (OR).
Сейчас цвет курсора 5 (зеленый), то есть 0000 0101 в двоичной. Мы хотим получить цвет 9 (серый), то есть 0000 1001 в двоичной. Для этого нужно «выключить» бит 2 и «включить» бит 3.
Сперва применим операцию AND. Берем число 5 (0000 0101) и число 251 (1111 1011). Операция AND сравнивает два бита в каждой позиции и оставляет 1, если оба бита равны единице, и 0 во всех других случаях. На выходе получаем (0000 0001) или 1 в десятичной.
Теперь надо «включить» бит 3. Берем получившееся число 1 и число 8 (0000 1000). Операция OR также сравнивает оба бита в каждой позиции и оставляет 1, если хотя бы один из бит равен единице, и 0 в других случаях. Получаем нужное нам число 9 (0000 1001).
Вот как это выглядит в бейсике:
Вообще, этот пример не слишком полезный, но суть, я надеюсь, передать удалось.
Ну, и раз я уже сделал отсылку на матрицу, то написал небольшую программу, которая выводит всем известные зеленые символы на черном фоне (ну, или пытается это делать).
Придумываю игру
И я решил пойти своим обычным путем: придумать основные механики для простенькой игры и учиться по ходу действия, а потом будь, что будет.
Спустя непродолжительное время (5 минут) я придумал такую банальную по современным меркам концепцию:
По лабиринту перемещается персонаж, при этом уровень находится в тумане войны. Игроку нужно найти от одного до трех ключей, чтобы открыть все запертые двери, а потом разыскать выход. Помимо игрока в лабиринте водятся монстры, которых надо убивать. Вот, собственно, и все. Звучит не очень сложно, и в современных реалиях набросать такую игру можно очень быстро. Но не на Commodore 64.
Свою будущую недонаписанную недоигру я хочу назвать Dungeon Penetration. Ну, а почему бы и нет?
Распределение памяти
Как я уже сказал, регистры памяти в Commodore 64 поделены на функциональные блоки — страницы. Например, регистры с 1024 по 2047 отвечают за Screen Memory, то есть то, что мы видим непосредственно на экране. А точнее, регистры 1024 — 2023. Оставшиеся — это указатели на спрайты, коих в Commodore встроено аж 8 штук на уровне железа. Но об этом позже.
Экран компьютера в стандартном режиме составляет 40 символов в ширину, и 25 в высоту (всего 1000 символов). Если поместить, например, в регистр 1524 (1024+500) число 81, то Commodore выведет прямо посреди экрана шарик.
Но меня сейчас это не очень волнует. Более важный вопрос — сколько памяти у меня вообще есть для написания программы? И ответ не так однозначен, как может показаться на первый взгляд.
Начальный экран пишет, что свободной памяти для программы на BASIC осталось почти 39 килобайт. И действительно, регистры с 2048 по 40959 используются исключительно для хранения бейсик-программ. Для моей небольшой игры этого, наверное, хватит, но хотелось бы научиться пользоваться дополнительными источниками.
Если взглянуть на распределение оперативной памяти, то увидим следующую картину:
Судя по таблице, нам доступно дополнительно как минимум 28 килобайт памяти (регистры 40960-49151 — 8Кб, 49152-53247 — 4Кб и 57344-65535 — 16Кб), но по факту доступно всего 4 килобайта. Первый диапазон — это BASIC ROM, который содержит инструкции для бейсика. Его можно переключить для свободного использования, но не в моем случае. Третий диапазон — KERNAL ROM. Он содержит все инструкции для операционной системы. Насколько я понимаю, его вообще лучше не трогать, если только не пиcать собственную операционку.
Таким образом, свободным для использования остается только второй диапазон: регистры 49152-53247. Конечно, есть отдельные свободные регистры, которые разбросаны по всей оперативке, но я не буду их пока трогать.
В итоге у меня остается 38Кб памяти для программы на бейсике и 4Кб памяти для других вещей. 42 килобайта, что неплохо. В Электронике МК-90 даже 15-ти не было. Но, как обычно, не все так просто.
Особенности графики
В Commodore 64 стоит продвинутый для своего времени графический чип VIC-2. Да такой продвинутый, что «наблюдает» он непосредственно за оперативной памятью.
Например, в его распоряжении находятся уже упомянутые регистры 1024-2023, которые отвечают за экран. Помимо экрана, VIC-2 также следит за спрайтами и наборами символов.
Проблема заключается в том, что видеочип умеет считывать только 16Кб памяти единовременно, то есть 1/4 от общей памяти компьютера. В эти 16 килобайт должен поместиться экран, набор символов, набор спрайтов, указатели на спрайты, их положение и прочее, и прочее.
В стандартной конфигурации видеочип считывает регистры с 0 до 16383. Давайте разберемся, что входит в эти регистры. С 0 до 1023 находятся регистры, важные для операционной системы, бейсика и прочих вещей. Регистры с 1024 по 2047 — это память экрана. А дальше расположена память, выделенная для программы на BASIC.
Если в своей игре я буду использовать спрайты, то данные каждого спрайта (попиксельно) занимают 63 байта. Допустим, я сделаю для каждого из восьми спрайтов анимацию из трех кадров. Выходит полтора килобайта (63*8*3). И эти 1,5Кб должны обязательно быть в поле зрения видеочипа. В таком случае, нам придется занимать этими спрайтами место, зарезервированное для бейсик-программы, что значительно сокращает память и может попросту сломать программу.
Разработчики предусмотрели и такой вариант. Двумя несложными строчками кода можно заставить видеочип смотреть в другую область памяти. Доступные варианты: с 0 до 16383, с 16384 до 32767, с 32678 до 49151 и с 49152 до 65536. Давайте пробовать.
Я пораздумал и решил переместить видеочип в последний блок. Да, теперь экран частично займет единственную свободную область памяти, но зато видеопамять вообще не будет затрагивать бейсик-программу. К тому же регистры 1024-2047, ранее отведенные для экрана, освободятся.
Но все пошло не по плану. При запуске программы я увидел какой-то набор полосок:
Дело в том, что видеокарте то я сказал собирать вещички, но процессор вообще не в курсе, что экран переехал. Он продолжает упорно моргать курсором и печатать в области 1024-2023.
Пробую заново. Теперь нам надо обновить регистр 648, который отвечает за положение текстового экрана.
Ииииииии… их нет! Придется лицезреть все те же грустные полоски и лезть в мануал.
Сейчас постараюсь объяснить, в чем же тут дело. В память компьютера зашиты наборы символов. Они занимают пространство в регистрах c 53248 до 57343 (так называемый Character generator ROM).
Если видеочип смотрит в первый или третий блоки памяти, то все нормально. Необходимые наборы символов бесшумной тенью покрывают часть памяти в нужном блоке (не оказывая на нее влияния), и видеокарта может их считать. Однако, если VIC-2 смотрит во второй или четвертый (наш) блок, то он не может найти символы из ROM, и поэтому обращается к регистру 53272, в котором говорится, на каком смещении от начала видеопамяти находятся наборы символов. А так как на нужном смещении символы вообще не определены, то чип считывает то, что находится в этих регистрах, а именно всякий мусор. Поэтому мы и видим такие полоски.
Так как регистр 53272 отвечает не только за смещение наборов символов, но и за положение памяти экрана внутри видеопамяти, то можно убить сразу двух зайцев: определить, что память экрана вообще не смещена, то есть начинается в регистре 49152, а набор символов смещен на 2048 регистров от начала.
В регистре 53272 биты 1-3 отвечают за положение символов (значение * 2048), а биты 4-7 — за положение экрана (значение * 1024). Нам нужна следующая последовательность: 0 0 0 0 0 0 1 0, или 2 в десятичной. Так и запишем.
Это ничего не изменит визуально, так как еще предстоит создать новые символы, но работает вроде правильно.
Создаю набор символов
В Commodore 64 несколько графических режимов. Первый, и стандартный — это текстовый (40 на 25 символов размером 8*8 пикселей каждый). И текст в Commodore можно использовать как тайлы. Именно этим режимом я и воспользуюсь.
Текстовый режим может быть стандартным (два цвета: цвет фона и цвет символа) и многоцветным (трехцветные символы, при этом два цвета одинаковые для всех символов). Я выбрал многоцветный формат.
Тут надо учесть, что если в обычном режиме символ состоит из 64 пикселей (8*8), то в многоцветном — из 32-ух (4*8). Из-за этого пиксели выглядят широкими. Все оттого, что одна строка символа занимает 1 байт (8 бит). В одноцветном режиме каждый бит отвечает за включение/отключение пикселя. В многоцветном один пиксель занимает два бита: их комбинации меняют цвет символа.
Я посчитал, что для игры мне потребуется 24 тайла. Каждый символ занимает 8 байт, что в сумме дает всего 192 байта. Да, видеочип будет считать, что все байты после 192 тоже входят в состав набора символов, но мы их просто не будем использовать.
Приступаем к творчеству. Встроенный в CBM prg Studio редактор в этом очень хорошо помогает. Нам нужно 8 тайлов для графики, один из которых будет пустым, а также цифры от 0 до 9, буквы: H, P, E, X, Y и двоеточие. И вот что получается:
IDE позволяет экспортировать числовые значения новых символов в формат бейсика. Получается следующее:
Команда DATA позволяет хранить числовые данные, которые потом нужно будет считать c помощью команды READ и поместить в нужные регистры памяти. Но сперва надо включить многоцветный режим и определить цвета символов.
Режимы переключаются в регистре 53270. Первый цвет символа определяется в регистре 53282, а второй — в регистре 53283.
Третий цвет определяется отдельно для каждого символа на экране. Эти цвета находятся в регистрах с 55296 по 56295, но в многоцветном режиме из 16 цветов можно выбрать только цвета 8-15. Пока что я это делать не буду, потому что компьютер находится в режиме ввода текста, и третий цвет символа будет зависеть от цвета курсора. Поэтому вводим временную команду POKE 646,10 для смены цвета курсора.
UPD. Оказалось, что можно не определять цвет для каждого символа, а просто поменять цвет курсора, и они автоматически будут появляться на экране в нужном виде. Так что временная команда превратилась в постоянную.
Теперь надо считать новые символы с помощью команды READ, а потом поместить эти данные начиная с регистра 51200. Помимо этого надо обнулить регистры, отвечающие за 32 символ в наборе — это символ, которым заполняются пустые области экрана.
И что-то получилось. Теперь можно печатать новыми символами (главное сопоставить их с клавишами) и даже нарисовать какой-нибудь уровень.
Итак, часть работы сделана. Остались свободными следующие регистры:
- с 1024 по 2047, или 1 Кб.
- c 50175 по 51200, или 1 Кб.
- с 51392 по 51456, или 64 байта (и это очень хорошо вписывается в концепцию спрайтов).
- с 51465 по 53247, или 1,7 Кб.
В эту память (почти 4Кб) мне надо уместить карту уровня (а лучше пары уровней) и спрайты игрока и врагов.
Карта уровня
Я подумал, что надо выделить под уровни не более 1 Кб памяти (да, у меня есть почти 40 Кб бейсик-памяти, и я могу просто засунуть все в массивы, но решил пойти иным путем). Эти уровни я помещу в освободившуюся память с 1023 по 2047 регистр.
Для кодирования данных карты я опять решил обратиться к двоичной системе счисления. Каждый уровень будет размером 16*16 блоков, что в сумме дает 256, а это идеально подходит для 8-ми битной архитектуры.
Информация о каждом блоке будет храниться в одном байте памяти. Биты 0-2 будут отвечать за графику. Всего 8 возможных символов из тех, что я уже нарисовал. Бит 3 будет отвечать за коллизию (0 — сквозь блок можно пройти, 1 — нельзя). Биты 4-7 — это номер триггера. Выходит не более 15 триггеров на уровень, так как 4 бита дают 16 возможных значений, а ноль указывает на отсутствие триггера.
Воспользуемся магией Excel. Сперва накидаю уровень.
Переводим все это в двоичную систему счисления.
И теперь все это надо перевести в десятичную систему, чтобы вставить в программу.
И, последний штрих на данный момент — вставить все это безобразие в DATA команды.
Когда вся программа будет написана, то можно значительно сократить код. Для этого надо убрать пробелы и заполнить строки по максимуму (не более 80-ти символов).
Осталось создать триггеры. Блок с триггерами будет начинаться сразу после конца уровня. Каждый триггер будет занимать два байта. Первый байт — это указатель на блок уровня, который триггер активирует. А второй байт — это тип триггера (убрать блок, получить ключ, закончить уровень, и т. д.). Кроме того, второй байт отвечает и за то, активируется ли триггер только при наличии определенного ключа. Биты 0-2 отвечают за тип триггера, а биты 3-5 — за проверку на наличие одного из трех ключей.
Для первого уровня информация о триггерах будет выглядеть так. Например триггер (46, 1) означает, что блок под номером 46 будет убран с карты, а триггер (16, 5) означает, что 16 блок — это выход с уровня.
Теперь надо считать данные триггеров и карты и поместить их в нужные регистры.
Я написал небольшую тестовую программу, которая считывает данные карты и выводит ее полностью на экран. Жопой чуял, что она будет работать медленно.
Но мне и не надо быстро. Как я уже сказал, уровень будет скрыт туманом войны, поэтому рисовать нужно будет только до 9 блоков за раз, а не весь уровень в 256 блоков. Можно было бы вообще отрисовать весь уровень в начале, а потом его не трогать, но это не так интересно.
Добавляю игрока. Работа со спрайтами.
Для игрока надо ввести координаты, описать управление и взаимодействие с объектами, и конечно нарисовать спрайты. О последнем я и хочу поговорить.
В Commodore 64 встроено 8 спрайтов на уровне железа. Они нестандартного размера в 24*21 пикселей (но это позволяет уместить изображения для них в 63 байта). Да, спрайтами я сейчас называю не сами изображения, а лишь оболочку. Изображения можно хранить в свободной памяти, а в самих спрайтах просто менять указатели на место хранения этих изображений, например, чтобы сделать анимацию.
Спрайты можно увеличить в два раза, перемещать, включать/выключать. Можно даже считывать коллизии спрайтов с символами и другими спрайтами. По аналогии с символами, спрайты могут быть одноцветными или многоцветными (при этом пиксели также расширяются). Для многоцветных спрайтов действует тоже правило, что и для символов: один цвет уникальный, два цвета общих.
Интересный факт.
Разрешение экрана Commodore 64 составляет 320*200 пикселей, а каждая координата спрайта хранится в одном байте (от 0 до 255 возможных значений).
И если с координатой Y все понятно (200 меньше, чем 255), то работа с координатой X вызывает некоторые трудности. Поэтому, если перемещать спрайт вправо, то в определенный момент он остановится, не достигнув границы экрана (регистр X перевалит за 255), и выскочит ошибка (На ассемблере регистр обнулится и пойдёт считать заново).
Чтобы решить эту проблему, разработчики ввели еще один регистр, с помощью которого можно передвинуть спрайт по горизонтали дальше значения 255. Проблема заключается в том, что это довольно муторное занятие.
Поэтому разработчики игр прошлого редко заботились о размещении спрайтов в правой части экрана, ведь намного проще было заполнить это пространство интерфейсом и полезной информацией, например, уровнем здоровья, диалогами и прочим.
Но в моем случае эта информация не понадобится.
Итак, самое время нарисовать спрайты, и в этом опять поможет CBM prj Studio. Вот так выглядит экран редактора спрайтов:
Спрайты игрока я решил сделать одноцветными — черными, чтобы выделялись на фоне уровня. Вторая причина — так как тайлы уровня составляют всего 8*8 пикселей, то и спрайты должны быть такого же размера. Если использовать многоцветный режим, то спрайты будут уж очень размытыми.
Враги же наоборот будут многоцветными (да, разные спрайты могут работать в разных режимах).
Спустя какое-то время у меня получились вот такие уродцы:
Думаю, на первое время хватит. У игрока будет простенькая анимация в два кадра.
Знакомым образом переводим изображения в бейсик.
Одна проблема — в настоящее время данные изображений занимают по 63 байта каждый. Для того, чтобы удобнее было их считывать по 64 байта, надо добавить для каждого изображения значение 0 в самый конец.
Изображения я помещу в самый конец свободной области с 51465 по 53247.
И вот изображения в памяти:
Теперь надо поработать со спрайтами. Я решил, что один спрайт будет содержать изображения игрока (они будут меняться при ходьбе), а второй спрайт — изображения врагов, которые будут меняться при столкновениях с разными видами. При этом спрайты врагов будут увеличены в два раза.
Сперва надо ввести переменные для положения игрока, включить спрайт игрока, указать, где находится изображение спрайта, и настроить анимацию перемещения.
Указатель на изображение первого (или нулевого) спрайта находится через 1016 регистров после начала экранной памяти. Изображения хранятся в памяти блоками по 64 байта, а первое изображение начинается с 51456-го регистра. Поэтому число в регистре указателя должно быть равно 36-ти. Оно равно количеству блоков по 64 байта от начала видеопамяти (49152+64*36=51456).
Для удобства я ввел три новые переменные. Кроме того, надо указать, что цвет нулевого спрайта — черный. Для этого нам нужен регистр 53287. И да, позиция игрока сейчас: X-24, Y-48. Именно в этих координатах начинается текстовый экран без учета границ.
Теперь надо включить спрайт игрока. За это отвечает регистр 53269. Каждый бит регистра от 0 до 7 указывает, включен (1) или выключен (0) соответствующий биту спрайт. Нам надо включить нулевой спрайт. И конечно надо указать позицию спрайта. За координаты X и Y нулевого спрайта отвечают регистры 53248 и 53249, соответственно. Помещаем в них значения переменных координат игрока:
Теперь попробую сделать перемещение игрока и анимацию. Использовать буду раскладку WASD. Номер последней нажатой клавиши клавиатуры размещен в регистре 197, а анимация будет меняться путем изменения указателя на изображения спрайта (с 36 на 37 и наоборот).
Я немного переработал код, и вот что получилось:
Я почистил код, убрал лишние пробелы. На данный момент программа занимает 3423 байта, так что я даже 9% доступного места не использовал. Двигаемся дальше.
Считываю данные карты и активирую триггеры
Стартовая позиция игрока будет в правом верхнем углу карты. Если высчитать точную позицию для спрайта, то получится по X — 136, по Y — 64. Надо внести значения в переменные и проверить.
Первым делом я переработал код. Вот так выглядят все константы:
Еще я дополнил код в части передвижения:
В целом, основная часть работы завершена. Осталось только описать функции для отрисовки экрана и проверки триггеров.
Вот код для отрисовки экрана. Он не очень быстрый, потому что все считывается через два цикла, но для бейсика пойдет.
И последний штрих — проверка триггеров. Я сделал проверку только на первый тип триггера (убрать блок с карты) и пятый (завершить игру). Чтобы активировать триггер надо просто врезаться в него с любой стороны.
Покажу на примере кнопки и двери, как это работает.
That's All Folks? Ассемблер
Пока что на этом все. Этой статьей я своих целей добился. Я понял, как работает память в Commodore 64 и как ей манипулировать. А также разобрался в различных графических режимах и спрайтах.
В процессе освоения всего этого я решил попробовать себя в ассемблере. Я написал небольшую программу на бейсике и на ассемблере, которая просто заполняет экран символами.
Вот, что получилось (видео):
Результат, как говорится, на лицо. Мало того, что программа на ассемблере меньше весит, так и исполняется в сотни раз быстрее. Поэтому я немного разочаровался в бейсике и последние абзацы этой статьи уже дописывал из под палки. Вместо этого я предпочел погружаться в изучение опкодов и паттернов программирования на ассемблере.
Так что теперь меня ждет прямая и косвенная адресация, регистры, флаги, шестнадцатеричные числа, самомодифицирующийся код и прочие прелести. А бейсик подождет.
Возможно, когда-нибудь я и про это напишу статью, но не сейчас.
P. S. Я веду Телеграм-канал со странным названием. Там я пишу о своих потугах сделать игру (с дискретным управлением), создать что-то адекватное в Blender, разбираюсь в Commodore 64 и просто делюсь мыслями об играх, коде и пиве. Если вам будет не сложно подписаться, то милости прошу=)
А что до игры?
Ну, я сделал больше половины. Большую часть времени заняло изучение регистров памяти. В игру осталось только добавить врагов и прописать другие типы триггеров и проверки на наличие ключей. Это делается не очень сложно.
Вот полное прохождение в ускоренном режиме:
Немного про память. Сама программа на бейсике (с учетом всех сокращений кода и комментариев) заняла 4121 байт. Уровень в памяти занял 271 байт. Набор символов — 192 байта. Спрайты - 320 байт. Итого, почти 4,8Кб памяти. Not great, not terrible.
Прикладываю весь код (без пробелов и с комментариями). До скорых встреч!