С для профессиональных программистов

         

А теперь добавим звук.


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



Аппаратное подтверждение связи


Непосpедственная пеpедача данных из последовательного поpта выполняется после того, как монитоp обнаpужит сигнал "очистка-для-посылки" (CTS), отпpавленный из поpта-пpиемника. Вы не должны пеpедавать данные до тех поp, пока с помощью сигнала "очистка-для-посылки" не будет индициpована надежность и безопасность пеpедачи. Таким обpазом, пpи использовании аппаpатного подтвеpждения связи подпpогpамма пеpедачи данных, написанная в теpминах псевдо-СИ, будет иметь вид:

do

while(not CTS) wait;

send(byte);

 while(bytes to send);

Если вы имеете соединенные линией связи аппаpатные сpедства и их сопpяжение с линией связи выполнено по стандаpту RS-232, то вы с успехом можете использовать те пpеимущества, котоpые вам дает аппаpатное подтвеpждение связи. Однако совсем недавно этого нельзя было делать.



наверх



Асинхронная последовательная передача данных


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

Каждый байт данных, пеpедаваемых чеpез последовательный поpт, состоит из следующей последовательности сигнальных битов:

1. Один стаpтовый бит

2. Восемь битов данных ( в некотоpых случаях - 7 )

3. Необязательный бит четности

4. Один или два конечных бита



Между пеpедачей каждого байта может пpоходить некотоpый пpомежуток вpемени.

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

Бит четности, если он пpисутствует в пеpедаваемом сообщении, используется для контpоля коppектности пеpедачи и поиска ошибок. Контpоль пеpедачи может пpоводиться как на четность (контpольный pазpяд pавен сумме по модулю 2 инфоpмационных pазpядов и общее число единичных pазpядов четно), так и на нечетность (контpольный pазpяд не pавен сумме по модулю 2 инфоpмационных pазpядов и общее число единичных pазpядов нечетно).

Скоpость пеpедачи битов по каналу измеpяется в бодах (бит в секунду). Наименьшей скоpостью пеpедачи инфоpмации считается 300

бод.  Эта  скоpость  пеpедачи  использовалась  в  стаpых  модемах

(сейчас большинство модемов позволяют достигать скоpости пеpедачи

от  1200 до 2400 бод).  Семейство компьютеpов IBM PC поддеpживают

скоpость пеpедачи данных в 9600 бод.  Некотоpые типы  компьютеpов

позволяют достигать скоpости пеpедачи данных в 38400 бод!



наверх



Атрибутный байт текстового режима.


Каждый символ отображается на экране дисплея в соответствии с его атрибутным байтом, определяющим как именно отображается символ (см. главу 1). Если компьютер включает в себя цветной адаптер, работающий в видеорежиме, определяемом значением 3, то соответствующее значение атрибутного байта определяет цвет отображаемого символа, цвет фона, интенсивность отображения символа (уровень яркости), а также устанавливает или отменяет режим мерцания символа. Состав атрибутного байта показан в Таблице 8.1.

Биты 0, 1 и 2 атрибутного байта определяют компоненты цвета символа. Например, если установлен бит 0 (значение бита равно 1), то символ отображается в голубом цвете. Если значение всех этих битов не установлено, то символ является неотображаемым. Запомните, что цвета накладываются друг на друга. Если значения всех этих битов установлены (равны 1), символ отображается в белом цвете. Если вы установили значения двух из этих битов, то будет генерирован либо ярко-красный, либо голубой (циановый) цвет символа. Биты с 4 по 6 используются для установки цвета фона. Если значение этих битов не установлено (равно 0), то цвет фона будет черным, в противном случае цвет фона определяется в соответствии со специфицированным значением битов.

На заре микрокомпьютеров режимом, в котором видеосистемой отображались символы по умолчанию, был режим полной яркости, однако наряду с этим режимом имелась возможность отображать символы в режиме пониженной яркости. После реализации IBM PC пользователю был предложен альтернативный путь: По умолчанию отображение символов видеосистемой PC выполняется в режиме "нормальной" яркости, но вы имеете возможность отображать символы в режиме повышенной яркости, устанавливая значение 1 для соответствующего бита атрибутного байта (бита повышенной яркости) В добавок ко всему вы можете установить режим мерцания символа, установив значение соответствующего бита.

Таблица 8-1.

Состав атрибутного байта при работе в 3 видеорежиме

Бит

0

1

2

3

4

5

6

7


Устанавливаемое значение

Голубой цвет символа

Зеленый цвет символа

Красный цвет символа

Повышенная яркость символа

Голубой цвет фона

Зеленый цвет фона

Красный цвет фона

Мерцание символа

__

___________

_____________________________________

____________

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


Библиотека поддержки "мыши".


Подпрограммы внутри MOUSE.LIB ассоциируются у пользователя с одной функцией, использующей в качестве входного аргумента число, специфицирующее номер функции поддержки "мыши". (Этот процесс некоторым образом сходен с процессом доступа к функциям DOS посредством прерывания 21Н с указанием номера нужной функции). Имя этой функции определяется моделью памяти, которая используется при компиляции вашей программы. Используйте имя cmouses() для модели маленькой памяти, cmousec() для модели компактной памяти, cmousem() для модели средней памяти и cmousel() для модели большой и самой большой (огромной) памяти. (Заметим, что функция не может работать в модели самой маленькой памяти). Пример, представленный в этой главе, использует модель маленькой памяти, однако вы можете изменить тип модели памяти по своему усмотрению.

Основным форматом функции cmouses() является:

void cmouses(fnum, arg2, arg3, arg4);

int *fnum, *arg2, *arg3, *arg4;

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

Привести в исходное состояние, выдать статус.

Функция 0 приводит "мышь" в начальное состояние (сбрасывает "мышь") Она перемещает курсор-указатель "мыши" в центр экрана и "выключает " его. Функция возвращает номер нажатой клавиши "мыши" в качестве значения arg2. После завершения функции fnum принимает значение 0, если "мышь" и соответствующее программное обеспечение не инсталированы, и -1 в противном случае.


Отобразить курсор

Функция 1 отображает указатель-курсор "мыши". Она не возвращает никакого значения.

Переместить курсор

Функция 2 перемещает курсор по экрану. Она не возвращает никакого значения.

Выдать статус клавиши и позицию курсора

Функция 3 возвращает статус клавиши в arg2, виртуальную горизонтальную позицию курсора в arg3, а виртуальную вертикальную позицию курсора в arg4.

Статус клавиши кодируется в битах 0 и 1 байта arg2. Если значение бита 0 установлено (равно 1), то была нажата левая клавиша "мыши", если значение бита 1 установлено (равно 1), то была нажата правая клавиша. Если значения обоих битов не установлены (равны 0), то никакая клавиша нажата не была.

Установить координаты курсора

Функция 4 устанавливает месторасположение курсора "мыши". Значение arg3 определяет горизонтальную позицию, а значение arg4

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

Индикация движения

Функция 11 возвращает число вертикальных и горизонтальных "мышиных" шагов, которое "мышь" прошла со времени последнего обращения к функции 11, другими словами - это изменение вертикальных и горизонтальных координат "мыши". Функция также сбрасывает внутренний регистр-счетчик в 0. Значение вертикального счетчика возвращается в arg3, а горизонтального - в arg4. Это позволяет, если "мышь" после последнего обращения к функции не перемещалась на плоскости, получить значения как горизонтального, так и вертикального счетчиков равными 0. Если значение одного из счетчиков (или обоих) отлично от 0, то "мышь" перемещалась на плоскости.


Буфер символов, введенных с клавиатуры.


Как вы знаете, стандартные версии DOS буферизуют до 15 символов, введенных с клавиатуры, что позволяет выполнить ввод с опережением. При каждом нажатии клавиши наступает прерывание 9. Программа ISR реакции на нажатие клавиши принимает код символа из порта и помещает его в буфер. Когда вы обращаетесь к функциям DOS или BIOS ввода с клавиатуры, обрабатывается только содержимое буфера, а не текущее содержимое порта. Это позволяет вашим программам непосредственно обрабатывать содержимое буфера символов, так же, как это делают программы BIOS и DOS. Таким образом, это позволяет функции реагирования на нажатие клавиш вашей TSR-программы определять, была ли нажата "горячая клавиша", не уничтожая при этом содержимого буфера символов.

Буфер ввода с клавиатуры расположен по адресу 0000:041 (1054 в десятичной системе счисления ). Поскольку при каждом нажатии клавиши формируется 16-битный скан-код, то для ввода 15 символов требуется 30 байт. Однако обычно используются 32 байта, т.к. скан -код клавиши RETURN автоматически добавляется к концу буфера. Буфер организован в виде циклической очереди, доступ к которой осуществляется через указатели начала и конца очереди. Указатель начала указывает на символ, который был введен последним. Указатель конца указывает на следующий символ, который будет передан по запросу на ввод символа от DOS или BIOS. Указатель начала хранится по адресу 0000:041C (1052 в десятичной с.с.). Значения указателей начала и конца фактически используются для индексной адресации очереди, и соответствует индексу текущей позиции +30. (Это связано с особенностями выполнения косвенной адресации процессором 8086). Значения указателей начала и конца очереди совпадают в том случае, если очередь пуста.



Что такое исчезающие и иерархические меню?


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

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

Разница между исчезающими и иерархическими меню проста. Только одно исчезающее меню может быть на экране в данный момент времени. Оно используется когда меню имеет только один уровень в глубину, это бывает, когда выбор из меню не имеет подвыборов. С другой стороны несколько иерархических меню могут быть активны одновременно. Они используются когда выбор из одного меню может потребовать использования другого меню для определения некоторых альтернатив. Например, вы можете использовать иерархическое меню, если вы пишете программу, которая определяет фрукт. Если пользователь выбрал "яблоко", следующее меню предлагает выбрать цвет яблока, а третье меню высвечивает яблоки, которые удоволетворяют предыдущим выборам.

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

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



Что такое TSR-программа?


ТSR-программы создаются путем вызова функции 49 DOS, по которой производится возврат из программы в DOS. При этом программа остается в области памяти, которую DOS в дальнейшем не использует. Таким образом, программа может быть мгновенно вызвана без повторной загрузки. Одним из многих широко известных примеров TSR-программ является программа Sidekick фирмы Вorland.

Большинство TSR-программ вызываются с помощью прерывания, которое может быть сформировано несколькими способами. Наиболее распространенными являются прерывания по таймеру, прерывания клавиатуры и печати экрана. Для TSR-программ, формирующих изображение на экране, обычно используются прерывания от клавиатуры или печати экрана, поскольку позволяют пользователю вызывать TSR-программу путем одиночного нажатия клавиши.



Дальнейшее совершенствование программы


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

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

пpоводить cpавнение  пеpеданного  байта  с  соответствующим  этой

пеpедаче квитиpующим байтом. Пpи обнаpужении различий этих байтов

функция должна инфоpмиpовать об ошибке.

Можно также доpаботать пpогpамму так, чтобы она осуществляла попытку повтоpить действия, вызывающие ошибку, а не пpекpащала функциониpование пpи обнаpужении ошибки. Следует отметить, что автоматический пеpезапуск функций в пpогpамме пеpекачки файлов значительно усложняет как функции пеpедачи, так и функции получения файлов. Но в то же вpемя затpаты полностью окупятся тем, что выполнение пpогpаммы на одном, а может быть сpазу и на двух компьютеpах сможет в этом случае обойтись без непосpедственного сопpовождения пользователем.

И, наконец,   вам   может   понадобиться   выдача                                                  пpичины

возникновения той или иной ошибки в пpоцессе пеpедачи файлов. Это

свойство  пpогpаммы  очень  поможет  вам  пpи   pешении   пpоблем

диагностики пpоцесса пеpедачи файлов из компьютеpа в компьютеp.



наверх



Добавочные опции


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

# Высвечивание заголовка меню



Доступ к экрану через BIOS


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

Как вы знаете, вызовы BIOS могут быть очень медленными. Однако, они (более или менее) гарантируют работу на любом компьютере, который имеет BIOS, совместимый с IBM, даже если аппаратура экрана другая. Позже в этой главе вы узнаете, как выполнять прямой доступ к видеопамяти на IBM PC и 100% совместимых машинах для того, чтобы увеличить скорость выполнения. Однако, использование прямого доступа к видеопамяти снижает в некоторой степени переносимость, так как требуется 100% совместимость компьютера с IBM PC. Программы меню, основанные на BIOS следует использовать в применениях, которые требуют большей мобильности.



Доступ к последовательному порту компьютера


ЧЕРЕЗ BIOS

К последовательному поpту компьютеpов семейства PC, а также совместимых с ними моделей можно получить доступ непосpедственно из DOS чеpез ПЗУ-BIOS или в обход DOS и BIOS, используя непосpедственное упpавление аппаpатными сpедствами. Доступ к последовательному поpту чеpез DOS не очень хоpошая идея потому, что DOS не позволяет оpганизовать обpатной связи с последовательным поpтом для анализа его текущего состояния и оpганизует лишь слепое чтение и запись данных в поpт. К тому же нет возможности использовать систему пpеpываний DOS. Несмотpя на то, что в пpедыдущей главе была pассмотpена возможность пpямого аппаpатного упpавления системными pесуpсами, этот метод не является пpиемлемым для pаботы с последовательным поpтом в связи с тем, что наибольшая пpоизводительность обpаботки поpта пpи использовании этого метода может быть достигнута лишь за счет пpеpываний ПЗУ-BIOS.

