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

Материал из Emuverse
Данный материал защищён авторскими правами!

Использование материала заявлено как добросовестное, исключительно для образовательных некоммерческих целей.

Автор: А. Евдокимов, А. Капульцевич, И. Капульцевич, ИД «Питер»

ГЛАВА ШЕСТАЯ,
в которой демонстрируются возможности ассемблера на примере создания многокадровых заставок

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

СОЗДАНИЕ ПРОСТЕЙШИХ ЗВУКОВ

Вероятно, вы давно и с большим нетерпением ждете того момента, когда мы наконец соблаговолим поведать о том, как у компьютера «прорезать голос». До сих пор мы применяли некие акустические суррогаты, однако терпеть и дальше такое «безобразие» уже нет никакой возможности. Более детально и всесторонне вопросы создания мелодий и шумовых эффектов (в том числе и для ZX Spectrum 128) будут рассмотрены в десятой главе. Пока же мы предлагаем не очень сложный способ получения звуков, основанный на использовании подпрограммы ПЗУ, к которой в конечном итоге обращается интерпретатор при выполнении оператора BEEP. Эта подпрограмма располагается по адресу 949 и требует указания в регистровых парах DE и HL соответственно длительности и высоты звука. Методику расчета этих параметров для получения конкретных музыкальных звуков мы объясним позже, а сейчас нас интересуют простые звуковые эффекты, не имеющие конкретной высоты, поэтому данные значения можно подбирать просто на слух. Пожалуй, самая простая программа, которую можно сравнить с резонатором, настроенным на определенную частоту, выглядит следующим образом:

       LD    DE,40
       LD    HL,500
       CALL  949
       RET

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

Рассмотрим программу, основанную на приведенном выше примере, генерирующую звуки длительностью порядка 1-3 секунд. Она отличается тем, что высота тона изменяется в цикле, а продолжительность звучания можно регулировать не только изменением DE, но и занося различные значения в регистр B. Мы предполагаем использовать ее в дальнейшем, для чего присвоим ей собственное имя:

SND    LD    B,10        ;количество циклов
       LD    HL,300      ;начальная частота звучания
       LD    DE,8        ;длительность звука
SND1   PUSH  BC
       PUSH  DE
       PUSH  HL
       CALL  949
       POP   HL
       POP   DE
       POP   BC
       DEC   HL          ;или INC HL
       DJNZ  SND1
       RET

После запуска вы услышите звук, увеличивающийся по высоте, но если где-то в игре потребуется, чтобы с течением времени частота уменьшалась, достаточно команду DEC HL заменить на INC HL. Для извлечения более коротких звуков (0.1-0.3 сек.) достаточно задать в SND другие значения регистров:

SND    LD    B,90
       LD    DE,2
       LD    HL,150
SND1   .........

Располагая этими программками, попробуйте поэкспериментировать, встроив, например, два резонатора в один цикл, либо, организовав несколько (в том числе и вложенных) циклов с одинаковыми или разными резонаторами. Уверены, что это занятие приведет вас ко многим «открытиям» и доставит немало приятных минут.

МАНИПУЛЯЦИИ С БУКВАМИ

Иметь в своем распоряжении множество наборов символов различных начертаний, подобных тем, которые мы привели в четвертой главе, и использовать их для печати текстов заставки и других частей программы — это замечательно, но не всегда целесообразно. Любой фонт занимает, во-первых, какую-то часть памяти компьютера, а во-вторых — определенное место на ленте и требует дополнительного времени при загрузке игровой программы. Но ведь у вас под рукой всегда имеется стандартный набор символов, и возникает вопрос, а нельзя ли его как-то преобразовать программным путем, чтобы получить видоизмененные буквы и цифры. В предыдущей главе мы описали процедуру BOLD, применение которой приводит к утолщению символов, и этот прием довольно часто используется даже в фирменных играх. Рассмотрим теперь два способа преобразования символов, первый из которых увеличивает их высоту в два раза, а второй делает их в два раза шире.

Печать символов высотой в два знакоместа

Чтобы лучше понять, как работает ассемблерная программа, удваивающая высоту символов, приведем сначала два рисунка. На первом из них (рис. 6.1, а) показан исходный символ стандартного набора, а на другом (рис. 6.1, б) — то, что должно получиться после преобразования. Нетрудно заметить, что все байты (начиная с верхнего) левого символа, повторяются дважды на правом рисунке. Таким образом, заставив программу проделать эту операцию с каждым из восьми байтов, мы получим увеличение высоты символа ровно в два раза. Если вам очень захочется, то можно таким способом «вырастить» буквы в три, четыре и более раз, но над программой тогда придется немного поработать. Заметим, что символы высотой, скажем, в шесть знакомест выглядят на экране весьма эффектно.

а б
Рис. 6.1. Действие процедуры BIGSYM

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

       ORG   60000
       ENT   $
       LD    A,(23296)   ;загружаем код печатаемого символа
BIGSYM LD    L,A
       LD    H,0         ;переписываем этот код в HL
       ADD   HL,HL       ;умножаем код на 8
       ADD   HL,HL
       ADD   HL,HL
       LD    DE,(23606)  ;в DE загружаем адрес начала
                         ; текущего фонта
       ADD   HL,DE
       EX    DE,HL
       LD    HL,(23684)  ;в HL помещаем адрес в видеобуфере,
                         ; по которому будет выводиться первый
                         ; байт измененного символа
       LD    B,4
       PUSH  HL          ;делаем две копии HL, чтобы
       PUSH  HL          ; использовать их во втором цикле
BIGS1  LD    A,(DE)      ;считываем байт из фонта
       LD    (HL),A      ;переписываем в видеобуфер
       INC   H
       LD    (HL),A      ;еще раз - ниже
       INC   H
       INC   DE          ;переходим к следующему байту
       DJNZ  BIGS1
       POP   HL          ;восстанавливаем HL
       LD    BC,32       ;вычисляем адрес первого байта
       ADD   HL,BC       ; второго знакоместа
       LD    B,4
BIGS2  LD    A,(DE)      ;аналогично циклу BIGS1
       LD    (HL),A
       INC   H
       LD    (HL),A
       INC   H
       INC   DE
       DJNZ  BIGS2
       POP   HL          ;восстанавливаем HL
       INC   HL          ;увеличиваем HL на 1 для подготовки
                         ; печати следующего символа
       LD    (23684),HL  ;записываем в системную переменную
                         ; адрес следующего знакоместа экрана
       RET

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

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

10 PRINT AT 5,5;
20 LET a$="Starting program BIGSYM!"
30 FOR n=1 TO LEN a$
40 POKE 23296,CODE a$(n)
50 RANDOMIZE USR 60000
60 NEXT n

Насладившись созерцанием новых букв, возможно, вы захотите воспользоваться процедурой BIGSYM в собственной программе. В последнем разделе этой главы, где приводится программа и описание многокадровой заставки, можно посмотреть, как это лучше сделать. Пока же можем сказать следующее: перед вызовом процедуры из ассемблера необходимо установить позицию печати в нужное место экрана, воспользовавшись командой RST 16, а в аккумулятор занести код выводимого символа. В этом случае надобность в команде LD A,(23296), предваряющей в приведенном примере процедуру BIGSYM, отпадает.

Печать символов шириной в два знакоместа

По аналогии с предыдущим параграфом, начнем с двух небольших рисунков, из которых можно легко понять, как осуществляется требуемое преобразование. Слева (рис. 6.2, а) приведен исходный символ, а справа (рис. 6.2, б) — то, что получится после работы программы DBLSYM. Сравнивая два этих рисунка, можно заметить, что правый символ получается в результате повторения каждого вертикального ряда битов, взятого из левого символа (напомним, что действие программы BIGSYM приводит к повтору горизонтальных рядов битов исходного символа).

а б
Рис. 6.2. Действие процедуры DBLSYM

