Я рос в 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 («слепой»).
Обложка семнадцатого выпуска Commodore Dossier
После хорошо проведённой ночи слепой герой игры просыпается и обнаруживает, что каким-то образом попал в городскую канализацию. Задача игрока — вернуть его обратно на улицу, пользуясь только звуками, издаваемыми его тростью.
Городские канализационные службы, чрезмерно заботясь о чистоте труб, каждые пять минут сливают воду. При каждом сливе нашего слепого героя забрасывает в другое место канализации, откуда он снова должен начинать свои поиски выхода. После третьего слива игра заканчивается. Игроку видна карта канализации, показывающая местоположение выхода и локации, которые он посетил.
Примерно осенью 1988 года, введя в компьютер листинг, я приступил к игре. В ней была спрайтовая анимация летучей мыши, пролетающей на совершенно чёрном экране перед полной луной, которая показалась мне отличной. Однако я не мог разобраться с управлением. Карта канализации конечно же помогала, но с ней было что-то не то. Локации, которые отмечались на карте как посещённые, не походили на пройденный мной путь. К тому же посещёнными часто отмечались даже стены.
Пример карты канализации из оригинальной игры
После пары попыток я сдался и занялся чем-то другим, но проблема карты и невразумительного управления запомнилась мне. Поэтому когда я наткнулся на скан этого выпуска Commodore Dossier, то понял, что настало время разобраться с ней раз и навсегда.
Разумеется, прежде чем начать, мне пришлось снова вбить весь листинг. Тогда, в 1988 году, у меня не было утилиты проверки контрольной суммы, опубликованной в Commodore Dossier. Может быть, карта канализации оказалась перепутанной из-за моей невнимательности и опечаток?
Быстрый поиск в Google вывел меня на образ диска с программой «CHECKSUM DOSSIER». Звучит многообещающе! Чтобы проверить, я запустил программу и напечатал короткую строку из листинга Blindganger: «240 RETURN», рядом с которой была указана контрольная сумма «7E». Потом я попробовал вставить опечатку и в саму строку, и в контрольную сумму. В обоих случаях на экране выводилось сообщение «FOUT IN REGEL», то есть «ОШИБКА В СТРОКЕ». Отлично!
Разве это не мило?
С помощью этой программы я ввёл весь листинг. Некоторые строки отсканированного журнала плохо читались, поэтому утилита оказалась незаменимой. Часто мне приходилось подбирать разные варианты сочетаний строк и контрольных сумм, пока они не совпадали. Ну что ж, пока всё идёт неплохо. Теперь у меня была копия оригинала, в которой точно не было опечаток. Конечно же, в этой копии не должно быть тех багов, которые возникали у меня в 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 (см. ссылку ниже), а потом начинайте печатать!
Позже я ещё внёс усовершенствования в (уже правильную) карту канализации, чтобы она проще читалась (см. пример ниже).
Крестом помечен выход!
Ссылки
- Копия нужных страниц из Commodore Dossier, выпуск 17, 1988 год.
- Оригинальная версия Blindganger 1988 года в листинге на BASIC или образе диска D64.
- Обновлённая версия Blindganger 2017 года как листинг на BASIC или образ диска D64.
- Утилита проверки контрольных сумм Commodore Dossier в виде образа диска D64.
Поделиться с друзьями
Комментарии (8)
DRDOS
07.07.2017 13:15Как же давно это было!!!
А удовольствия от таких убогих игр, было больше чем сейчас от ультросовременных :(((Pro-invader
07.07.2017 14:59+4Если вы про Elite, то она не убогая. Скорее сейчас больше действительно убогих игр.
Ilias
07.07.2017 14:59не очень понятно про контрольную сумму. если она сошлась для старой версии игры — значит тогда все играли в нее с багом? или старая сумма подошла и после исправления 244 на 255?
PatientZero
07.07.2017 15:00+1Да, получается, что все играли с багом, а контрольную сумму указали для строки с 244.
Vladal
07.07.2017 14:59Мне понравилась фраза
Меня особенно умиляет тот факт, что он и по сей день использует для ведения бухгалтерии C128, хоть и в эмуляторе. Ему близок подход «не сломалось — не чини».
И наверняка у отца автора статьи бухгалтерская учетная система отнюдь не самая простая «сколько у меня денег в кошельке — получил столько-то, потратил столько-то».
В наших реалиях вынужден часто допиливать систему учета при изменении законодательства или форм отчетности у различных фондов…
divanus
Вспомнилась игра Elite и как ее народ разбирал по кирпичикам на ассемблере.
saboteur_kiev
Мы и есть тот народ… прямо в кодах и разбирали… В Спектруме всего около 1200 инструкций.
divanus
Игрался как раз и ассемблер спектрумовский изучал в году так 1994м