Исправляем баги в стиле 1988 года

в 7:00, , рубрики: BASIC, commodore 64, commodore dossier, ненормальное программирование, разработка игр

Исправляем баги в стиле 1988 года - 1

Я рос в 80-х, в десятилетие, когда домашние компьютеры превратились из диковинки в мейнстрим. В моей младшей школе стояло несколько домашних компьютеров Phillips P2000T и пара Apple Macintosh. У моего друга был Commodore 64, на котором мы играли в игры, а однажды мой отец купил для управления финансами Commodore 128. (Меня особенно умиляет тот факт, что он и по сей день использует для ведения бухгалтерии C128, хоть и в эмуляторе. Ему близок подход «не сломалось — не чини».)

Почти сразу после C128 мы купили C64. С128 использовали для бизнеса, а C64 — для развлечений. Я чётко помню, как играл в Space Taxi, Super Cycle, Velocipede, Last Ninja II, Electrix и другие игры. К тому же на этом компьютере я начал учиться программированию.

До изобретения World Wide Web оставалась ещё пара лет, поэтому изучение программирования в основном заключалось в чтении книг и журналов. В компьютерных журналах часто публиковались листинги исходного кода, которые читателю нужно было перепечатывать. Результат мог быть любым: игрой, утилитой для копирования диска, программой для рисования под GEOS или — чаще чем хотелось бы — чем-то, работающим кое-как из-за опечаток. В какой-то момент журналы начали публиковать листинги, рядом с каждой строкой которых была указана контрольная сумма. У них были специальные программы, проверявшие контрольную сумму каждой вводимой строки. Такие программы сильно помогали. Но всё равно это был один из самых медленных и подверженных ошибкам способов копирования компьютерных программ за всю историю компьютеров. Тем не менее, процесс был довольно интересным (по крайней мере, мне так казалось).

Одним из таких журналов был Commodore Dossier, «практический журнал для активных владельцев Commodore», выпускавшийся с 1984 по 1988 год. В семнадцатом, ставшем последним, выпуске содержался листинг довольно интересной игры. Она называлась Blindganger, что в переводе с нидерландского означает «хитрец», но здесь использовалось ещё и значение слова blind («слепой»).

image
Обложка семнадцатого выпуска Commodore Dossier

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

Городские канализационные службы, чрезмерно заботясь о чистоте труб, каждые пять минут сливают воду. При каждом сливе нашего слепого героя забрасывает в другое место канализации, откуда он снова должен начинать свои поиски выхода. После третьего слива игра заканчивается. Игроку видна карта канализации, показывающая местоположение выхода и локации, которые он посетил.

Примерно осенью 1988 года, введя в компьютер листинг, я приступил к игре. В ней была спрайтовая анимация летучей мыши, пролетающей на совершенно чёрном экране перед полной луной, которая показалась мне отличной. Однако я не мог разобраться с управлением. Карта канализации конечно же помогала, но с ней было что-то не то. Локации, которые отмечались на карте как посещённые, не походили на пройденный мной путь. К тому же посещёнными часто отмечались даже стены.

image
Пример карты канализации из оригинальной игры

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

Разумеется, прежде чем начать, мне пришлось снова вбить весь листинг. Тогда, в 1988 году, у меня не было утилиты проверки контрольной суммы, опубликованной в Commodore Dossier. Может быть, карта канализации оказалась перепутанной из-за моей невнимательности и опечаток?

Быстрый поиск в Google вывел меня на образ диска с программой «CHECKSUM DOSSIER». Звучит многообещающе! Чтобы проверить, я запустил программу и напечатал короткую строку из листинга Blindganger: «240 RETURN», рядом с которой была указана контрольная сумма «7E». Потом я попробовал вставить опечатку и в саму строку, и в контрольную сумму. В обоих случаях на экране выводилось сообщение «FOUT IN REGEL», то есть «ОШИБКА В СТРОКЕ». Отлично!

image
Разве это не мило?

С помощью этой программы я ввёл весь листинг. Некоторые строки отсканированного журнала плохо читались, поэтому утилита оказалась незаменимой. Часто мне приходилось подбирать разные варианты сочетаний строк и контрольных сумм, пока они не совпадали. Ну что ж, пока всё идёт неплохо. Теперь у меня была копия оригинала, в которой точно не было опечаток. Конечно же, в этой копии не должно быть тех багов, которые возникали у меня в 1988 году. Но… они были!

Чтобы выяснить причину, мне нужно было найти код, показывающий на экране карту канализации. Карта показывается при завершении игры, то есть когда игрок находит выход или после третьего слива воды. Я сосредоточился на втором случае и начал искать в листинге на BASIC числа 0 или 3.

В строке 1780 нашлась переменная MAAL, которая сравнивалась с нулём. Эта переменная инициализировалась со значением 3 в строке 1550 и уменьшалась с каждым сливом канализации.

1780 MAAL=MAAL-1:IFMAAL=0THENGOTO1810

Блок кода в строках 1810-1840 выполняется, если MAAL равняется нулю. Строка 1840 — это бесконечный цикл. Посмотрев на три остальные строки BASIC, я понял, что нашей следующей целью является подпрограмма в машинном коде, расположенная по адресу памяти 16540 ($409C).

1810 POKECROSS+4096,1           :REM MARK EXIT ON THE MAP
1820 SYS16540                   :REM COPY MAP TO SCREEN
1830 POKESID+11,0:POKE53248+21,0:REM STOP SOUND AND DISABLE SPRITES
1840 GOTO1840                   :REM ENDLESS LOOP

Эта подпрограмма состоит из двух частей. Первая копирует 1000 байт из области памяти, начиная с $6000, в экранную память ($0400-$07E7). Она заполняет весь экран (40 столбцов на 25 строк = 1000 байт) картой канализации.