Поскольку работа этой процедуры не столь очевидна, как может показаться с первого взгляда, то для лучшего ее понимания мы приводим небольшую схемку (рис. 6.3), отражающую пути перемещений битов при многократном выполнении команд сдвигов. Напоминаем, что эти команды вместе с аналогичными схемами приведены в разделе «Организация циклов в ассемблере» пятой главы.

Рис. 6.3. Перемещения битов
       ORG   60000
       ENT   $
       LD    A,(23296)   ;начало - как в процедуре BIGSYM
DBLSYM LD    L,A
       LD    H,0
       ADD   HL,HL
       ADD   HL,HL
       ADD   HL,HL
       LD    DE,(23606)
       ADD   HL,DE
       EX    DE,HL
       LD    HL,(23684)
       LD    B,8         ;количество повторений внешнего цикла
       PUSH  HL
; Во внешнем цикле по очереди берутся 8 байтов исходного символа
;  для преобразования во внутреннем цикле
DBLS1  PUSH  BC          ;сохраняем BC для внешнего цикла
       PUSH  DE          ;сохраняем текущий адрес
                         ; в символьном наборе
       LD    A,(DE)
       LD    DE,0        ;подготавливаем пару DE для последующего
                         ; формирования в ней расширенного
                         ; байта символа
       LD    B,8         ;количество повторений внутреннего цикла
                         ; (равное числу бит в одном байте)
; Во внутреннем цикле каждый бит исходного символа дважды копируется
; в регистровую пару DE, где в конце концов получаем расширенный
; в два раза байт (рис. 6.3)
DBLS2  RLCA
       PUSH  AF
       RL    E
       RL    D
       POP   AF
       RL    E
       RL    D
       DJNZ  DBLS2
       LD    (HL),D      ;вывод преобразованных байтов из
       INC   HL          ; пары DE на экран
       LD    (HL),E
       DEC   HL
       POP   DE          ;восстанавливаем значение DE
       INC   H
       INC   DE          ;переходим к обработке следующего
                         ; байта из набора символов
       POP   BC          ;восстанавливаем BC для внешнего цикла
       DJNZ  DBLS1
       POP   HL
       INC   HL          ;переходим к следующему
       INC   HL          ; знакоместу экрана
       LD    (23684),HL  ;записываем в системную переменную
                         ; адрес следующего символа
       RET

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

Теперь в вашем распоряжении имеются три программы, в той или иной мере изменяющие форму стандартных символов и, чтобы лучше понять их работу, можно порекомендовать слегка «поиздеваться» над символами, создавая комбинированные программы. Например, часть символа увеличить по высоте, а оставшуюся часть — по ширине, или BIGSYM и DBLSYM соединить с программой BOLD. Надо сказать, что последний вариант дает совсем недурные результаты, в чем вы сможете убедиться, дойдя до последнего раздела этой главы.

КАК СДЕЛАТЬ НАЗВАНИЕ ИГРЫ

Думается, вы согласитесь, какую важную роль играет в заставке изображение названия игры и все, что его окружает. Для доказательства достаточно загрузить какую-нибудь фирменную игру и убедиться, что так оно и есть. Если вы достаточно внимательно изучили предыдущие страницы, то уже должны быть в состоянии изменить стандартный набор символов и сделать вполне приличное название. Но все же хотелось бы научиться создавать что-то оригинальное и, кроме того, независимое от стандартного фонта. Можно, конечно, загрузить Art Studio или The Artist II и попробовать нарисовать название там, но кто-то после первых же штрихов сделает простой вывод: пожалуй, это не для меня. Учитывая все сказанное, мы хотим предложить способ получения названий (и не только их), в основе которого лежит хорошо знакомый вам оператор DRAW.

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

  10 READ y: IF y=-1 THEN GO TO 100
  20 READ x,hgt,len,ink,dx
  30 LET y=113-y: LET x=x+40
  40 INK ink
  50 FOR i=1 TO hgt
  60 PLOT x,y: DRAW len,0
  70 PLOT x,y+1: DRAW len,0
  80 LET y=y-4: LET x=x+dx: NEXT i
  90 GO TO 10
 100 STOP
1000 REM --- Буква «Н» ---
1010 DATA 0,0,12,6,2,0
1020 DATA 20,6,2,14,2,0
1030 DATA 0,20,12,10,2,0
1040 REM --- Буква «А» ---
1050 DATA 0,46,12,4,1,-1
1060 DATA 0,46,12,10,1,1
1070 DATA 24,42,2,16,1,0
1080 REM --- Буква «Р» ---
1090 DATA 0,72,12,10,3,0
1100 DATA 0,82,2,13,3,0
1110 DATA 24,82,2,13,3,0
1120 DATA 4,96,1,2,3,0
1130 DATA 24,96,1,2,3,0
1140 DATA 8,92,4,8,3,0
1150 REM --- Буква «Д» ---
1160 DATA 0,112,10,4,6,-1
1170 DATA 0,112,10,10,6,1
1180 DATA 40,100,2,34,6,0
1190 REM --- Буква «Ы» ---
1200 DATA 0,139,12,10,4,0
1210 DATA 16,150,2,13,4,0
1220 DATA 40,150,2,13,4,0
1230 DATA 20,163,1,2,4,0
1240 DATA 40,163,1,2,4,0
1250 DATA 24,160,4,8,4,0
1260 DATA 0,167,4,8,4,0
1270 DATA 16,169,1,6,4,0
1280 DATA 20,171,6,4,4,0
1290 DATA 44,169,1,6,4,0
1300 DATA -1

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

Рис. 6.4. Название для игры НАРДЫ

Вот такая симпатичная надпись, в которой каждая буква составлена из отрезков параллельных двойных линий, нарисованных DRAW'ами. В высоту укладывается двенадцать таких линий плюс промежутки между ними. Точно такие любой из вас сможет легко начертить по линейке на листе бумаги, измерить их длины и координаты, после чего ввести в программу свои вычисления в виде блока данных. Посмотрим, как это делается. Прежде всего разберемся со структурой строки блока данных, поскольку с подобным построением раньше мы еще не встречались, а именно оно все и определяет. Строка списка DATA содержит шесть элементов, каждому из которых в программе соответствует определенная переменная, а именно:

  • y — вертикальная координата начала линии (в пикселях);
  • x — горизонтальная координата начала линии (опять же в пикселях);
  • hgt — высота буквы (количество двойных линий плюс промежутки между ними);
  • len — длина проводимой горизонтальной линии (снова в пикселях);
  • ink — цвет линии (0…7);
  • dx — приращение линии (еще раз в пикселях).

Работу программы лучше всего показать на примере какой-нибудь буквы, скажем, «Д» (рис. 6.5, а), при построении которой участвуют все эти переменные (правда, некоторые величины в списках DATA равны нулю).

Рис. 6.5. Построение буквы «Д»
  • 10, 20 — в этих строках порция данных переписывается из строки 1160 в переменные, после чего они принимают значения: y=0, x=112, hgt=10, len=4, ink=6 и dx=-1.
  • 30 — центровка названия.
  • 40 — устанавливается цвет линий (желтый).
  • 50 — начало цикла рисования фрагмента буквы.
  • 60, 70 — рисуются подряд две горизонтальные линии (длиной по len=4 пикселя).
  • 80 — координаты y и x изменяются, после чего в этом же цикле выводится следующая пара линий, и так — 10 раз (hgt), в результате получится левый элемент буквы (рис. 6.5, б).
  • 90 — после того, как hgt пар линий будет напечатано, переход на начало.

На следующем этапе построения буквы «Д» прочитывается вторая порция данных из строки 1170: y=0, x=112, hgt=10, len=10, ink=6, dx=1 и рисуется правая часть буквы (рис. 6.5, в), каждая линия которой составляет 10 пикселей. Наконец, на основании третьей порции (строка 1180): y=40, x=100, hgt=2, len=34, ink=6 и dx=0 ставятся две последние линии по 34 пикселя (рис. 6.5, г), завершающие построение буквы «Д».

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

       ORG   60000
       LD    HL,DATA
