##
ГЛАВА ВОСЬМАЯ,

в которой рассказывается о том, как управлять ходом игры

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

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

При разработке игровых программ немыслимо обойтись без опроса клавиатуры. Действительно, чтобы во время игры управлять спрайтами, перемещая их хотя бы по четырем направлениям, программа обязана безошибочно различать одну из четырех нажатых клавиш. Например, O должна соответствовать движению влево, P - вправо, Q - вверх и A - вниз. В Бейсике, как вы помните, для этой цели мы пользовались функцией `INKEY$`, а одна из возможных программ, «узнающих», к примеру, символ P, могла бы выглядеть так:

    100 IF INKEY$<>"P" THEN GO TO 100

Соответствующий ей фрагмент ассемблерной программы имеет следующий вид:

           XOR   A
           LD    (23560),A   ;в системную переменную LAST_K (код
                             ; последней нажатой клавиши) заносится 0
    LOOP   LD    A,(23560)   ;из этой системной переменной
                             ; считывается значение кода нажатой клавиши
           CP    "P"         ;сравнение двух кодов - находящегося
                             ; в регистре A и символа P
           JR    NZ,LOOP     ;если результат сравнения не равен 0,
                             ; то переход на метку LOOP, если 0,
           RET               ; то выход

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

    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"

После того как вы нажмете клавишу 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

    ; Адрес 32766=127ґ256+254, в B заносится адрес полуряда,
    ;  а в C - адрес порта (254).
    KEY    LD    BC,32766
           IN    A,(C)
    ; Один из способов проверки данного бита - у отпущенной клавиши
    ;  бит установлен (1), у нажатой сбрасывается в 0
           BIT   2,A
           JR    NZ,KEY
           RET

Второй способ принципиально не отличается от первого. Перед чтением в аккумулятор помещается старший байт адреса соответствующего порта, а младший байт задается в явном виде в команде IN:

    KEY    LD    A,#7E       ;в аккумулятор заносится старший байт
                             ; адреса порта #7EFE
           IN    A,(254)     ;считывание из порта (254 или #FE -
                             ; младший байт адреса)
           BIT   2,A         ;проверка нажатия третьей от края
                             ; клавиши (M)
           JR    NZ,KEY
           RET

Рассмотрим программу, в которой при нажатии клавиш Q, A, O и P изменяются координаты точки на экране. Сами точки будем ставить в бейсик-программе, которую напишем позже, но подразумевая использование процедуры из Бейсика, воспользуемся для передачи координат точки, как и раньше, областью буфера принтера, определив адрес передаваемых параметров константой XY.

           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        ; иначе очищаем экран

Чтобы увидеть эту процедуру в действии, необходимо дополнить ее небольшой бейсик-программкой, задача которой состоит только в том, чтобы ставить на экране точку в соответствии с координатами (XY), полученными в ассемблерной программе.

    100 POKE 23296,100: POKE 23297,100
    110 PLOT PEEK 23296, PEEK 23297
    120 RANDOMIZE USR 60000: GO TO 110

Попробуйте ее ввести и исполнить, а затем понажимайте клавиши 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
           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

Эту программу можно рассматривать как вполне независимый кадр заставки. Если вы решите использовать ее в своей собственной игре, единственное, что вам потребуется, это перенести после выхода введенное имя из строки NAME в таблицу «рекордов». И, конечно же, вам нужно будет проследить, чтобы имена меток, задействованные в приведенной программе не повторялись в вашей игре. Естественно, что при необходимости наименования меток можно и заменить.

### УПРАВЛЕНИЕ ДЖОЙСТИКОМ

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

Наверное, вам известно, что в Бейсике определить положение ручки джойстика можно с помощью функции `IN 31`. В ассемблере для этих же целей удобнее всего применять команду IN A,(31), после выполнения которой в аккумуляторе появится некоторое число, отдельные биты которого и определяют «статус» джойстика. Значения имеют не все биты, а только 5 младших, причем, в отличие от клавиатуры, в нейтральном положении все биты сброшены в 0 (конечно, если порт джойстика вообще подключен), а установка какого-то бита в 1 означает поворот ручки или нажатие кнопки «огонь». При диагональном наклоне ручки будут установлены сразу два бита. В табл. 8.2 показано соответствие пяти младших битов, получаемых в аккумуляторе после выполнения команды IN A,(31), направлениям наклона ручки джойстика и нажатию кнопки «огонь». Три старших бита не определены, поэтому в таблице они заменены знаками вопроса.

Таблица 8.2. Значения битов порта джойстика.

Направление | Код
Вправо | ???00001
Влево | ???00010
Вверх | ???00100
Вниз | ???01000
Огонь | ???10000

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

           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        ;при нажатии кнопки «огонь»
                             ; экран очищается

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

           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

### СОВМЕСТНОЕ УПРАВЛЕНИЕ КЛАВИАТУРОЙ И ДЖОЙСТИКОМ

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

Глава 7 Глава 9

Оглавление