2. ЭЛЕМЕНТАРНАЯ ГРАФИКА В МАШИННЫХ КОДАХ
Когда мы слышим сочетание слов "компьютерная графика", то
нам сразу представляются сложные многоцветные трехмерные изоб-
ражения и желательно, чтобы при этом что-то двигалось, лучше,
если побольше, побыстрее и как можно более плавно.
Все это, конечно же так, но начинается компьютерная графи-
ка, тем не менее, не с этого. Когда Вы даете команду компьютеру
PRINT "*" и он это делает, Вы уже работаете с графикой, хотя об
этом и не задумывались. Можно считать так, что как только Вы
делаете что-то, что приводит к изменению изображения на экране
Вашего телевизора или монитора, Вы уже занимаетесь компьютерной
графикой, особенно если Вам понятно, почему эти изменения
происходят именно так, а не иначе и в какой-то степени можете
ими управлять.
Итак, если Вы из БЕЙСИКа напечатаете звездочку на Вашем
экране, то Вам потребуется изрядная доля воображения для того,
чтобы считать, что это компьютерная графика и убедить своих
друзей, что у Вас есть дизайнерские способности. А что, если Вы
сделаете то же самое из машинного кода? А если при этом Вы ее
не напечатаете, а нарисуете по точкам? Все дело принимает
совсем другой оборот, не правда ли? Итак, все дело не в терми-
нах, а в целенаправленности Ваших усилий, в способности заду-
мать что-то и найти способы, как это реализовать.
Если Вы не нашли до сих пор достаточно времени, чтобы ос-
воить программирование в машинных кодах, то Вы не только сузили
круг своих технических возможностей, но и ограничили возможнос-
ти для самовыражения, для дальнейших творческих поисков. Наш
пример с печатанием звездочки здесь как раз и служит для того,
чтобы дать представление о том, что и в графике значение имеет
не только конечный результат, но и путь, который к нему привел.
В этой главе мы попробуем дать Вам те основы, которые не-
обходимы для того, чтобы начать эксперименты с графикой из ма-
шинного кода. Как и в любом другом вопросе, связанном с про-
граммированием на "Спектруме", мы не надеемся дать полную и
исчерпывающую картину. Как и всегда, "ИНФОРКОМ" видит главную
задачу в том, чтобы помочь сделать первый шаг, а дальше Вы сами
раскроете свои таланты.
2.1 ПОНЯТИЕ О КАНАЛАХ И ПОТОКАХ
Первое, что нам потребуется, это разобраться с концепцией
потоков и каналов "Спектрума". Для многих начинающих пользова-
телей "Спектрума" такие понятия, как каналы и потоки могут зву-
чать, как непонятные жаргонные обозначения, но на самом деле за
ними скрывается интересная концепция, которая позволит Вам
взять от компьютера то, что другими способами взять не так
просто.
Работая в БЕЙСИКе, Вы можете и не задумываться о потоках и
каналах, а вот программируя в машинных кодах, без них не обой-
тись.
Можете представить себе, что канал - это некоторое техни-
ческое устройство, используемое для ввода/вывода информации.
Надо, правда, оговориться, что канал - не всегда техническое
устройство. Каналом, например, может быть файл на диске или,
скажем, в памяти Вашего компьютера. В файл ведь тоже можно
заносить информацию и можно ее оттуда считывать.
Проще всего представить концепцию каналов и потоков на
примере морского побережья с многочисленными заливами и бухта-
ми. Со стороны суши в них впадают многочисленные ручьи и реки.
Так вот, эти заливы и бухты - это каналы, а те ручьи и реки,
которые в них впадают - это потоки, подключенные к каналам.
Те, кто более глубоко заинтересуются этой концепцией,
могут найти информацию в нашем издании "ZX-РЕВЮ" (N12, 1991г.,
с.227; N 5,6, 1992 г.,с. 111), мы же здесь рассмотрим этот во-
прос в минимально необходимом объеме.
Стандартными каналами "Спектрума" для вывода информации
являются каналы "К" - нижние две строки экрана (системное ок-
но), "S" - главная часть экрана и "P" - стандартный "ZX-прин-
тер".
К этим каналам стандартно подключены потоки:
- поток #0 - к каналу "К";
- поток #1 - тоже подключен к каналу "K";
- поток #2 - подключен к каналу "S";
- поток #3 - к каналу "P".
Таким образом, оказываются идентичными следующие команды
ввода/вывода:
PRINT #0 "Hello"; A$ - то же самое, что и INPUT "Hello"; A$
PRINT "Hello" - то же самое, что и PRINT #2 "Hello"
LPRINT "Hello" - то же самое, что и PRINT #3 "Hello"
Номер, стоящий после знака # в вышеприведенных примерах,
является номером потока. Поскольку эти потоки подключены стан-
дартно и переподключены быть не могут, мы программируем на
БЕЙСИКе и используем операторы INPUT, PRINT, LPRINT без указа-
ния номера потока.
Это то, что касалось стандартных каналов и потоков, но они
могут быть и нестандартными. Так, если Вы работаете в локальной
сети, то сеть становится еще одним каналом, к которому Вы под-
ключите поток.
Вы знаете, что "Спектрум" может в любой момент времени вы-
полнять только одно дело. Например, либо он печатает на экране,
либо на принтере. Одновременно выдавать информацию и туда и ту-
да он не может, поэтому в любой момент времени задействован
только один канал ввода/вывода и, соответственно, только один
поток, связанный с ним. Этот канал и этот поток называются
текущими. В большинстве случаев, когда Вы работаете с компью-
тером, текущим является канал "K", несколько реже - канал "S".
Программируя в машинном коде, переключаться с канала "K"
на "S" (и наоборот) очень просто. Информацию о том, какой канал
является текущим в данный момент, несет нулевой бит системной
переменной TVFLAG (5С3СH - 23612). Когда он выключен (равен
нулю), используется канал "S", а когда включен - "K".
Две небольшие процедуры продемонстрируют разницу в их ис-
пользовании.
Демо_S
213C5C LD HL,TVFLAG
3600 LD (HL),00 ; Выключили бит 0 систем-
; ной переменной TVFLAG.
3E2A LOOP LD A,42 ; Загрузили в аккумулятор
; число 42 (код символа
; "*".
D7 RST 10H ; Выдали на печать по те-
; кущему каналу то, что
; находится в аккумулято-
; ре.
18FB JR LOOP ; Возврат для повтора.
Демо_K
213C5C LD HL,TVFLAG
3601 LD (TVFLAG),01 ; Включили бит 0 систем-
; ной переменной TVFLAG.
3E2A LOOP LD A,42
D7 RST 10H ; Печать символа "*".
18FB JR LOOP ; Возврат для повтора.
Не менее просто переключаться с каналов "S" или "K" на
канал "P". Здесь тоже достаточно изменить один бит. Это первый
бит системной переменной FLAGS (5C3BH - 23611). Он должен быть
выключен для каналов "S" и "K", но включен для канала "P".
Демо_P
213B5C LD HL,FLAGS
CBCE SET 1,(FLAGS) ; Включили бит 1 систем-
; ной переменной FLAGS.
0600 LD B,00 ; Обнуление счетчика
; (подготовка к печати
; 256-ти символов).
3E2A LOOP LD A,42
D7 RST 10H ; Печать символа "*".
10FB DJNZ LOOP ; Возврат для повтора,
; пока счетчик не достиг-
; нет нуля.
С9 RET ; Возврат в вызывающую
; программу.
Вы можете также изменить текущий канал, переключившись на
другой поток. Это можно сделать вызовом процедуры ПЗУ, называ-
ющейся CHAN_OPEN и находящейся по адресу 1601H. Перед тем, как
ее вызывать, следует в аккумуляторе установить номер желаемого
потока.
3E02 LD A,02 ; Ввели номер желаемого
; потока.
CD0116 CALL 1601H ; Сделали его текущим.
3E2A LOOP LD A,42
D7 RST 10H
18FB JR LOOP
Нам необходимо знать эти азы потому, что если мы исполь-
зуем для печати из машинного кода команду процессора RST 10H,
то должны иметь в виду, что она выдает информацию ТОЛЬКО В
ТЕКУЩИЙ КАНАЛ. Прежде, чем Вы дадите компьютеру команду, что бы
Вы хотели, чтобы он напечатал, надо сначала определиться, куда
он будет это печатать и как.
Команду RST 10H Вы можете использовать для печати любых
символов, будь то символ стандарта ASCII или графический сим-
вол. Это может быть управляющий символ и даже токен ключевого
слова стандартного БЕЙСИКа. Поместите код того, что хотите на-
печатать, в аккумулятор и дайте команду RST 10H. При этом обыч-
ные символы займут одно знакоместо, управляющие коды сделают
то, что им положено, а токены ключевых слов будут развернуты и
займут столько знакомест, сколько букв в этом ключевом слове.
Вам надо также знать, что команда RST 10H никогда не портит
содержимое регистров процессора, кроме BC', DE' (альтернатив-
ные) и аккумулятора, который портит не всегда.
2.2 ПЕЧАТЬ НА ЭКРАН ИЗ МАШИННОГО КОДА
2.2.1. Печать чисел.
1. Целые числа от 0 до 9. Выполнить печать целого числа
от 0 до 9 можно двумя способами.
Во-первых, Вы можете напечатать его обычным способом, как
и любой другой символ. Для этого поместите в аккумулятор
процессора его код (код 0 - 48 (30H),... код 9 - 57 (39H)) и
дайте команду RST 10H.
Во-вторых, можно это число напечатать, загрузив в аккуму-
лятор не его код, а само число, но в этом случае печать следует
выполнять не командой RST 10H, а вызовом специально для этого
предназначенной процедуры ПЗУ - CALL 15EFH (десятиричный адрес
- 5615), она называется OUT_CODE. Это, конечно, удобнее, но при
своей работе эта процедура портит регистр E (имейте это в
виду).
Возможен и промежуточный вариант, когда Вы засылаете в
аккумулятор само число, а не его код, затем прибавляете к нему
30H (получаете его код) и затем даете RST 10H.
LD A,N
ADD A,30H
RST 10
По расходу памяти это то же самое, что и CALL OUT_CODE, но
не портит регистр E.
2. Целые числа от 0 до 9999.
Для печати целых чисел, меньших чем 10000, введите это
число в регистровую пару BC и вызовите процедуру ПЗУ OUT_NUM_1.
Она находится по адресу 1A1BH (6683).
Если Ваше число содержится в виде двух байтов в известном
Вам адресе памяти, то Вы можете воспользоваться процедурой ПЗУ
OUT_NUM_2 (1A28H = 6696). В этом случае перед вызовом процедуры
надо в регистровую пару HL заслать адрес, в котором находится
Ваше число.
И в том и в другом случае, если Вы попробуете через эти
процедуры распечатать число, которое больше 9999, результат
будет неверным.
3. Целые числа от 0 до 65535.
В этом случае Ваше число тоже должно быть помещено в
регистровую пару BC, но в отличие от предыдущего случая
необходимо делать не один вызов процедур ПЗУ, а два.
Сначала оно должно быть конвертировано в интегральную (пя-
тибайтную форму) и помещено на стек калькулятора. Это делается
вызовом процедуры STACK_BC (CALL 2D2BH = 11563). И только после
этого оно может быть напечатано, как действительное число с
плавающей точкой. Это выполняется вызовом процедуры PRINT_FP,
расположенной по адресу 2DE3H (11747).
4. Отрицательные целые числа.
Знак "минус" имеет код 2DH. Поместите его в аккумулятор,
выполните RST 10H и абсолютную величину числа печатайте, как
показано выше.
5. Произвольные действительные числа (числа с плавающей
точкой).
Такое число занимает 5 байтов и хранить его ни в каком ре-
гистре, ни в регистровой паре невозможно. В этом случае Вами
должны быть приняты меры для того, чтобы предварительно размес-
тить его на вершине стека калькулятора. Когда это сделано, вы-
зов процедуры PRINT_FP (2DE3H=11747) напечатает его на экране.
Здесь надо сделать пару предупреждений для тех, кто рабо-
тает с калькулятором "Спектрума", "зашитым" в ПЗУ.
Во-первых, после работы PRINT_FP число со стека калькуля-
тора снимается и, если Вам оно еще может потребоваться, то по-
заботьтесь предварительно продублировать вершину стека кальку-
лятора (соответствующая команда в сводке команд калькулятора
имеется - см. "Первые шаги в машинном коде", М.: "ИНФОРКОМ",
1990г.,1992г.).
Во-вторых, в процедурах ПЗУ, обслуживающих калькулятор,
есть ошибка. Она заключается в том, что это число при вызове
PRINT_FP снимается со стека не всегда. Если Ваше действительное
число находится в диапазоне от -1 до +1 (ноль исключается), то
вместо Вашего числа на вершине стека остается 0. Обращайте на
это внимание.
2.2.2. Печать символьных строк.
У Вас есть по крайней мере три способа печатать текстовые
сообщения, если Вы работаете в машинном коде. Но во всех случа-
ях этот текст должен храниться в памяти компьютера и начинаться
с известного Вам адреса.
Самый простой метод состоит в следующем. Регистровая пара
DE должна содержать адрес, с которого начинается Ваша символь-
ная строка, а в регистровой паре BC необходимо предварительно
установить длину этой строки. Печать выполняется вызовом проце-
дуры PR_STRING, которая находится по адресу 203СH (8252).
Во-вторых, символьная строка может быть Вами получена в
результате работы встроенного калькулятора. В этом случае она
находится на вершине стека калькулятора и может быть напечатана
прямо оттуда вызовом процедуры ПЗУ PR_STR_1 (адрес 2036H=8246).
Третья возможность, самая мощная, и именно она применяется
в большинстве случаев в игровых, прикладных и системных про-
граммах.
У Вас может быть целый набор из N различных сообщений. И
Вы, допустим, хотите напечатать k-ое сообщение. Тогда можете
действовать следующим образом:
- установите в аккумуляторе число k-1;
- установите в регистровой паре DE адрес, указывающий на
байт, находящийся перед первым байтом самого первого сообщения
из Вашей таблицы сообщений. Имейте в виду, что этот байт должен
быть больше или равен 80H, но меньше, чем FFH, т.е. старший
(седьмой) бит в этом байте должен быть включен (он явится мар-
кером начала текстового сообщения);
- вызовите процедуру PO_MSG (0C0AH=3082) и Ваше сообщение
будет напечатано на экране.
Впрочем, у этого метода есть ряд ограничений и требований.
Их необходимо также иметь в виду при программировании:
- недопустимо использование графических символов или
токенов ключевых слов БЕЙСИКа, т.е. коды печатаемых символов
должны быть менее 128 (80H). Впрочем, можно ведь и поменять
символьный набор, заменив на время символы ASCII на нужные Вам
графические шаблоны;
- во-вторых, Вам надо при заполнении таблицы сообщений
сделать так, чтобы последеий символ каждого сообщения имел
включенный 7-ой бит, тем самым он будет служить маркером конца
i-го сообщения и компьютер всегда по номеру, заданному в
аккумуляторе, найдет нужное Вам сообщение;
- в таблице не может быть пустых строк.
2.2.3. Полезные советы.
Работая с графикой из машинного кода, Вам надо иметь в
виду некоторые особенности, связанные с работой встроенного
калькулятора. Дело в том, что он не вполне свободен от разно-
образных ошибок и неточностей, а они могут доставить головную
боль начинающему программисту. Совсем другое дело, когда он
предупрежден.
Итак, во-первых, если Вам необходимо выдать на экран сим-
волы блочной графики (коды с 80H по 8FH) - эти символы распо-
ложены на цифровом ряду клавиатуры, - то коррумпируются (пор-
тятся) системные переменные, расположенные в адресах с 5C92H
(23698) по 5C99H (23705). Те, кто читал "Первые шаги в машинном
коде", знают, что здесь хранятся две первые ячейки памяти
встроенного калькулятора (из шести стандартных). Это ячейки M0
и M1. Почти во всех случаях Вам эта порча безразлична, но если
Вы активно работаете с калькулятором и именно они Вам и нужны,
то примите меры по сохранению их значений в другом месте, на-
пример на стеке калькулятора или в других ячейках.
Во-вторых, при печати чисел мы довольно активно пользова-
лись процедурой ПЗУ PRINT_FP, которая распечатывает содержимое
вершины стека калькулятора, а она во время своей работы "пор-
тит" все шесть ячеек памяти калькулятора, т.е. все содержимое
системной переменной MEMBOT с адреса 5C92H (23698) по адрес
5CAFH (23727). Опять же для Вас это почти всегда безразлично,
за исключением тех редких случаев, когда Вы оставили там на
хранение данные без присмотра.
А вот третий случай довольно часто встречается у тех, кто
много работает со встроенным калькулятором. Дело в том, что в
таблице системных переменных есть переменная под названием MEM,
она расположена в адресе 5C68H (23656) и занимает 2 байта. В
ней хранится адрес, по которому расположена память калькулято-
ра, т.е. адрес системной переменной MEMBOT. Если Вам достаточно
тех шести ячеек памяти калькулятора, которые есть стандартно,
то нет проблем. А если же Вам их мало, то Вы создаете их по-
больше, для чего меняете адрес MEMBOT и, естественно, указание
на нее, содержащееся в MEM. Так вот, после такой замены проце-
дура PRINT_FP правильно работать не сможет.