; Считывание параметров из блока данных
CICL1  LD    B,(HL)      ;Y-координата начала линии
       INC   B
       RET   Z           ;выход, если это конец блока данных
       LD    A,112       ;отсчитывать будем не снизу, а сверху.
                         ; Кроме того, сразу добавляем смещение
                         ; от верхнего края в 64 пикселя и
                         ; увеличиваем на 1, поскольку выполнялась
                         ; команда INC B (175-64+1=112)
       SUB   B
       LD    B,A
       INC   HL
       LD    A,(HL)      ;X-координата начала линии
       ADD   A,40        ;центрируем надпись
       LD    C,A
       INC   HL
       LD    D,(HL)      ;высота рисуемой части буквы
       INC   HL
       LD    E,(HL)      ; и ее ширина
       INC   HL
       LD    A,16        ;управляющий код для INK
       RST   16
       LD    A,(HL)      ;код цвета
       RST   16
       INC   HL
       LD    A,(HL)      ;наклон рисуемой части
       INC   HL
       PUSH  HL          ;сохраняем текущий адрес в блоке данных
; Цикл рисования части буквы, описанной очередной порцией данных
CICL2  PUSH  AF          ;сохраняем в стеке аккумулятор
       CALL  DRAW        ;чертим первую линию
       DEC   B           ;вторая линия будет на 1 пиксель ниже
       CALL  DRAW        ;чертим вторую линию
       DEC   B           ;переходим ниже
       DEC   B           ;пропускаем еще два ряда пикселей
       DEC   B
       POP   AF          ;восстанавливаем в аккумуляторе
                         ; параметр наклона
       PUSH  AF
       ADD   A,C         ;смещаем X-координату для
       LD    C,A         ; получения наклона
       POP   AF
       DEC   D           ;повторяем заданное параметром
       JR    NZ,CICL2    ; «высота» количество раз
       POP   HL          ;восстанавливаем адрес в блоке данных
       JR    CICL1       ;переходим на начало
; Подпрограмма рисования одной горизонтальной линии
DRAW   PUSH  BC          ;сохраняем нужные регистры
       PUSH  DE          ; в машинном стеке
       CALL  8933        ;ставим точку с координатами
                         ; из регистров B и C
       POP   DE          ;регистр E нам понадобится,
                         ; поэтому восстанавливаем
       PUSH  DE          ; и снова сохраняем
       EXX
       PUSH  HL          ;сохраняем регистровую пару HL'
       EXX
       LD    C,E         ;длина линии (горизонтальное смещение)
       LD    B,0         ;вертикальное смещение - 0 (линия
                         ; идет горизонтально)
       LD    DE,#101     ;положительное значение
                         ; для обоих смещений
       CALL  9402        ;рисуем линию
       EXX
       POP   HL          ;восстанавливаем регистры из стека
       EXX
       POP   DE
       POP   BC
       RET

; Блоки данных для рисования букв

; буква «Н»
DATA   DEFB  0,0,12,6,2,0
       DEFB  20,6,2,14,2,0
       DEFB  0,20,12,10,2,0
; буква «А»
       DEFB  0,46,12,4,1,-1
       DEFB  0,46,12,10,1,1
       DEFB  24,42,2,16,1,0
; буква «Р»
       DEFB  0,72,12,10,3,0
       DEFB  0,82,2,13,3,0
       DEFB  24,82,2,13,3,0
       DEFB  4,96,1,2,3,0
       DEFB  24,96,1,2,3,0
       DEFB  8,92,4,8,3,0
; буква «Д»
       DEFB  0,112,10,4,6,-1
       DEFB  0,112,10,10,6,1
       DEFB  40,100,2,34,6,0
; буква «Ы»
       DEFB  0,139,12,10,4,0
       DEFB  16,150,2,13,4,0
       DEFB  40,150,2,13,4,0
       DEFB  20,163,1,2,4,0
       DEFB  40,163,1,2,4,0
       DEFB  24,160,4,8,4,0
       DEFB  0,167,4,8,4,0
       DEFB  16,169,1,6,4,0
       DEFB  20,171,6,4,4,0
       DEFB  44,169,1,6,4,0
       DEFB  -1           ;указатель конца блока данных

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

СКРОЛЛИНГИ ОКОН

Плавное перемещение изображений по экрану в разных направлениях можно достаточно часто увидеть в компьютерных играх. Пожалуй, легче сказать, где оно не используется, чем наоборот, поэтому решение такой задачи представляется нам достаточно важным. Мы уже показали, как формировать самые разные окна, а теперь попробуем написать несколько процедур для их плавного перемещения (скроллинга) во всех четырех направлениях. Затем на примере двух программ покажем, как применять такие процедуры для решения конкретных задач. Учтите, что каждая из приведенных ниже программ скроллинга сдвигает изображение в окне только на один пиксель. Следовательно, если вам потребуется переместить изображение в какую-то сторону, скажем, на 50 пикселей, то соответствующую процедуру следует выполнить несколько раз подряд в цикле, например:

       LD    B,50        ;количество сдвигов
LOOP   PUSH  BC          ;сохраняем содержимое регистра B
       ..........        ;процедура скроллинга
       POP   BC          ;восстанавливаем регистр B
       DJNZ  LOOP
       RET

До того, как мы приступим к детальному описанию процедуры скроллинга окна вверх, желательно рассмотреть все команды и подпрограммы ПЗУ, которые встречаются здесь впервые. Таких наберется всего две: чрезвычайно полезная команда LDIR, перемещающая блок памяти с инкрементом (т. е. с увеличением содержимого регистров, в которых записаны адреса пересылок) и подпрограмма ПЗУ, расположенная по адресу 8880. Рассмотрим их в том порядке, как они встречаются в программе.

Процедура 8880 вычисляет адрес байта в видеобуфере по координатам точки, заданным в пикселях. Началом отсчета считается левый верхний угол экрана. Таким образом, входными данными к подпрограмме являются:

  • вертикальная координата, помещаемая в аккумулятор;
  • горизонтальная координата, помещаемая в регистр C,

а выходными:

  • в регистровой паре HL возвращается вычисленный адрес байта видеобуфера;
  • в регистр A помещается значение от 0 до 7, численно равное величине смещения заданной точки в пикселях от левого края того знакоместа, для которого рассчитывается адрес.

Более подробно рассмотрим действие команды LDIR. С ее помощью группа байтов, расположенных в сторону увеличения адресов от ячейки, на которую указывает HL, пересылается в область памяти, адресуемую регистровой парой DE. Количество передаваемых байтов определяется регистровой парой BC. Чтобы почувствовать всю прелесть этой команды и оценить ее по достоинству, приведем текст цикла, выполняющего то же, что и LDIR.

MET    LD    A,(HL)
       LD    (DE),A
       INC   HL
       INC   DE
       DEC   BC
       LD    A,B
       OR    C
       JR    NZ,MET
       RET

Глядя на этот фрагмент, можно заметить очевидные достоинства команды LDIR: в цикле изменяется регистр A, а в LDIR он не затрагивается, кроме того, программа занимает 8 строк текста вместо одной и работает примерно в два с половиной раза медленнее.

Если в программу ввести исходные данные, то может получиться эффект, который мы наблюдаем в некоторых играх, например, в SOKOBAN’е. Суть его состоит в том, что из ПЗУ с адреса 0 переписываются 6144 байта в область экранной памяти, начиная с адреса 16384. Если это действие повторять в цикле, то создастся впечатление бегущей по экрану ряби:

       LD    BC,6144
       LD    HL,0
       LD    DE,16384
       LD    A,255