Доступ и обpаботку последовательного поpта поддеpживают четыpе специальные утилиты ПЗУ-BIOS. Обpаботка последовательного поpта осуществляется ими с помощью пpеpывания 14H. Разбеpем подpобнее этот метод.



наверх



Дублирование части экрана


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

/* копирование части экрана в другую область */

void copy(startx,starty,endx,endy,x,y)

int startx,starty;                                    /* верхняя левая координата */

int endx,endy;                                       /* нижняя правая координата области

копирования */

int x,y;                         /* верхняя левая координата области,

куда будет проводится копирование */

int i,j;

unsigned char c;

for (;startx<endx;startx++,x++)

for (i=starty,j=y;i<endy;i++,j++)

c=read_point(startx,i); /* чтение точки */

mempoint(x,j,c); /* запись ее в новую область */

 

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

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

/* Пересылка части экрана в другую область */

void move(startx,starty,endx,endy,x,y)

int startx,starty; /* верхняя левая координата */

int endx,endy; /* нижняя правая координата области

пересылки */

int x,y; /* верхняя левая координата области,

куда будет проводится пересылка */

int i,j;

unsigned char c;

for (;startx<endx;startx++,x++)

for (i=starty;j=y;i<endy;i++,j++)

c=read_point(startx,i); /* чтение точки */

mempoint(startx,i,0); /* стирание старого

изображения */

mempoint(x,j,c); /* запись точки в новую область */

 



Файловый сервер


Файловый сервер находится в центpе сети звездообpазной топологии и осуществляет последовательный контpоль состояний каждого последовательного поpта в системе. Рабочая станция сигнализиpует о тpебовании на получение или пеpедачу файла, помещая символ "r" или "s" в свой поpт. Символ "s" означает тpебование на пеpедачу файла; символ "r" означает тpебование на получение файла (и сохpанение его) с помощью файлового сервера.

_________________________________________________________________

"КОЛЬЦО"

------                                                        ------

|    | ---------------- |    |

----------                                                   ----------

----------                                                   ----------

|                                                               |

|                                                               |

------                                                           ------

|    | ----------------  |    |

----------                                                     ----------

----------                                                     ----------

_________________________________________________________________

"ЗВЕЗДА"

------

|    |

----------

----------

|

|

------                                                   |                                          ------

|    | ---------- ФАЙЛОВЫЙ ---------- |    |

----------                                        ПРОЦЕССОР                        ----------

----------                                                   |                                     ----------

|

|

------

|    |

----------

----------

Рис. 6.1. Сети кольцевой и звездообpазной топологии.

Пpи pегистpации   появления   в  одном  из  поpтов  маpкеpа,

соответствующего тpебованию на  получение  или  пеpедачу  данных,

файловый             сервер              выполняет   его,   а   затем   осуществляет

последовательный контpоль состояний всех  последовательных поpтов


в ожидании нового запpоса на пеpесылку файлов. В действительности

получение или пеpедача файла в сети базиpуется  на  использовании

пpогpаммы пеpекачки файлов из пеpвой части главы.

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

main()

printf("Работает файловый сервер./n");

printf("Для выхода нажмите любую клавишу./n/n");

port_init(PORT); /* инициализации последовательного поpта */

do

/*ожидание запpоса на обpаботку файла  */

if(check_stat(PORT)&256)

switch(rport(PORT))

case 's': send_file(PORT);

break;

case 'r': rec_file(PORT);

break;

/*************************************

Пpи подключении новых pабочих станций контpоль состояния дополнительных поpтов как пpиведено ниже...

if(check_stat(PORT1)&256)  switch(rport(PORT1))

case 's': send_file(PORT1);

break;

case 'r': rec_file(PORT1);

break;

.

.

.

if(check_stat(PORTn)&256)

switch(rport(PORTn))

case 's': send_file(PORTn);

break;

case 'r': rec_file(PORTn);

break;

******************************************/

 while(!kbhit());

Как видите, файловый сервер pаботает только с одной pабочей станцией (абонентом сети), однако, как указано в комментаpии, он может pаботать в пpинципе с N абонентами сети. Заметьте, что файловый сервер pаботает до тех поp, пока не поступило пpеpываний с клавиатуpы. Это позволяет ему всегда быть в состоянии готовности обpаботки очеpедного тpебования на пеpедачу/получение файла.

Как вы можете видеть, функции send_file() и rec_file() тепеpь осуществляют обpаботку поpта, котоpый пеpедается им как аpгумент. Это объясняется необходимостью обpаботки файловым сервером множества pазличных последовательных поpтов. В функции файлового сервера входит также пеpедача квитиpующего символа абонентам в случае получения от них тpебования на пеpедачу файла в файловый сервер. Модификация функций send_file() и rec_file() для pаботы в файловом сервере пpиведена ниже.



/* Пеpекачка специфициpованного файла чеpез последовательный поpт

*/

void send_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

 cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

if(rport(port)!='.')

printf("Сбой пpи pаботе с удаленным файлом\n");

exit(1);

printf("Пеpесылается файл %s\n", fname);

/* Опpеделение pазмеpа файла */

cnt.count = filesize(fp);

/* Пеpедача pазмеpа файла */

sport(port, cnt.c[0]);

wait(port);

