Как написать игру на ассемблере для ZX Spectrum/Глава 06
- ГЛАВА ШЕСТАЯ,
- в которой демонстрируются возможности ассемблера на примере создания многокадровых заставок
Когда в первой главе мы рассматривали основные части игровой программы, то в общих чертах показали, как строится многокадровая заставка и какие функции должны выполнять отдельные ее элементы. Теперь настало время всерьез поговорить о том, как реализовать эти элементы в виде законченных подпрограмм, а затем собрать их в единое целое для получения готовой заставки. Конечно, трудно дать ответы на все вопросы, с которыми вы можете столкнуться при решении данной задачи, поэтому мы решили ограничиться только теми проблемами, которые ранее не рассматривались совсем. Среди них — создание простейших звуковых эффектов, преобразование символов стандартного набора таким образом, чтобы надписи не стыдно было поместить на самое видное место экрана, скроллинги окон с графическими изображениями и текстами во всех направлениях, а также включение в программу элемента случайности. После того, как все это будет подробно рассмотрено, станет возможно, наконец, непосредственно заняться многокадровой заставкой.
СОЗДАНИЕ ПРОСТЕЙШИХ ЗВУКОВ
Вероятно, вы давно и с большим нетерпением ждете того момента, когда мы наконец соблаговолим поведать о том, как у компьютера «прорезать голос». До сих пор мы применяли некие акустические суррогаты, однако терпеть и дальше такое «безобразие» уже нет никакой возможности. Более детально и всесторонне вопросы создания мелодий и шумовых эффектов (в том числе и для 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), отражающую пути перемещений битов при многократном выполнении команд сдвигов. Напоминаем, что эти команды вместе с аналогичными схемами приведены в разделе «Организация циклов в ассемблере» пятой главы.

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).

Вот такая симпатичная надпись, в которой каждая буква составлена из отрезков параллельных двойных линий, нарисованных DRAW'ами. В высоту укладывается двенадцать таких линий плюс промежутки между ними. Точно такие любой из вас сможет легко начертить по линейке на листе бумаги, измерить их длины и координаты, после чего ввести в программу свои вычисления в виде блока данных. Посмотрим, как это делается. Прежде всего разберемся со структурой строки блока данных, поскольку с подобным построением раньше мы еще не встречались, а именно оно все и определяет. Строка списка DATA содержит шесть элементов, каждому из которых в программе соответствует определенная переменная, а именно:
- y — вертикальная координата начала линии (в пикселях);
- x — горизонтальная координата начала линии (опять же в пикселях);
- hgt — высота буквы (количество двойных линий плюс промежутки между ними);
- len — длина проводимой горизонтальной линии (снова в пикселях);
- ink — цвет линии (0…7);
- dx — приращение линии (еще раз в пикселях).
Работу программы лучше всего показать на примере какой-нибудь буквы, скажем, «Д» (рис. 6.5, а), при построении которой участвуют все эти переменные (правда, некоторые величины в списках DATA равны нулю).
|
|
|
|
- 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), который обычно вызывается из меню как один из кадров заставки.

Перед запуском ассемблерной программы следует с адреса 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), создавая совершенно бесподобный эффект.

А теперь перейдем непосредственно к программе.
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 места попадания и номер изображения для пулевого отверстия. Теперь можно обратиться к самой программе и кратко ее прокомментировать.

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 программа будет неоднократно возвращаться из кадров, то чтобы избежать хаотического наложения окон, целесообразно каждый раз очищать экран.

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