MET    PUSH  BC
       PUSH  DE
       PUSH  HL
       LDIR
       POP   HL
       POP   DE
       POP   BC
       INC   HL
       DEC   A
       JR    NZ,MET
       RET

Кроме рассмотренной команды LDIR в ассемблерных программах довольно широко используются и другие команды пересылок байтов:

  • LDDR — перемещение блока памяти с декрементом. Ее действие аналогично команде LDIR, только пересылается группа байтов, расположенных в сторону уменьшения адресов от ячейки, на которую указывает HL. Количество передаваемых байтов также определяется в BC.
  • LDI — пересылка содержимого одной ячейки памяти с инкрементом. Байт из ячейки, адресуемой регистровой парой HL, переносится в ячейку, адресуемую парой DE; содержимое HL и DE увеличивается на 1, а BC уменьшается на 1. Если в результате выполнения команды BC=0, то флаг P/V сбрасывается, в противном случае P/V=1.
  • LDD — пересылка содержимого одной ячейки памяти с декрементом. Действие аналогично команде LDI, только содержимое регистровых пар HL и DE уменьшается на 1.

Покончив с теорией, можно заняться более приятным делом и написать процедуры для скроллинга окон для всех направлений. Начнем со смещения окна вверх. Перед вызовом этой подпрограммы нужно определить уже известные по предыдущим примерам переменные ROW, COL, HGT и LEN, записав в них координаты и размеры окна. Предварительно не помешает убедиться, что окно не выходит за пределы экрана, так как в целях упрощения программы подобные проверки в ней не выполняются. Добавим, что это в равной мере относится и к другим процедурам скроллингов.

SCR_UP LD    A,(COL)
       LD    C,A
       LD    A,(HGT)
       LD    B,A
       LD    A,(ROW)
; Значения из переменных ROW, COL и HGT умножаем на 8,
;  то есть переводим знакоместа в пиксели
       SLA   A
       SLA   A
       SLA   A
       SLA   B
       SLA   B
       SLA   B
       DEC   B           ;потому что один ряд пикселей просто
                         ; заполняется нулями
       SLA   C
       SLA   C
       SLA   C
       PUSH  AF
       PUSH  BC
       CALL  8880        ;вычисляем адрес верхнего левого угла окна
       POP   BC
       POP   AF
SCRUP1 INC   A           ;следующий ряд пикселей
       PUSH  AF
       PUSH  BC
       PUSH  HL
       CALL  8880        ;вычисляем адрес
       POP   DE
       PUSH  HL
       LD    A,(LEN)     ;пересылаем столько байт, сколько
                         ; умещается по ширине окна
       LD    C,A
       LD    B,0
       LDIR
       POP   HL
       POP   BC
       POP   AF
       DJNZ  SCRUP1
       LD    (HL),0      ;в последний ряд пикселей записываем нули
       LD    D,H
       LD    E,L
       INC   DE
       LD    A,(LEN)     ;по ширине окна,
       DEC   A           ; минус 1
       RET   Z           ;выходим, если только одно знакоместо
       LD    C,A
       LD    B,0
       LDIR              ;иначе обнуляем и все остальные байты ряда
       RET

Обратите особое внимание на последние строки процедуры, где в нижний ряд пикселей записываются нулевые байты. Этот прием весьма распространен и применяется для заполнения любой области памяти произвольным значением. Чтобы понять идею, нужно хорошо представлять, как работает команда LDIR. Сначала в первый байт массива, адресуемый парой HL, заносится какой-то определенный байт, в DE переписывается значение из HL и увеличивается на 1, в BC задается уменьшенный на единицу размер заполняемого массива, а дальше с выполнением команды LDIR происходит следующее. Байт из первого адреса (HL) переписывается во второй (DE), затем DE и HL увеличиваются, то есть HL будет указывать на второй байт, а DE — на третий. На следующем круге число из второго адреса пересылается в третий, но после первого выполнения цикла второй байт уже содержит ту же величину, что и первый, поэтому второй и третий байты к этому моменту станут равны первому. И так далее, до заполнения всего массива.

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

Вернемся к рассмотрению подпрограмм скроллингов окон. Процедура сдвига окна вниз во многом похожа на предыдущую:

SCR_DN LD    A,(COL)
       LD    C,A
       LD    A,(HGT)
       LD    B,A
       LD    A,(ROW)
       ADD   A,B         ;начинаем перемещать изображение не
                         ; сверху, как в SCR_UP, а снизу
       SLA   A
       SLA   A
       SLA   A
       DEC   A
       SLA   B
       SLA   B
       SLA   B
       DEC   B
       SLA   C
       SLA   C
       SLA   C
       PUSH  AF
       PUSH  BC
       CALL  8880
       POP   BC
       POP   AF
SCRDN1 DEC   A           ;следующий ряд пикселей (идем вверх)
       PUSH  AF
       PUSH  BC
       PUSH  HL
       CALL  8880
       POP   DE
       PUSH  HL
       LD    A,(LEN)
       LD    C,A
       LD    B,0
       LDIR
       POP   HL
       POP   BC
       POP   AF
       DJNZ  SCRDN1
       LD    (HL),0
       LD    D,H
       LD    E,L
       INC   DE
       LD    A,(LEN)
       DEC   A
       RET   Z
       LD    C,A
       LD    B,0
       LDIR
       RET

Теперь приведем подпрограмму, выполняющую скроллинг окна влево. Она уже совсем не похожа на предшествующие, но в принципе повторяет известную вам процедуру SCRLIN, описанную в разделе «Бегущая строка» предыдущей главы. Но это и понятно: ведь там мы также скроллировали окно, только оно имело фиксированные размеры в одно знакоместо высотой и 32 — шириной. Здесь же мы приводим универсальную процедуру, но и с ее помощью можно получить тот же эффект.

SCR_LF LD    A,(HGT)     ;количество повторений такое же,
       LD    B,A         ; сколько строк занимает окно
       LD    A,(ROW)     ;номер верхней строки
SCRLF1 PUSH  AF          ;дальше все очень похоже на процедуру SCRLIN
       PUSH  BC
       CALL  3742
       LD    A,(COL)
       LD    B,A
       LD    A,(LEN)
       DEC   A
       ADD   A,B
       ADD   A,L
       LD    L,A
       LD    B,8
SCRLF2 PUSH  HL
       LD    A,(LEN)
       AND   A
SCRLF3 RL    (HL)
       DEC   HL
       DEC   A
       JR    NZ,SCRLF3
       POP   HL
       INC   H
       DJNZ  SCRLF2
       POP   BC
       POP   AF
       INC   A
       DJNZ  SCRLF1
       RET

И наконец, для полного комплекта, напишем соответствующую подпрограмму, выполняющую скроллинг окна вправо:

SCR_RT LD    A,(HGT)
       LD    B,A
       LD    A,(ROW)
SCRRT1 PUSH  AF
       PUSH  BC
       CALL  3742
       LD    A,(COL)
       ADD   A,L
       LD    L,A
       LD    B,8
SCRRT2 PUSH  HL
       LD    A,(LEN)
       AND   A
SCRRT3 RR    (HL)
       INC   HL
       DEC   A
       JR    NZ,SCRRT3
       POP   HL
       INC   H
       DJNZ  SCRRT2
       POP   BC
       POP   AF
       INC   A
       DJNZ  SCRRT1
       RET

А сейчас рассмотрим программу, в которой демонстрируются возможности вертикального скроллинга вверх. После запуска вы увидите две быстро сменяющие друг друга картинки: сначала весь экран заполняется красивым орнаментом (фактурой), а затем средняя его часть стирается процедурой очистки окна. После этого строка за строкой снизу вверх начнет медленно перемещаться текст с правилами игры (рис. 6.6), который обычно вызывается из меню как один из кадров заставки.

Рис. 6.6. Правила игры

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

       ORG   60000
       ENT   $