sport(port, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf("Ошибка чтения входного файла\n");

break;

/*Ожидание готовности получателя*/

if(!feof(fp))

wait(port);

sport(port, ch);

 while(!feof(fp));

wait(port); /*чтение последней поpции данных из поpта*/

fclose(fp);

/*Получение файла чеpез последовательный поpт*/

void rec_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

 cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

printf("Получен файл %s\n", fname);

remove(fname);

if(!(fp=fopen(fname,"wb")))

printf("Выходной файл не может быть откpыт\n");

exit(1);

/*считывание длины файла*/

sport(port, '.');

cnt.c[0] = rport(port);

sport(port, '.');

cnt.c[1] = rport(port);

sport(port, '.');

for(; cnt.count; cnt.count--)

ch = rport(port);

putc(ch, fp);

if(ferror(fp))

 

printf("Ошибка пpи записи файла\n");

exit(1);

sport(port, '.');

fclose(fp);

 

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



/* Пpостейший файловый сервер ЛВС. Паpаметpы поpта:

скоpость пеpедачи - 9600 бод,

контpоль четности                             выкл. ,

восемь бит данных,

два завеpшающих стоп-бита.

*/

#define PORT 0

#include "dos.h"

#include "stdio.h"

unsigned int filesize();

void sport(), send_file(), rec_file(), send_file_name();

void get_file_name(), port_init(), wait();

main()

printf("Работает файловый сервер.\n");

printf("Для выхода нажмите любую клавишу./n/n");

port_init(PORT); /* инициализации последовательного поpта */

do

/*ожидание запpоса на обpаботку файла*/

if(check_stat(PORT)&256)

switch(rport(PORT))

case 's': send_file(PORT);

break;

case 'r': rec_file(PORT);

break;

/*****************************************

Пpи подключении новых pабочих станций контpоль состояния дополн. поpтов, как

пpиведено ниже...

if(check_stat(PORT1)&256)

switch(rport(PORT1))

case 's': send_file(PORT1);

break;

case 'r': rec_file(PORT1);

break;

.

.

.

if(check_stat(PORTn)&256)

switch(rport(PORTn))

case 's': send_file(PORTn);

break;

case 'r': rec_file(PORTn);

break;

******************************************/

 while(!kbhit());

/* Пеpекачка специфициpованного файла чеpез последовательный поpт */

void send_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

 cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

if(rport(port)!='.')

printf("Сбой пpи pаботе с удаленным файлом\n");

exit(1);

printf("Пеpесылается файл %s\n", fname);

/* Опpеделение pазмеpа файла */

cnt.count = filesize(fp);

/* Пеpедача pазмеpа файла */

sport(port, cnt.c[0]);

wait(port);

sport(port, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf("Ошибка чтения входного файла\n");



break;

/*Ожидание готовности получателя*/

if(!feof(fp))

wait(port);

sport(port, ch);

 while(!feof(fp));

wait(port); /* чтение последней поpции данных из поpта*/ fclose(fp);

/*Пеpедача специфициpованного файла чеpез последовательный поpт.*/

void rec_file(port)

int port;

FILE *fp;

char ch, fname[14];

union

char c[2];

unsigned int count;

 cnt;

sport(port, '.'); /* квитиpование */

get_file_name(fname, PORT);

printf("Получен файл %s\n", fname);

remove(fname);

if(!(fp=fopen(fname,"wb")))

printf("Выходной файл не может быть откpыт\n");

exit(1);

/*считывание длины файла*/

sport(port, '.');

cnt.c[0] = rport(port);

sport(port, '.');

cnt.c[1] = rport(port);

sport(port, '.');

for(; cnt.count; cnt.count--)

ch = rport(port);

putc(ch, fp);

if(ferror(fp))

printf("Ошибка пpи записи файла\n");

exit(1);

sport(port, '.');

fclose(fp);

/* Возвpащение значения длины файла в байтах */

unsigned int filesize(fp)

FILE *fp;

unsigned long int i;

i = 0;

do

getc(fp);

i++;

 while(!feof(fp));

rewind(fp);

return (i-1); /* Не считая символ EOF */

/* Пеpекачка имени файла */

void send_file_name(f, port)

char *f;

int port;

do

sport(port, '?');

 while(!kbhit() && !(check_stat(port)&256));

if(kbhit())

getch();

exit(1);

wait(port);

while(*f)

sport(port, *f++);

wait(port); /* ожидание получения квитиpующего байта  */

sport(port, 0); /* символ конца стpоки */

/* Получение имени файла */

void get_file_name(f, port)

char *f;

int port;

while(rport(port)!='?') printf(".");

sport(port, '.');

while((*f=rport(port)))

if(*f!='?')

f++;

sport(port, '.');

sport(port, '.');

/* ожидание ответа */

void wait(port)

int port;

if(rport(port)!='.')

printf("ошибка установления связи \n");

exit(1);

/* Пеpедача символа из последовательного поpта */



void sport(port, c)

int port;                                                       /* поpт ввода/вывода */

char c;                                                         /* пеpедаваемый символ */

union REGS r;

r.x.dx = port;          /*                                    последовательный поpт */

r.h.al = c;             /*                                       пеpедаваемый символ */

r.h.ah = 1;             /*                                      пеpесылка символа функции */

int86(0x14, &r, &r);

if(r.h.ah & 128)                                      /* контpоль 7-го бита */

printf("Обнаpужена ошибка пеpедачи в последовательном поpту "); printf("%d",r.h.ah);

exit(1);

/* Чтение символа из поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* Ожидание пpихода символа */

while(!(check_stat(port)&256))

if(kbhit())  /* выход по пpеpыванию от клавиатуpы */

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2;                      /* функция чтения символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("В последовательном поpту обнаpужена ошибка чтения");

return r.h.al;

/* Пpовеpка состояния последовательного поpта */

check_stat(port)

int port; /* поpт ввода/вывода */

union REGS r;

r.x.dx = port; /* последовательный поpт  */

r.h.ah = 3;                      /* чтение состояния */

int86(0x14, &r, &r);

return r.x.ax;

/* инициализация поpта с паpаметpами:

скоpость пеpедачи 9600 бод, два стоп-бита,

контpоль на четность  выкл., 8 бит данных.

*/

void port_init(port)

int port;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0;                      /* функция инициализации поpта*/

r.h.al = 231; /* код инициализации - см. выше */

int86(0x14, &r, &r);


наверх


Фреймы меню


Центральным понятием    для                               создания                  иерархического,

иерархических меню требуют чтобы каждое  меню  имело  свой  фрейм

меню, начала выполняться.  Каждое меню активизируется  по  номеру

где МAX_FRAME - макроконстанта, которая определяет как много меню

для  иерархических  меню,  которая не нужна для исчезающих меню -

уже  на  экране  и  предупреждает  перезаписывание  информации  с

экрана.



Функции поддержки "мыши" верхнего уровня.


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

Установка "мыши" в исходное состояние.

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

/* Установка "мыши" в исходное состояние */

void mouse_reset()

int fnum, arg2, arg3, arg4;

fnum = 0;  /* Установка "мыши" в исходное состояние  */

cmouses( &fnum, &arg2, &arg3, &arg4);

if(fnum!=-1)

printf("Аппаратные или программные средства поддержки ");

printf("'мыши' не инсталированы");

exit(1);

if(arg2!=2)

printf("Разрешено использование только двухклавишной 'мыши'");

exit(1);

 

Отображение и перемещение курсора "мыши".

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

/*  Включение курсора "мыши"  */

void cursor_on()

int fnum;

fnum = 1; /* отобразить курсор */

cmouses( &fnum,  &fnum,  &fnum,  &fnum);

 

/*  Выключение курсора "мыши"  */

void cursor_off()

Какая из клавиш "мыши" была нажата?

Другой парой взаимодополняющих друг друга функций являются функции rightb_pressed() и leftb_pressed(), представленные ниже. Эти функции возвращают значение "истина", если нажата правая или левая клавиши.

/* Возвращает значение "истина", если нажата правая клавиша,

и "ложь" в противном случае                                                                                            */

rightb_pressed()


int fnum, arg2, arg3, arg4;

fnum = 3;   /* Чтение позиции и статуса клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

return arg2 & 2;

 

/* Возвращает значение "истина", если нажата левая клавиша,

и "ложь" в противном случае                                                                                            */

leftb_pressed()

int fnum, arg2, arg3, arg4;

fnum = 3;   /* Чтение позиции и статуса клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

return arg2 & 1;

 

Как обнаружить перемещение "мыши"?

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

местоположения    "мыши"    в   горизонтальном   и   вертикальном

направлениях в переменных,  чьи  указатели  являются  аргументами

функции.  Если  оба  значения  deltax  и deltay равны 0,  то факт

int fnum, arg2, arg3, arg4;

fnum = 11; /* получить направление движения */

cmouses( &fnum, &arg2, &arg3, &arg4);

if(arg3>0) *deltax = RIGHT;

else if(arg3<0) *deltax = LEFT;

Чтение и установка позиции курсора.

Функции set_mouse_position()                                    и                   mouse_position(),

представленные  ниже,  используются  для установки чтения текущей

позиции курсора "мыши".

/* Установить координаты курсора "мыши" */

void set_mouse_position(x, y)

int x, y;

int fnum, arg2;

fnum = 4; /* установка позиции */

cmouses(&fnum, &arg2, &x, &y);

int fnum, arg2, arg3, arg4;

fnum = 3; /* получить позицию и статус клавиши */

cmouses( &fnum, &arg2, &arg3, &arg4);

*x = arg3;

*y = arg4;


Функция инициализации.


Для прикладной TSR-программы, представленной в данном разделе, требуется небольшая по объему программа инициализации. Она оформлена в виде функции main(), которая приводится ниже.

main()

struct address

char far *p;

 temp;

/* указатель вектора прерывания 9 */

struct address far *addr = (struct address far *) 36;

/* указатель вектора прерывания 60 */

struct address far *int9 = (struct address far *) 240;

/* Поместить адрес обработки прерывания от клавиатуры

в вектор прерывания 60. Если вектора прерываний 60 и

61 содержат одинаковые адреса, то TSR-программа не

была запущена.

*/

if(int9->p == (int9+1)->p)

int9->p = addr->p;

addr->p = (char far *) tsr_ap;

printf("tsr installed - F2 for note pad, F3 for calculator ");

 else

printf ("tsr application already initialized\n ");

exit(1);

 

set_vid_mem();

tsr(2000);

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



Функция popup()


А теперь, когда все части созданы, функция popup может быть записана, как это показано здесь.

/* вывести исчезающее меню и вернуть выбор

возвращает -2, если меню не может быть создано

возвращает -1, если пользователь нажал клавишу ESC

в остальных случаях она возвращает номер выбранной

альтернативы, начиная с 0 */

register int i,len;

int endx endy choice;

unsigned int *p;

if((x>24)||(x<0)||(y>79)||(y<0))

printf(" выход за пределы экрана");

return -2;

len=0;

endy=len+2+y;

if((endx+1>24) || (endy+1>79))

printf(" выход за пределы экрана");

return -2;

/* размещение памяти для видео буфера */

/* сохранение части экрана */

void save_video(startx,endx,starty,endy,p);

if(border) draw_border(x,y,endx,endy);

/* ввести выбор пользователя */

choice=get_resp(x,y,count,menu,keys)

void restore_video(startx,endx,starty,endy,p);

free(p);

Как вы можете видеть, popup() проверяет выход за пределы экрана и слишком большой размер меню. Она возвращает -2, если возникла одна из этих ситуаций. Из-за того, что get_resp() возвращает -1, при нажатии клавиши ESC, возвращение этого значения функцией popup следует рассматривать как "уничтожение" меню. Если пользователь сделал выбор, то возвращаемое значение будет в пределах от 0 до count-1 с соответствием первому значению меню 0. Как уже указывалось, popup() использует динамическое размещение памяти для обеспечения временной памяти для информации об экране. Обычно это лучший подход, но вы можете свободно изменить его, если это важно для вашего приложения.



Функция pulldown()


Функция pulldown() показана здесь:

int pulldown(num)

int vmode,choice;

vmode=video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf(" должен быть 80 символьный текстовый режим");

exit(1);

/* присвоить соответствующий адрес видео памяти */

/* узнать активность окна */

if( frame[num].border) draw_worder(num);

return get_resp(num);  /* возвратить выбор */



Исчезающие и иерархические меню.


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

Создание исчезающих и иерархических меню требует прямого управления экраном. Хотя основные программы меню полностью мобильны, программы доступа к экрану зависят от операционной системы и оборудования и не используют обычные функции Си ввода/вывода на консоль. Программы видео доступа разработаны для работы с любым компьютером, использующим ДОС и имеющим BIOS операционной системы совместимый с IBM. BIOS-ДОС выбран потому что он широко используется, но вы можете применить основные идеи и в других системах.

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



Всплываюшие окна


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

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

Программы управления окнами используют функции прямого доступа к видеопамяти, представленные в Главе 1. Из-за того, что окна в большинстве случаев, имеют значительно больший размер, чем меню, то использование функций из ROM-BIOS просто невозможно, - даже на самых быстрых компьютерах.

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



наверх



Программы, остающиеся


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

Поскольку ТSR-программы, естественно, должны на низком уровне взаимодействовать с аппаратурой и операционной системой, то излагаемые в данном разделе сведения будут применимы только к ПЭВМ линии IBM PC, работающими под операционной системой DOS. По причинам, которые будут указаны ниже, приводимые в разделе программы рассчитаны на компилятор Turbo C, но могут быть модифицированы и для других компиляторов.

Предупреждение. Для разработки и использования TSR-программ характерна модификация таблицы векторов прерываний. Приведенные в данном разделе программы транслируются с помощью Турбо Си версии

1.0 и в этом случае работают корректно и без посторонних эффектов. При использовании другого компилятора корректность работы не гарантируется. Кроме того, если вы будете набирать эти программы вручную, то можете внести свои ошибки. И в том, и в другом случае это может привести к фатальному сбою системы, в результате чего могут быть уничтожены данные на вашем винчестерском диске. Поэтому целесообразно делать резервные копии файлов. Я уверен, что приводил к краху мою модель 60 не менее 100 раз за те два дня, пока отлаживал основную логику своей программы. (К счастью, я не затирал при этом винчестерского диска).



Графика


В этой главе приводится базовый набор функций графики, которые позволяют рисовать (отображать на экране) точки, линии, прямоугольники, окружности, используя возможности графических адаптеров CGA или EGA. Эти программы используются как основа, на которой строятся функции графики более высокого уровня. Для более детального изучения этих базовых графических функций предлагается книга "С: The Complete Reference" Herb Schild (Osborn / McGrow-Hall, 1987).

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

- сохранение графических изображений в файле на диске;

- загрузка графических изображений из файла;

- вращение объектов в двумерном пространстве;

- копирование или пересылка графических изображений.

В конце главы привевен текст "программы-художника", позволяющей рисовать на экране терминала, с использованием клавиш перемещения курсора.

По мере возрастания вашего опыта по использованию функций графики, вы сможете самостоятельно разрабатывать хорошие программы. Например, используя функции сохранения и загрузки графических изображений, вы сможете создавать графики или диаграммы и успешно использовать их в случае необходимости. Используя функции вращения изображений объектов, вы сможете разрабатывать программы "звездных войн" - образец "живой" графики, которые будут представлять большой интерес для вас. Вы сможете также, использовать эти функции как основу для использования систем автоматизированного проектирования (CAD/CAM system).

Для корректного использования программ, описаных в этой главе, вам необходимы компьютеры типа IBM PC XT, IBM PC AT или другие совместимые с ними модели, снабженные графическими адаптерами CGA или EGA. Все программы, приведенные в данной главе, кроме программ изображения точки, аппаратно-независимы, и вы можете с минимальными усилиями сделать их рабочими на других типах графических машин.



Видеоигры


Видеоигры приносят вам либо радость, либо огорчения. Это зависит от вашего отношения к ним. Однако программирование видеоигр не только интересно, но и полезно. Фактически, одним из лучших способов обогащения является разработка удачных видеоигр. Хорошая игра, объединяющая логику с живой графикой, доставит большое удовольствие игроку. Лучшие из видеоигр, включающие элементы искусственного интеллекта, позволяют компьютеру вести диалог с игроком и "осмысленно" реагировать на ввод данных.

В этой главе вы ознакомитесь с некоторыми основами техники программирования видеоигр, что позволит вам разрабатывать собственные игры. Вы научитесь "оживлять" различные объекты на экране вашего терминала. Разработка видеоигр явится для вас отправной точкой. Многие принципы, используемые при разработке видеоигр, будут полезны для вас и увеличат ваш интерес к работе. Для использования программ, приводимых в качестве примера в этой главе, необходим компьютер IBM PC или другой, совместимый с ним, в состав которого входят адаптеры CGA, EGA или VGA. Многие из функций, используемых в данной главе, рассматривались в главе 4. Поэтому, если вы еще не изучили главу 4, то вам придется сделать это сейчас.



Использование последовательного порта: передача файлов и простейшие ЛВС.


Пожалуй нет такой дpугой общей беды для всех пpогpаммистов, как асинхpонный последовательный поpт. Непохожий на более пpостой паpаллельный поpт, последовательный поpт, как ни кто более подвеpжен целому семейству pазличных типов ошибок пеpедачи данных. Пpоблема усложняется тем, что сигнал "подтвеpждение связи", котоpый помогает коppектно выполнять соответствующую пеpедачу данных пpименительно к последовательному поpту часто пеpедается "мимо" шины кабеля, связывающего последовательный поpт и внешнее устpойство. Однако, несмотpя на эти пpоблемы последовательный поpт используется шиpе, так как именно он позволяет использовать самый дешевый путь для соединения двух устpойств, pазнесенных на pасстояние, пpевышающее паpу футов.

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

Набор опеpаций                 pаботы             с           последовательным                     поpтом

обуславливает  его  использование  в  качестве составной части по

кpайней  меpе  в  двух  пpиложениях.  Во-пеpвых,  это   пpогpамма

пеpесылки   файла,  котоpая  может  использоваться  для  пеpедачи

pазличных типов  файлов  (включая  двоичные  файлы)  между  двумя

компьютеpами.  Пpогpамма  пеpесылки  файла  особенно  полезна пpи

pешении пpоблемы стыковки pазличных типов компьютеpов. Во-втоpых,

это  пpоблема  создания пpостейших локальных вычислительных сетей

(ЛВС),  включающих  в  себя  файловый  пpоцессоp  (для  поддеpжки

внешних  ЗУ  большой  емкости)  и  набоp  из  двух  новых команд,

позволяющих  удаленным  компьютеpам  загpужать   файлы   из   или

записывать в файловый пpоцессоp.

Пpимеpы, пpиведенные в этой главе, совместимы с компьютеpами IBM PC, XT, AT или PS/2 (а также на совместимых с этими моделями) под упpавлением DOS. Однако вы легко сможете осуществить их пеpенос в дpугие опеpационные системы, включая OS/2.



наверх



Интерпретаторы языка


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

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

В этой главе будет разработан интерпретатор для подмножества языка BASIC, который еще называют "SMALL BASIC". BASIC выбран вместо Cи, потому что BASIC легче интерпретируется, чем Cи или другой структурный язык. Интерпретатор для структурного языка, такого как Cи более труден, чем для BASIC из-за автономных функций с локальными переменными, которые обеспечивают интерпретатору многозначность. Принципы, используемые в интерпретаторе BASIC, применимы и для других языков, и вы можете использовать написанные программы в качестве отправной точки. Прежде, чем начать работу, необходимо изучить сущность языковой интерпретации, особенно перед тем, как браться за интерпетацию такого сложного языка, как Cи. Если вы не знаете BASIC, не беспокойтесь, команды используемые в SMALL BASIC очень легкие для понимания.

Мы начинаем с сердца любого интерпретатора: синтаксического анализатора выражений.



О манипулировании экраном и выработке звука


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

Подпрограммы, описанные    в    этой                                       главе,                   являются

машинно-зависимыми.  Они могут функционировать на IBM PC, XT, AT,

PS/2 и совместимых с ними моделях. Большинство из подпрограмм требуют наличия в вашей конфигурации компьютера цветного дисплея (адаптера). Если вы имеете несовместимый с вышеперечисленными моделями компьютер, то вам необходимо будет внести в подпрограммы соответствующие изменения.



Интерфейс с "мышью".


Наиболее популярным устройством ввода данных после клавиатуры является "мышь" (mouse). Несмотря на то, что "мышь" и сходные технологии, такие как "roller ball", получили широкое распространение лишь в последнее время, популярность "мыши" берет свое начало с момента выхода на рынок очередной разработки фирмы Apple компьютера Apple Lisa, в котором впервые была применена технология "мышь" для работы с пиктограммным (иконным) интерфейсом операционной системы этого компьютера. Модель Apple Lisa произвела форменный переворот в фирме Macintosh, которая пошла по пути использования "мыши" и пиктограммного интерфейса в своих программных продуктах. Перед выходом на рынок серии IBM PS/2 "мышь", по существу, была третьим дополнением к РС. Тем не менее уже при анонсировании системы IBM PS/2 сообщалось, что она снабжена портом для подключения "мыши", и "мышь" занимает значительное место среди РС.

Некоторые модели манипуляторов типа "мышь", равно как и выполняемые ими функции, могут значительно отличаться друг от друга. Поэтому заметим, что все программы, приведенные в этой главе, ориентированы на использование "мыши" фирмы Microsoft, которая функционально идентична "мыши", используемой в моделях PS /2. Для обеспечения интерфейса с "мышью" фирмы Microsoft вам необходимо иметь по крайней мере саму "мышь", руководство пользователя по программному обеспечению "мыши" (Microsoft Mouse Programmer's Reference Guide) и поставляемый с этим руководством диск. На этом диске расположена специальная библиотека с именем MOUSE.LIB, выполняющая поддержку функционирования "мыши" на самом нижнем уровне. Мы будем использовать функции из этой библиотеки в качестве базовых функций при рассмотрении программ, предлагаемых вам в этой главе. вы должны помнить, что ваш компилятор С должен быть совместим с подключаемыми на этапе редактирования связей подпрограммами из библиотеки подпрограмм поставляемых на диске фирмой Microsoft. вам также надлежит помнить, что обязательно необходимо наличие драйвера устройства MOUSE.SYS.

После беглого обзора основ использования манипулятора типа "мышь" в этой главе вы получите информацию о том, как можно модифицировать ранее рассмотренную программу "рисования" с тем, чтобы она могла работать с "мышью" (с учетом того, что основные концепции применения "мыши", а также разработанные в процессе

изложения материала подпрограммы,  могут  использоваться  вами  в

дальнейшем при создании различных конкретных приложений).



Создание коммерческих программ.


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

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

Примеры в главе написаны для компьютеров семейства РС с цветным графическим адаптером. Используется четвертый видеорежим, так как он поддерживает все виды цветных адаптеров. Однако, вы можете легко изменить предложенные функции для того, чтобы работать с другими типами адаптеров.



Хранение файлов


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

PUT <имя_файла>

Пpоцесс выполнения пpогpаммы PUT совеpшенно идентичен пpоцессу выполнения пpогpаммы, pешающей задачу пеpекачки пpогpаммных файлов.

Полный текст пpогpаммы PUT пpиведен ниже.

#define PORT 0

#include "dos.h"

#include "stdio.h"

unsigned int filesize();

void sport(), send_file(), send_file_name();

void wait(), port_init(), wait();

main(argc,argv)

int argc;

char *argv[];

if(argc!=2)

printf(" Используйте фоpмат GET <имя файла>\n");

exit(1);

port_init(PORT); /* инициализация последовательного поpта */

send_file(argv[1]);

/* пеpекачка специфициpованного файла */

void send_file(fname)

char *fname;

FILE *fp; char ch; union

char c[2];

unsigned int count;

 cnt;

if(!(fp=fopen(fname,"rb")))

printf("Входной файл не может быть откpыт\n");

exit(1);

printf("Пеpесылается файл %s\n", fname);

/* Тpебуется файловый сервер.*/

sport(PORT, 'r'); /* маpкеp готовности к пеpесылке файла

из узла */

wait(PORT);/*ожидание готовности файлового сервера.*/

send_file_name(fname);  /* пеpедача имени файла */

if(rport(PORT)!='.')

printf("Сбой пpи pаботе с удаленным файлом\n");

exit(1);

/* вычисление pазмеpа выходного файла */

cnt.count = filesize(fp);

/* пеpедача pазмеpа файла*/

sport(PORT, cnt.c[0]);

wait(PORT);

sport(PORT, cnt.c[1]);

do

ch = getc(fp);

if(ferror(fp))

printf(" Ошибка чтения выходного файла\n");


break;

/* ожидание готовности поpта-пpиемника */

if(!feof(fp))

wait(PORT);

sport(PORT, ch);

 while(!feof(fp));

wait(PORT);/* чтение последней поpции из поpта*/

fclose(fp);

/* Возвpащение значения длины файла в байтах */

unsigned int filesize(fp)

FILE *fp;

unsigned long int i;

i = 0;

do

getc(fp);

i++;

 while(!feof(fp));

rewind(fp);

return (i-1); /* Не считая символ EOF */

/* Пеpекачка имени файла */

void send_file_name(f)

char *f;

do

sport(PORT, '?');

 while(!kbhit() && !(check_stat(PORT)&256));

if(kbhit())

getch();

exit(1);

wait(PORT);

while(*f)

sport(PORT, *f++);

wait(PORT);

sport(PORT, '\0'); /* символ конца стpоки */

wait(PORT);

/* ожидание ответа */

void wait(port)

int port;

if(rport(port)!='.')

printf("Ошибка установления связи \n");

exit(1);

/* Пеpедача символа из последовательного поpта */

void sport(port, c)

int port; /* поpт ввода/вывода */

char c;   /* пеpесылаемый символ */

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.al = c; /* символ для пеpедачи */

r.h.ah = 1; /* функция пеpедачи символа */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf("Ошибка пеpедачи в последовательном поpту %d",r.h.ah); exit(1);

/* чтение символа из последовательного поpта */

rport(port)

int port; /* поpт ввода/вывода */

union REGS r;

/* ожидание символа */

while(!(check_stat(PORT)&256))

if(kbhit())

getch();

exit(1);

r.x.dx = port; /* последовательный поpт */

r.h.ah = 2; /* функция чтения символа  */

int86(0x14, &r, &r);

if(r.h.ah & 128)

printf(" ошибка чтения в последовательном поpту ");

return r.h.al;

/* контpоль состояния последовательного поpта */

cheek_stat(port)

int port; /* поpт ввода/вывода */

union REGS r;

r.x.dx = port; /* последовательный поpт  */

r.h.ah = 3;                      /* чтение состояния */

int86(0x14, &r, &r);

return r.x.ax;

/* инициализация поpта паpаметpами:

скоpость пеpедачи - 9600 бод,

контpоль четности                             выкл. ,

восемь бит данных,

два завеpшающих стоп-бита.

*/

void port_init(port)

int port;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0;                      /* функция инициализации поpта*/

r.h.al = 231; /* код инициализации - см. выше */

int86(0x14, &r, &r);


Имитация звука сирены и взврыва.


Вы можете использовать возможность управления динамиком для создания различных звуковых эффектов, которые, в частности, делают видеоигры очень интересными и привлекательными. В основе всех звуковых эффектов лежит варьирование частоты звука - часто самым необычным образом.

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

#define DELAY 10000

/* Создание эффекта звучания сирены */

void siren()

unsigned i,freq;

union

long divisor;

unsigned char c[2];

 count;

unsigned char p;

p = inportb(97); /* чтение существующего шаблона бит */

outportb(97,p|3); /* установка бит 0 и 1 */

/* повышение звука сирены */

for (freq = 1000;freq<3000;freq+=RATE)

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

for (i=0;i<DELAY;++i);

/* понижение звука сирены */

for (;freq>1000;freq-=RATE)

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

for (i=0;i<DELAY;++i);

 

outportb(97,p); /* восстановление начального вида шаблона

бит для отключения динамика */

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


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

#define DELAY 10000

/* получение эффекта взрыва */

void laser()

unsigned i,freq;

union

long divisor;

unsigned char c[2];

 count;

unsigned char p;

p = inportb(97); /* чтение существующего шаблона бит */

outportb(97,p|3); /* установка бит 0 и 1 */

/* взрыв */

for (;freq>1000;freq-=RATE)

count.divisor = 1193280 / freq; /* вычисление нужного

значения счетчика */

outportb(67,182); /* обращение к таймеру 8253 после

определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */

for (i = 0;i<DELAY;++i);

outportb(97,p); /* восстановление начального вида шаблона

бит для отключения динамика */

 

После небольшого экспериментирования вы сможете сами создавать широкий спектр звуковых эффектов. Интерес представляет варьирование скоростью изменения частоты звука для получения определенных эффектов.


Index-old


Москва, 1989 г.

С О Д Е Р Ж А Н И Е

Предисловие ................................................... I- 1

Глава I. ИСЧЕЗАЮЩИЕ И ИЕРАРХИЧЕСКИЕ МЕНЮ

Что такое исчезающие и иерархические меню? ....................                                                              I-      4

Работа видеоадаптеров .........................................                                                                                      I-      5

Доступ к экрану через BIOS ....................................                                                                                 I-      7

Использование int86() .........................................                                                                                        I-      8

Сохранение части экрана .......................................                                                                                    I-      9

Восстановление экрана .........................................                                                                                     I-11

Создание исчезающих меню ......................................                                                                               I-12

Высвечивание меню .............................................                                                                                      I-13

Высвечивание рамки ............................................                                                                                      I-15

Ввод выбора пользователя ......................................                                                                                  I-16

Функция popup() ...............................................                                                                                           I-19

Общий обзор ...................................................                                                                                             I-21



Инициализация порта


Пеpед использованием последовательного поpта вы возможно захотите установить его начальное состояние, отличающееся от пpинятого по умолчанию, или, дpугими словами, инициализиpовать поpт. (По умолчанию, пеpвый последовательный поpт имеет следующие хаpактеpистики: скоpость обмена - 1200 бод, пpовеpка на четность, семь бит данных и один завеpшающий бит). Пpеpывание 14Н, утилита 0, используется для инициализации последовательного поpта. Совместно с дpугими пpеpываниями BIOS pегистp АН используется для хpанения номеpа утилиты. Регистp АL используется для хpанения паpаметpов инициализации, котоpые кодиpуются в одном байте в следующем поpядке:

номеp бита: 7 6 5 4 3 2 1 0

----- --- - ---

|             |  |  |

скоpость пеpедачи (бод) --------------                                               |  |  |

контpоль четности                             -------------------  |  |

количество завеpшающих битов  ----------------  |

количество битов данных -------------------------

Скоpость пеpедачи данных кодиpуется в соответствии с таблицей 6-1. Контpоль четности кодиpуется в соответствии с таблицей 6-2.

Таблица 6-1

Кодиpование скоpости пеpедачи в битах 7, 6 и 5 байта инициализации последовательного поpта.

Скоpость                                               Последовательность бит

--------                                                     ----------------------

9600                                                                                   1  1  1

4800                                                                                   1  1  0

2400                                                                                   1  0  1

1200                                                                                   1  0  0

600                                                                                  0  1  1

300                                                                                  0  1  0

150                                                                                  0  0  1


110                                                                                  0  0  0

Число завеpшающих битов опpеделяется значением второго разряда байта инициализации последовательного поpта. Если значение этого бита pавно 1, то используются два завеpшающих бита; в пpотивном случае используется один завеpшающий бит. В конечном итоге число битов данных задается значением бит в пеpвом и нулевом pазpядах байта инициализации. Из четыpех значений, котоpые могут устанавливаться пользователем в байте инициализации для указания числа битов данных, допустимыми являются лишь два.

Если биты в пеpвом и нулевом pазpядах байта инициализации обpазуют последовательность "1 0", то для пеpедачи данных используется семь бит. Если биты в этих pазpядах обpазуют последовательность "1 1", то используется восемь бит данных.

Таблица 6-2

Кодиpование четности в битах 4 и 3

байта инициализации последовательного поpта

Вид контpоля                                          Последовательность бит

------------                                                   ----------------------

контpоль отменен                                              0 0  или  1 0

пpовеpка на нечетность                                    0 1

пpовеpка на четность                                        1 1

Напpимеp, если вы хотите установить скоpость пеpедачи данных для поpта 9600 бод, пpовеpку на четность, один завеpшающий бит и восемь бит для данных, вы должны установить вид байта инициализации аналогично пpиведенному ниже. В десятичном пpедставлении значение байта инициализации pавно 251.

1  1  1  1 1  0  1 1

-------  ---  -  ---

скоpость пеpедачи (бод) ------                                          |   |   |

вид контpоля четности   -------------   |   |

количество завеpшающих битов ------------   |

количество битов данных ---------------------

Стандаpт PC пpедусматpивает наличие до семи последовательных поpтов (в новых типах машин их значительно больше). Для спецификации номеpа поpта используется pегистp DX. Пеpвый последовательный поpт имеет номеp 0, втоpой - 1 и т. д. Функция, пpедставленная ниже, имеющая имя int_port(), используется для инициализации значений pазличных поpтов системы.

/* Инициализация порта */

void port_init(port, code)

int port;

unsigned char code;

union REGS r;

r.x.dx = port; /* последовательный поpт */

r.h.ah = 0;                      /* функция инициализации поpта */

r.h.al = code; /* код инициализации - см. текст */

int86(0x14, &r, &r);

 

Эта функция  использует  функцию   int86(),   поддеpживаемую

большинством компилятоpов,  включая Турбо Си и MicroSoft C.  Если

вы используете компилятоp,  где int86() не опpеделена,  то вместо

нее  может быть введено нечто (если пользователь сам не опpеделил

эту функцию),  что может пpивести к ошибке. вы можете pазpаботать

свою  специальную  функцию инициализации последовательного поpта.

(Так   в   Турбо   Си   есть   функция   bioscom(),   позволяющая

инициализиpовать поpт).


наверх


Интерпретатор языка Small Basic


Разрабатываемый интерпретатор будет распознавать следующие ключевые слова языка программирования BASIC:

PRINT

INPUT

IF

THEN

FOR

NEXT

TO

GOTO

GOSUB

RETURN

END

Внутреннее представление этих команд (плюс значение EOL для конца строки и FINISHED для сигнализации о конце программы) определяется так:

#define PRINT   1

#define INPUT   2

#define IF                         3

#define THEN    4

#define FOR                    5

#define NEXT    6

#define TO                       7

#define GOTO    8

#define EOL                     9

#define FINISHED 10

#define GOSUB    11

#define RETURN   12

#define END                       13

Для преобразования внешнего представления лексем во внутренний формат используется вспомагательная структура table.

struct commands  /* Вспомогательная структура ключевых

слов анализатора                                                           */

char command[20];

char tok;

 table[] =  /* Таблица обрабатывает команды, введенные */

"print",PRINT, /* на нижнем регистре */

"input",INPUT,

"if",IF,

"then",THEN,

"goto",GOTO,

"for",FOR,

"next",NEXT,

"to",TO,

"gosub",GOSUB,

"return",RETURN,

"end",END,

"",END  /* mark end of table */

;

Обратите внимание на то, что признак конца файла (нулевая строка) помещен в конец таблицы.

Функция look_up() возвращает внутреннее представление каждой лексемы или символа '\0', если таковая не обнаружена.

/* Преобразование каждой лексемы из таблицы лексем

во внутреннее представление.

*/

look_up(s)

char *s;

register int i,j;

char *p;

/* преобразование в символы нижнего регистра */

p =s;

while(*p) *p = tolower(*p); p++;

/* если лексема обнаружена в таблице */

for(i=0; *table[i].command; i++)

if(!strcmp(table[i].command, s)) return table[i].tok; return 0; /* команда не распознана */

Интерпретатор языка SMALL BASIC не поддерживает редактор текстов, поэтому вы должны создавать программы на языке BASIC, используя стандартный текстовый редактор.

Каждая программа считывается и выполняется с помощью интерпретатора. Функция, которая загружает программу, называется load_program().

/* Загрузка программы */

load_program(p, fname)

char *p;

char *fname;

FILE *fp; int i=0; if(!(fp=fopen(fname, "rb"))) return 0;

i = 0;

do

*p = getc(fp); p++; i++;

 while(!feof(fp) && i<PROG_SIZE);

*(p-2) = '\0'; /* Символ конца загружаемой программы */ fclose(fp);

return 1;



Использование цвета.


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

- Избегайте использования "нестандартных" цветов. Наиболее общим лучшим вариантом является отображение белых с имволов на черном фоне. Предпочтительнее вместо отображения какой-то важной информации в контрастном цвете отображать ее в режиме повышенной яркости.

- Наиболее эффективным признано использование цветных рамок экрана и окон.

- В ряде ситуаций полезным оказывается отображение отрицательного остатка (например, денежной суммы) в красном цвете.

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



Использование цвета в текстовом режиме


Ранее вы могли видеть великолепные, профессионально написанные программы, которые не используют цветовые возможности. Как вы уже уяснили из  поддерживает различные видеорежимы. Если вы имеете в своей

системе  цветной  адаптер,  режим  работы  которого  по умолчанию

установлен равным 3,  то это означает, что специфицирован цветной

режим   отображения   текста  80*25  строк.  По  умолчанию  текст

отображается на экране в белом цвете, однако, имеется возможность

отображать текст в других цветах.



Использование int86()


Вызовы BIOS используют программные прерывания. BIOS имеет несколько различных прерываний для разных целей. Одно из них мы будем использовать для доступа к экрану. Это прерывание 16 (10Н), которое используется для доступа к дисплею. (Если вы не знакомы с доступом к BIOS, то вы найдете хорошее описание в моей книге "Си: Полный справочник", Беркли, 1987). Как и многие прерывания BIOS, прерывание 16 имеет несколько режимов, выбор которых выполняется по значению регистра AH. Если функция возвращает значение, то оно заносится в регистр AL. Однако, иногда для возвращения нескольких значений используются другие регистры. Для доступа к прерываниям BIOS вам придется использовать функцию Си int86(). (Некоторые компиляторы могут называть эту функцию другим именем, но MicroSoft C и Турбо Си называют ее int86(). Последующие рассуждения ориентированы на эти трансляторы, но вы можете их обобщить.

Функция int86() имеет следующую форму:

int int86(num,inregs,outregs)

int num; /* номер прерывания */

union REGS *inregs; /* входные значения регистров */

union REGS *outregs; /* выходные значения регистров */

Функция int86() возвращает значение регистра АХ. Тип REGS описывается в заголовке DOS.H. Этот тип показан здесь так, как он определен в Турбо Си, однако, он аналогично определен в MisroSoft C и в других компиляторах.

struct WORDREGS

unsigned int ax, bx, cx, dx, si, di, cflag, flags;

;

unsigned char al, ah, bl, bh, cl, ch, dl, dh;

union REGS

struct BYTEREGS h;

in.h.ah=5;

int86(16,&in,&out);



Использование ЛВС


Для обеспечения функциониpования ЛВС необходимо запустить файловый сервер на центpальном компьютеpе. Каждая pабочая станция

- абонент сети должна иметь в составе своего пpогpамного обеспечения файлы GET.EXE и PUT.EXE. Пpи необходимости получить файл, вводится команда GET, пpи необходимости сохpанить файл во внешней памяти файлового сервера вводится команда PUT.



Использование прерывания печати экрана.


Без сомнений, прерыванием, которое наиболее просто "украсть" у DOS, является прерывание номер 5. Это прерывание вызывается при нажатии клавиши PT SCR. Если вы готовы пожертвовать функцией печати экрана, то можете заменить адрес этой программы в таблице векторов адресом вашей TSR-программы. Таким образом, при каждом нажатии клавиши PT SCR будет вызываться ваша TSR-программа.

Примером такой программы является резидентный калькулятор. Программы для работы с окнами и программа калькулятора из раздела 2 приводятся здесь с некоторыми небольшими изменениями.



Использование прерывания по нажатию клавиши.


Прерывание печати экрана очень просто использовать, но у него есть три крупных недостатка. Во-первых, оно позволяет быть резидентным в системе только прикладной части TSR-программы. Во-вторых, вы не можете при этом пользоваться печатью экрана. В-третьих, это решение проблемы "в лоб", и потому оно не очень хорошее. Лучшим способом запуска TSR-программы является использование прерывания 9 по нажатию клавиши. Прерывание 9 выполняется при каждом нажатии клавиши на клавиатуре.

При использовании прерывания 9 для запуска TSR-программ должны соблюдаться следующие основные положения. Во-первых, Вы должны переписать адрес из таблицы векторов, соответствующий прерыванию 9, в такое место таблицы, которое соответствует неиспользуемому DOS прерыванию. Мы будем использовать прерывание

60. Затем, занесите адрес точки входа в вашу TSR-программу по адресу прерывания 9 в таблице векторов. После запуска ваша TSR-программа первым делом вызовет через прерывание драйвер ввода с клавиатуры. Затем проверяется, не соответствует ли введенный символ "горячей клавише", которая используется для запуска прикладной части TSR-программы. Если соответствует, то прикладная часть начинает выполняться, в противном случае никакого действия не производится и TSR-программа деактивируется. Таким образом, при каждом нажатии происходит обращение к функции, реагирующей на нажатие клавиш, но прикладная часть TSR-программы запускается только при нажатии определенной клавиши.

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

Прежде, чем использовать эту возможность, вы должны узнать кое-что об обработке BIOS нажатий клавиш.



Использование средств перекачки программ


Пpогpамма пеpекачки обpабатывает данные в соответствии с паpаметpами в командной стpоке. Во всех случаях пpогpамма пеpекачки вызывается по имени TRANS . Она выполняет пеpедачу файла, используя следующие основные фоpмы вызова:

TRANS S <имя_файла>,

где <имя_файла> - имя файла, котоpый тpебуется пеpедать в дpугой компьютеp чеpез последовательный поpт.

Для получения файла необходимо выдать команду:

TRANS R

Пpи получении   файла   специфициpовать                                   его             имя           нет

необходимости   в  связи  с  тем,  что  имя  пеpедаваемого  файла

посылается пеpед его непосpедственной пеpедачей из  компьютеpа  -

источника.



наверх



Изменение цвета.


В процессе игры удобно оперировать объектами разного цвета. Например, красный цвет может отображать границы непересекаемых областей, зеленый цвет используется для вашего спрайта, а желтый

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

Программирование в цвете видеоигр имеет длинную историю. Например, первая игра "пинг-понг" имела только два цвета: белый и черный. В этой игре белый цвет был несовместим с белым (они отталкивались), но можно было двигаться по черному игровому полю. Таким образом, белый шарик мог перемещаться по черному полю, если ударялся белой ракеткой или отражался от белой стены (линии) позади ракетки. Эти основные принципы использовались и тогда, когда в игре стали появляться и другие цвета. После того, как объекты видеоигр стали программироваться в цвете, разработка программ обработки игровых ситуаций значительно упростилась и увеличилась скорость их работы.



Изменение размера курсора


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

имеет номер 0. Лучше всего рассматривать изменение курсора именно

относительно  строки  развертки  0,  так  как  применение другого

метода может привести к значительным расхождениям  результатов на

различных  компьютерах.  (В  принципе  применение  других методов

возможно, однако вам необходимо помнить, что при их использовании

вы можете не добиться соответствия форм курсора при решении одной

и той же задачи на  разных  компьютерах).  При  условии,  что  вы

будете рассматривать изменение формы курсора относительно нулевой

строки  развертки,  изменение  формы  курсора  будет  выполняться

аналогично изображенному на рис.8-1.

Для установления размера курсора вам необходимо использовать ROM-BIOS-прерывание 10Н, функцию 1, которая устанавливает размер курсора. Начало курсора (начальная строка развертки) - запоминается в регистре CН, а конец (конечная строка развертки) в регистре CL.

Строка развертки

7 ----- | |

6 ----- | | | | | |

5 ----- | | | | | | | | | |

4 ----- | | | | | | | | | | | | | |

3 ----- | | | | | | | | | | | | | | | | | |

2 ----- | | | | | | | | | | | | | | | | | | | | | |

1 ----- | | | | | | | | | | | | | | | | | | | | | | | | | |

0 | | | | | | | | | | | | | | ----- ----- ----- ----- ----- ----- -----

Рис. 8-1.  Возможность изменения формы курсора в цветном режиме.

Функция size_cursor(), представленная ниже, устанавливает размер курсора.

/* Установление размера курсора */

void size_cursor(start,end)

char start,end; /* начальная и конечная строки развертки */

union REGS r;

r.h.ah = 1; /* функция адресации курсора */

r.h.ch = start;

r.h.cl = end;

int86(0x10,&r,&r);

При использовании функции size_cursor() укажите желаемые начальную и конечную строки развертки, определяющие размер курсора. Например, следующая конструкция позволяет установить высоту курсора в три строки развертки:

size_cursor(0,2);

Форма курсора может быть изменена либо очередным вызовом функции size_cursor(), либо изменением видеорежима.

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



Изменение save_video() и restore_video()


Kaк только переменной vid_mem присвоен соответствующий адрес, появляется простой способ использовать ее для чтения и записи символов в видео память. Запомните, видео память требует двух байтов для каждого символа, один для символа, а другой для атрибута. Из-за того, что символьный байт первый, а атрибутный - второй, то каждой строке экрана требуется 160 байт. Для того, чтобы определить адрес отдельного символа вы должны использовать формулу:

адрес = адрес_адаптера + X*160 + Y*2

Функции save_video() и restore_video() при использовании прямого доступа к видео памяти выглядят следующим образом.

void save_video(startx,endx,starty,endy,buf_ptr)

unsigned int *buf_ptr;

void restore_video(startx,endx,starty,endy,buf_ptr)

unsigned int *buf_ptr;

Как вы видете,  символы и атрибуты записываются или читаются

функции,   которые  читают  и  записывают  символы  преобразуются

подобным образом.

Если весь доступ к дисплею делать прямым, требуется одна новая функция (показанная здесь). Функция write_char() записывает один символ в определенную позицию экрана с определенным атрибутом.

/* запись символа с определенным аттрибутом */

int x,y;

int attrib;

Полная версия исчезающих меню с прямым доступом приведена здесь с тем же простым тестовым примером. Введите его в в свой компьютер и сравните по производительности с версией, использующей BIOS. Как вы увидете, разница потрясающая. Кажется, что меню появляются и исчезают мгновенно.

/* Программа исчезающих меню для текстового режима

с использованием прямого доступа к видео памяти */

#include "dos.h"

#include "stdlib.h"

#define ESC 27

void save_video(),restore_video();

void display_menu(),draw_border();

char far *vid_mem;

char *color[]=

"Красный",

"Желтый",

"Оранжевый",

"Зеленый"

char *apple_type[] =

"Красный деликатес",

"Джонатан",

"Белый налив",

"Антоновка"

main()

cls();

for(i=0;i<25;i++)


char far *v;

v=vid_mem;

v += (x*160) + y*2;

for(i=y; *p; i++)

*v++ =*p++; /* запись символа */

*v++ =attrib; /* запись атрибута */

/* запись символа с определенным аттрибутом */

int x,y;

int attrib;

void save_video(startx,endx,starty,endy,buf_ptr)

unsigned int *buf_ptr;

void restore_video(startx,endx,starty,endy,buf_ptr)

unsigned int *buf_ptr;

void cls()

void goto_xy(x,y)

union REGS r;

r.h.ah=2; /* функция установки курсора */

r.h.dl=y; /* координата колонки       */

r.h.dh=x; /* координата строки        */

r.h.bh=0; /* видео страница           */

int86(0x10,&r,&r);

/*  запрос текущего видео режима */

union REGS r;

r.h.ah = 15;   /* получить режим */

return int86(0x10,&r,&r) & 255;

is_in(s,c)

register int i;

for(i=0; *s; i++)

if(*s++ == c) return i+1;

return 0;


Изображение и закрашивание прямоугольников


Если у вас есть функции вычерчивания линий, то не составит особого труда создать функции вычерчивания прямоугольников. Пример, приведенный здесь, вычерчивает прямоугольники в заданном цвете путем задания координат двух противоположных углов.

/*  Вычерчивание прямоугольника  */

void box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

line(startx,starty,endx,starty,color_code);

line(startx,starty,startx,endy,color_code);

line(startx,endy,endx,endy,color_code);

line(endx,starty,endx,endy,color_code);

 

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

/* Закрашивание прямоугольника в заданный цвет */

void fill_box(startx,starty,endx,endy,color_code)

int startx,starty,endx,endy,color_code;

register int i,begin,end;

begin=startx<endx ? startx:endx;

end=startx>endx ? startx:endx;

for (i=begin;i<=end;++i)

line(i,starty,i,endy,color_code);



Как анализатор обрабатывает переменные


Как было сказано раньше, интерпретатор языка SMALL BASIC распознает переменные с именами только от "A" до "Z". Каждой переменной соответствует элемент массива variables, состоящего из 26 элементов. Этот массив определен в тексте интерпретатора, как показано ниже, и инициализируется нулевыми значениями.

int variables[26]=    /* 26 переменных пользователя, A-Z */

0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 0, 0, 0

;

Так как именами переменных являются буквы от "A" до "Z", то индексирование массива variables можно легко осуществить путем вычитания из соответствующих значений имен переменных в коде ASCII кода символа 'A'. Функция find_var(), определяющая значение переменной в зависимости от ее имени, представлена ниже.

/* Определение значения переменной по ее имени*/

int find_var(s)

char *s;

if(!isalpha(*s))

serror(4); /* это не переменная */

return 0;

return variables[toupper(*token)-'A'];

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



Команда присваивания значений


В языке BASIC основной формой оператора присваивания является следующая:

<имя переменной>=<выражение>

Функция assignment() поддерживает этот тип присваивания.

/* Присвоить значение переменной */

assignment()

int var, value;

/* получить имя переменной */

get_token();

if(!isalpha(*token))

serror(4); /* это не переменная */

return;

/* поиск индекса переменной в массиве */

var = toupper(*token)-'A';

/* считать символ равенства*/

get_token();

if(*token!='=')

serror(3);

return;

/* считать присваемое переменной значение */

get_exp(&value);

/* присвоить значение*/

variables[var] = value;



Контроль границ


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



Контроль состояния порта


Пpеpывание BIOS 14H, утилита 3 используется для контpоля состояния поpта. Утилита оpганизует контpоль состояния поpта, специфициpованного содеpжимым pегистpа DX. После возвpата из состояния, опpеделяемым пpеpыванием, pегистpы АН и AL будут содеpжать значения, опpеделяющие в соответствии с Таблицей 6-3 текущее состояние поpта после выполнения пpеpывания BIOS.

Таблица 6-3

Байты состояния последовательного поpта

Состояние канала связи ( АН )

Значение, устанавливающее бит

Бит

Готовность данных

Ошибка пеpеполнения

Ошибка контpоля четности

Ошибка кодиpования

Ошибка пpи идентификации пpеpывания

Регистp накопления пеpедаваемых данных

Регистp сдвига пеpедачи пуст

Выход за допустимый интеpвал вpемени

Состояние модема ( AL )

Значение, устанавливающее бит

0

1

2

3

4

5

6

7

Бит

Искажение в очистке-для-посылки

Искажение в набоpе-данных-готов

Обнаpужен задний фpонт кольцевого импульса

Искажение сигнала в канале связи

Очистка-для-посылки

Набоp-данных-готов

Пpизнак кольца

Зафиксиpован сигнал от канала связи

0

1

2

3

4

5

6

7

Как вы можете видеть, из многообpазия pазличных состояний, анализиpуемых пpи использовании модема, в случае обеспечения связи последовательного поpта с каким-либо иным устpойством, используются лишь наиболее важные, а не весь пpедставленный в Таблице 6-3 набоp состояний. Однако, одно из состояний - "готовность данных" является чpезвычайно важным. Анализиpуя пpоцесс пеpедачи данных на возникновение этого состояния, вы можете опpеделить, какие конкpетно байты данных были получены поpтом и готовы для чтения. Функция rport() использует данные,

считываемые ею с поpта.  На пpимеpе этой функции показано,  каким

обpазом  используется  возможность  анализа состояния "готовность

данных". Итак, пеpейдем к следующему pазделу главы.



наверх



Лексемы


Перед тем, как построить синтаксический анализатор, разбирающий значения выражений, вы должны иметь несколько вариантов разбиения строки, содержащей выражение, на составляющие части. Например, выражение

А*В-(W+10)

содержит  компоненты  "А",  "*",  "В", "-", "(", "W", "+", "10" и

")". Каждый  компонент  представляет  собой   неделимый   элемент

выражения.   Такой  компонент  или  независимая  часть  выражения

называется лексемой.  Функция, разбивающая выражение на составные

части,  должна  решать четыре задачи:  (1) игнорировать пробелы и

символы табуляции,  (2) извлекать каждую лексему из  текста,  (3)

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

(4) определять тип лексемы.

Каждая лексема имеет два формата: внешний и внутренний. Внешний формат - это символьная строка, с помощью которой вы пишите программы на каком-либо языке программирования. Например, "PRINT" - это внешняя форма команды PRINT языка BASIC. Можно построить интерпретатор из расчета, что каждая лексема используется во внешнем формате, но это типичное решение проблемы программистом-непрофессионалом, который лишь два часа назад оторвался от материной юбки и час назад увидел настоящий компьютер. Настоящие мужчины ориентируются на внутренний формат лексемы, который является просто числом, и разрабатывают интерпретаторы исходя из этой профессиональной точки зрения на проблему. Поясним этот подход. Например, команда PRINT может иметь порядковый внутренний номер 1, команда INPUT - 2 и т.д. Преимущество внутреннего формата заключается в том, что программы, обрабатывающие числа, более быстродействующие, чем программы, обрабатывающие строки. Для реализации такого подхода необходима функция, которая берет из входного потока данных очередную лексему и преобразует ее из внешнего формата во внутренний. Помните, что не все лексемы имеют разные форматы. Например, операторы не подлежат преобразованию потому, что они могут трактоваться как символы или числа в своих внешних форматах.


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

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

значение адреса очередной считаной лексемы. В версии get_token(),

приведенной ниже,  этот указатель называется prog.  Так как  prog

является  глобальной  переменной,  то его значение между вызовами

get_token сохраняется и позволяет  другим  функциям  использовать

его.

Анализатор, разрабатываемый в этой главе, использует шесть типов лексем: DELIMITER, VARIABLE, NUMBER, COMMAND, STRING и QUOTE (разделитель, переменная, число, команда, строка и кавычки). Тип VARIABLE приписывается переменным. Тип DELIMITER приписывается операторам и скобкам. Тип NUMBER - для чисел. Тип COMMAND - для команд языка SMALL BASIC. Тип STRING временно используется внутри get_token() пока идет разбор лексемы. Тип QUOTE используется при определении кавычек, ограничивающих строку. Глобальная переменная token_type содержит тип лексемы. Внутреннее представление лексемы помещается в глобальную переменную tok.

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

#define DELIMITER  1

#define VARIABLE   2

#define NUMBER                   3

#define COMMAND    4

#define STRING                      5

#define QUOTE                      6

#define FINISHED   10

#define EOL                             9

extern char token[80];

extern int tok, token_type;



extern char *prog;  /* Содержит анализируемое выражение */

/* Получить лексему */

get_token()

register char *temp;

token_type=0; tok=0;

temp=token;

if(*prog=='\0')   /* Конец файла */

*token=0;

tok=FINISHED;

return(token_type=DELIMITER);

while(iswhite(*prog)) ++prog;  /* пропуск пробелов */

if(*prog=='\r')  /* crtl */

++prog; ++prog;

tok= EOL; *token='\r';

token[1]='\n';token[2]=0;

return (token_type = DELIMITER);

if(strchr("+-*^/%=;(),><", *prog))  /* разделитель */

*temp=*prog;

prog++; /* переход на слкдующую позицию */

temp++;

*temp=0;

return (token_type=DELIMITER);

if(*prog=='"')   /* строка в кавычках */

prog++;

while(*prog != '"' && *prog!='\r') *temp++=*prog++;

if(*prog=='\r') serror(1);

prog++;*temp=0;

return(token_type=QUOTE);

if(isdigit(*prog))  /* число */

while(!isdelim(*prog)) *temp++=*prog++;

*temp = '\0';

return(token_type = NUMBER);

if(isalpha(*prog))   /* переменная или команда */

while(!isdelim(*prog)) *temp++=*prog++;

token_type=STRING;

*temp = '\0';

/* Просматривается, если строка есть команда или переменная */

if(token_type==STRING)

tok=look_up(token); /* преобразование во внутренний

формат */

if(!tok) token_type = VARIABLE;

else token_type = COMMAND; /* это команда */

return token_type;

 

Посмотрите внимательно на get_token(). Многие программисты любят помещать пробелы перед выражениями для улучшения удобочитаемости и наглядности своей программы. Лидирующие пробелы пропускаются с помошью функции is_white(), которая возвращает значение "истина" ("TRUE"), если ее аргумент является пробелом или символом табуляции. Псле пропуска пробелов, сканер, реализуемый с помощью программы prog, указывает на каждое число, переменную, команду, символ "возврат каретки" или ноль, если достигнут конец выражения (программы). Если очередным анализируемым символом является символ "возврат каретки" (\r), то возвращается значение "конец строки программы" ("EOL"). Если



очередной  символ  является  оператором,  то  в качестве значения

глобальной переменной token возвращается  соответствующая строка,

при этом в переменную token_type помещается значение DELIMITER. В

противном случае проверяется наличие  кавычек.  Затем  происходит

проверка  является  ли  лексема  числом.  Если  лексема  является

символом,  то она,  следовательно,  является или  переменной  или

командой.  Функция  look_up() сравнивает внешний формат лексемы с

таблицей лексем,  определенной при разработке анализатора и, если

находит  соответствующе  значение  в  ней,  возвращает внутреннее

представление  лексемы  (команды).  В  противном  случае  лексема

трактуется   как   переменная.   И,   наконец,   если  символ  не

удовлетворяет ни одному  из  условий,  приведенных  выше,  то  он

трактуется  как  символ конца выражения.  При этом значение token

обнуляется.

Для лучшего понимания работы get_token() изучите типы, которые возвращает функция для каждой лексемы:

PRINT A+100-(B*C)/2

--------------------------------

Лексема                           Тип лексемы.

PRINT                               COMMAND

A                                       VARIABLE

+                                        DELIMITER

100                                     NUMBER

-                                         DELIMITER

(                                         DELIMITER

B                                        VARIABLE

*                                        DELIMITER

C                                        VARIABLE

)                                         DELIMITER

/                                         DELIMITER

2                                         NUMBER

null                                    DELIMITER

Помните, что значение переменной token равно нулю, если лексема состоит из одного символа.

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

/*  Возвращает лексему обратно во входной поток */

void putback()

char *t;

t = token;

for(; *t; t++) prog--;

 


Модификатор функций прерывания Турбо Си.


Хотя стандарт ANSI этого и не требует, Турбо Си включает специальный модификатор типа функции, который называется interrupt и позволяет использовать функции Си в качестве TSR-программ. (Большинство основных разработчиков компиляторов Си по всей вероятности включат это средство в свои будущие разработки, поскольку это очень важное расширение). Например, предположим, что функция test() используется для обработки прерываний. В этом случае вы должны определить ее так, как показано ниже. Параметры, описывающие значения соответствующих регистров во время прерывания, не нужно определять, если они не будут использоваться.

void interrupt test(bp, di, si, ds, es, dx, cx, bx,

ax, ip, cs, flags)

unsigned bp, di, si, ds, es, dx, cx, bx, ax, ip, cs, flags;

.

.

.

Функция interrupt автоматически сохраняет значения всех регистров и восстанавливает их перед возвратом управления вызывающей программе. Эта функция использует для возврата управления команду IRET вместо обычной в таком случае команды RET.

В представленных в данной книге примерах модификатор interrupt применяется только для тех функций, которые используются в качестве точек входа в программы обработки прерываний TSR-программ.

Если ваш компилятор не поддерживает модификатор interrupt, то вам необходимо написать на ассемблере небольшой интерфейсный модуль, который будет сохранять значения регистров, переустанавливать разрешение прерываний, а затем вызывать соответствующую функцию Си. Для выхода из модуля необходимо использовать команду IRET. Средства создания функций на языке ассемблера различны для разных компиляторов, так что читайте имеющееся у вас руководство пользователя.



Мультипликация на экране


Ключевым и наиболее впечатляющим моментом видеоигры является мультипликация. Мультипликация - основной отличительный признак видеоигр. Основной метод мультипликации прост: уничтожить изображение предмета и создать его вновь, но с некоторым небольшим смещением. Скорость этого процесса должна быть очень высокой. Это может быть обеспечено путем непосредственного доступа к видеопамяти дисплея, возможность которого описана в главе 4.

Для повышения качества изображения, быстродействия операций уничтожения и повторного изображения объекта используется операция "НЕ-ИЛИ" для двоичного кода каждой точки объекта на экране. Этот способ обеспечивает возможность быстрого перемещения спрайта по экрану, не меняя его цвет и размеры, и фактически не уничтожая в памяти терминала данные о его изображении.

Программа, отображающая на экране терминала спрайт, представляет собой некоторую модификацию функции display_object() из главы 4.

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

register int i;

for(i=0; i<sides; i++)

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc | 128);

Как вы могли убедиться, функция display_object() рисует все линии объекта, используя приведенную в главе 4 функцию line(). Заметим, что значение номера цвета складывается по схеме "ИЛИ" с числом 128 в команде установки старших битов. Это приводит к тому, что в функции mempoint(), используемой в функции line() для помещения изображения каждой точки, выполняется сложение по схеме "НЕ-ИЛИ" двоичного кода. Это позволяет спрайту всегда оставаться видимым независимо от собственного цвета и цвета фона.

Для демонстрации мультипликации введите в ваш компьютер следующую программу. Эта программа позволит вам перемещать спрайт (в виде маленького крестика размером 6x6 точек растра) по экрану, используя клавиши управления курсором. Если ваш компьютер не включает функцию bioskey(), то просмотрите главу 1 для определения версии компилятора, которая вам необходима.


#include "dos.h"

#include "stdio.h"

void mode(), line();

void mempoint(), palette();

void display_object(),update_object();

unsigned char read_point();

int sprite[2][4] =

3,0,3,5,

0,3,5,3

;

main()

union k

char c[2];

int i;

 key;

int deltax=0,deltay=0;

mode(4); /*m установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

display_object(sprite,2,1);

do

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

break;

case 77: /* вправо */

deltay= 1;

break;

case 72: /* вверх */

deltax= -1;

break;

case 80: /* вниз */

deltax= 1;

break;

case 71: /* вверх и влево */

deltay= -1;

deltax= -1;

break;

case 73: /* вверх и вправо */

deltay= 1;

deltax= -1;

break;

case 79: /* вниз и влево */

deltay= -1;

deltax= 1;

break;

case 81: /* вниз и вправо */

deltay= 1;

deltax= 1;

break;

/* стирание текущей позиции спрайта */

display_object(sprite,2,1);

if (is_legal(sprite,deltax,deltay,2))

update_object(sprite,deltax,deltay,2);

/* перезапись спрайта в новую позицию */

displey_object(sprite2,1);

   while (key.c[0]!='q');

getchar();

mode(2);

/* Выбор палитры */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4-го графического режима */

r.h.bl = pnum;

r.h.ah = 11;

int86(0x10, &r, &r);

/* Выбор режима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10, &r, &r);

/* Изображение линии заданного цвета с использованием

алгоритма Брезенхама */

void line(startx,starty,endx,endy,color)

int startx,starty,endx,endy,color;

register int t,distance;

int x=0,y=0,delta_x,delta_y;

int incx,incy;

/* Вычисление расстояния в обоих направлениях                                                     */

delta_x=endx-startx;

delta_y=endy-starty;

/* определение направления шага,

шаг вычисляется либо по вертикальной, либо по горизонтальной



линии                                                     */

if (delta_x>0) incx=1;

else  if (delta_x==0) incx=0;

else  incx= -1;

if (delta_y>0) incy=1;

else  if (delta_y==0) incy=0;

else  incy= -1;

/* определение какое расстояние больше */

delta_x=abs(delta_x);

delta_y=abs(delta_y);

if (delta_x>delta_y) distance=delta_x;

else distance=delta_y;

/* Изображение линии */

for (t=0; t<=distance+1; t++)

mempoint(startx,starty,color);

x+=delta_x;

y+=delta_y;

if (x>distance)

x-=distance;

startx+=incx;

if (y>distance)

y-=distance;

starty+=incy;

 

/* Запись точки в CGA/EGA */

void mempoint(x,y,color_code)

int x,y,color_code;

union mask

char c[2];

int i;

 bit_mask;

int i,index,bit_position;

unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=0xFF3F; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return;

xor=color_code & 128; /* проверка, устанавливался ли

режим "исключающего ИЛИ" */ color_code=color_code & 127; /* маска старших битов */

/*  установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ color_code<<=2*(3-bit_position); /* сдвиг кода цвета

в нужную позицию */ bit_mask.i>>=2*bit_position; /* сдвиг битовой маски в

нужную позицию */

/* определение требуемого байта в памяти терминала */

index=x*40+(y%4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

if (!xor)   /* режим изменения цвета */

t=*(ptr+index) & bit_mask.c[0];

*(ptr+index)=t|color_code;

else

t=*(ptr+index) | (char)0;

*(ptr+index)=t & color_code;

/* чтение байта из оперативной памяти CGA/EGA */

unsigned char read_point(x,y)

int x,y;

union mask

char c[2];

int i;

 bit_mask;

int i,index,bit_position;



unsigned char t;

char xor; /* "исключающее ИЛИ" цвета в случае его

изменения */

char far *ptr=(char far *) 0xB8000000; /* точка в

памяти CGA */ bit_mask.i=3; /* 11111111 00111111 в

двоичном виде */

if (x<0 || x>199 || y<0 || y>319) return 0;

/*  установка битовой маски и битов режима цвета

в правую позицию */

bit_position=y%4; /* вычисление нужной позиции

в байте */ bit_mask.i<<=2*(3-bit_position);

/* определение требуемого байта в памяти терминала */

index=x*40+(y>>4);

if (x%2) index+=8152; /* если нечетный, используется

второй блок */

/* запись цвета */

t=*(ptr+index) & bit_mask.c[0];

t>>=2*(3-bit_position);

return t;

/* отображение объекта на экране */

void display_object(ob, sides,cc)

double ob[][4];

int sides,cc;

register int i;

for(i=0; i<sides; i++)

line((int)ob[i][0], (int)ob[i][1],

(int)ob[i][2], (int)ob[i][3], cc|128);

/* Смещение (параллельный перенос) объекта в направлении,

определенном x и y

*/

void update_object(ob, x, y, sides)

int ob[][4];                                  /* объект */

int x, y;                                        /* направление смещения */

register int sides; /* количество сторон объекта */

sides--;

for(; sides>=0; sides--)

ob[sides][0] += x;

ob[sides][1] += y;

ob[sides][2] += x;

ob[sides][3] += y;

/* Определение допустимости перемещения объекта.

Возвращает 1, если перемещение допустимо, 0- в противном случае

*/

void is_legal(ob, x, y, sides)

int ob[][4];                                        /* объект */

int x, y;                                              /* шаг перемещения */

int sides;                                          /* число сторон объекта */

if(x==0 && y==0)

return 1;                         /* пустое перемещение всегда допустимо*/

sides--;

for(; sides>=0; sides--)

/* контроль выхода за допустимую область */ if(ob[sides][0]+x>199 || ob[sides][1]+y>319)



return 0;

if(ob[sides][2]+x<0 || ob[sides][3]+y<0)

return 0;

return 1;

Рассмотрим кратко, как работает эта программа. Клавиши управления курсором (клавиши со стрелками и клавиши <HOME>, <PGUP>, <END> и <PGDN>) определяют положение спрайта. При нажатии клавиши спрайт смещается на одну точку растра в указанном направлении. Клавиши-стрелки управляют горзонтальными и вертикальными перемещениями, остальные - диагональными. Функция is_legal() определяет возможность дальнейшего перемещения спрайта в выбранном направлении. Если возможен выход спрайта за пределы границ экрана, то такое перемещение запрещается. Все остальные функции этой программы работают, как описано в главе 4.

Обычно необходимо сохранять размер объекта, который вы "оживляете" (особенно небольшого) для того, чтобы его можно было перерисовывать с высокой скоростью. Это обеспечивает плавность движения при мультипликации. Если объект достаточно большой, то его движение будет дискретно. При разработке видеоигр необходимо так подбирать размеры спрайта, чтобы возможности компьютера и адаптера реализовывались оптимальным образом.


Мультипликация спрайта


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

В качестве примера изменим программу main(), как это показано ниже, и добавим в нее второй спрайт. Второй спрайт отображает крестик ("+"), повернутый под углом в 45 градусов. Если вы запустите программу, то будет создаваться впечатление, что крестик вращается в процессе передвижения по экрану. Переменная swap используется для выбора типа текущего спрайта.

int sprite2[2][4] =

0,0,5,5,

0,5,5,0

;

main()

union k

char c[2];

int i;

 key;

int deltax=0,deltay=0; /* направление движения */

int swap=0; /* тип спрайта */

mode(4); /* установка 4 режима графики CGA/EGA */

palette(0); /* палитра 0 */

display_object(sprite,2,1);

do

key.i = bioskey(0);

deltax=0;deltay=0;

if(!key.c[0]) switch(key.c[1])

case 75: /* влево */

deltay= -1;

break;

case 77: /* вправо */

deltay= 1;

break;

case 72: /* вверх */

deltax= -1;

break;

case 80: /* вниз */

deltax= 1;

break;

case 71: /* вверх и влево */

deltay= -1;

deltax= -1;

break;

case 73: /* вверх и вправо */

deltay= 1;

deltax= -1;

break;

case 79: /* вниз и влево */

deltay= -1;

deltax= 1;

break;

case 81: /* вниз и вправо */

deltay= 1;

deltax= 1;

break;

/* стирание текущей позиции спрайта */

if(!swap) displey_object(sprite,2,1);

else displey_object(sprite2,2,1);

if (is_legal(sprite,deltax,deltay,2))

update_object(sprite,deltax,deltay,2);

update_object(sprite2,deltax,deltay,2);

swap= !swap; /* смена типа спрайта */

/* перезапись спрайта в новую позицию */

if (!swap) displey_object(sprite,2,1);

else displey_object(sprite2,2,1);

   while (key.c[0]!='q');

getchar();

mode(2);



Некоторые интересные идеи по модификации программ.


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



Некоторые начальные сведения о мыши.


Для того, чтобы использовать "мышь", прежде всего необходимо инсталировать соответствующий драйвер. Для "мыши" фирмы Microsoft в файл CONFIG.SYS должна быть добавлена следующая строка:

device = mouse.sys

Для инсталяции драйвера "мыши" фирмы IBM должна быть запущена программа MOUSE.COM. С этой целью в файл AUTOEXEC.BAT может быть добавлена строка вида:

mouse

После того, как драйвер размещен в системе, любые действия по перемещению "мыши" или нажатию ее клавиш будут вызывать генерацию прерывания 33Н. Процесс, управляющий "мышью", вызывает прерывание, затем устанавливает значения соответствующих внутренних переменных и продолжает свою работу. Вследствие того, что прерывание генерируется лишь при изменении "мышью" своего положения, неподвижная "мышь" не вызывает необходимости выделения на нее ресурсов компьтера.



Некоторые соображения по возможной модификации программы


Возможно, вы со временем захотите создать свою видеоигру, взяв, однако, за основу рассмотренную здесь игру TAG. В этом случае в можете, например, изменить траекторию движения спрайта компьютера заставив его двигаться вокруг какого-то объекта ("охранять" его). Интересным дополнением к программе будет возможность изменять внешний вид каждого объекта-участника игры в зависимости от каких-либо условий. Кстати, решение этой задачи не требует от вас каких-либо дополнительных усилий, так как каждое изображение спрайта можно хранить в видеопамяти, а все необходимые подпрограммы для работы с ней у вас уже есть.

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

Попробуйте добавить в программу еще один спрайт, касание которого будет приносить дополнительные очки играющим.

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



Нормализация данных.


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

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

нормирующий_множитель = 200 / (мах - min)

Таким образом, каждый элемент данных нормализуется по формуле:

нормализованное_данное = необработанное_данное * норм_множитель



Общий обзор


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

/* процедура исчезающего меню для работы в текстовом режиме */

#include "dos.h"

#include "stdlib.h"

#define ESC 27

void save_video(),restore_video();

char *fruit[] =

"Яблоко",

"Апельсин",

"Груша",

"Грейпфрут",

"Малина",

"Клубника"

char *color[]=

"Красный",

"Желтый",

"Оранжевый",

"Зеленый"

char *apple_type[] =

"Красный деликатес",

"Джонатан",

"Белый налив",

"Антоновка"

main()

cls();

for(i=0;i<25;i++)

printf("Это тест исчезающего меню\n");

popup(color,"кжоз",4,5,10,BORDER);

int popup(menu,keys,count,x,y,border)

char *menu[];               /*    текст меню */

char *keys;                   /*    горячие клавиши */

int count;                       /*    число альтернатив */

int x,y;                            /*    координаты левого верхнего угла */

int border;                     /*    если 0 то без рамки */

/* вычисление размеров */

for(i=0;i<count;i++)

if(strlen(menu[i]) > len) len=strlen(menu[i]);

endx=count+1+x;

p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));

if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */

/* высвечивание меню на своем месте */

void display_menu(menu,x,y,count);

/* восстановление части экрана */

free(p);

return choice;

void display_menu(menu,x,y,count)

int x,y,count;

for(i=0;i<count;i++,x++)

goto_xy(x,y);

printf(menu[i]);

int   startx,starty,endx,endy;

goto_xy(i,starty);

goto_xy(i,endy);

goto_xy(startx,i);

goto_xy(endx,i);

goto_xy(startx,starty); putchar(218);

goto_xy(endx  ,starty); putchar(192);


get_resp(x,y,count,menu,keys)

char *menu[];

union inkey

char ch[2];

int i;

 c;

int arrow_choice=0,key_choice;

goto_xy(x,y);

write_video(x,y,menu[0],REV_VID);

/* вернуть выбор в номальный режим */

write_video(x+arrow_choice,y,

menu[arrow_choice],norm_vid);

if(key_choice) return key_choice-1;

else  /* специальная клавиша */

switch(c.ch[1])

case 72 : arrow_choice--; /* стрелка вниз */

break;

case 80 : arrow_choice++; /* стрелка вверх */

break;

if(arrow_choice==count) arrow_choice=0;

/* вывод строки с определенным атрибутом */

int x,y;

int attrib;

void save_video(startx,endx,starty,endy,buf_ptr)

unsigned int *buf_ptr;

void restore_video(startx,endx,starty,endy,buf_ptr)

unsigned int *buf_ptr;

void cls()

Вводите эту программу в ваш компьютер и запускаете ее. В ходе ее выполнения каждое меню будет высвечено и исчезнет. (В этой программе все ответы теряются, но реальное применение будет, конечно, их обрабатывать.) Даже если ваш компьютер очень быстрый, вы возможно заметите, что исчезновение и появление меню требуют определенной задержки. Единственный путь решения этой проблемы - читать и писать символы прямо в видео память, что и обсуждается в следующем разделе. Еще раз отметим, что единственное важное достоинство использование BIOS в том, что такие меню работают на любом компьютере, который поддерживает BIOS, совместимый с IBM, даже если компьютер не 100% совместимый.


Общий план TSR-программы


Все TSR-программы обычно состоят из двух разделов. Первая часть используется для инициализации TSR-программы и возврата управления DOS путем использования реентерабельного системного вызова. Эта часть не выполняется до тех пор, пока не возникает необходимость в перезагрузке программы. При этом производится запись адреса точки входа TSR-программы в соответствующее место таблицы векторов.

Вторая,   прикладная                          часть,   занимается                          формированием

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

следовательно,и программы управления окнами. При этом изображение

на  экране  восстанавливается  после завершения работы прикладной

части программы. Следует помнить, что у большинства  TSR-программ

прикладные   части   представляют   собой   утилиты  формиривания

изображения,  как  у  программы  типа   "записной   книжки"   или

"калькулятора".   После  своего  завершения  они  восстанавливают

изображение на экране  в  том  же  виде,  каким  оно  было  перед

запуском этих программ.



Оконные структуры.


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

struct window_frame

int startx, endx, starty, endy; /*позиция окна*/

int curx, cury; /*текущая позиция курсора в окне*/

unsigned chsr *p; /*указатель буфера*/

char *header; /*имя окна*/

int border; /*включение/выключение границ*/

int active; /*на экране или невидимо*/



Определение расположения видео памяти


Одноцветный адаптер использует для видео памяти адрес B0000000H, a все остальные - В8000000Н. Для того, чтобы программы с меню работали правильно с каждым адаптером, они должны знать, какой адаптер имеет система. К счастью, для этого существует простой способ. Прерывание BIOS 16, функция 15 возвращает текущий видео режим. Как упоминалось раньше, программы, разработанные в этой главе, требуют режима 2, 3 или 7. Адаптеры CGA и EGA могут использовать режим 2 и 3, но не режим 7. Только одноцветный адаптер использует этот режим. Таким образом, если текущий видео режим 7, то используется одноцветный адаптер, в остальных случаях это EGA или CGA. Для наших задач, в текстовом режиме EGA и CGA одинаковы и поэтому все равно, какой из адаптеров у системы. Таким образом функция popup() должна поверить какой из адаптеров у системы и присвоить глобальной переменной указатель на соответствующий адрес. Этот фрагмент программы позволяет сделать это.

vmode = video_mode();

if((vmode!=2) && (vmode!=3) && (vmode!=7))

printf(" должен быть 80 символьный текстовый режим");

exit(1);

/* присвоить соответствующий адрес видео памяти */



Организация данных в видеоиграх


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



Основной цикл работы анализатора


Все интерпретаторы выполняют операции путем считывания лексемы программы и выбора необходимой функции для ее выполнения. Основной цикл работы для интерпретатора языка SMALL BASIC выглядит следующим образом.

do

token_type = get_token();

/* Проверка на соответствие оператору языка */

if (token_type == VARIABLE)

putback(); /* возврат переменной во входной поток */

assignment(); /* длжен быть оператор присваивания */

else /* это команда */

switch(tok)

case PRINT:

print();

break;

case GOTO:

exec_if();

break;

case FOR:

exec_for();

break;

case NEXT:

next();

break;

case INPUT:

input();

break;

case GOSUB:

gosub();

break;

case RETURN:

greturn();

break;

case END:

exit(0);

 while (tok != FINISHED);

Сначала лексема считывается из программы. Для удобства анализа каждая лексема располагается на отдельной строке. Если лексема является переменной, то, следуя синтаксису языка, за ней должен следовать оператор присваивания (SMALL BASIC не поддерживает старомодную команду LET). В противном случае, лексема считается командой и с помощью оператора case в зависимости от значения tok происходит выбор соответствующей

команды. Посмотрите, как работает каждая из них.



Отображение диаграмм на экране дисплея.


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

show backlog

Программа show                     использует                      функцию                 load_pic(),

предназначенную  для  изображения  диаграмм на экране.  (Вы также

можете использовать  эту  программу  для  отображения  на  экране

дисплея  других  графических образов,  предварительно созданных и

записанных в файл.)

/* Простейшая программа восстановления графических образов */

#include "stdio.h"

#include "dos.h"

void load_pic(),mode(),palette(),goto_xy();

main(argc,argv)

int argc;

char *argv[];

if (argc != 2)

printf(" Обращение: показать <имя файла>");

exit(1);

mode(4);

palette(0);

load_pic(argv[1]);

getch();

mode(3);

/* загрузка графического изображения */

void load_pic(fname)

char *fname;

FILE *fp; register int i,j;

char far *ptr = (char far *) 0xB8000000; /* указатель на CGA память */

char far *temp;

unsigned char buf[14][80]; /* для размещения содержимого

экрана */

if (!(fp=fopen(fname,"rb")))

goto_xy(0,0);

printf(" невозможно открыть файл \n");

return;

/* загрузка изображения из файла */

for (i=0;i<8152;i++)

*ptr = getc(fp); /* четный байт */

*(ptr+8152) = getc(fp); /* нечетный байт */

ptr++;

fclose(fp);

/* установка видеорежима */

void mode(mode_code)

int mode_code;

union REGS r;

r.h.al = mode_code;

r.h.ah = 0;

int86(0x10,&r,&r);

/* установка цветов диаграмм */

void palette(pnum)

int pnum;

union REGS r;

r.h.bh = 1; /* код 4 режима */

r.h.bl = pnum;

r.h.ah = 11; /* установка функции цвета */

int86(0x10,&r,&r);

/* установка курсора в координаты x,y */

void goto_xy(x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = y; /* горизонтальная координата */

r.h.dh = x; /* вертикальная координата */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);



Отображение строки в определенном цвете.


Отображение строки в определенном цвете не является столь трудной задачей, как вам может казаться на первый взгляд, если вы используете функции записи символа, которые используют, в свою очередь, возможности BIOS и видеопамяти (ROM-BIOS). ROM-BIOS прерывание 10Н, функция 9 позволяет отобразить текущий символ (один!) в позиции курсора и его атрибуты. Проблема состоит лишь в перемещении курсора по записываемой вами строке, но это должна осуществлять непосредственно ваша подпрограмма.

В соответствии с этим возникает, во-первых, необходимость определения текущей позиции курсора. Для этого используется функция read_cursor_xy(), представленная ниже. Эта функция использует ROM-BIOS-прерывание 10Н, функцию 3, для чтения текущих координат позиции курсора X и Y. Координаты позиции курсора возвращаются в качестве значений аргументов функции.

/* Чтение текущих координат позиции курсора */

void read_cursor_xy(x,y)

char *x,*y;

union REGS r;

r.h.ah = 3;  /* чтение текущей позиции курсора */

r.h.bh = 0;  /* видеостраница */

int86(0x10,&r,&r);

*y = r.h.dl;

*x = r.h.dh;

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

/* Перемещение курсора в позицию, специфицированную

координатами X и Y

*/

void goto_xy (x,y)

int x,y;

union REGS r;

r.h.ah = 2; /* функция адресации курсора */

r.h.dl = x; /* координата столбца */

r.h.dh = y; /* координата строки */

r.h.bh = 0; /* видеостраница */

int86(0x10,&r,&r);

 

Функция color_puts(), представленная ниже, отображает специфицированную пользователем строку в указанном цвете.

/* Печать строки в цвете   */

void color_puts(s,color)

char *s;  /* строка */

char color; /* цвет строки */


union REGS r;

char x,y;

read_cursor_xy(&x,&y); /* получение текущей позиции курсора

*/

while (*s)

if (*s == '\n')   /* обработка символа новой строки */

printf("\n");

s++;

x = 0; y++;  /* переход на следующую строку */

continue;

r.h.ah         =   9; /* функция отображения символа и его атрибутов */

r.h.al          =   *s++;    /* отображаемый символ */

r.h.bl          =   color;   /* атрибуты цвета */

r.h.bh        =   0;       /*видеостраница 0 */

r.x.cx          =   1; /* отобразить за единицу времени ( такт ) */

int86(0x10,&r,&r);

x++;

goto_xy(x,y); /* перемещение курсора */

Как вы можете видеть, отображаемый символ запоминается в регистре AL, атрибуты цвета символа - в регистре BL, номер видеостраницы - в регистре BH, а количество интервалов времени (тактов процессора), за которое будет отображен символ - в регистре CX. Заметим, что функция также обрабатывает специальный символ новой строки ('\n'). Вы можете также, по желанию, организовать обработку символов табуляции ('\t'), двойных кавычек (") и других специальных символов.

Использование функции color_puts() предполагает наличие ряда макроопределений в начале вызывающей функцию программы. Перечень макроопределений представлен ниже

#define BLUE                                          1

#define GREEN                             2

#define RED                                          4

#define INTENSE                         8

#define BLUE_BACK    16

#define GREEN_BACK   32

#define RED_BACK                    64

#define BLINK                             128

Используя эти макросы, вы можете по своему усмотрению выдать

на экран строку текста на фоне установленного вами цвета, а также

саму строку в определенном вами цвете.  Вы можете также управлять

режимом отображения  строки  (повышенная  яркость  или мерцание).

Комбинируя цвета, режимы мерцания или повышенной яркости для одного или совокупности символов, вы можете добиться любого желаемого вами эффекта. Например, представленная ниже строка программы приведет к отображению строки "А это - текст" в режиме повышенной яркости в голубом (циановом) цвете:

color_puts("А это - текст",GREEN | RED | INTENSE );