Как написать игру на ассемблере для ZX Spectrum/Глава 08
- ГЛАВА ВОСЬМАЯ,
- в которой рассказывается о том, как управлять ходом игры
Одним из главных достоинств компьютерных игр, как вы сами прекрасно знаете, является возможность играющего влиять на события, разворачивающиеся перед ним на экране. Это влияние можно реализовать по-разному, например, нажимая определенные клавиши клавиатуры или наклоняя в ту или иную сторону ручку джойстика и нажимая кнопку «огонь». При этом в большинстве игр предусматривается выбор любого вида управления, который обычно осуществляется через «Меню». Настало время и нам рассмотреть эти вопросы с разных точек зрения, а именно: как управлять спрайтами с помощью клавиатуры или джойстика и при этом учесть ограничения, обусловленные размерами экрана, как определить, подключен к компьютеру джойстик или нет, как организовать управление игрой, если играющих двое, и некоторые другие.
УПРАВЛЕНИЕ С ПОМОЩЬЮ КЛАВИАТУРЫ
При разработке игровых программ немыслимо обойтись без опроса клавиатуры. Действительно, чтобы во время игры управлять спрайтами, перемещая их хотя бы по четырем направлениям, программа обязана безошибочно различать одну из четырех нажатых клавиш. Например, O должна соответствовать движению влево, P — вправо, Q — вверх и A — вниз. В Бейсике, как вы помните, для этой цели мы пользовались функцией INKEY$, а одна из возможных программ, «узнающих», к примеру, символ P, могла бы выглядеть так:
100 IF INKEY<pre> XOR A LD (23560),A ;в системную переменную LAST_K (код ; последней нажатой клавиши) заносится 0 LOOP LD A,(23560) ;из этой системной переменной ; считывается значение кода нажатой клавиши CP "P" ;сравнение двух кодов - находящегося ; в регистре A и символа P JR NZ,LOOP ;если результат сравнения не равен 0, ; то переход на метку LOOP, если 0, RET ; то выход
lt;>"P" THEN GO TO 100
Соответствующий ей фрагмент ассемблерной программы имеет следующий вид:
KEY XOR A LD (23560),A MET1 LD A,(23560) CP "P" ;сравнение двух кодов ; Если результат сравнения не равен нулю (то есть нажата не P), ; то переход на метку MET2, после которой проверяются нажатия других клавиш JR NZ,MET2 LD DE,TXT1 PRINT LD BC,5 ;вывод на экран символа, CALL 8252 ; соответствующего нажатой клавише LD A,13 RST 16 JR KEY ;переход на начало программы MET2 CP "O" ;проверка нажатия клавиши O JR NZ,MET3 LD DE,TXT2 JR PRINT MET3 CP "Q" ;проверка нажатия клавиши Q JR NZ,MET4 LD DE,TXT3 JR PRINT MET4 CP "A" ;проверка нажатия клавиши A JR NZ,MET5 LD DE,TXT4 JR PRINT MET5 CP "0" ;проверка нажатия клавиши 0 JR NZ,MET1 ;если коды не совпадают, ; повторяем все сначала RET ; иначе - выход из программы ; Данные для печати TXT1 DEFM "KEY P" TXT2 DEFM "KEY O" TXT3 DEFM "KEY Q" TXT4 DEFM "KEY A"
Надо заметить, что эта программка уже неоднократно применялась нами для разных целей, например, для выхода из циклов, для перехода к кадрам в многокадровой заставке, но она может использоваться и как образец для создания более сложных программ, например, следующей:
; Адрес 32766=127×256+254, в B заносится адрес полуряда, ; а в C - адрес порта (254). KEY LD BC,32766 IN A,(C) ; Один из способов проверки данного бита - у отпущенной клавиши ; бит установлен (1), у нажатой сбрасывается в 0 BIT 2,A JR NZ,KEY RET
После того как вы нажмете клавишу P, O, Q или A, программа напечатает в левом верхнем углу экрана одну из фраз, перечисленных в блоке данных, например, «KEY Q».
В игровых программах, как вы знаете, часто требуется опрашивать несколько клавиш одновременно, например, чтобы выполнять сложные перемещения спрайтов, включающие помимо вертикальных и горизонтальных еще и диагональные направления. Для подобных ситуаций приведенные выше способы опроса клавиатуры оказываются непригодными и, чтобы заставить все-таки спрайты перемещаться в любую сторону, придется воспользоваться командой IN, с помощью которой выполняется ввод данных из какого-либо порта. Существует несколько способов чтения из портов, но нас будут интересовать только два из них:
- IN reg, (C) — ввод байта из порта и помещение его в регистр, причем reg — один из регистров A, B, C, D, E, H или L, а адрес порта содержится в паре BC (в C — младший байт адреса, в B — старший).
- IN A,(port) — ввод байта из порта с номером port и помещение его в аккумулятор. При этом полный 16-разрядный адрес порта составляется из значения port (младший байт) и значения аккумулятора (старший байт).
Применяя одну или другую команду, можно получить два способа опроса клавиатуры. Первый из них очень похож на использование функции IN в Бейсике. Напомним, что все клавиши группируются по полурядам, то есть по 5 клавиш. Каждому полуряду соответствует определенный порт. Адреса «клавиатурных» портов отличаются только старшим байтом, а младший всегда равен 254 (#FE). Все эти адреса представлены в табл. 8.1 в десятичной, шестнадцатеричной и двоичной нотации. Предположим, что нам нужно определить нажатие клавиши M, тогда в регистровую пару BC необходимо записать адрес 32766. Из порта считываем значение для полуряда, а затем, чтобы определить нажатие конкретной клавиши, проверяем соответствующий ей бит (от бита 0 — для крайних клавиш до 4-го бита — для центральных). Так как клавиша M занимает третье место от края, то она будет определяться состоянием 2-го бита полученного из порта байта. Если этот бит окажется сброшенным в 0, это будет означать, что клавиша нажата. (Из-за упрощенной аппаратной реализации клавиатуры, примененной в ZX Spectrum, достоверно (в общем случае) можно определить одновременное нажатие не более двух каких-либо клавиш — Примеч. ред.)
Таблица 8.1. Адреса портов для опроса клавиатуры
Полуряд | DEC | HEX | BIN |
Space…B | 32766 | 7FFE | 01111111 11111110 |
Enter…H | 49150 | BFFE | 10111111 11111110 |
P…V | 57342 | DFFE | 11011111 11111110 |
0…6 | 61438 | EFFE | 11101111 11111110 |
1…5 | 63486 | F7FE | 11110111 11111110 |
Q…T | 64510 | FBFE | 11111011 11111110 |
A…G | 65022 | FDFE | 11111101 11111110 |
CS…V | 65278 | FEFE | 11111110 11111110 |
KEY LD A,#7E ;в аккумулятор заносится старший байт ; адреса порта #7EFE IN A,(254) ;считывание из порта (254 или #FE - ; младший байт адреса) BIT 2,A ;проверка нажатия третьей от края ; клавиши (M) JR NZ,KEY RET
Второй способ принципиально не отличается от первого. Перед чтением в аккумулятор помещается старший байт адреса соответствующего порта, а младший байт задается в явном виде в команде IN:
ORG 60000 XY EQU 23296 KEY LD HL,(XY) ;запись координат точки в HL ; В регистр A заносится старший байт полуряда, ; в котором располагается клавиша Q LD A,251 IN A,(254) ;читаем из порта значения для полуряда ; Проверка бита 0 (команду RRCA вместо BIT здесь удобнее применять ; потому, что клавиша Q в полуряду занимает крайнее положение) RRCA ; Если клавиша не нажата (на что указывает установленный бит), ; то следующую команду пропускаем JR C,KEY1 ; Увеличиваем значение вертикальной координаты, которое находится в регистре H INC H KEY1 LD A,253 IN A,(254) RRCA ;клавиша A JR C,KEY2 DEC H ;уменьшаем вертикальную координату KEY2 LD A,223 IN A,(254) RRCA ;клавиша P JR C,KEY3 INC L ;увеличиваем горизонтальную координату ; Так как клавиши P и O находятся в одном полуряду, ; то выполнять команду IN дважды нет необходимости KEY3 RRCA ;клавиша O JR C,KEY4 DEC L ;уменьшаем горизонтальную координату KEY4 LD (XY),HL LD A,127 IN A,(254) BIT 2,A RET NZ ;выход, если клавиша M не нажата JP 3435 ; иначе очищаем экран
Рассмотрим программу, в которой при нажатии клавиш Q, A, O и P изменяются координаты точки на экране. Сами точки будем ставить в бейсик-программе, которую напишем позже, но подразумевая использование процедуры из Бейсика, воспользуемся для передачи координат точки, как и раньше, областью буфера принтера, определив адрес передаваемых параметров константой XY.
100 POKE 23296,100: POKE 23297,100 110 PLOT PEEK 23296, PEEK 23297 120 RANDOMIZE USR 60000: GO TO 110
Чтобы увидеть эту процедуру в действии, необходимо дополнить ее небольшой бейсик-программкой, задача которой состоит только в том, чтобы ставить на экране точку в соответствии с координатами (XY), полученными в ассемблерной программе.
ORG 60000 ENT $ XOR A CALL 8859 LD A,68 LD (23693),A CALL 3435 LD A,2 CALL 5633 ; Очистка строки для ввода имени LD HL,NAME LD DE,NAME+1 LD BC,19 LD (HL)," " LDIR ; Вывод таблицы символов в рамке CALL TABL CALL LINES LD A,68 LD (23693),A LD BC,#506 ;начальные координаты курсора в таблице LD E,0 ;номер символа в строке ввода SET 3,(IY+48) ;режим ввода прописных букв ; Управление курсором и печать выбранного символа в строку KEYS CALL SETCUR ;вывод курсора XOR A LD (23560),A WAIT LD A,(23560) ;ожидание нажатия клавиши AND A JR Z,WAIT CP "P" ;перемещение курсора на JR Z,RIGHT ; один шаг вправо CP "O" ;перемещение курсора JR Z,LEFT ; на один шаг влево CP "Q" ;перемещение курсора JR Z,UP ; на один шаг вверх CP "A" ;перемещение курсора JR Z,DOWN ; на один шаг вниз CP "M" ;печать выбранного символа JR Z,SELECT ; в строке ввода JR KEYS ; Перемещение курсора вправо RIGHT LD A,C ;проверка достижения курсором CP 24 ; правой границы таблицы JR NC,KEYS CALL RESCUR ;удаление курсора на прежнем месте INC C ;изменение положения курсора INC C CALL SETCUR ;установка курсора на букву таблицы JR KEYS ; Перемещение курсора влево LEFT LD A,C ;проверка достижения курсором CP 7 ; левой границы таблицы JR C,KEYS CALL RESCUR DEC C DEC C CALL SETCUR JR KEYS ; Перемещение курсора вверх UP LD A,B ;проверка достижения курсором CP 6 ; верхней границы таблицы JR C,KEYS CALL RESCUR DEC B DEC B CALL SETCUR JR KEYS ; Перемещение курсора вниз DOWN LD A,B ;проверка достижения курсором CP 11 ; нижней границы таблицы JR NC,KEYS CALL RESCUR INC B INC B CALL SETCUR JR KEYS ; Выбор символа, который затем будет напечатан в строке или выбор ; функции для редактирования этой строки SELECT PUSH BC PUSH DE CALL SND ;звуковой сигнал, издаваемый при ; перемещении символа из таблицы в ; набираемую строку POP DE POP BC LD A,B CP 11 JR NZ,MOVE ;печать символа LD A,C CP 20 JR Z,DELETE ;удаление символа в строке CP 22 JR Z,SPACE ;печать пробела в строке CP 24 RET Z ;выход из программы ; Перемещаем символ из таблицы в набираемую строку и смещаем курсор ; на позицию вправо, при этом делаем проверку того, чтобы символ ; не вышел за заданные границы строки (слева и справа). MOVE LD A,E CP 20 JP NC,KEYS LD D,0 PUSH BC PUSH DE LD A,B ;по вертикальной координате курсора ; определяем адрес данных строки ; таблицы (STR1, STR2, STR3 или STR4) SUB 5 LD HL,D_STR LD E,A ADD HL,DE LD E,(HL) INC HL LD D,(HL) EX DE,HL LD A,C ;по горизонтальной координате находим ; код символа в блоке данных SUB 6 LD C,A LD B,0 ADD HL,BC POP DE POP BC LD A,(HL) ;помещаем код символа в A LD HL,NAME ;определяем адрес в строке NAME ADD HL,DE ; для ввода символа LD (HL),A ;помещаем символ в строку ввода CALL PR_STR ;выводим строку ввода на экран INC E ;смещаем позицию ввода вперед JP KEYS ; Удаление неправильно набранного символа DELETE LD A,E ;проверка достижения начала строки ввода AND A JP Z,KEYS DEC E ;уменьшаем позицию ввода LD D,0 LD HL,NAME ADD HL,DE LD (HL)," " ;заменяем удаляемый символ пробелом CALL PR_STR JP KEYS ; Ввод пробела SPACE LD A,E ;проверка достижения конца строки ввода CP 20 JP NC,KEYS LD D,0 LD HL,NAME ADD HL,DE LD (HL)," " CALL PR_STR INC E ;увеличиваем позицию ввода JP KEYS ; Вывод курсора изменением байта атрибутов RESCUR LD A,68 ;PAPER 0, INK 4, BRIGHT 1 JR PRATTR ; Удаление курсора восстановлением байта атрибутов SETCUR LD A,79 ;PAPER 1, INK 7, BRIGHT 1 ; Вычисляем адрес атрибутов знакоместа и заносим ; по этому адресу байт из аккумулятора PRATTR LD L,B LD H,0 ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL PUSH AF LD A,H ADD A,#58 LD H,A LD A,L ADD A,C LD L,A POP AF LD (HL),A RET ; Подпрограмма печати таблицы символов TABL LD DE,STR LD BC,LENSTR JP 8252 ; Подпрограмма печати введенной строки PR_STR PUSH BC PUSH DE LD DE,STR5 LD BC,LENLIN CALL 8252 POP DE POP BC RET ; Подпрограмма рисования рамки LINES EXX PUSH HL LD A,66 LD (23695),A LD BC,#8A2C ;B = 138, C = 44 CALL 8933 LD DE,#101 LD BC,160 ;B = 0, C = 160 CALL 9402 LD DE,#FF01 LD BC,#3D00 ;B = 61, C = 0 CALL 9402 LD DE,#1FF LD BC,160 CALL 9402 LD DE,#101 LD BC,#3D00 CALL 9402 POP HL EXX RET ; Короткий звуковой сигнал SND LD B,30 LD HL,350 LD DE,2 SND1 PUSH BC PUSH DE PUSH HL CALL 949 POP HL POP DE POP BC SBC HL,DE DJNZ SND1 RET ; Данные таблицы символов STR DEFB 22,5,6 STR1 DEFM "1 2 3 4 5 6 7 8 9 0" ;символы через один пробел DEFB 22,7,6 STR2 DEFM "A B C D E F G H I J" DEFB 22,9,6 STR3 DEFM "K L M N O P Q R S T" DEFB 22,11,6 STR4 DEFM "U V W X Y Z . d s e" STR5 DEFB 22,19,5,16,5,">",16,2 NAME DEFM "····················" DEFB 16,5,"<" LENSTR EQU $-STR ;длина строки для печати таблицы LENLIN EQU $-STR5 ;длина строки ввода имени ; Адреса данных символов в таблице D_STR DEFW STR1,STR2,STR3,STR4
Попробуйте ее ввести и исполнить, а затем понажимайте клавиши Q, A, O и P — по экрану в разных направлениях потянутся четкие прямые линии подобно использованию функции PEN в графическом редакторе. Нажав клавишу M, в любой момент можно очистить экран и начать рисовать новую «картину».
В заключение этого раздела приведем еще один пример управления с помощью клавиатуры, с которым мы иногда встречаемся, загружая те или иные игровые программы. Он полезен еще и тем, что дает вариант решения некоторых побочных проблем, таких, например, как учет ограничений на перемещение курсора (или спрайта) по экрану, введение дополнительных функций управления и некоторые другие.
Представим себе, что в конце игры необходимо набрать имя играющего, чтобы затем записать его в раздел меню HI SCORE. Для этого, при достижении определенных результатов, вызывается кадр, в котором вы видите примерно такую таблицу, какая изображена на рис. 8.1. Далее, управляя курсором с помощью клавиш Q, A, O и P, требуется выбрать из таблицы буквы вашего имени, нажимая после каждой клавишу выбора M. При этом набранные буквы из таблицы будут переноситься в строку, расположенную ниже. Если какой-то символ набран неверно, его можно стереть, «нажав» в таблице букву d (delete), для печати пробела используется буква s (space), а для ввода имени и завершения этой части программы — буква e (enter). Надо сказать, что такой способ ввода имени не самый удобный, однако он имеет право на существование в случаях, когда играющий еще плохо знаком с клавиатурой ZX Spectrum, но имеет некоторое представление о латинском алфавите.
ORG 60000 XY EQU 23296 JOY LD HL,(XY) ;в регистре H вертикальная координата, ; а в L - горизонтальная IN A,(31) ;читаем из порта джойстика RRCA ;проверяем бит 0 JR NC,JOY1 ;если в 0, переходим к проверке ; следующего бита INC L ;увеличиваем горизонтальную координату JOY1 RRCA ;аналогично проверяем остальные биты JR NC,JOY2 DEC L ;уменьшаем горизонтальную координату JOY2 RRCA JR NC,JOY3 DEC H ;увеличиваем вертикальную координату JOY3 RRCA JR NC,JOY4 INC H ;уменьшаем вертикальную координату JOY4 LD (XY),HL ;новые координаты передаем ; бейсик-программе RRCA RET NC JP 3435 ;при нажатии кнопки «огонь» ; экран очищается
Эту программу можно рассматривать как вполне независимый кадр заставки. Если вы решите использовать ее в своей собственной игре, единственное, что вам потребуется, это перенести после выхода введенное имя из строки NAME в таблицу «рекордов». И, конечно же, вам нужно будет проследить, чтобы имена меток, задействованные в приведенной программе не повторялись в вашей игре. Естественно, что при необходимости наименования меток можно и заменить.
УПРАВЛЕНИЕ ДЖОЙСТИКОМ
В динамичных играх управление спрайтами с помощью Kempston-джойстика часто оказывается более удобным, чем от клавиатуры — нужно помнить всего лишь о четырех сторонах света, да в пылу борьбы не забыть, что есть еще кнопка «огонь».
Наверное, вам известно, что в Бейсике определить положение ручки джойстика можно с помощью функции IN 31. В ассемблере для этих же целей удобнее всего применять команду IN A,(31), после выполнения которой в аккумуляторе появится некоторое число, отдельные биты которого и определяют «статус» джойстика. Значения имеют не все биты, а только 5 младших, причем, в отличие от клавиатуры, в нейтральном положении все биты сброшены в 0 (конечно, если порт джойстика вообще подключен), а установка какого-то бита в 1 означает поворот ручки или нажатие кнопки «огонь». При диагональном наклоне ручки будут установлены сразу два бита. В табл. 8.2 показано соответствие пяти младших битов, получаемых в аккумуляторе после выполнения команды IN A,(31), направлениям наклона ручки джойстика и нажатию кнопки «огонь». Три старших бита не определены, поэтому в таблице они заменены знаками вопроса.
Таблица 8.2. Значения битов порта джойстика.
Направление | Код |
Вправо | ???00001 |
Влево | ???00010 |
Вверх | ???00100 |
Вниз | ???01000 |
Огонь | ???10000 |
Давайте сначала на примере простой программки перемещения точки, аналогичной той, которую мы описали в начале первого раздела данной главы, рассмотрим принцип использования джойстика. Причем, как и в случае с клавиатурой, точку на экран будем ставить с помощью бейсик-программы.
ORG 60000 ENT $ LD A,5 LD (23693),A XOR A CALL 8859 CALL 3435 ; Основная часть программы LD BC,#505 ;в регистре B вертикальная координата Y, ; а в C горизонтальная JOY IN A,(31) ;читаем данные из порта джойстика LD E,A ;освобождаем аккумулятор ; для проверки границ LD HL,SAM1 ;задаем адрес первого спрайта RRC E ;сдвигаем E вправо на один бит JR NC,JOY1 LD A,C CP 30 ;задаем границу перемещения вправо JR NC,JOY1 ;если правая граница достигнута, ; то увеличивать X уже нельзя - ; переходим на метку JOY1 INC C ;увеличиваем координату X, что соответствует ; перемещению самолета вправо LD HL,SAM2 ;задаем адрес второго спрайта JOY1 RRC E JR NC,JOY2 LD A,C CP 1 JP M,JOY2 DEC C ;уменьшаем координату X LD HL,SAM3 ;задаем адрес третьего спрайта JOY2 RRC E JR NC,JOY3 LD A,B CP 22 JR NC,JOY3 INC B ;увеличиваем координату Y JOY3 RRC E JR NC,JOY4 LD A,B CP 1 JP M,JOY4 DEC B ;уменьшаем координату Y JOY4 RRC E RET C ;если кнопка «огонь» нажата - ; выходим из программы ; Вывод на экран по принципу XOR одного из трех спрайтов самолета PUSH BC PUSH HL LD A,SPRXOR ;устанавливаем режим вывода XOR CALL PTBL ;печатаем один из самолетов LD BC,10 ;вводим задержку CALL 7997 POP HL POP BC PUSH BC LD A,SPRXOR ;устанавливаем режим вывода XOR CALL PTBL ;стираем изображение самолета POP BC JR JOY ;переходим в начало программы ; для изменения координат PTBL ......... ; Заголовок данных спрайта первого самолета, соответствующего полету вперед и назад SAM1 DEFB 4 DEFB 0,0,5,0,1,5,1,0,5,1,1,5 ; Данные для первого самолета DEFB 0,0,5,0,95,254,56,66 DEFB 0,0,160,0,250,127,28,66 DEFB 61,1,1,5,13,0,0,0 DEFB 188,128,128,160,176,0,0,0 ; Заголовок данных спрайта второго самолета, соответствующего повороту вправо SAM2 DEFB 4 DEFB 0,0,5,0,1,5,1,0,5,1,1,5 ; Данные для второго самолета DEFB 0,0,5,0,11,95,78,35 DEFB 0,0,160,0,244,62,28,8 DEFB 29,1,1,2,2,0,0,0 DEFB 176,128,128,192,224,192,0,0 ; Заголовок данных спрайта третьего самолета, соответствующего повороту влево SAM3 DEFB 4 DEFB 0,0,5,0,1,5,1,0,5,1,1,5 ; Данные для третьего самолета DEFB 0,0,5,0,47,124,56,16 DEFB 0,0,160,0,208,250,114,196 DEFB 13,1,1,3,7,3,0,0 DEFB 184,128,128,64,64,0,0,0
Чтобы точка, перемещаясь по экрану, не ушла за его пределы, необходимо ввести ограничения. Как это осуществить, покажем на конкретном примере, в котором мы используем приведенный выше принцип для воспроизведения реальной игровой ситуации, например, для управления самолетом. Прежде всего создадим три спрайта (блоки SAM1, SAM2 и SAM3), первый из которых будет соответствовать полету самолета прямо, второй повороту вправо (самолет при этом должен слегка наклониться) и, наконец, третий — его повороту влево. Таким образом, наклоняя ручку джойстика в ту или иную сторону, вы можете в приведенной ниже программе легко изменять положение спрайта на экране.
ORG 60000 ENT $ LD A,5 LD (23693),A XOR A CALL 8859 CALL 3435 ; Основная часть программы LD BC,#505 ;задаем исходное положение вертолета KEY PUSH BC CALL KBDJOY ;читаем данные из портов POP BC RRCA ;поворачиваем ручку джойстика вправо ; или нажимаем клавишу P - ; полет вертолета вправо JR NC,KEY1 INC C KEY1 RRCA ;поворачиваем ручку джойстика влево ; или нажимаем клавишу O - ; полет вертолета влево JR NC,KEY2 DEC C KEY2 RRCA ;поворачиваем ручку джойстика вниз ; или нажимаем клавишу A - ; полет вертолета вниз JR NC,KEY3 INC B KEY3 RRCA ;поворачиваем ручку джойстика вверх ; или нажимаем клавишу Q - ; полет вертолета вверх JR NC,KEY4 DEC B KEY4 RRCA ;при нажатии кнопки «огонь» джойстика ; или клавиши M - выход RET C ; Подпрограмма вывода на экран изображения вертолета в двух фазах, ; каждая из которых соответствует одному из положений винта XOR A ;формируем звуковой сигнал, OUT (254),A ; имитирующий работу двигателя CALL CHECK ;проверка достижения границ экрана LD A,16 OUT (254),A LD A,SPRXOR ;задаем режим вывода спрайта LD HL,WERT1 ;устанавливаем адрес спрайта PUSH BC PUSH HL CALL PTBL ;выводим вертолет в первой фазе LD BC,5 ;задаем задержку между фазами CALL 7997 ; вращения винта POP HL POP BC LD A,SPRXOR ;режим вывода спрайта PUSH BC PUSH HL CALL PTBL ;стираем вертолет в первой фазе POP HL POP BC XOR A OUT (254),A ; Вывод вертолета во второй фазе LD A,16 ;звуковой сигнал OUT (254),A LD A,SPRXOR ;режим вывода спрайта LD HL,WERT2 ;устанавливаем адрес спрайта ; с другим расположением винта PUSH BC PUSH HL CALL PTBL ;выводим спрайт во второй фазе LD BC,5 CALL 7997 POP HL POP BC LD A,SPRXOR ;режим вывода спрайта PUSH BC PUSH HL CALL PTBL ;стираем с экрана спрайт во второй фазе POP HL POP BC JR KEY ; Подпрограмма проверки границ экрана CHECK LD A,C AND A ;сравниваем координату X вертолета ; с заданной левой границей экрана JR NZ,CONT1 INC C CONT1 CP 29 ;сравниваем координату X вертолета ; с заданной правой границей экрана JR NZ,CONT2 DEC C CONT2 LD A,B ;задаем верхнюю границу экрана AND A ;сравниваем координату Y вертолета ; с заданной верхней границей экрана JR NZ,CONT3 INC B CONT3 CP 21 ;сравниваем координату Y вертолета ; с заданной нижней границей экрана RET NZ DEC B RET ; Подпрограмма чтения данных из портов клавиатуры и джойстика KBDJOY IN A,(31) ;опрашиваем порт джойстика LD E,A ;запоминаем полученные биты ; Проверяем, подключен ли порт джойстика (ручку невозможно ; повернуть сразу и вправо и влево - если оба бита установлены, ; порт не подключен) AND 3 CP 3 JR NZ,KBDJ1 ;если да, переходим к опросу клавиатуры LD E,0 ; иначе очищаем коллектор битов KBDJ1 LD HL,DKEY ;адрес блока данных клавиатуры KBDJ2 LD C,(HL) ;младший байт адреса порта INC C ;проверка на 0 (конец блока данных) DEC C LD A,E ;значение коллектора в аккумулятор RET Z ;выход, если конец данных INC HL LD B,(HL) ;старший байт адреса порта INC HL IN A,(C) ;читаем из порта CPL ;инвертируем биты AND (HL) ;проверяем конкретный бит INC HL JR Z,KBDJ3 LD A,(HL) ;если бит установлен, читаем код направления OR E ; и объединяем с коллектором LD E,A KBDJ3 INC HL JR KBDJ2 ;продолжаем чтение ; Данные управляющих клавиш: ; первое число - младший байт порта ; второе число - старший байт порта ; третье число - маска бита ; четвертое - код направления (аналогично кодам джойстика) DKEY DEFB #FE,#FB,1,8 ;Q - вверх DEFB #FE,#FD,1,4 ;A - вниз DEFB #FE,#DF,2,2 ;O - влево DEFB #FE,#DF,1,1 ;P - вправо DEFB #FE,#7F,4,16 ;M - «огонь» DEFB 0 ;метка конца блока данных PTBL ......... ; Заголовок первой фазы спрайта «вертолет» WERT1 DEFB 7 DEFB 0,1,6,1,0,6,1,1,6,1,2,6 DEFB 2,0,6,2,1,6,2,2,6 ; Данные первой фазы спрайта «вертолет» DEFB 0,0,4,28,56,32,24,24 DEFB 0,0,0,1,2,2,4,4 DEFB 60,60,255,129,66,36,36,24 DEFB 0,0,0,128,64,64,32,32 DEFB 7,2,1,0,1,2,4,14 DEFB 255,36,36,255,0,0,0,0 DEFB 224,64,128,0,128,64,32,112 ; Заголовок второй фазы спрайта «вертолет» WERT2 DEFB 9 DEFB 0,0,6,0,1,6,0,2,6 DEFB 1,0,6,1,1,6,1,2,6 DEFB 2,0,6,2,1,6,2,2,6 ; Данные второй фазы спрайта «вертолет» DEFB 0,0,30,127,255,127,14,0 DEFB 0,0,0,129,231,129,24,24 DEFB 0,0,120,254,255,254,112,0 DEFB 0,0,0,1,2,2,4,4 DEFB 60,60,255,129,66,36,36,24 DEFB 0,0,0,128,64,64,32,32 DEFB 7,2,1,0,1,2,4,14 DEFB 255,36,36,255,0,0,0,0 DEFB 224,64,128,0,128,64,32,112
СОВМЕСТНОЕ УПРАВЛЕНИЕ КЛАВИАТУРОЙ И ДЖОЙСТИКОМ
Прочитав название параграфа, многие наверняка подумали — а в чем тут собственно проблема, достаточно объединить вместе блоки управления клавиатурой и джойстиком и вроде бы все, можно пользоваться как тем так и другим. Не будем вас разочаровывать, так оно и есть, и в принципе подобную схему вполне можно использовать для совместного управления. Но при этом нужно помнить о том, что если потребуется изменить управляющие клавиши, вам придется вносить в текст программы довольно много изменений. То же самое можно сказать и о смене типа джойстика. Поэтому хотелось бы иметь универсальную процедуру опроса клавиатуры и джойстика, в которой перечисленные изменения можно было выполнить с наименьшей затратой сил. Ниже приводится программа, использующая подобную процедуру, обозначенную меткой KBDJOY. В ней на экран выводится вертолет с вращающимся винтом (рис. 8.2 а,б) и стрекочущим двигателем. Нажимая клавиши Q, A, O, P или наклоняя ручку джойстика, вы сможете легко убедиться в том, что программа работает как положено. А для усиления эффекта можно попробовать нажать какую-нибудь из клавиш и одновременно повернуть джойстик — вертолет послушно полетит по диагонали.
а | б |
Рис. 8.2. Спрайт «вертолет» |
ORG 60000 ENT $ LD A,5 LD (23693),A XOR A CALL 8859 CALL 3435 ; Основная часть программы LD BC,#505 ;задаем исходное положение вертолета KEY PUSH BC CALL KBDJOY ;читаем данные из портов POP BC RRCA ;поворачиваем ручку джойстика вправо ; или нажимаем клавишу P - ; полет вертолета вправо JR NC,KEY1 INC C KEY1 RRCA ;поворачиваем ручку джойстика влево ; или нажимаем клавишу O - ; полет вертолета влево JR NC,KEY2 DEC C KEY2 RRCA ;поворачиваем ручку джойстика вниз ; или нажимаем клавишу A - ; полет вертолета вниз JR NC,KEY3 INC B KEY3 RRCA ;поворачиваем ручку джойстика вверх ; или нажимаем клавишу Q - ; полет вертолета вверх JR NC,KEY4 DEC B KEY4 RRCA ;при нажатии кнопки «огонь» джойстика ; или клавиши M - выход RET C ; Подпрограмма вывода на экран изображения вертолета в двух фазах, ; каждая из которых соответствует одному из положений винта XOR A ;формируем звуковой сигнал, OUT (254),A ; имитирующий работу двигателя CALL CHECK ;проверка достижения границ экрана LD A,16 OUT (254),A LD A,SPRXOR ;задаем режим вывода спрайта LD HL,WERT1 ;устанавливаем адрес спрайта PUSH BC PUSH HL CALL PTBL ;выводим вертолет в первой фазе LD BC,5 ;задаем задержку между фазами CALL 7997 ; вращения винта POP HL POP BC LD A,SPRXOR ;режим вывода спрайта PUSH BC PUSH HL CALL PTBL ;стираем вертолет в первой фазе POP HL POP BC XOR A OUT (254),A ; Вывод вертолета во второй фазе LD A,16 ;звуковой сигнал OUT (254),A LD A,SPRXOR ;режим вывода спрайта LD HL,WERT2 ;устанавливаем адрес спрайта ; с другим расположением винта PUSH BC PUSH HL CALL PTBL ;выводим спрайт во второй фазе LD BC,5 CALL 7997 POP HL POP BC LD A,SPRXOR ;режим вывода спрайта PUSH BC PUSH HL CALL PTBL ;стираем с экрана спрайт во второй фазе POP HL POP BC JR KEY ; Подпрограмма проверки границ экрана CHECK LD A,C AND A ;сравниваем координату X вертолета ; с заданной левой границей экрана JR NZ,CONT1 INC C CONT1 CP 29 ;сравниваем координату X вертолета ; с заданной правой границей экрана JR NZ,CONT2 DEC C CONT2 LD A,B ;задаем верхнюю границу экрана AND A ;сравниваем координату Y вертолета ; с заданной верхней границей экрана JR NZ,CONT3 INC B CONT3 CP 21 ;сравниваем координату Y вертолета ; с заданной нижней границей экрана RET NZ DEC B RET ; Подпрограмма чтения данных из портов клавиатуры и джойстика KBDJOY IN A,(31) ;опрашиваем порт джойстика LD E,A ;запоминаем полученные биты ; Проверяем, подключен ли порт джойстика (ручку невозможно ; повернуть сразу и вправо и влево - если оба бита установлены, ; порт не подключен) AND 3 CP 3 JR NZ,KBDJ1 ;если да, переходим к опросу клавиатуры LD E,0 ; иначе очищаем коллектор битов KBDJ1 LD HL,DKEY ;адрес блока данных клавиатуры KBDJ2 LD C,(HL) ;младший байт адреса порта INC C ;проверка на 0 (конец блока данных) DEC C LD A,E ;значение коллектора в аккумулятор RET Z ;выход, если конец данных INC HL LD B,(HL) ;старший байт адреса порта INC HL IN A,(C) ;читаем из порта CPL ;инвертируем биты AND (HL) ;проверяем конкретный бит INC HL JR Z,KBDJ3 LD A,(HL) ;если бит установлен, читаем код направления OR E ; и объединяем с коллектором LD E,A KBDJ3 INC HL JR KBDJ2 ;продолжаем чтение ; Данные управляющих клавиш: ; первое число - младший байт порта ; второе число - старший байт порта ; третье число - маска бита ; четвертое - код направления (аналогично кодам джойстика) DKEY DEFB #FE,#FB,1,8 ;Q - вверх DEFB #FE,#FD,1,4 ;A - вниз DEFB #FE,#DF,2,2 ;O - влево DEFB #FE,#DF,1,1 ;P - вправо DEFB #FE,#7F,4,16 ;M - «огонь» DEFB 0 ;метка конца блока данных PTBL ......... ; Заголовок первой фазы спрайта «вертолет» WERT1 DEFB 7 DEFB 0,1,6,1,0,6,1,1,6,1,2,6 DEFB 2,0,6,2,1,6,2,2,6 ; Данные первой фазы спрайта «вертолет» DEFB 0,0,4,28,56,32,24,24 DEFB 0,0,0,1,2,2,4,4 DEFB 60,60,255,129,66,36,36,24 DEFB 0,0,0,128,64,64,32,32 DEFB 7,2,1,0,1,2,4,14 DEFB 255,36,36,255,0,0,0,0 DEFB 224,64,128,0,128,64,32,112 ; Заголовок второй фазы спрайта «вертолет» WERT2 DEFB 9 DEFB 0,0,6,0,1,6,0,2,6 DEFB 1,0,6,1,1,6,1,2,6 DEFB 2,0,6,2,1,6,2,2,6 ; Данные второй фазы спрайта «вертолет» DEFB 0,0,30,127,255,127,14,0 DEFB 0,0,0,129,231,129,24,24 DEFB 0,0,120,254,255,254,112,0 DEFB 0,0,0,1,2,2,4,4 DEFB 60,60,255,129,66,36,36,24 DEFB 0,0,0,128,64,64,32,32 DEFB 7,2,1,0,1,2,4,14 DEFB 255,36,36,255,0,0,0,0 DEFB 224,64,128,0,128,64,32,112