; Задание адресов новых фонтов и постоянных атрибутов экрана
LATF   EQU   64000-256
RUSF   EQU   LATF+768
       LD    A,6
       LD    (23693),A
       LD    A,0
       CALL  8859
       CALL  3435
       LD    A,2
       CALL  5633
; Ввод символа UDG для рисования фактуры
       LD    HL,UDG
       LD    (23675),HL
; Заполнение всего экрана фактурой
       CALL  DESK
; Начало основной программы
       LD    HL,RUSF
       LD    (23606),HL
NEW    LD    HL,TEXT
CYCLE1 CALL  PRINT
       LD    B,10
CYCLE2 PUSH  HL
       PUSH  BC
       CALL  SCR_UP
       CALL  PAUSE
       POP   BC
       POP   HL
       JR    Z,EXIT
       DJNZ  CYCLE2
       LD    A,(HL)
       AND   A
       JR    NZ,CYCLE1
       LD    B,80        ;расстояние между текстами
CYCLE3 PUSH  BC
       CALL  SCR_UP
       CALL  PAUSE
       POP   BC
       JR    Z,EXIT
       DJNZ  CYCLE3
       JR    NEW
; Восстановление стандартного набора символов и выход
EXIT   LD    HL,15360
       LD    (23606),HL
       RET
; Для того, чтобы строки выводимого текста можно было успеть прочитать,
;  в цикл вставлена подпрограмма задержки, а нажав клавишу Space,
;  можно в любой момент завершить работу программы.
PAUSE  LD    BC,6
       CALL  7997
       LD    A,(23560)
       CP    " "
       RET
; Подпрограмма печати строки текста
PRINT  PUSH  BC
       LD    DE,PAR
       LD    BC,5
       CALL  8252
       LD    B,24        ;число символов в строке
PR_CI  LD    A,(HL)
       RST   16
       INC   HL
       DJNZ  PR_CI
       POP   BC
       RET
PAR    DEFB  22,15,4,16,0
; Подпрограмма заполнения экрана фактурой
DESK   LD    BC,704
       LD    A,#14
       RST   16
       LD    A,1
       RST   16
DESK1  LD    A,144
       RST   16
       DEC   BC
       LD    A,B
       OR    C
       JR    NZ,DESK1
       LD    A,#14
       RST   16
       LD    A,0
       RST   16
; Создание в фактуре окна для вывода текста
       CALL  CLSV
       CALL  SETV
       RET
; Подпрограммы
CLSV   .........
SETV   .........
SCR_UP .........
; Данные для окна с текстом
COL1   DEFB  4
ROW1   DEFB  4
LEN1   DEFB  24
HGT1   DEFB  12
ATTR   DEFB  %01000010
; Данные для окна в фактуре
COL    DEFB  3
ROW    DEFB  3
LEN    DEFB  26
HGT    DEFB  13
; Данные для выводимого в окно текста
TEXT   DEFM  "••••P•I•R•A•M•I•D•A•••••"
       DEFM  "••••••••••••••••••••••••"
       DEFM  "Celx igry sostoit w tom,"
       DEFM  "~toby••perwym••postawitx"
       DEFM  "swoi••fi{ki••na••wer{inu"
       DEFM  "piramidy. Dlq |togo nuv-"
       DEFM  "no ispolxzowatx gorizon-"
       DEFM  "talxnye••i••wertikalxnye"
       DEFM  "peredwiveniq fi{ek,krome"
       DEFM  "togo,velatelxno wospolx-"
       DEFM  "zowatxsq••dopolnitelxny-"
       DEFM  "mi prodwiveniqmi,•••sutx"
       DEFM  "kotoryh••sostoit••w tom,"
       DEFM  "~toby sostawitx fi{ki••w"
       DEFM  "wide treugolxnika, posle"
       DEFM  "~ego sdelatx hod werhnej"
; При желании текст можно продолжить
       DEFB  0
; Данные для фактуры вокруг окна
UDG    DEFB  248,116,34,71,143,7,34,113

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

Рис. 6.7. Раскрытие ворот замка скроллингом окон

А теперь перейдем непосредственно к программе.

       ORG   60000
       ENT   $
; Подготовка экрана
       LD    A,7
       LD    (23693),A
       XOR   A
       CALL  8859
       CALL  3435
       LD    A,2
       CALL  5633
; Назначение нового адреса символов UDG
       LD    HL,UDG
       LD    (23675),HL
; Вывод на экран изображения крепостной стены с воротами
       CALL  SCRN
       LD    BC,0
       CALL  7997
; Основная часть программы, в которой створки ворот
;  со стуком разъезжаются в разные стороны
       LD    B,64
MAIN   PUSH  BC
       CALL  SCR_LF
       LD    A,0
       OUT   (254),A
       CALL  SCR_RT
       LD    A,16
       OUT   (254),A
       LD    BC,6
       CALL  7997
       POP   BC
       DJNZ  MAIN
       RET
; Формирование изображения крепостной стены с воротами:
; Изображение стены
SCRN   LD    DE,D_WALL
       LD    BC,7
       CALL  8252
       LD    BC,384
SCRN1  LD    A,147
       RST   16
       DEC   BC
       LD    A,B
       OR    C
       JR    NZ,SCRN1
; Зеленая трава
       LD    DE,D_GRAS
       LD    BC,5
       CALL  8252
       LD    B,32
SCRN2  LD    A," "
       RST   16
       DJNZ  SCRN2
; Зубцы на стене
       LD    BC,#400     ;AT 4,0
       CALL  PR_AT
       LD    B,16
SCRN3  LD    DE,D_BATT
       PUSH  BC
       LD    BC,10
       CALL  8252
       POP   BC
       DJNZ  SCRN3
; Ворота
       LD    BC,#908     ;AT 9,8
       CALL  PR_AT
       LD    B,16
SCRN4  LD    A," "
       RST   16
       DJNZ  SCRN4
; Бойницы
       LD    H,4
SCRN5  LD    A,H
       ADD   A,7
       LD    B,A
       LD    C,4
       CALL  PR_AT
       LD    A," "
       RST   16
       LD    C,27
       CALL  PR_AT
       LD    A," "
       RST   16
       DEC   H
       JR    NZ,SCRN5
; Штыри решетки
       LD    A,16
       RST   16
       LD    A,5
       RST   16
       LD    B,10        ;Y
       LD    H,8
SCRN6  LD    L,16
       LD    C,8         ;X
SCRN7  CALL  PR_AT
       LD    A,145
       RST   16
       INC   C
       DEC   L
       JR    NZ,SCRN7
       INC   B
       DEC   H
       JR    NZ,SCRN6
; Пики решетки
       LD    BC,#A08     ;AT 10,8
       CALL  PR_AT
       LD    B,16
SCRN8  LD    A,144
       RST   16
       DJNZ  SCRN8
; Узор решетки
       LD    L,2
       LD    B,13
SCRN9  LD    C,8
       CALL  PR_AT
       LD    B,16
SCRN10 LD    A,146
       RST   16
       DJNZ  SCRN10
       LD    B,16
       DEC   L
       JR    NZ,SCRN9
       RET
; Подпрограмма позиционирования вывода спрайтов
PR_AT  LD    A,22
       RST   16
       LD    A,B
       RST   16
       LD    A,C
       RST   16
       RET
; Подпрограммы скроллинга окон
SCR_LF ........
SCR_RT ........
; Данные для левого окна
COL    DEFB  8
ROW    DEFB  10
HGT    DEFB  8
LEN    DEFB  8
; Данные для правого окна
COL1   DEFB  16
ROW1   DEFB  10
LEN1   DEFB  8
HGT1   DEFB  8
; Данные для рисования крепостной стены и решетки
UDG    DEFB  34,34,34,119,34,34,34,34   ;пики   (144)
       DEFB  34,34,34,34,34,34,34,34    ;штыри  (145)
       DEFB  54,42,170,255,170,42,54,34 ;узор   (146)
       DEFB  255,2,2,2,255,32,32,32     ;кирпич (147)
