Как написать игру на ассемблере для 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