Как выяснилось, в этой части нет никаких багов. Карта может казаться странной, пока вы не заметите, что все сплошные части — это стены, символами "@" отмечены горизонтальные секции труб, символами «A» — вертикальные секции, символы с «B» по «J» обозначают разные типы углов и пересечений, «K» — это ямы, а «L» — выход. Эти символы неслучайны. Они соответствуют экранным кодам с 0 по 12. Карта — это просто дамп на экран внутреннего представления карты в игре.

Вторая часть подпрограммы копирует 1000 байт из области памяти, начиная с $7000, в цветовую ОЗУ ($D800-$DBE7). Так все посещённые локации закрашиваются жёлтым.

L40CB:  LDA     #$FF    ; >-- инициализация --
        STA     $FB     ;                      |
        LDA     #$6F    ;                      |
        STA     $FC     ;                      |
        LDA     #$F4    ;                      |
        STA     $FD     ;                      |
        LDA     #$D7    ;                      |
        STA     $FE     ; <--------------------
        LDX     #$04    ; >-- внешний цикл -----------------
L40DD:  LDY     #$FA    ; >-- внутр. цикл I ---             |
L40DF:  LDA     ($FB),Y ; (копирование байтов) |            |
        STA     ($FD),Y ;                      |            |
        DEY             ;                      |            |
        BNE     L40DF   ; <-------------------              |
        LDY     #$FA    ; >-- внутр. цикл II -----------    |
L40E8:  INC     $FB     ; (обновление начальных адресов) |  |
        BNE     L40EE   ;                                |  |
        INC     $FC     ;                                |  |
L40EE:  INC     $FD     ;                                |  |
        BNE     L40F4   ;                                |  |
        INC     $FE     ;                                |  |
L40F4:  DEY             ;                                |  |
        BNE     L40E8   ; <----------------------------     |
        DEX             ;                                   |
        BNE     L40DD   ; <-------------------------------

Байты копируются четырьмя блоками по 250 байт. Внешний цикл использует регистр X как счётчик с 4 до 0.

        LDX     #$04   
        ;
        ; внутренний цикл
        ;
        DEX            
        BNE     L40DD

Первый внутренний цикл использует регистр Y как счётчик с #$FA (250) до 0.

L40DD:  LDY     #$FA   
L40DF:  LDA     ($FB),Y
        STA     ($FD),Y
        DEY            
        BNE     L40DF  

Для копирования байтов во внутреннем цикле используется непрямая индексная адресация. В этом режиме адресации регистр Y используется как смещение, прибавляемое к 16-битному начальному адресу, хранящемуся в нулевой странице.

Например, команда LDA ($FB),Y выполняется следующим образом:

  • Считывается 16-битный начальный адрес в прямом порядке байтов, хранящийся в участках $FB и $FC нулевой страницы. Прямой порядок байтов означает, что наименьший значимый байт хранится в самом нижнем адресе ($FB). $FB инициализируется в #$FF, а $FC — в #$6F, поэтому 16-битный начальный адрес будет $6FFF.
  • Для вычисления действительного адреса к начальному адресу прибавляется значение регистра Y. Регистр Y инициализируется в начале цикла в #$FA. Прибавляя к $6FFF, получаем действительный адрес $70F9.
  • В накопитель (регистр A) загружается байт из действительного адреса.

Регистр Y ведёт обратный отсчёт от 250 до 0. Заметьте, что минимальное значение Y, используемое как индекс, равно 1. (Когда команда DEY уменьшает Y до нуля, то следующая команда ветвления BNE проходит мимо и выходит из внутреннего цикла.)

Первый загружаемый байт находится по адресу $7000. Поскольку минимальное значение Y равно 1, начальный адрес источника должен быть инициализирован в $7000 — 1 = $6FFF. Аналогично, первый сохраняемый байт находится по адресу $D800, поэтому целевой начальный адрес должен инициализироваться в $D800 — 1 = $D7FF. Вместо этого он инициализируется в $D7F4!

Случайная опечатка и ввод #$F4 вместо #$FF маловероятны. Но в десятичном счислении это соответствует вводу 244 вместо 255. Похоже, автор игры случайно нажал на 4 вместо 5 при вводе константы 255! Это сдвинуло цвета, использованные в карте, на 11 позиций относительно самой карты. В свою очередь, это значит, что на карте неправильно отмечались посещённые локации.

Виновный в ошибке байт находится в операторе задания данных в строке 3670 листинга на BASIC:

3670 DATA 252,169,244,133,253

После замены на правильное значение (255), карта канализации начинает отображаться правильно. И это означает… что мы исправили ошибку 29-летней давности!

Чтобы отпраздновать это, я собрал версию оригинальной игры 2017 года с этим исправлением, а также с исправлениями некоторых других ошибок, найденных при изучении кода:

  • Посещённые локации теперь правильно отображаются на карте, показываемой в конце игры.
  • При расчёте оставшегося времени учитывается штраф за падение в яму.
  • Громкость шума с улиц изменена таким образом, что теперь зависит от расстояния до выхода (как и задумывалось авторами игры).
  • Перевод на английский. (Нажмите и удерживайте в игре клавишу H для просмотра справки.)

Blindganger 2017 также доступен в форме листинга на BASIC, включая контрольные суммы. Чтобы ощутить себя в 80-х, скачайте и запустите утилиту проверки контрольных сумм Commodore Dossier (см. ссылку ниже), а потом начинайте печатать!

Позже я ещё внёс усовершенствования в (уже правильную) карту канализации, чтобы она проще читалась (см. пример ниже).

Исправляем баги в стиле 1988 года - 5
Крестом помечен выход!

Ссылки

Автор: PatientZero

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js