; Данные позиционирования печати
D_BATT DEFB  17,2,16,7,147,147,17,0,32,32
D_WALL DEFB  22,6,0,17,2,16,7
D_GRAS DEFB  22,18,0,17,4

ВВОД ЭЛЕМЕНТА СЛУЧАЙНОСТИ

Любая игра потеряет всякий смысл, если действия компьютера можно будет предугадать на всех этапах развития сюжета. Чтобы придать персонажам видимость самостоятельности и непредсказуемости поведения, в игровых программах довольно широко используются так называемые случайные числа. Строго говоря, получить действительно случайные значения программным путем нет никакой возможности, вы можете лишь заставить компьютер вырабатывать более или менее длинную последовательность неповторяющихся величин, но в конце концов она все же начнет повторяться. Поэтому такие числа обычно называют псевдослучайными. В Бейсике для их получения используется функция RND, которая вырабатывает по определенному закону числа от 0 до 1 и далее они обычно преобразуются программными средствами в числа из заданного диапазона. В ассемблере работать с дробными величинами значительно сложнее, отчего программисты с этой целью редко прибегают к использованию подпрограмм ПЗУ, а пишут, как правило, свои аналогичные процедуры.

В качестве «случайных» чисел довольно часто используют последовательность кодов ПЗУ. Такой метод крайне прост и дает неплохую степень случайности в циклах. Если вы не забыли, именно такой метод мы применили в программе «растворения» символов, описанной в предыдущей главе. Сейчас же мы расскажем и о некоторых других способах получения псевдослучайных чисел.

Иногда «случайные» числа извлекают из системного регистра регенерации R. Поскольку его значение постоянно увеличивается после выполнения каждой команды микропроцессора, предугадать, что же он содержит в какой-то момент времени практически невозможно. Таким образом, простейший генератор случайных чисел может выглядеть так:

       LD    A,R

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

Кроме того, есть и еще один недостаток использования регистра регенерации. Значение его никогда не превышает 127. Иными словами, седьмой бит этого регистра обычно «сброшен» в 0, и, дойдя до значения 127, он вновь обнуляется.

Однако, справедливости ради, стоит заметить, что это относится лишь к тем программам, в которых регистр R не изменяется принудительным образом. При желании вы можете установить его 7-й бит и тогда постоянно будете получать из него значения от 128 до 255. Правда, делается это обычно в целях защиты программ (например, такой метод применен в игре NIGHT SHADE), но это уже совсем другая тема.

Когда требуется получить наибольшую степень случайности, прибегают к математическим расчетам. Разберем одну из таких «математических» подпрограмм. Несмотря на ее простоту, она вырабатывает все же достаточно длинную последовательность неповторяющихся значений, чтобы их можно было рассматривать в качестве случайных.

RND255 PUSH  BC
       PUSH  DE
       PUSH  HL
; Регистровая пара HL загружается значением из счетчика «случайных» чисел
;  (это может быть, например, системная переменная 23670/23671,
;  которая используется Бейсиком для тех же целей)
       LD    HL,(ADDR)
       LD    DE,7        ;дальше следует расчет очередного
                         ; значения счетчика
       ADD   HL,DE
       LD    E,L
       LD    D,H
       ADD   HL,HL
       ADD   HL,HL
       LD    C,L
       LD    B,H
       ADD   HL,HL
       ADD   HL,BC
       ADD   HL,DE
       LD    (ADDR),HL   ;сохранение значения счетчика «случайных»
                         ; чисел для последующих расчетов
       LD    A,H         ;регистр A загружается значением
                         ; старшего байта счетчика
       POP   HL
       POP   DE
       POP   BC
       RET
ADDR   DEFW  0

Эта процедура возвращает в аккумуляторе «случайные» числа от 0 до 255. Однако в подавляющем большинстве случаев нужно иметь возможность получать значения из произвольного диапазона. С этой целью дополним подпрограмму RND255 расчетами по ограничению максимального значения и назовем новую процедуру просто RND. Перед обращением к ней в регистре E задается верхняя граница вырабатываемых «случайных» чисел. Например, для получения в аккумуляторе числа от 0 до 50 в регистр E нужно загрузить значение 51:

RND    CALL  RND255
       LD    L,A
       LD    H,0
       LD    D,H
       CALL  12457
       LD    A,H
       RET
RND255 .........

Здесь вновь появилась еще одна подпрограмма ПЗУ, расположенная по адресу 12457. Она выполняет целочисленное умножение двух чисел, записанных в регистровых парах DE и HL. Произведение возвращается в HL. Если в результате умножения получится число, превышающее 65535, то будет установлен флаг CY, иначе выполняется условие NC. Проверка переполнения может оказаться полезной, когда перемножаются не известные заранее величины. В подпрограмме RND это условие проверять не нужно, так как оба сомножителя не превышают величины 255 (H и D предварительно обнуляются).

После того, как мы получили в свое распоряжение подпрограмму генерации случайных чисел, рассмотрим один занимательный пример ее применения. Представьте себе некое подобие мишени, состоящей, как и положено, из окружностей и ряда цифр, характеризующих заработанные вами очки при попадании в ту или иную ее часть. Вы нажимаете любую клавишу компьютера и в ту же секунду раздается звук, очень похожий на пролетающую мимо вашего уха пулю, а в мишени появляется отверстие с рваными краями. Нажимаете еще раз — снова попадание, но уже совсем в другом месте (рис. 6.8) и изображение отверстия тоже стало каким-то другим. Повторив эту процедуру много раз, вы легко можете убедиться в том, что «пули», как и при настоящей стрельбе, ложатся на мишень совершенно случайно. То же самое можно сказать и о характере отверстий. Отсюда ясно, что программа, реализующая эту игрушку, должна вырабатывать для каждого выстрела три случайных числа: координаты X и Y места попадания и номер изображения для пулевого отверстия. Теперь можно обратиться к самой программе и кратко ее прокомментировать.

Рис. 6.8. Программа МИШЕНЬ
       ORG   60000
       ENT   $
; Задание постоянных атрибутов экрана
       LD    A,7
       LD    (23693),A
       XOR   A
       CALL  8859
       CALL  3435
       LD    A,2
       CALL  5633
; Ввод символов UDG - три «пулевые отверстия»
       LD    HL,UDG
       LD    (23675),HL

; Основная часть программы
       CALL  MISH        ;рисование мишени
MAIN   CALL  WAIT        ;ожидание нажатия любой клавиши
       CP    " "
       RET   Z
       LD    A,22
       RST   16
       LD    E,20        ;задание диапазона для координаты Y
       CALL  RND
       RST   16
       LD    E,30        ;задание диапазона для координаты X
       CALL  RND
       RST   16
       LD    A,16
       RST   16
       LD    A,6
       RST   16
       LD    E,3         ;задание номера «пулевого отверстия»
       CALL  RND
       ADD   A,144       ;вычисление кода спрайта
       RST   16
       CALL  SND         ;звуковой сигнал
       JR    MAIN

; Подпрограмма вывода на экран мишени
MISH   LD    C,20
       CALL  CIRC
       LD    C,40
       CALL  CIRC
       LD    C,60
       CALL  CIRC
       LD    C,80
       CALL  CIRC
       LD    DE,TEXT
       LD    BC,LENTXT
       JP    8252

; Подпрограмма рисования окружностей
CIRC   EXX
       PUSH  HL
       EXX
       PUSH  BC
       LD    A,120
       CALL  11560
       LD    A,90
       CALL  11560
       POP   BC
       LD    B,0
       CALL  11563
       CALL  9005
       EXX
       POP   HL
       EXX
       RET

; Подпрограмма остановки счета
WAIT   XOR   A
       LD    (23560),A
WAIT1  LD    A,(23560)
       AND   A
       JR    Z,WAIT1
       RET
