Как написать игру для ZX Spectrum на ассемблере

       

Печатающий квадрат


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

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

Поскольку такой способ печати текстов может встречаться в игровой программе неоднократно, необходимо создать универсальную процедуру, которая сможет выводить любой заданный текст. В этом случае перед ее вызовом достаточно будет лишь указать нужные параметры в некоторых регистрах. Какие это должны быть параметры? Во-первых, конечно, сам текст, а точнее, адрес строки символов. Другим параметром может быть длина выводимой строки, но от этого значения можно избавиться несколькими способами (вообще же везде, где это только возможно, нужно стараться избегать лишних параметров). Например, можно вставить байт длины строки непосредственно перед ее началом, но это не слишком удобно. Более распространенный способ - ограничить строку, вставив в ее конец какой-нибудь конкретный байт, служащий маркером конца текста. Обычно для этих целей используется байт 0 и такой формат строки называют ASCIIZ-форматом. Существуют и другие способы задания строк, но о них мы поговорим чуть позже.

Используя при выводе строк способом «печатающего квадрата» ASCIIZ-строки, перед вызовом процедуры будем заносить их адреса в регистровую пару HL. Алгоритм самой подпрограммы может быть таким: считываем из строки очередной символ и если он равен нулю, заканчиваем вывод; печатаем символ; следом выводим пробел с синим фоном и возвращаем позицию печати на один символ назад; получаем короткий звук, имитирующий стук пишущей машинки и делаем небольшую задержку; возвращаемся на начало цикла. Кроме этого, перед завершением программы нужно стереть с экрана синий квадрат, для чего достаточно просто вывести пробел.


Программа, выполняющая все эти действия будет выглядеть примерно таким образом:

T_TAPE LD A,(HL) ;читаем из строки очередной символ AND A ;проверяем на 0 JR NZ,TTAPE1 ;если нет, продолжаем LD A," " ;стираем изображение RST 16 ; «печатающего квадрата» RET ;выход TTAPE1 RST 16 ;печатаем считанный символ INC HL ;перемещаем указатель текущего ; символа на следующий PUSH HL ;сохраняем в стеке значение указателя LD DE,TXTCUR ;формируем изображение LD BC,4 ; «печатающего квадрата» CALL 8252 CALL 3405 ;сбрасываем временные атрибуты XOR A ;в аккумуляторе 0 OUT (254),A ;«выключаем» динамик ; ------------------ ; Задержка (PAUSE 5) LD BC,5 ;пауза 5/50 секунды CALL 7997 ;вызов подпрограммы PAUSE ; ------------------ LD A,%00010000 ;устанавливаем 4 бит аккумулятора OUT (254),A ;«включаем» динамик POP HL ;восстанавливаем значение указателя JR T_TAPE ;переход на начало цикла TXTCUR DEFB 17,1," ",8

Как вы видите, в этой небольшой процедуре появилось несколько новых команд, поэтому помимо кратких комментариев дадим еще и более основательные пояснения.

Команда LD A,(HL) в принципе очень похожа на известную вам LD A,(Address), но отличается от нее тем, что в аккумулятор загружается значение не из какой-то конкретной ячейки памяти, а из той, на которую указывает регистровая пара HL. Например, если в HL записать число 16384, то в регистр A загрузится содержимое ячейки, находящейся по адресу 16384, то есть выполнится команда LD A,(16384). Эту инструкцию очень удобно применять в тех случаях, когда значение адреса для чтения или записи заранее неизвестно и может быть любой переменной величиной. Попутно скажем, что существует и обратная команда, то есть LD (HL),A, которая записывает содержимое аккумулятора по адресу, указанному в HL.

Существенное преимущество этого типа команд состоит еще и в том, что кроме аккумулятора таким способом можно пересылать и содержимое любого другого регистра данных, в том числе и регистров H и L, например, LD L,(HL) или LD (HL),H. И даже более того, по адресу, указанному в HL, можно записывать не только содержимое регистров, но и непосредственные числовые значения, конечно, не превышающие 255, скажем, LD (HL),153. Что же касается аккумулятора, то для него имеется возможность адресации не только с помощью пары HL, но также и BC либо DE, и эти команды еще не раз появятся в нашей книге.



В приведенной процедуре встретились две логические команды AND A и XOR A, хотя в данном примере они применены и не совсем по назначению, а скорее для сокращения машинного кода. Первая из них выполняет то же действие, что и команда CP 0, а вторая заменяет инструкцию LD A,0 и такой способ записи среди программистов считается хорошим стилем. В самом деле, при объединении аккумулятора по принципу AND с самим собой, ноль может получиться лишь тогда, когда он имеет нулевое значение (кстати, с тем же успехом можно пользоваться инструкцией OR A). Весьма ценно и то, что при этом содержимое аккумулятора не изменяется, а команда воздействует только на флаги, и в частности, на флаг Z, который нам и нужно проверить. Другая команда, XOR A, обнулит содержимое аккумулятора при любом его изначальном значении. Ведь, как мы уже говорили, при объединении двух чисел по принципу XOR любой бит будет сброшен в 0, если соответствующие биты одинаковы в обоих числах. То есть при объединении по XOR двух одинаковых величин результат всегда будет нулевым. Запомните это обстоятельство, поскольку в дальнейшем мы всегда будем пользоваться описанными приемами. Единственный случай, когда команда LD A,0 незаменима, это если нужно сохранить значение флагового регистра для последующей проверки. Помните, что все логические команды воздействуют на флаги, а инструкции загрузки регистров - нет.

Вы, наверное, знаете, что для получения звука в Бейсике, кроме оператора BEEP, можно воспользоваться командой OUT, которая записывает число в указанный порт. Динамик в Spectrum'е подключен к порту 254, а за его «включение» или «выключение» ответственен 4-й бит посылаемого байта. Остальные биты несут другую нагрузку, а здесь нас интересуют еще биты 0, 1 и 2, которые, как уже было сказано в разделе главы 4, определяют цвет бордюра. При заданных в процедуре значениях аккумулятора 0 и 16 получится черный бордюр, но его легко изменить на любой другой цвет, просто прибавив к коду нужного цвета значение «бита для динамика» (точнее, код цвета объединяется со значением 4-го бита аккумулятора по принципу OR, для чего, кстати, логические команды в основном и используются). Быстрое чередование значения этого бита приводит к появлению звука. Более подробно о получении различных звуковых эффектов мы расскажем в .



В процедуре «печатающий квадрат» показано применение еще одной полезной подпрограммы ПЗУ, которая вызывается при интерпретации оператора PAUSE. Перед обращением к ней необходимо в пару BC поместить величину задержки, измеряемую в 50-х долях секунды - так же, как и в Бейсике. Если в BC поместить 0, то получится бесконечная пауза, прерываемая только при нажатии на любую клавишу (опять же, как в Бейсике).

Теперь приведем пример использования данной процедуры и напечатаем на экране таким способом какой-нибудь конкретный текст. Например:

ORG 60000 ENT $ CALL SETSCR ;подготавливаем экран LD A,22 ;помещаем текущую позицию печати в ; начало 5-й строки экрана (AT 5,0) RST 16 LD A,5 RST 16 XOR A RST 16 LD HL,TEXT ;указываем адрес ASCIIZ-строки с текстом CALL T_TAPE ;выводим методом телетайпа RET TEXT DEFM "COMPUTER Sinclair ZX-Spectrum" DEFB 0 ;подпрограмма из раздела «Подготовка ; экрана к работе из ассемблера» главы 4


Содержание раздела