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

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

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

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

ГЛАВА ВОСЬМАЯ,
в которой рассказывается о том, как управлять ходом игры

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

УПРАВЛЕНИЕ С ПОМОЩЬЮ КЛАВИАТУРЫ

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

ПолурядDECHEXBIN
Space…B327667FFE01111111 11111110
Enter…H49150BFFE10111111 11111110
P…V57342DFFE11011111 11111110
0…661438EFFE11101111 11111110
1…563486F7FE11110111 11111110
Q…T64510FBFE11111011 11111110
A…G65022FDFE11111101 11111110
CS…V65278FEFE11111110 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, в любой момент можно очистить экран и начать рисовать новую «картину».

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

Рис. 8.1. Ввод имени играющего

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