; Подпрограммы
RND    .........
SND    LD    B,80
       LD    HL,150
       LD    DE,1
SND1   .........
; Данные для мишени
TEXT   DEFB  22,10,14
       DEFM  "10"
       DEFB  22,10,18
       DEFM  "8"
       DEFB  22,10,21
       DEFM  "6"
       DEFB  22,10,23
       DEFM  "4"
       DEFB  22,10,11
       DEFM  "8"
       DEFB  22,10,8
       DEFM  "6"
       DEFB  22,10,6
       DEFM  "4"
LENTXT EQU   $-TEXT
; Данные для «пулевых отверстий»
UDG    DEFB  4,20,62,60,127,60,40,8
       DEFB  9,95,252,63,126,44,8,8
       DEFB  16,48,244,63,28,56,28,8

МНОГОКАДРОВАЯ ЗАСТАВКА

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

Что касается окон, то в принципе этот вопрос нами уже решен и можно было бы к нему вновь не возвращаться. Однако использование подпрограмм, работающих с окнами в том виде, в котором мы их предложили ранее, не всегда удобно. Если все параметры окон фиксированы, то нелепо каждый раз переопределять переменные ROW, COL и иже с ними. Лучше составить блоки данных, содержащие сведения о размерах, местоположении и всех прочих характеристиках, а затем выполнять любые преобразования окон, передавая подпрограммам единственное значение — адрес таблицы параметров (то бишь блока данных).

В начале книги мы говорили о существовании такой группы регистров, как индексные. Группа эта немногочисленна и включает в себя лишь два регистра: IX и IY. Как мы уже сообщали, оба они состоят из 16 бит и разделить их на половинки «законными» методами невозможно, поэтому они обычно рассматриваются не как регистровые пары, а как отдельные регистры.

Но для каких целей они существуют, в каких ситуациях их удобно применять и в каких группах команд они могут встречаться? Обычно эти регистры используются при обработке массивов, блоков данных или таблиц, а употребляются они практически во всех типах команд, в которых может принимать участие регистровая пара HL. За более подробной информацией на этот счет можете обратиться к Приложению I, где в алфавитном порядке приведены все команды микропроцессора.

Известно, что в Бейсике к любому элементу массива можно обратиться по индексу, например, оператор LET b=a(8) присвоит переменной b значение 8-го элемента массива a(). В ассемблере подобная запись может выглядеть так:

       LD    B,(IX+7)

Обратите внимание, что первый элемент массива имеет индекс 0, а не 1. В отличие от массивов Бейсика, максимальный индекс у регистров IX и IY не может превышать 127, но зато допускаются отрицательные значения номера элемента массива. Таким образом, общий размер адресуемой области составляет 256 байтовых элементов, а индексный регистр указывает на его «середину».

Реальным примером большой структуры данных могут служить системные переменные Бейсика. Обычно они адресуются регистром IY, который указывает на переменную ERR_NR, находящуюся по адресу 23610. Кстати, именно в связи с этим регистр IY лучше оставить в покое и никак не изменять его в своих программах, по крайней мере, до тех пор, пока вы так или иначе используете операционную систему компьютера. Что касается регистра IX, то им вы можете смело пользоваться при любых обстоятельствах.

Наверное, лучше всего объяснить идею применения индексных регистров на конкретном примере. В качестве такого примера приведем уже знакомые вам процедуры очистки окон и установки в них постоянных атрибутов (тем более, что они понадобятся при составлении программы многокадровой заставки), но графические переменные заменим единой структурой, первый элемент которой адресуем через IX. Саму же структуру оставим без изменения, то есть на первом месте (по смещению 0, задаваемом как IX+0 или просто IX) по-прежнему будет находиться параметр COL, на втором (задаваемом как IX+1) — ROW, дальше LEN, HGT и ATTR. А чтобы новые процедуры отличить от описанных выше, изменим их имена на SETW и CLSW:

SETW   LD    DE,#5800
       LD    B,(IX+3)    ;HGT
       LD    C,(IX+2)    ;LEN
       LD    A,(IX+1)    ;ROW
       LD    L,A
       LD    H,0
       ADD   HL,HL
       ADD   HL,HL
       ADD   HL,HL
       ADD   HL,HL
       ADD   HL,HL
       ADD   HL,DE
       LD    A,L
       ADD   A,(IX)      ;COL
       LD    L,A
       LD    A,(IX+4)    ;ATTR
SETW1  PUSH  BC
       PUSH  HL
SETW2  LD    (HL),A
       INC   HL
       DEC   C
       JR    NZ,SETW2
       POP   HL
       POP   BC
       LD    DE,32
       ADD   HL,DE
       DJNZ  SETW1
       RET
; -----------------
CLSW   LD    B,(IX+3)    ;HGT
       LD    C,(IX+2)    ;LEN
       LD    A,(IX+1)    ;ROW
CLSW1  PUSH  AF
       PUSH  BC
       PUSH  DE
       CALL  3742
       POP   DE
       LD    A,L
       ADD   A,(IX)      ;COL
       LD    L,A
       LD    B,8
CLSW2  PUSH  HL
       PUSH  BC
       LD    B,C
CLSW3  LD    (HL),0
       INC   HL
       DJNZ  CLSW3
       POP   BC
       POP   HL
       INC   H
       DJNZ  CLSW2
       POP   BC
       POP   AF
       INC   A
       DJNZ  CLSW1
       RET

Сравнив эти подпрограммы с SETV и CLSV, вы найдете много общего. Собственно, отличаются они друг от друга только способом передачи параметров. И это лишний раз доказывает, насколько ассемблер гибче всех прочих языков программирования — всякий раз вы можете использовать совершенно новые или видоизмененные процедуры, наиболее пригодные для применения именно в данном конкретном случае.

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

WIN0   DEFB  5,0,23,3,%01001110
WIN1   DEFB  8,6,17,13,%01010111
WIN2   DEFB  13,5,13,15,%01011110
WIN3   DEFB  13,9,13,11,%01101000
WIN4   DEFB  6,5,12,15,%01110101

Теперь перед обращением к процедурам SETW и CLSW достаточно в регистр IX записать адрес соответствующего блока и окно появится на экране. Напишем подпрограмму формирования окна заданных размеров и черной «тени» под ним:

WINDOW INC   (IX)        ;смещаем окно вправо (COL)
       INC   (IX+1)      ; и вниз (ROW)
       LD    A,(IX+4)    ;ATTR
       PUSH  AF
       LD    (IX+4),1    ;байт атрибутов для «тени»
       CALL  SETW        ;изменяем только атрибуты
       DEC   (IX)        ;возвращаем окно на прежнее место
       DEC   (IX+1)
       POP   AF
       LD    (IX+4),A    ;восстанавливаем байт атрибутов
       CALL  CLSW        ;очищаем окно
       CALL  SETW        ;окрашиваем заданным цветом
       CALL  BOX         ;рисуем рамку вокруг окна
       RET
; Подпрограмма, рисующая рамку вокруг окна
BOX    LD    A,(IX+1)    ;ROW
       PUSH  AF
       CALL  3742        ;вычисляем адрес экрана
       LD    A,L
       ADD   A,(IX)      ;COL
       LD    L,A
       LD    B,(IX+2)    ;LEN
BOX1   LD    (HL),255    ;проводим верхнюю линию
       INC   HL
       DJNZ  BOX1
       LD    B,(IX+3)    ;HGT
       POP   AF
BOX2   PUSH  AF          ;по точкам рисуем боковые линии
                         ; сверху вниз
       PUSH  BC
       CALL  3742
       LD    A,L
       ADD   A,(IX)
       LD    L,A
       LD    B,8
BOX3   PUSH  HL
       LD    A,(HL)
       OR    128         ;левая точка
       LD    (HL),A
       LD    A,(IX+2)
       ADD   A,L
       DEC   A
       LD    L,A
       LD    A,(HL)
       OR    1           ;правая точка
       LD    (HL),A
       POP   HL
       INC   H
       DJNZ  BOX3
       POP   BC
       POP   AF
       INC   A
       DJNZ  BOX2
       DEC   H
       LD    B,(IX+2)    ;LEN
BOX4   LD    (HL),255    ;проводим нижнюю линию
       INC   HL
       DJNZ  BOX4
       RET

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

       ORG   60000
       ENT   $
       LD    A,38
       LD    (23693),A
       LD    A,4
       CALL  8859
       CALL  BOLD

Затем сформируем окно, в которое должно быть помещено название игры (рис. 6.9), а в нижней части заставки напечатаем принятый в таких случаях текст «Select options 0 to 4» («Выберите опции от 0 до 4»). Поскольку к метке MENU программа будет неоднократно возвращаться из кадров, то чтобы избежать хаотического наложения окон, целесообразно каждый раз очищать экран.

Рис. 6.9. Многокадровая заставка
MENU   CALL  3435
       LD    A,2
       CALL  5633
       LD    DE,TEXT5
       LD    BC,ENDTXT-TEXT5
       CALL  8252        ;»Select options 0 to 4»
       LD    IX,WIN0     ;окно для названия игры
       CALL  CLSW
       CALL  SETW
       CALL  BOX

В сформированном окне напечатаем название игры FOOTBALL, для чего используем расширенные подпрограммой DBLSYM буквы.

       LD    DE,COORD    ;позиционирование курсора
       LD    BC,TEXT-COORD
       CALL  8252
       LD    HL,TEXT     ;вывод названия игры
       LD    B,8
MET    LD    A,(HL)
       PUSH  HL
       PUSH  BC
       CALL  DBLSYM
       POP   BC
       POP   HL
       INC   HL
       DJNZ  MET

Сделаем для меню окно с черной тенью и поместим в него текст, в соответствии с которым вы можете обратиться к одному из четырех кадров, нажав клавиши 1-4, или начать игру, выбрав клавишу 0 (для упрощения программы нажатие 0 или 4 возвращает вас в редактор GENS4 или в Бейсик).

       LD    IX,WIN1     ;окно основного меню
       CALL  WINDOW
       LD    DE,TEXT1    ;текст меню
       LD    BC,TEXT2-TEXT1
       CALL  8252

Теперь можно написать блок управления, который, как ни странно, выглядит довольно простым. Программка «крутится» в цикле, пока не нажата одна из указанных в кавычках клавиш. При нажатии же на клавишу, команда сравнения CP изменит биты флагового регистра (в частности флаг Z установится в ноль), после чего следующая команда JR Z,KADR? осуществит переход на выбранный вами кадр.

       XOR   A
       LD    (23560),A
CYCLE  LD    A,(23560)
       CP    "1"
       JR    Z,KADR1
       CP    "2"
       JR    Z,KADR2
       CP    "3"
       JR    Z,KADR3
       CP    "4"
       JR    Z,EXIT
       CP    "0"
       JR    NZ,CYCLE
; При нажатии на клавиши 0 или 4 восстанавливаются атрибуты экрана и стандартный
;  набор символов, после чего происходит выход в редактор GENS или в Бейсик.
EXIT   LD    A,7
       CALL  8859        ;белый бордюр
       LD    A,%00111000 ;стандартные атрибуты
       LD    (23693),A
       CALL  3435
       LD    A,2
       CALL  5633
       LD    HL,15360
       LD    (23606),HL
       RET

Части программы, формирующие окна кадров и печатающие в них тексты, исключительно похожи друг на друга. Тем не менее, имеются и некоторые отличия, которые определяются конкретными данными для окон и текстов. Каждый из кадров начинается со звукового сигнала SND, о котором мы говорили в начале этой главы и заканчивается процедурой WAIT, которая фиксирует кадр на экране, позволяя увидеть его содержание и выбрать дальнейшие действия (в этой программе чисто умозрительно, за исключением клавиши Е, которая действительно возвращает вас в меню).

KADR1  CALL  SND         ;звуковой сигнал
       LD    IX,WIN2
       CALL  WINDOW      ;вывод окна
       LD    DE,TEXT2    ;текст в окне
       LD    BC,TEXT3-TEXT2
       CALL  8252
       CALL  WAIT        ;ожидание нажатия клавиши E
       JP    MENU        ;возврат в меню
; Формирование окна Кадра 2 и печать в нем текста
KADR2  CALL  SND
       LD    IX,WIN3
       CALL  WINDOW
       LD    DE,TEXT3
       LD    BC,TEXT4-TEXT3
       CALL  8252
       CALL  WAIT
       JP    MENU
; Формирование окна Кадра 3 и печать в нем текста
KADR3  CALL  SND
       LD    IX,WIN4
       CALL  WINDOW
       LD    DE,TEXT4
       LD    BC,TEXT5-TEXT4
       CALL  8252
       CALL  WAIT
       JP    MENU
; Подпрограмма ожидания нажатия клавиши E
WAIT   XOR   A
       LD    (23560),A
WAIT1  LD    A,(23560)
       CP    "E"
       RET   Z
       CP    "e"
       JR    NZ,WAIT1
       RET
SND    LD    B,10
       LD    HL,350
       LD    DE,4
SND1   .........
DBLSYM .........
BOLD   .........
SETW   .........
CLSW   .........
WINDOW .........
BOX    .........
; Данные для формирования окна с названием игры
WIN0   .........
; Данные для формирования всех окон с тенями
WIN1   .........
WIN2   .........
WIN3   .........
WIN4   .........
; Данные для печати названия игры
COORD  DEFB  22,1,8,16,6,19,1
TEXT   DEFM  "FOOTBALL"
; Данные для печати текста Меню
TEXT1  DEFB  22,7,13,16,7,17,2,19,1
       DEFM  "M E N U"
       DEFB  22,9,9
       DEFM  "0. START••GAME"
       DEFB  22,11,9
       DEFM  "1. KEYBOARD"
       DEFB  22,13,9
       DEFM  "2. JOYSTICK"
       DEFB  22,15,9
       DEFM  "3. HI••SCORE"
       DEFB  22,17,9
       DEFM  "4. DEMO MODE"
; Данные для печати текста Кадра 1
TEXT2  DEFB  22,6,14,17,3,16,1
       DEFM  "Define keys"
       DEFB  22,8,15
       DEFM  "LEFT....O"
       DEFB  22,10,15
       DEFM  "RIGHT...P"
       DEFB  22,12,15
       DEFM  "UP......Q"
       DEFB  22,14,15
       DEFM  "DOWN....A"
       DEFB  22,16,15
       DEFM  "FIRE....M"
       DEFB  22,18,16
       DEFM  "MENU--E"
; Данные для печати текста Кадра 2
TEXT3  DEFB  22,10,16,17,5,16,0
       DEFM  "Joystick"
       DEFB  22,12,14
       DEFM  "1. KEMPSTON"
       DEFB  22,14,14
       DEFM  "2. SINCLAIR"
       DEFB  22,16,14
       DEFM  "3. CURSOR"
       DEFB  22,18,16
       DEFM  "MENU--E"
; Данные для печати текста Кадра 3
TEXT4  DEFB  22,7,8,17,6,16,1
       DEFM  "Hi score"
       DEFB  22,8,7
       DEFM  "PETR...726"
       DEFB  22,10,7
       DEFM  "IGOR...694"
       DEFB  22,12,7
       DEFM  "ALEX...605"
       DEFB  22,14,7
       DEFM  "SERG...523"
       DEFB  22,16,7
       DEFM  "WLAD...419"
       DEFB  22,18,8
       DEFM  "MENU--E"
; Данные для текста под заставкой
TEXT5  DEFB  22,21,6,16,7,17,4,19,0
       DEFM  "Select options 0 to 4"
ENDTXT