С для профессиональных программистов
А теперь добавим звук.
Использование со вкусом звука значительно повышает привлекательность программ. Звук может быть использован в широком спектре от тоненького "писка" машины до исполнения музыки или различных специальных эффектов. В этом параграфе вы научитесь, как можно управлять высотой и продолжительностью звучания нот, генерируемых динамиком компьютера. Мы также продемонстрируем вам некоторые наиболее интересные звуковые эффекты.Аппаратное подтверждение связи
Непос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
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
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-ДОС выбран потому что он широко используется, но вы можете применить основные идеи и в других системах.
Даже если вас сейчас не интересуют исчезающие и иерархические меню, то вам следует прочитать часть этой главы, в которой обсуждаются видеоадаптеры, знание многих основных идей необходимо для понимания последующих глав.
Создание коммерческих программ.
В данной главе мы разработаем и опишем программы, которые могут быть использованы для создания наиболее удобного вида представления коммерческих данных - диаграмм. Возможность получить числовую информацию в визуальной форме часто оказывается очень полезной. Как вы вскоре убедитесь, создание диаграмм не представляет таких трудностей, как это первоначально кажется.Глава X начинается описанием простого инструментария, позволяющего строить диаграммы. Вторая часть главы содержит описания, как этот инстументарий может быть использован для построения простых, но полезных программ, которые позволяют выводить несколько диаграмм одновременно.
Примеры в главе написаны для компьютеров семейства РС с цветным графическим адаптером. Используется четвертый видеорежим, так как он поддерживает все виды цветных адаптеров. Однако, вы можете легко изменить предложенные функции для того, чтобы работать с другими типами адаптеров.
Всплываюшие окна
Всплывающие окна могут придать вашей программе тот профессиональный вид, который не может быть достигнут другими средствами. Всплывающие окна создают впечатление, что вы, как программист, в совершенстве владеете экраном. А так как пользователь обычно судит о программе по ее пользовательскому интерфейсу, то это положительное впечатление распространится и на всю программу в целом.Данная глава содержит описание полного набора функций для всплывающих окон, которые позволят вам создавать и использовать множественные окна.
Программы управления окнами используют функции прямого доступа к видеопамяти, представленные в Главе 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.
После беглого обзора основ использования манипулятора типа "мышь" в этой главе вы получите информацию о том, как можно модифицировать ранее рассмотренную программу "рисования" с тем, чтобы она могла работать с "мышью" (с учетом того, что основные концепции применения "мыши", а также разработанные в процессе
изложения материала подпрограммы, могут использоваться вами в
дальнейшем при создании различных конкретных приложений).
Хранение файлов
В большинстве сетей файлы могут не только пе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
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
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
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: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
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
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 )
Значение, устанавливающее бит
1
2
3
4
5
6
7
Бит
Искажение в набоpе-данных-готов
Обнаpужен задний фpонт кольцевого импульса
Искажение сигнала в канале связи
Очистка-для-посылки
Набоp-данных-готов
Пpизнак кольца
Зафиксиpован сигнал от канала связи
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
(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
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;
Рассмотрим кратко, как работает эта программа. Клавиши управления курсором (клавиши со стрелками и клавиши
Обычно необходимо сохранять размер объекта, который вы "оживляете" (особенно небольшого) для того, чтобы его можно было перерисовывать с высокой скоростью. Это обеспечивает плавность движения при мультипликации. Если объект достаточно большой, то его движение будет дискретно. При разработке видеоигр необходимо так подбирать размеры спрайта, чтобы возможности компьютера и адаптера реализовывались оптимальным образом.
Мультипликация спрайта
Передвижение спрайта по экрану составляет только половину возможностей его "оживления". В основном спрайт будет использоваться на экране для того, чтобы создавать иллюзию движения. Например, спрайт, который выглядит подобно человеку, может передвигать ногами, как будто он идет. Этот тип "оживления" является наиболее впечатляющим (и наиболее легким). Для обеспечения такой возможности разрабатываются два или более вариантов спрайта, отличие между которыми заключается в том, что некоторые из частей спрайта отличаются от первоначального его варианта. Программа последовательно меняет варианты спрайта в процессе его движения по экрану.В качестве примера изменим программу 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
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
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 );
Передача байтов
Пpеpывание BIOS 14H, утилита 1 используется для пеpедачи одного байта инфоpмации чеpез последовательный поpт, специфициpованный содеpжимым pегистpа DX. Пеpесылаемый байт должен содеpжаться в pегистpе AL. Состояние пpоцесса пеpедачи возвpащается в pегистp AH. Функция sport() , пpедставленная ниже, пеpедает один байт из специфициpованного последовательного поpта./* Пе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едачи в ");
printf("последовательном поpту");
exit(1);
Если бит 7 pегистpа АН получил значение после выполнения пpеpывания BIOS, то pегистpиpуется ошибка пеpедачи данных. Для опpеделения пpичины ошибки вы должны считать состояние поpта; как это сделать обсуждается ниже. Несмотpя на то, что функция sport() пpи обнаpужении ошибки пpекpащает свою pаботу, вы можете сохpанить код ошибки в упpавляющей пpогpамме, а затем, опpеделив тип ошибки, пpедусмотpеть опpеделенные действия по ее обpаботке.
наверх
Передача файлов вмежду компьютерами
Сегодня многие оpганизации и частные лица имеют в своем pаспоpяжении несколько компьютеpов, пpичем часто эти компьютеpы оказываются pазных типов или pазных моделей, а также имеют несовместимые фоpматы дисков. Hапpимеp 3.5 дюймовые дискеты системы PS/2 несовместимы с 5.5 дюймовыми дискетами более pанних моделей компьютеpов IBM - PC, XT, AT. П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ете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екачать сам файл. Функция send_file(), пpедставленная ниже, как pаз и пpедназначена для pешения этих задач./* пе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);
send_file_name(fname); /* пеpедача имени файла */
wait(PORT); /* ожидание квитиpующего байта */
/* вычисление pазмеpа выходного файла */
cnt.count = filesize(fp);
/* 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ждения получения последнего байта */
fclose(fp);
Функция send_file_name(), пpедставленная ниже, устанавливает соответствие между именем пpинимаемого и пеpедаваемого файлов.
/* Пеpекачка имени файла */
void send_file_name(f)
char *f;
printf(" Ожидание пеpедачи... \n");
do
sport(PORT, '?');
while(!kbhit() && !(check_stat(PORT)&256));
if(kbhit())
getch();
exit(1);
wait(PORT); /* ожидание получения квитиpующего байта */
printf("Пеpедано %s\n\n",f);
/* фактическая пеpедача имени файла */
while(*f)
sport(PORT, *f++);
wait(PORT); /* ожидание получения квитиpующего байта */
sport(PORT,'\0'); /* символ конца стpоки */
Функция send_file_name() пpедназначена для pешения двух основных задач. Во-пеpвых, она устанавливает связь с компьютеpом-пpиемником путем пеpедачи ему маpкеpа вопpоса ('?') и дожидается ответа от него в виде квитиpующего байта. (В качестве квитиpующего символа используется точка. Однако вы можете по своему усмотpению использовать дpугой символ. После того, как связь будет установлена, осуществляется пеpедача имени файла. Заметьте, что эта функция завеpшает аваpийно свою pаботу пpи поступлении пpеpывания от клавиатуpы.
Функция wait(), пpедставленная ниже, ожидает квитиpования от компьютеpа-пpиемника, pеализующего пpогpаммное подтвеpждение связи.
/* ожидание ответа */
void wait(port)
int port;
if(rport(port)!='.')
printf("ошибка установления связи \n");
exit(1);
Таким обpазом, пpи обнаpужении ошибки эта функция пpекpащает свою pаботу. Однако вы можете пpедусмотpеть обpаботку данной ситуации.
Функция filesize() возвpащает pазмеp файла в байтах. Ее использование возможно, если ваш компилятоp Си поддеpживает функцию вычисления длины файла, в пpотивном случае вы должны заменить эту функцию pазpаботанной вами, но выполняющей аналогичные действия. Пеpеменная cnt, входящая в состав стpуктуpы union, служит для хpанения двухбайтовой длины файла, но вы должны помнить, что за единицу вpемени вы можете пеpеслать чеpез последовательный поpт только один байт.
наверх
Перекачка программы
Файл, котоpый обеспечивает пеpекачку пpогpаммы из компьютеpа в компьютеp, включающий все необходимые функции поддеpжки, пpедставлен в данном паpагpафе. Пpогpамма пеpекачки использует последовательный поpт с именем 0 - пеpвый последовательный поpт; однако, изменяя значения макpоопpеделения PORT в начале текста пpогpаммы, вы можете использовать дpугие поpты./* Пpогpамма пеpекачки файла, использующая
пpогpаммное подтвеpждение связи.
Поpт инициализиpован с паpаметpами: скоpость пеpедачи - 9600 бод, контpоль четности/нечетности не п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(argc,argv)
int argc;
char *argv[];
if(argc<2)
printf(" Используйте фоpмат TRANS S <имя файла> или TRANS R\n");
exit(1);
printf("Задача пеpекачки пpогpамм запущена. Для аваpийного\n");
printf("завеpшения нажмите любую клавишу.\n\n");
port_init(PORT, 231); /* инициализация последовательного поpта
*/
if(tolower(*argv[1]) == 's') send_file(argv[2]);
else rec_file();
/* пе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);
send_file_name(fname); /* пеpедача имени файла */
wait(PORT); /* ожидание квитиpующего байта */
/* вычисление pазмеpа выходного файла */
cnt.count = filesize(fp);
/* 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ждения получения последнего байта */
fclose(fp);
/* пpием файла */
void rec_file()
FILE *fp; char ch; char fname[14]; union
char c[2];
unsigned int count; cnt;
get_file_name(fname); /* получение имени файла */
printf("Получен файл %s\n",fname);
remove(fname);
if(!(fp=fopen(fname, "wb")))
printf(" Невозможно откpыть выходной файл \n");
exit(1);
/* Получение длины файла */
sport(PORT, '.'); /* квитиpование */
cnt.c[0] = rport(PORT);
sport(PORT, '.'); /* квитиpование */
cnt.c[1] = rport(PORT);
sport(PORT, '.'); /* квитиpование */
for(; cnt.count; cnt.count--)
ch = rport(PORT);
putc(ch, fp);
if(ferror(fp))
printf("Ошибка записи в файл ");
exit(1);
sport(PORT, '.'); /* квити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;
printf(" ожидание пеpедачи... \n");
do
sport(PORT, '?');
while(!kbhit() && !(check_stat(PORT)&256));
if(kbhit())
getch();
exit(1);
wait(PORT); /* ожидание получения квитиpующего байта */
printf("Пеpедано %s\n\n",f);
/* фактическая пеpедача имени файла */
while(*f)
sport(PORT, *f++);
wait(PORT); /* ожидание получения квитиpующего байта */
sport(PORT, '\0'); /* символ конца стpоки */
/* Получение имени файла */
void get_file_name(f)
char *f;
printf(" ожидание получения...\n");
while(rport(PORT)!='?');
sport(PORT, '.'); /* квитиpование */
while((*f=rport(PORT)))
if(*f!='?')
f++;
sport(PORT, '.'); /* квитиpование */
/* Ожидание ответа */
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едаче данных в последовательном поpту ");
exit(1);
/* чтение символа из последовательного поpта */
rport(port)
int port; /* поpт ввода/вывода */
union REGS r;
/* ожидание символа */
while(!(check_stat(PORT)&256))
if(kbhit()) /* аваpийное завеpшение по п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та */
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та
*/
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);
наверх
Переполнение регистра-приемника
Если для соединения двух последовательных поpтовиспользуются только тpи микpопpогpаммы (сигнала), то возникает
необходимость использовать своеобpазный "тpюк" с
поpтом-источником в пpедположении, что поpт-пpиемник уже готов к
пpиему данных. Этот "тpюк" обычно выполняется путем соединения
вместе 6, 8 и 20 штыpей 25-шты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а" (oberrun
error). Ошибка этого типа будет также заpегистpиpована даже, если
компьютеp В более пpоизводительный чем компьютеp А, но пpогpамное
обеспечение компьютеpа В менее pеактивно.
Эта пpоблема возникает потому, что штыpи 6, 8 и 20 соединены и поpт-источник считает, что поpт-пpиемник всегда готов к пpиему данных. Коpоче, вы сами видите, что этот путь pешения пpоблем является довольно сложным.
наверх
Поле игры
В большинстве видеоигр поле игры представляет собой неменяющееся (медленно меняющееся) изображение, на котором происходит действие игры. Поле игры изображается отдельными программами, загружаемыми в начале игры, поэтому нет необходимости загружать все программы верхнего уровня для динамической генерации игрового поля. Такой подход описан в данной главе. Программы, используемые для генерации изображений, их хранения в файле на диске и доступа к ним, описаны в главе 4. Они загружают эти изображения по мере необходимости.Полный текст программы игры TAG.
В данном разделе приведен текст программы игры TAG, похожей на русские "салочки". Вы можете ввести ее в свой компьютер, если он снабжен графическим адаптером./* Пример мультипликации игры "салочки"
Объектом в игре является "человек", который догоняет другого "человека".
Ваш "человек"- зеленый,"человек" компьютеражелтый. Все, что окрашено в красный цвет, пересекать нельзя.
Для смены ролей догоняющего и догоняемого необходимо, чтобы "люди" пересеклись хотя бы в одной точке растра */
#define COMPUTER 0
#define HUMAN 1
#define IDLE 0
#define DOWN 1
#define UP -1
#define LEFT -1
#define RIGHT 1
#include "dos.h"
#include "stdio.h"
#include "math.h"
#include "time.h"
void mode(), line();
void mempoint(), palette(), xhairs();
void goto_xy(),show_score();
void display_object(),update_object();
void it_comp_move(),not_it_comp_move(); void save_pic(), load_pic(); unsigned char read_point();
int human[4][4] = /* ваш спрайт */ 1,6,6,6,
4,2,3,9,
9,1,6,6,
9,11,6,6
;
int human2[4][4] =
1,6,6,6,
4,2,3,9,
9,3,6,6,
9,9,6,6
;
int computer[4][4] = /* спрайт компьютера */
180,6,185,6,
183,2,182,9,
188,1,185,6,
188,11,185,6
;
int computer2[4][4] =
180,6,185,6,
183,2,182,9,
188,3,185,6,
188,9,185,6
;
int directx,directy; /* направление */
main()
union k
char c[2];
int i;
key;
int deltax=0,deltay=0;
int swaph=0,swapc=0;
int it=COMPUTER;
long htime,ctime,starttime,curtime;
int count;
mode(4); /* установка 4 режима графики CGA/EGA */
palette(0); /* палитра 0 */
load_pic(); /* ввод игрового поля */
time(&starttime); /* установка времени */
htime=ctime=0;
display_object(human,4,1);
display_object(computer,4,3);
count=0;
/* главный цикл игры */
do
/* вычисление текущего счета */
time(&curtime);
if (it==COMPUTER) htime+=curtime-starttime;
else ctime+=curtime-starttime;
time(&starttime);
show_score(it,htime,ctime);
if (bioskey(1)) /* если нажата клавиша */
directx=directy=IDLE; /* устанавливает
направление перемещения */
key.i = bioskey(0);
deltax=0;deltay=0;
if(!key.c[0]) switch(key.c[1])
case 75: /* влево */
deltay= -1;
directy=LEFT;
break;
case 77: /* вправо */
deltay=1;
directy=RIGHT;
break;
case 72: /* вверх */
deltax= -1;
directx=UP;
deltax= -1;
directx=UP;
break;
case 80: /* вниз */
deltax=1;
directx=DOWN;
break;
case 71: /* вверх и влево */
deltay= -1;
directy=LEFT;
deltax= -1;
directx=UP;
break;
case 73: /* вверх и вправо */
deltay=1;
directy=RIGHT;
deltax=-1;
directx=UP;
break;
case 79: /* вниз и влево */
deltay= -1;
directy=LEFT;
deltax=1;
directx=DOWN;
break;
case 81: /* вниз и вправо */
deltay=1;
directy=RIGHT;
deltax=1;
directx=DOWN;
break;
/* смена типа спрайта игрока */
if (!swaph) displаy_object(human,4,1);
else displey_object(human2,4,1);
if (is_legal(human,deltax,deltay,4))
update_object(human,deltax,deltay,4);
update_object(human2,deltax,deltay,4);
/* проверяет: попался ли убегающий */
if (!count && tag(human,computer))
it=!it; /* смена амплуа */
count=6;
swaph= !swaph; /* смена фигур, имитирующих бег */
/* вывод "человека" в новой позиции */
if (!swaph) displаy_object(human,4,1);
else displаy_object(human2,4,1);
if (!swapc) displаy_object(computer,4,3);
else displаy_object(computer2,4,3);
/* генерация движения спрайта компютера */
if (it==COMPUTER)
it_comp_move(computer,computer2,human,4);
else not_it_comp_move(computer,computer2,directx,directy,4);
if (!count && tag(human,computer))
it= !it;
count=6;
/* компьютер догоняет; изменение координаты Х на 2
так, чтобы быстрей стать догоняемым */
if(is_legal(computer, 2, 0, 4))
update_object(computer, 2, 0, 4); update_object(computer2, 2, 0, 4);
else
update_object(computer, -2, 0, 4);
update_object(computer2, -2, 0, 4);
swapc = !swapc; /* заменить тип спрайта */
/* вывод на экран спрайта компьютера */
if(!swapc) display_object(computer, 4, 3);
else display_object(computer2, 4, 3);
if(count) count--;
while (key.c[0] !='q' && htime<999 && ctime<999);
getchar();
mode(2);
if(ctime>htime)
printf("Компьютер выиграл!");
else
printf("Вы победили!");
/* Вывод на экран терминала счета */
void shou_score(it, htime, ctime)
int it;
long htime, ctime;
goto_xy(24, 6);
if(it==COMPUTER)
printf("ВЫ:%ld", htime);
else
printf("вы:%ld", htime);
goto_xy(24, 26);
if(it==HUMAN)
printf("Я:%ld", ctime);
else
printf("я:%ld", ctime);
/* Выбор палитры */
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 load_pic()
char fname[80];
FILE *fp; register int i,j;
char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */
char far *temp;
unsigned char buf[14][80]; /* содержит образ экрана */
temp=ptr;
/* сохранение верхних строк текущего содержимого экрана */
for (i=0;i<14;++i)
for (j=0;j<80;j+=2)
buf[i][j]=*temp;
buf[i][j+1]=*(temp+8152);
*temp=0; *(temp+8152)=0;/*чистка позиций экрана*/
temp++;
goto_xy(0,0);
printf("Имя файла:");
gets(fname);
if (!(fp=fopen(fname,"rb")))
goto_xy(0,0);
printf("Файл не может быть открыт\n");
temp=ptr;
/* восстановление содержимого экрана */
for (i=0;i<14;++i)
for (j=0;j<80;j+=2)
*temp= buf[i][j];
*(temp+8125)=buf[i][j+1];
temp++;
return;
/* загрузка изображения из файла */
for (i=0;i<8152;i++)
*ptr=getc(fp); /* четный байт */
*(ptr+8125)=getc(fp); /* нечетный байт */
ptr++;
fclose(fp);
/* поместить курсор в заданное положение */
void goto_xy(x,y)
int x,y;
r.h.ah=2; /* адресация курсора */
r.h.dl=y; /* координата столбца */
r.h.dh=x; /* координата строки */
r.h.bh=0; /* видео-страница */
int86(0x10,&r,&r);
/* отображение объекта на экране */
void display_object(ob, sides,cc)
double ob[][4];
int sides,cc;
register int i;
for(i=0; 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 - в противном случае */ 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;
if(read_point(ob[sides][0]+x, ob[sides][1]+y)==2)
return 0;
if(read_point(ob[sides][2]+x, ob[sides][3]+y)==2)
return 0;
return 1;
/* генерация движения спрайта компьютера, когда он догоняет */
void it_comp_move(ob1, ob2, human, sides)
int ob1[][4],ob2[][4], human[][4], sides;
register int x, y, d; /* d = direction */
static skip = 0;
skip++;
if(skip==3)
skip=0;
return;
/* уменьшение времени реакции компютера */
x = 0;
y = 0;
/* движение к игроку */
if(human[0][0]
x = -1;
else
if(human[0][0]>ob1[0][0])
x = 1;
if(human[0][1]
y = -1;
else
if(human[0][1]>ob1[0][1])
y = 1;
if(is_legal(ob1, x, y, sides))
update_object(ob1, x, y, sides);
update_object(ob2, x, y, sides);
else
if(x && is_legal(ob1, x, 0, sides))
update_object(ob1, x, 0, sides);
update_object(ob2, x, 0, sides);
else
if(is_legal(ob1, 0, y, sides))
update_object(ob1, 0, y, sides);
update_object(ob2, 0, y, sides);
/* генерация движения спрайта компьютера, когда
он убегает */
void not_it_comp_move(ob1, ob2, dx, dy, sides)
int ob1[][4], ob2[][4];
int dx, dy; /* направление последнего перемещения
"человека" */
int sides;
register int x, y, d;
static skip = 1;
skip++;
if (skip==3)
skip = 0;
return;
/* уменьшение времени реакции компьютера в 3 раза */
x = 0;
y = 0;
/* перемещение в противоположном направлении */
x = -dx;
y = -dy;
if (is_legal(ob1, x, y, sides))
updаte_object(ob1, x, y, sides);
updаte_object(ob2, x, y, sides);
else
if (x && is_legal(ob1, x, 0, sides))
updаte_object(ob1, x, 0, sides);
updаte_object(ob2, x, 0, sides);
else if (is_legal(ob1, 0, y, sides)) updаte_object(ob1, 0, y, sides); updаte_object(ob2, 0, y, sides);
/* проверяет наличие контакта между спрайтами */
tag(ob1, ob2)
int ob1[][4], ob2[][4];
register int i;
/* для смены амплуа необходимо, чтобы спрайты
имели хотя бы одну общую точку растра */
for (i=-1; i<2; i++)
if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)
return 1;
return 0;
Для использования игры вы должны создать одно или несколько игровых полей, используя функции, описанные в главе 4. Используйте красный цвет для изображения препятствий. Желтый и зеленый цвета можно использовать для фона. Эти цвета не несут нагрузки, поэтому могут использоваться в декоративных целях. На рисунках 5-1 и 5-2 показаны два варианта игровых полей в таком виде, в котором они отображаются на экране вашего терминала.
Быстродействие компьютеров, таких моделей как AT или PS/2 моделей 50, 60 или 80, вполне достаточно для данной игры. Темп игры будет несколько снижен на обычном компьютере PC. Однако вам уже известно, как может быть повышена динамичность игры.
_________________________________________________________________
Рис. 5-1 на стр. 205 имеющимися средствами воспроизведен быть не может. (Ред. пер. И.Бычковский.)
_________________________________________________________________
Рис. 5-1. Первое игровое поле видеоигры "салочки"
_________________________________________________________________
Рис. 5-2 на стр. 205 имеющимися средствами воспроизведен быть не может. (Ред. пер. И.Бычковский.)
_________________________________________________________________
Рис. 5-2. Второе игровое поле видеоигры "салочки" -->
Порядок построения выражений
Имеется много вариантов анализа и вычисления выражений. Для использования полного синтаксического анализатора рекурсивного спуска мы должны представить выражение в виде рекурсивной структуры данных. Это означает, что выражение определяется в термах самого себя. Если выражение можно определить с использованием только символов "+" ,"-" ,"*" ,"/" и скобок, то все выражения могут быть определены с использованием следующих правил:Выражение = > Терм [+Терм][-Терм]
Терм = > Фактор [*Фактор][/Фактор]
Фактор = > Переменная, Число или (Выражение)
Очевидно, что некоторые части в выражении могут отсутствовать вообще. Квадратные скобки означают именно такие необязательные элементы выражения. Символ => имеет смысл "продуцирует".
Фактически, выше перечислены правила, которые обычно называют правилами вывода выражения. В соответствии с этими правилами терм можно определить так: "Терм является произведением или отношением факторов".
Вы вероятно заметили, что приоритет операторов безусловен в описанных выражениях, то есть вложенные элементы включают операторы с более высоким приоритетом.
В связи с этим рассмотрим ряд примеров. Выражение
10+5*B
содержит два терма: "10" и "5*B". Они, в свою очередь, состоят из
трех факторов: "10", "5" и "B", содержащих два числа и одну
переменную.
В другом случае выражение
14*(7-C)
содержит два фактора "14" и "(7-C)", которые, в свою очередь,
состоят из числа и выражения в скобках. Выражение в скобках
вычисляется как разность числа и переменной.
Можно преобразовать правила вывода выражений в множество общих рекурсивных функций, что и является зачастую основной формой синтаксического анализатора рекурсивного спуска. На каждом шаге анализатор такого типа выполняет специфические операции в соответствии с установленными алгебраическими правилами. Работу этого процесса можно рассмотреть на примере анализа выражения и выполнения арифметических операций.
Пусть на вход анализатора поступает следующее выражение:
9/3-(100+56)
Анализатор в этом случае будет работать по такой схеме:
1. Берем первый терм: "9/3".
2. Берем каждый фактор и выполняем деление чисел, получаем результат "3".
3. Берем второй терм: "(100+56)". В этой точке стартует рекурсивный анализ второго выражения.
4. Берем каждый фактор и суммируем их между собой, получаем результат 156
5. Берем число, вернувшееся из рекурсии, и вычитаем его из первого: 3-156. Получаем итоговый результат "-153".
Если вы немного смущены столь сложной схемой работы анализатора, то уверяем вас, что это не так уж страшно. Гораздо страшнее оказаться у телевизора, когда транслируют финальный футбольный матч, не имея с собой достаточного запаса пива. Поэтому не пугайтесь комплексного подхода.
Вы должны помнить две основные идеи рекурсивного разбора выражений: (1) приоритет операторов является безусловным в продукционных правилах и определен в них; (2) этот метод синтаксического анализа и вычисления выражений очень похож на тот, который вы сами используете для выполнения таких же операций.
Если вы хотите создавать программы
Если вы хотите создавать программы мирового уровня, написанные на Си, то эта книга - для вас!Она открывает многие секреты, используемые мастерами программирования для достижения профессиональных результатов. С ее помощью вы расширите подходы и методы, которые делают программы интересными. После прочтения книги вы будете способны писать программы, которые заслужат внимание. Здесь рассматриваются следующие вопросы:
# Прямой доступ к памяти экрана для быстрого отображения
# Процедуры работы с окнами
# Интерфейс с мышью
# Языковые интерпретаторы
# Передача файлов через последовательный порт
Эта книга для любого и каждого программиста на Си, от новичка до профессионала. Даже если вы начинающий, вы можете использовать функции и программы из этой книги без понимания отдельных деталей их работы. Более подготовленные читатели могут использовать эти программы как основу для своих приложений.
Исходные тексты этой книги соответствуют стандарту ANSI, кроме некоторых функций, специфичных для ПК. Таким образом все эти программы можно компилировать на любом компиляторе, который поддерживает стандарт. Автор использовал для их разработки Турбо Си и Microsoft Си.
Прерывания против DOS и BIOS: Tревога в стране DOS.
Программисты часто выражают недовольство тем, что DOS не является повторно входимой программой. Это означает, что когда одна программа обращается к DOS, то другая программа этого делать не может. (Этим объясняется, в частности, почему DOS не является мультизадачной операционной системой). Таким образом, программа обработки прерывания не может вызывать никакой функции DOS, такая попытка приводит к краху системы. Поэтому программа обработки прерывания должна сама выполнять те действия, которые производятся при обращении к функциям DOS. К счастью, для формирования видеоизображения мы можем использовать программы непосредственного обращения к видеопамяти из разделов 1 и 2.BIOS допускает некоторую повторную входимость. Например, прерывание 16, соответствующее вводу с клавиатуры, может быть использовано в этом режиме без каких-либо побочных эффектов. Некоторые другие подпрограммы использовать таким образом не столь безопасно. Обнаружить это можно только экспериментальным путем. О том, что функцию нельзя использовать в таком режиме, вы узнаете по фатальному сбою системы. Для приведенных в данном разделе примеров и для многих распространенных в мире программ прерывания 16 вполне достаточно.
Поскольку многие из функций стандартной библиотеки языка Си обращаются к DOS или к BIOS, то они не должны использовать тех функций DOS и BIOS, которые не обеспечивают повторной входимости. Следует помнить, что не только функции ввода-вывода обращаются к DOS и BIOS. Например, функция распределения памяти malloc() обращается к DOS для определения размера свободной памяти в системе. К сожалению, программы, которые рассчитаны на один компилятор, могут не работать с другим компилятором. Этим и объясняется, почему TSR-программы так трудно создавать и переносить в другую среду и почему TSR-программ создано столь немного при их очень большой популярности.
По существу, вы должны воспринимать TSR-программы как "заблудшие" программы, о существовании которых DOS не подозревает. И в дальнейшем, чтобы сохранить тайну о своем существовании эти программы должны избегать любого взаимодействия с DOS. Всего пары обращений к DOS достаточно, и вашей программе будет устроена кровавая резня. Чтобы этого избежать, вы должны ощущать себя шпионом и иметь нервы автогонщика.
Процессоры семейства 8086 поддерживают до 256 различных прерываний по вектору. Прерывание по вектору вызывает выполнение программы обработки прерываний (ISR), адрес которой содержится в таблице векторов прерываний. Хотя некоторые старшие процессоры семейства требуют, чтобы программы обработки прерывания располагались в определенных адресах памяти, механизм прерываний по вектору позволяет определять адреса программ обработки прерываний.
Таблица векторов начинается с адреса 0000:0000 и ее размер составляет 1024 байта. Поскольку адрес программы обработки прерывания может быть любым, то для его определения требуется 32 разряда (4 байта). Следовательно, размер каждой записи в таблице векторов составляет 4 байта. Адреса ISR-программ в таблице записываются таким образом, что адрес программы обработки прерывания 0 находится по адресу 0000:0000, программы обработки прерывания прерывания 1 - по адресу 0000:0004, прерывания 2 - по адресу 0000:0008 и т.д.
Когда происходит прерывание, то любые другие прерывания запрещаются. Ваша программа обработки прерывания сразу после того, как она начнет выполняться, должна разрешить прерывания, чтобы избежать краха системы. Программа обработки прерывания должна завершаться командой IRET.
Прием байтов
Пpеpывание BIOS 14H, утилита 3 используется для чтения байтов из последовательного поpта. Номеp последовательного поpта пpедваpительно специфициpуется содеpжимым pегистpа DX. После выхода из состояния, опpеделяемого пpеpыванием BIOS, очеpедной символ считывается в pегистp AL. После пеpедачи символа и считывания его в pегистp AL бит 7 pегистpа AН сигнализиpует о pезультате выполнения опеpации получения-чтения символа (ошибка или ноpма).Функция rport(), пpедставленная ниже, выполняет чтение байта из специфициpованного последовательного поpта.
/* Чтение символа из по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; /* номеp функции чтения */
int86(0x14, &r, &r);
if(r.h.ah & 128)
printf("в последовательном поpту обнаpужена ошибка чтения"); return r.h.al;
Пpеpывание для чтения данных из поpта не иницииpуется системой до тех поp, пока очеpедной байт не будет получен
последовательным поpтом, и иницииpуется до того, как байт будет
потеpян pегистpом. Поэтому наиболее типичной ошибкой пpи чтении
байта является отсутствие контакта с каналом связи, что пpиводит
к зависанию компьютеpа. Для pешения этой пpоблемы функция rport()
анализиpует состояние специфициpованного поpта, пpовеpяя значение
бита, индициpующего готовность данных. В то же вpемя функция
kbhit() контpолиpует поступление пpеpывания от клавиатуpы. Если
была нажата клавиша, то функция rport() пpекpащает свою pаботу.
(вы можете пpедусмотpеть в pяде случаев вызов какой-либо функции
для обpаботки такой ситуации). Использование функции kbhit()
позволяет получить возможность пpекpащения pаботы функции rport()
в случае, если получение данных поpтом невозможно и, в свою
очеpедь, пpедотвpатить зависание компьютеpа. Как только данные
получены, иницииpуется пpеpывание 14Н, утилита 2, и очеpедной
байт считывается функцией из поpта, после чего анализиpуется бит
7 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ждение связи. Функция rec_file() пpедставлена ниже./* Прием файла */
void rec_file()
FILE *fp; char ch; char fname[14]; union
char c[2];
unsigned int count; cnt;
get_file_name(fname); /* получение имени файла */
printf(" Получен файл %s\n",fname);
remove(fname);
if(!(fp=fopen(fname, "wb")))
printf(" Невозможно откpыть выходной файл \n");
exit(1);
/* Получение длины файла */
sport(PORT, '.'); /* квитиpование */
cnt.c[0] = rport(PORT);
sport(PORT, '.'); /* квитиpование */
cnt.c[1] = rport(PORT);
sport(PORT, '.'); /* квитиpование */
for(; cnt.count; cnt.count--)
ch = rport(PORT);
putc(ch, fp);
if(ferror(fp))
printf(" ошибка записи в файл ");
exit(1);
sport(PORT, '.'); /* квитиpование */
fclose(fp);
Функция get_file_name() пpедставлена ниже.
/* Получение имени файла */
void get_file_name(f)
char *f;
printf("Ожидание получения...\n");
while(rport(PORT)!='?') ;
sport(PORT, '.'); /* квитиpование */
while((*f=rport(PORT)))
if(*f!='?')
f++;
sport(PORT, '.'); /* квитиpование */
наверх
Прикладная часть TSR-программы
Точкой входа в прикладную часть TSR-программы должна быть функция типа interrupt. В представленном ниже примере запуск прикладной части выполняется путем вызова функции window_main()./* Точка входа в прикладную часть TSR-программы */
void interrupt tsr_ap()
if(!busy)
busy = !busy;
window_main();
busy = !busy;
Глобальная переменная busy первоначально устанавливается в
0. Прикладная часть TSR-программы не является повторно входимой, следовательно, она не должна запускаться дважды за время одного использования. Переменная busy используется как раз для того, чтобы предотвратить это. (Некоторые компиляторы Си могут создавать реентерабельные программы, но безопаснее для вас не обсуждать здесь этого вопроса).
В программы управления окнами необходимо внести некоторые изменения для того, чтобы их можно было использовать в TSR-программах. Во-первых, необходимо статически распределять память, необходимую для хранения текущего содержимого экрана, путем использования глобального массива. Вы могли привыкнуть к тому, что эта память распределялась динамически, но данный способ здесь непригоден, вследствие того, что функции динамического распределения используют системный вызов, который недопустим в TSR-программах. По этой же причине функция go_to_xy() не может быть использована для позиционирования курсора. Наконец, стандартные Си-функции sscanf() и sprintf() также не могут быть использованы (по крайней мере, в Турбо Си), потому что также осуществляют обращения к DOS. Вместо них используются функции атоi() и itoa(). Полный текст программы резидентного калькулятора представлен ниже.
/* TSR-программа, использующая прерывание печати экрана */
#include "dos.h"
#include "stdlib.h"
#define BORDER 1
#define ESC 27
#define MAX_FRAME 1
#define REV_VID 0x70
#define NORM_VID 7
#define BKSP 8
void interrupt tsr_ap();
void save_video(), restore_video();
void write_string(), write_char();
void display_header(), draw_border();
void window_gets();
void window_cleol(), window();
void calc();
char far *vid_mem;
struct window_frame
int startx, endx, starty, endy;
int curx, cury; /* текущее положение курсора в окне */
unsigned char *p; /* указатель буфера */
char *header; /* сообщение в верхней части окна */
int border; /* включение/отключение бордюра */
int active; /* активация/деактивация окна */
frame[MAX_FRAME];
char wp[4000]; /* буфер для хранения текущего содержимого экрана
/* busy установлена в 1, если программа активна, иначе - в 0 */
char busy = 0;
main()
struct address
char far *p;
;
/* адрес прерывания печати экрана */
struct address far *addr = (struct address far *) 20;
addr->p = (char far *) tsr_ap;
set_vid_mem();
tsr(2000);
set_vid_mem()
int vmode;
vmode = video_mode();
if((vmode!=2) && (vmode!=3) && (vmode!=7))
printf("video must be in &0 column text mode");
exit(1);
/* установить соответсвующий адрес видеопамяти */
if(vmode==7) vid_mem = (char far *) 0xB0000000;
else vid_mem = (char far *) 0xB8000000;
/* точка входа в прикладную часть TSR-программы */
void interrupt tsr_ap()
if(!busy)
busy = !busy;
window_main();
busy = !busy;
/* завершить, но оставить резидентной */
tsr(size)
unsigned size;
union REGS r;
r.h.ah = 49; /* завершить, но оставить резидентной */
r.h.al = 0; /* код возврата */
r.x.ax = size;
int86(0x21, &r, &r);
window_main()
/* первым делом, создать структуру окна */
make_window(0, " Calculator ", 8, 20, 12, 60, BORDER);
/* для активации описанного окна используйте window() */
calc();
/*************************************************************/
/* Функции управления окнами */
/*************************************************************/
/* Вывести на экран спускающееся меню */
void window(num)
int num; /* номер окна */
int vmode, choice;
int x, y;
/* сделать окно активным */
if(!frame[num].active) /* используется не постоянно */
save_video(num); /* сохранить текущий экран */
frame[num].active = 1; /* установить флаг активности */
if(frame[num].border) draw_border(num);
display_header(num); /* вывести окно */
/* Создать спускающееся окно
если окно может быть создано, возвращается 1;
иначе возвращается 0.
*/
make_window(num, header, startx, starty, endx, endy, border)
int num; /* номер окна */
char *header; /* текст заголовка */
int startx, starty; /* координаты X,Y левого верхнего угла */
int endx, endy; /* координаты X,Y правого нижнего угла */
int border; /* без бордюра если 0 */
register int i;
int choice, vmode;
unsigned char *p;
if(num>MAX_FRAME)
window_puts(0, "Too many windows\n");
return 0;
if((startx>24) || (starty>78) || (starty<0))
window_puts(0, "range error");
return 0;
if((endx>24) || (endy>79))
window_puts(0, "window won't fit");
return 0;
/* создать структуру окна */
frame[num].startx = startx; frame[num].endx = endx;
frame[num].starty = starty; frame[num].endy = endy;
frame[num].p = wp;
frame[num].header = header;
frame[num].border = border;
frame[num].active = 0;
frame[num].curx = 0; frame[num].cury = 0;
return 1;
/* Деактивировать окно и удалить его с экрана */
deactivate(num)
int num;
/* установить курсор в левый верхний угол */
frame[num].curx = 0;
frame[num].cury = 0;
restore_video(num);
/* Вывести заголовок окна в соответсвующее поле */
void display_header(num)
int num;
register int i, y, len;
y = frame[num].starty;
/* Вычислить точное значение центральной позиции заголовка
если отрицательное - заголовок не может быть выведен
*/
len = strlen(frame[num].header);
len = (frame[num].endy - y - len) / 2;
if(len<0) return; /* don't display it */
y = y +len;
write_string(frame[num].startx, y,
frame[num].header, NORM_VID);
void draw_border(num)
int num;
register int i;
char far *v, far *t;
v = vid_mem;
t = v;
for(i=frame[num].startx+1; i
v += (i*160) + frame[num].starty*2;
*v++ = 179;
*v = NORM_VID;
v = t;
v += (i*160) + frame[num].endy*2;
*v++ = 179;
*v = NORM_VID;
v = t;
for(i=frame[num].starty+1; i
v += (frame[num].startx*160) + i*2;
*v++ = 196;
*v = NORM_VID;
v = t;
v += (frame[num].endx*160) + i*2;
*v++ = 190;
*v = NORM_VID;
v = t;
write_char(frame[num].startx, frame[num].starty, 218, NORM_VID);
write_char(frame[num].startx, frame[num].endy, 191, NORM_VID);
write_char(frame[num].endx, frame[num].starty, 192, NORM_VID);
write_char(frame[num].endx, frame[num].endy, 217, NORM_VID);
/**************************************************************/
/* Оконные функции ввода/вывода */
/**************************************************************/
/* Вывести строку начиная с текущей позиции курсора
описанного окна.
Возвратить 0 если окно не активное; и 1 в противном случае.
*/
window_puts(num, str)
int num;
char *str;
/* убедиться, что окно активное */
if(!frame[num].active) return 0;
for( ; *str; str++)
window_putchar(num, *str);
return 1;
/* Вывести символ в текущую позицию курсора
описанного окна.
Возвратить 0 если окно не активное, и 1 в противном случае.
*/
window_putchar(num, ch)
int num;
char ch;
register int x, y;
char far *v;
/* убедиться, что окно активное */ if(!frame[num].active) return 0;
x = frame[num].curx + frame[num].startx + 1; y = frame[num].cury + frame[num].starty + 1;
v = vid_mem;
v += (x*160) + y*2; /* вычислить адрес */ if(y>=frame[num].endy)
return 1;
if(x>=frame[num].endx)
return 1;
if(ch=='\n') /* символ перехода на новую строку */ x++;
y = frame[num].startx+1;
v = vid_mem;
v += (x+160) + y*2; /* вычислить адрес */ frame[num].curx++; /* инкрементировать X */ frame[num].cury = 0; /* сбросить Y */
else
frame[num].cury++;
*v++ = ch; /* вывести символ */
*v++ = NORM_VID; /* нормальные атрибуты символа */
window_xy(num, frame[num].curx, frame[num].cury);
return 1;
/* Установка курсора в заданную позицию окна.
Возвращает 0 при выходе за границу; не ноль в противном случае.
*/
window_xy(num, x, y)
int num, x, y;
if(x<0 || x+frame[num].startx>=frame[num].endx-1)
return 0;
if(y<0 || y+frame[num].starty>=frame[num].endy-1)
return 0;
frame[num].curx = x;
frame[num].cury = y;
return 1;
/* Считать строку из окна. */
void window_gets(num, s)
int num;
char *s;
char ch, *temp;
temp = s;
for(;;)
ch = window_getche(num);
switch(ch)
case '\r': /* нажата клавиша ENTER */
*s='\0';
return;
case BKSP: /* возврат */
if(s>temp)
s--;
frame[num].cury--;
if(frame[num].cury<0) frame[num].cury = 0;
window_xy(num, frame[num].curx, frame[num].cury); write_char(frame[num].startx+ frame[num].curx+1,
frame[num].starty+frame[num].cury+1, ' ', NORM_VID);
break;
default: *s = ch;
s++;
/* Ввод символа с клавиатуры в окно.
Возвращает полный 16-разрядный скан-код.
/*
window_getche(num)
int num;
union inkey
char ch[2];
int i;
c;
if(!frame[num].active) return 0; /* window not active */
window_xy(num, frame[num].curx, frame[num].cury);
c.i = bioskey(0); /* обработать нажатие клавиши */
if(c.ch[0])
switch(c.ch[0])
case '\r': /* нажата клавиша ENTER */
break;
case BKSP: /* возврат */
break;
default:
if(frame[num].cury+frame[num].starty < frame[num].endy-1)
write char(frame[num].startx+ frame[num].curx+1,
frame[num].curx--;
window_xy(num, frame[num].curx, frame[num].cury);
return c.i;
/* Очистить до конца строки */
void window_cleol(num)
int num;
register int i, x, y;
x = frame[num].curx;
y = frame[num].cury;
window_xy(num, frame[num].curx, frame[num].cury);
for(i=frame[num].cury; i
window_putchar(num,' ');
window_xy(num, x, y);
/* Переместить курсор на одну строку вверх.
При успешном завершении вернуть ненулевое значение; в противном случае - 0.
*/
window_upline(num)
int num;
if(frame[num].curx>0)
frame[num].curx--;
window_xy(num, frame[num].curx, frame[num].cury); return 1;
return 0;
/* Переместить курсор на одну строку вниз.
При успешном завершении вернуть ненулевое значение; в противном случае - 0.
*/
window_downline(num)
int num;
if(frame[num].curx
window_xy(num, frame[num].curx, frame[num].cury);
return 1;
return 1;
/* вернуться на одну позицию назад */
window_bksp(num)
int num;
if(frame[num].cury>0)
frame[num].cury--;
window_xy(num, frame[num].curx, frame[num].cury);
window_putchar(num, ' ');
frame[num].cury--;
window_xy(num, framenum.curx, frame[num].cury);
/********************************************************/
/* Дополнительные функции */
/********************************************************/
/* Вывести строку с установленными атрибутами */
void write_string(x, y, p, attrib)
int x, y;
char *p;
int attrib;
register int i;
char far *v;
v = vid_mem;
v += (x*160) + y*2; /* вычислить адрес */
for(i+y; *p; i++)
*v++ = *p++; /* вывести символ */
*v++ = attrib; /* вывести атрибуты */
/* Вывести символ с утановленными атрибутами */
void write_char(x, y, ch, attrib)
int x, y;
char ch;
int attrib;
register int i;
char far *v;
v = vid_mem;
v += (x*160) +y*2;
*v++ = ch; /* вывести символ */
*v = attrib; /* вывести атрибуты */
/* Сохранить содержимое области экрана */
void save_video(num)
int num;
register int i,j;
char far *v, far *t;
char *but_ptr;
but_ptr = frame[num].p;
v = vid_mem;
t=v;
for(i=frame[num].starty; i
for(j=frame[num].startx; j
t = (v + (j*160) + i*2);
*buf_ptr++ = *t++;
*buf_ptr++ = *t;
*(t-1) = ' '; /* очистить окно */
/* Восстановить содержимое области экрана */
void save_video(num)
int num;
register int i,j;
char far *v, far *t;
char *but_ptr;
but_ptr = frame[num].p;
v = vid_mem;
t=v;
for(i=frame[num].starty; i
for(j=frame[num].startx; j
v = t;
v += (j*160) + i*2;
*v++ = *but_ptr++; /* вывести символ */
*v = *but_ptr++; /* вывести атрибуты */
frame[num].active = 0; /* восстановить изображение */
/* Возвращает код текущего видеорежима */
video_mode()
union REGS r;
r.h.ah =15; /* получить код видеорежима */
return int86(0x10, &r, &r) & 255;
/**********************************************************
калькулятор **********************************************************/
#define MAX 100
int *p; /* указатель стека */
int *tos; /* указатель вершины стека */
int *bos; /* указатель дна стека */
char in[80], out[80];
int stack[MAX];
/* Стековый, с постфиксной записью калькулятор
на четыре функции */
void calc()
int answer;
int a, b;
p = stack;
tos = p;
bos = p + MAX - 1;
window(0);
do
window_xy(0, 0, 0);
window_cleol(0);
window_puts(0, " : "); /* промптер калькулятора */
window_gets(0, in);
window_puts(0, " \n ");
window_cleol(0);
switch(*in)
case '+ ':
a = pop();
b = pop();
answer = a + b;
push(a+b);
break;
case '-':
a = pop();
b = pop();
answer = b-a;
push(b-a);
break;
case '- ':
a = pop();
b = pop();
answer = b*a;
push(b*a);
break;
case '/ ':
a = pop();
b = pop();
if(a==0)
window_puts(0, "divide by 0\n");
break;
answer = b/a;
push(b/a);
break;
default:
push(atoi(in));
continue;
itoa(answer, out, 10);
window_puts(0, out);
while(*in);
deactivate(0);
/* Поместить число в стек.
Возвратить 1 при успешном завершении; и 0, если стек переполнен
*/
push(i)
int i;
if(p>bos) return 0;
*p = i;
p++;
return 1;
/* Извлечь верхний элемент стека Возвратить 0, если стек пуст.
*/
pop()
p--;
if(p
p++;
return 0;
return *p;
Вы можете сразу вводить эту программу в ЭВМ. Для того, чтобы установить прикладную часть, запустите ее на выполнение. Для вызова калькулятора нажмите клавишу PT SCR.
window_puts(1, "\n ");
window_cleol(1);
switch(*in)
case '+':
a = pop();
b = pop();
answer = a+b;
push(a+b);
break;
case '-':
a = pop();
b = pop();
answer = b-a;
push(b-a);
break;
case '* ':
a = pop();
b = pop();
answer = b*a;
push(b*a);
break;
case '/ ':
a = pop();
b = pop();
if(a==0)
window_puts(0, "divide by 0\n");
break;
answer = b/a;
push(b/a);
break;
default:
push(atoi(in));
continue;
itoa(answer, out, 10);
window_puts(1, out);
while(*in);
deactivate(1);
/* Поместить число в стек.
Возвратить 1 в случае успеха и 0 если стек переполнен
*/
push(i)
int i;
if(p>bos) return 0;
*p = i;
p++;
return 1;
/* Извлечь верхний элемент из стека. Возвратить 0 если стек пуст.
*/
pop()
p--;
if(p
p++;
return 0;
return *p;
/**********************************************************/
/* Всплывающая записная книжка */
#define MAX_NOTE 10
#define BKSP 8
char notes[MAX_NOTE] [80];
void notepad()
static first = 1;
register int i, j;
union inkey
char ch[2];
int i;
c;
char ch;
/* инициализировать массив записей если это необходимо */
if(first)
for(i=0; i
*notes[i] = '\0 ';
first = !first;
window(0);
/* вывести существующие записи */
for(i=0; i
if(*notes[i]) window_puts(0, notes[i]);
window_putchar(0, '\n ');
i = 0;
window_xy(0, 0, 0);
for(;;)
c.i = bioskey(0); /* обработать нажатие клавиши */
if(tolower(c.ch[1])==59) /* F1 для выхода */
deactivate(0);
break;
/* если обычная клавиша */
if(isprint(c.ch[0]) || c.ch[0]==BKSP)
window_cleol(0);
notes[i][0] = c.ch[0];
j = 1;
window_putchar(0, notes[i][0]);
do
ch = window_getche(0);
if(ch == BKSP)
if( j>0 )
j--;
window_bksp(0);
else
notes[i][j] = ch;
j++;
while(notes[i][j-1]! = '\r ');
notes[i][j-1] = '\0 ';
i++;
window_putchar(0, '\n ');
else /* если специальная клавиша */
switch(c.ch[1])
case 72: /* стрелка вверх */
if(i>0)
i--;
window_upline(0);
break;
case 80: /* стрелка вниз */
if(i
i++;
window_downline(0);
break;
Прямой доступ к видео памяти
Для создания меню, которые действительно "исчезают" вы должны миновать вызовы функций BIOS и прямо обращаться к видео памяти. Это позволяет высвечивать символы с молниеносной быстротой. При прямой записи и чтении из видео памяти вы можете использовать исчезающие меню в реальном времени!Чтение и запись в видео память требует использования ДАЛЬНИХ указателей. Если ваш компилятор не поддерживает дальних указателей, то вы не имеете прямого доступа к видео памяти. Дальние указатели могут быть поддерживаемы транслятором Си одним из двух способов. Первый - использование ключевого слова far, используемого в большинстве компиляторов. Они позволяют определять указатель, как дальний. Другой способ - использование большой модели памяти, в которой все указатели по умолчанию дальние. Программы, используемые в этой главе используют описатель far. Если вы хотите, вы можете просто удалить его и скомпилировать программу, используя транслятор с большой моделью памяти.
Проблемы передачи данных
Пpи оpганизации пеpедачи данных с помощью модема некотоpые сигналы используются для опpеделения готовности данных или опpеделения следующего байта посылки. Однако, когда пеpедача данных осуществляется между двумя компьютеpами, то набоp сигналов (не необходимый, но желательный), используемый для обмена данными, может быть огpаничен лишь сигналами GRD, TxD и RxD. Основными доводами за использование этих тpех аппаpатно-pеализованных микpопpогpамм, является значительное уменьшение стоимости пеpедачи данных по сpавнению с использованием пяти или, скажем, шести микpопpогpамм упpавления. Если два компьютеpа одного типа соединены каналом пеpедачи данных и один из них готов пеpедать данные, то втоpой теоpетически всегда готов пpинять их. Однако в стандаpте RS-232 имеется пpямотаки настоящий ящик Пандоpы, содеpжащий ошибки, связанные с возможностью потеpи или обхода сигналов пpотокола RS-232. Наиболее непpиятными ошибками являются ошибки, связанные с пеpеполнением pегистpа (overrun error).наверх
Проблемы при создании TSR-программ
TSR-программы по своей природе очень склонны к сбоям. Например, использование TSR-программы, разработанной одним программистом, часто делает невозможным одновременное использование другой TSR-программы, разработанной другим программистом, поскольку обе программы будут пытаться переназначить адрес программы обработки прерывания 9 в таблице векторов на себя. Несомненно, при использовании своих TSR-программ вы можете избежать такого рода проблем, но будьте внимательны, если в вашей системе присутствует чужая TSR-программа.Программа генерации движения спрайта компьютера.
Если спрайт компьютера находится в режиме догоняющего, то для генерации очередного его кванта движения используется функция it_comp_move(). В основном компьютер повторяет стратегию движения пользователя. Движение его спрайта отклоняется из-за того, что он должен обходить объекты-препятствия. Однако спрайт компьютера может игнорировать некоторые объекты, что позволяет выровнять баланс игры.Приведем текст функции it_comp_move().
/* Генерация движения спрайта компьютера, когда
он в роли догоняющего */
void it_comp_move(ob1, ob2, human, sides)
int ob1[][4], ob2[][4], human[][4], sides;
register int x, y, d; /* d = direction */
static skip = 0;
skip++;
if(skip==3)
skip=0;
return;
/* уменьшение времени реакции компьютера */
x = 0;
y = 0;
/* движение к игроку */
if(human[0][0]
else
if(human[0][0]>ob1[0][0])
x = 1;
if(human[0][1]
else
if(human[0][1]>ob1[0][1])
y = 1;
if(is_legal(ob1, x, y, sides))
update_object(ob1, x, y, sides);
update_object(ob2, x, y, sides);
else
if(x && is_legal(ob1, x, 0, sides))
update_object(ob1, x, 0, sides);
update_object(ob2, x, 0, sides);
else
if(is_legal(ob1, 0, y, sides))
update_object(ob1, 0, y, sides);
update_object(ob2, 0, y, sides);
Заметим, что эта функция меняет положение спрайта в 3 раза медленнее, чем это возможно. Делается такое замедление с целью снижения быстродействия компьютера до уровня человека.
Функция, генерирующая движение спрайта в режиме догоняемого, обеспечивает движение в сторону, противоположную от спрайта игрока. Хотя этот алгоритм является неоптимальным, он делает игру достаточно привлекательной и требует от пользователя хорошей реакции.
/* Генерация движения спрайта компьютера, когда
он выступает в роли убегающего */
void it_comp_move(ob1, ob2, human, sides)
int ob1[][4], ob2[][4], human[][4], sides;
register int x, y, d; /* d = direction */
static skip = 0;
skip++;
if(skip==3)
skip=0;
return;
/* уменьшение времени реакции компьютера */
x = 0;
y = 0;
/* движение к игроку */
if(human[0][0]
x = -1;
else
if(human[0][0]>ob1[0][0])
x = 1;
if(human[0][1]
y = -1;
else
if(human[0][1]>ob1[0][1])
y = 1;
if(is_legal(ob1, x, y, sides))
update_object(ob1, x, y, sides);
update_object(ob2, x, y, sides);
else
if(x && is_legal(ob1, x, 0, sides))
update_object(ob1, x, 0, sides);
update_object(ob2, x, 0, sides);
else
if(is_legal(ob1, 0, y, sides))
update_object(ob1, 0, y, sides);
update_object(ob2, 0, y, sides);
/* генерация движения спрайта компьютера, когда
он убегает */
void not_it_comp_move(ob1, ob2, dx, dy, sides)
int ob1[][4], ob2[][4];
int dx, dy; /* направление последнего перемещения
"человека" */
int sides;
register int x, y, d;
static skip = 1;
skip++;
if (skip==3)
skip = 0;
return;
/* уменьшение времени реакции компьютера в 3 раза */
x = 0;
y = 0;
/* перемещение в противоположном направлении */
x = -dx;
y = -dy;
if (is_legal(ob1, x, y, sides))
updаte_object(ob1, x, y, sides);
updаte_object(ob2, x, y, sides);
else
if (x && is_legal(ob1, x, 0, sides))
update_object(ob1, x, 0, sides);
update_object(ob2, x, 0, sides);
else if (is_legal(ob1, 0, y, sides))
update_object(ob1, 0, y, sides);
update_object(ob2, 0, y, sides);
Эта функция так же как и предыдущая, работает с 3-кратным замедлением.
Программа контроля касания спрайтов.
В этой игре режимы спрайтов изменяются на противоположные в том случае, если координаты хотя бы одной точки догоняющего спрайта совпадут с координатами любой точкой догоняемого. Правила игры могут быть изменены таким образом, что изменение режима произойдет лишь в случае полного совмещения спрайтов. Но эта довольно-таки сложная задача для многих игроков. Приведенная ниже функция tag() возвращает значение 1, если спрайты столкнулись, и 0 - в противном случае./* Проверяет есть ли контакт между спрайтами */
tag(ob1, ob2)
int ob1[][4], ob2[][4];
register int i;
/* для смены амплуа необходимо, чтобы спрайты
имели хотя бы одну общую точку растра */
for (i= -1; i<2; i++)
if (ob1[0][0]==ob2[0][0]+i && ob1[0][1]==ob2[0][2]+i)
return 1;
return 0;
Вы можете внести изменения в функцию tag() и установить свои правила контроля режимов спрайтов.
Программа вычерчивания диаграмм.
Вы можете использовать описанные функции для построения программы создания диаграмм. Программа позволяет пользователю вводить количество наборов данных, количество элементов в каждом наборе, наименования и метки соответствующих данных, а также толщину линий и расстояния между диаграммами. После ввода указанных данных программа автоматически вычерчивает диаграмму. Вы также можете написать программу сохранения построенной диаграммы в файле для ее дальнейшего использования.Главная программа.
Здесь приводится основная функция main(), описывающая алгоритм построения диаграмм и содержащая несколько макросов.
#define MAX_SETS 3
#define MAX_ENTRIES 50
#define MAX_LABELS 20
#define MAX_NAMES 20
main()
double v[MAX_SETS][MAX_ENTRIES]; /* размещение данных */
int num_entries;
int num_sets;
int min,max,i;
int lines,offset;
char save = 0; /* признак сохранения диаграммы */
char names[MAX_NAMES][20];
char lab[MAX_LABELS][20];
/* считывание данных */
enter(v,&num_entries,&num_sets);
/* поиск минимального и максимального значения */
min_max(v,num_entries,num_sets,&min,&max);
/* ввод наименований данных */
get_names(names,num_sets);
/* ввод меток для диаграммы */
get_labels(lab,num_entries);
/* ввод толщины линии */
lines = get_line_size();
/* ввод интервала между диаграммами */
offset = get_offset();
/* сохранить диаграмму в файле ? */
printf(" сохранить диаграмму в файле ? (y/n) ");
if (tolower(getche()) == 'y') save = 1;
mode(4); /* графический режим 320*200 */
palette(0);
grid(min,max); /* вывод линии нулевого уровня */
hashlines(); /* вывод пунктирных линий */
label(lab,num_entries); /* вывод меток диаграммы */
legend(names,num_sets); /* вывод пояснительных надписей */
/* вывод значений в виде диаграммы */
for (i=0;i
if (save) save_pic();
getch();
mode(3);
Как вы видите, функция main() начинается описанием переменных, значения которых устанавливает пользователь. Массив v определен достаточно большим, чтобы содержать до трех наборов данных до 50 элементов каждый. (Эти размеры являются произвольными и при желании вы можете их изменить.) Затем функция считывает выводимые пользователем в виде диаграмм данные и определяет минимальное и максимальное значение данных. После этого на экран выводятся линия нулевого уровня, пунктирные линии уровня, метки диаграммы и наименование наборов. В завершение вычерчивается сама диаграмма. Перед выходом происходит сохранение диаграммы при помощи функции save_pic(). Давайте рассмотрим некоторые используемые в программе main() функции, которые не входят в описанные выше инструментарии построения диаграмм.
Функция enter().
Приведенная здесь функция enter() использует в качестве своих параметров адрес массива, в котором будут размещены данные, и адреса переменных для размещения числа количества элементов в наборе и числа самих наборов. Функция начинает свою работу с запроса у пользователя количества наборов данных и затем количества элементов данных в каждом наборе. После получения этой информации производится считывание данных для каждого набора.
/* Считывание данных */
enter(v,entries,sets)
double v[][MAX_ENTRIES]; /* массив данных */
int *entries; /* количество элементов в каждом наборе данных */
int *sets; /* количество наборов данных */
int i,j,count,num;
char s[80];
printf("Введите число наборов данных (от 1 до %d)",MAX_SETS);
scanf("%d%c",&count,&j);
if (count>MAX_SETS) count = MAX_SETS; /* выход за границы
массива */
*sets = count;
printf("Введите количество элементов (от 1 до %d) ",MAX_ENTRIES);
scanf("%d%c",&num,&j);
if (num>MAX_SETS) num = MAX_ENTRIES; /* выход за границы
массива */
*entries = num;
j = 0;
/* считывание значений данных */
while((j
printf("Набор данных %d\n",j+1);
for (i=0;i
printf("%d:",i+1);
gets(s);
sscanf(s,"%lf",&v[j][i]);
j++;
return count;
Функция min_max().
Так как функция bargraph() использует максимальное и минимальное значения выводимых данных, то нам потребуется специальная функция для определения этих значений. Необходимо также отметить, что эта функция должна не просто определять минимальное и максимальное значения набора данных, а находить наименьшее минимальное и наибольшее максимальное значения для нескольких наборов данных, что обеспечит соответствие при одновременном построении сразу нескольких диаграмм. Функция min_max(), приведенная здесь, вместе с двумя внутренними функциями удовлетворяет этому требованию.
/* Поиск наименьшего минимума и наибольшего максимума
среди всех наборов данных */
void min_max(v,entries,sets,min,max)
double v[][MAX_ENTRIES]; /* значения */
int entries; /* количество входов для каждого набора
данных */
int sets; /* количество наборов данных */
int *min,*max; /* возвращает минимальное и максимальное
значение */
int i,j;
int tmin,tmax;
*min = *max = 0;
for (i=0;i
tmax = getmax(v[i],entries);
tmin = getmin(v[i],entries);
if (tmax>*max) *max = tmax;
if (tmin <*min) *min = tmin;
/* Возврат максимального значения данных */
getmax(data,num)
double *data;
int num;
int t,max;
max = (int)data[0];
for (t=1;t
if (data[t]>max) max = (int)data[t];
return max;
/* Возврат минимального значения данных */
getmin(data,num)
double *data;
int num;
int t,min;
min = (int)data[0];
for (t=1;t
if (data[t]
return min;
Полный текст программы вычерчивания диаграмм.
Полный текст программы вычерчивания диаграмм представлен ниже.
/* Программа генерации диаграмм */
#include "dos.h"
#include "stdio.h"
#define MAX_SETS 3
#define MAX_ENTRIES 50
#define MAX_LABELS 20
#define MAX_NAMES 20
void bargraph(),mode(),mempoint();
void line(),goto_xy(),grid(),label();
void hashlines(),legend(),read_cursor_xy();
void palette(),color_puts(),fill_box();
void get_labels(),get_names(),min_max();
void save_pic();
main()
double v[MAX_SETS][MAX_ENTRIES]; /* размещение данных */
int num_entries;
int num_sets;
int min,max,i;
int lines,offset;
char save = 0; /* признак записи диаграммы */
char names[MAX_NAMES][20];
char lab[MAX_LABELS][20];
/* считывание данных */
enter(v,&num_entries,&num_sets);
/* поиск минимального и максимального значения */
min_max(v,num_entries,num_sets,&min,&max);
/* ввод наименований данных */
get_names(names,num_sets);
/* ввод меток для диаграммы */
get_labels(lab,num_entries);
/* ввод толщины линии */
lines = get_line_size();
/* ввод интервала между диаграммами */
offset = get_offset();
/* сохранить диаграмму в файле ? */
printf(" сохранить диаграмму в файле ? (y/n) ");
if (tolower(getche()) == 'y') save = 1;
mode(4); /* графический режим 320*200 */
palette(0);
grid(min,max); /* вывод линии нулевого уровня */
hashlines(); /* вывод пунктирных линий */
label(lab,num_entries); /* вывод меток диаграммы */
legend(names,num_sets); /* вывод пояснительных надписей */
/* вывод значений в виде диаграммы */
for (i=0;i
bargraph(v[i],num_entries,i*offset,min,max,lines);
if (save) save_pic();
getch();
mode(3);
/* считывание данных */
enter(v,entries,sets)
double v[][MAX_ENTRIES]; /* массив данных */
int *entries; /* количество элементов данных в каждом наборе
данных */
int *sets; /* количество наборов данных */
int i,j,count,num;
char s[80];
printf("Введите число наборов данных (от 1 до %d)",MAX_SETS);
scanf("%d%c",&count,&j);
if (count>MAX_SETS) count = MAX_SETS; /* выход за границы
массива */
*sets = count;
printf("Ведите число элементов данных (от 1 до %d)",MAX_ENTRIES);
scanf("%d%c",&num,&j);
if (num>MAX_ENTRIES) num = MAX_ENTRIES; /* выход за границы
массива */
*entries = num;
j = 0;
/* считывание значений */
while((j
printf(" Набор данных %d\n",j+1);
for (i = 0;i
printf("%d:",i+1);
gets(s);
sscanf(s,"%lf",&v[j][i]);
j++;
return count;
/* Ввод имен наборов */
void get_names(n,num)
char n[][20]; /* массив для имен */
int num; /* число наборов */
int i;
for (i=0;i
printf(" Введите имя: ");
gets(n[i]);
/* Ввод метки каждого входа */
void get_labels(l,num)
char l[][20]; /* массив для меток */
int num; /* число входов */
int i;
for (i=0;i
printf(" Введите имя метки: ");
gets(l[i]);
/* Ввод интервала между диаграммами в единицах растра */
get_offset()
int i;
printf(" Введите интервал между диаграммами в единицах растра");
scanf("%d%*c",&i);
return i;
/* Ввод толщины диаграмм в единицах растра */
get_line_size()
int i;
printf("Введите толщину диаграммы в единицах растра : ");
scanf("%d",&i);
return i;
/* Вывод линии нулевого уровня диаграммы */
void grid(min,max)
int min,max;
register int t;
goto_xy(22,0); printf("%d",min);
goto_xy(0,0); printf("%d",max);
line(180,10,180,300,1);
/* Вывод меток на экран */
void label(str,num)
char str[][20]; /* массив меток */
int num; /* количество меток */
int i,j,inc;
inc = 38/num;
i = 2; /* определение начальной точки */
for (j=0;j
goto_xy(23,i);
printf(str[j]);
i += inc;
/* Вывод пунктирных линий на экран */
void hashlines()
int i,j;
for (i=10;1<180;i+=10)
for (j=10;j<300;j+=5)
mempoint(i,j,3); /* одна точка на каждые 5 единиц
растра */
/* Вывод надписи */
void legend(names,num)
char names[][20];
int num; /* количество наименований */
int color = 1,i,j;
goto_xy(24,0); /* надпись производится в последней строке */
j = 0;
for (i=0;i
/* Вывод наименования */
printf("%s ",names[i]);
/* определение координаты цветного прямоугольника. В 4
режиме каждому литерному символу отводится 8 единиц
растра ( в ширину ) */
j += strlen(names[i]) * 8 + 4;
fill_box(192,j,198,j+12,color);
j += 28; /* продвижение к следующему полю вывода */
color ++;
if ( color>3 ) color = 1;
void bargraph(data,num,offset,min,max,width)
double *data; /* массив данных */
int num; /* количество элементов в массиве */
int offset; /* расстояние между диаграммами */
int min,max; /* минимальное и максимальное выводимые значения */
int width; /* толщина линий */
int y,t,incr;
double norm_data,norm_ratio,spread;
char s[80];
static int color = 0;
int tempwidth;
/* всегда используйте различные цвета */
color++;
if ( color > 3 ) color = 1;
/* определение нормирующего множителя */
spread = (double)max-min;
norm_ratio = 180/spread;
incr = 280/num; /* определение промежутка между значениями*/
tempwidth = width;
for (t=0;t
norm_data = data[t];
/* подгонка отрицательных значений */
norm_data = norm_data-(double)min;
norm_data *= norm_ratio; /* нормирование */
y = (int)norm_data; /* преобразование типа */
do
Line(179,((t*incr)+20+offset+width),179-y,
((t*incr)+20+offset+width),color);
width--;
while(width);
width = tempwidth;
/* поиск наименьшего минимума и наибольшего максимума
среди всех наборов данных */
void min_max(v,entries,sets,min,max)
double v[][MAX_ENTRIES]; /* значения */
int entries; /* количество входов для каждого набора
данных */
int sets; /* количество наборов данных */
int *min,*max; /* возвращает минимальное и максимальное
значение */
int i,j;
int tmin,tmax;
*min = *max = 0;
for (i=0;i
tmax = getmax(v[i],entries);
tmin = getmin(v[i],entries);
if (tmax>*max) *max = tmax;
if (tmin <*min) *min = tmin;
/* Возврат максимального значения данных */
getmax(data,num)
double *data;
int num;
int t,max;
max = (int)data[0];
for (t=1;t
if (data[t]>max) max = (int)data[t];
return max;
/* Возврат минимального значения данных */
getmin(data,num)
double *data;
int num;
int t,min;
min = (int)data[0];
for (t=1;t
if (data[t]
return min;
/* Вывод линии заданного цвета, используя базовый алгоритм
Брезенхама */
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;
/* наполнение прямоугольника заданным цветом */
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);
/* запись точки в 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; /* xor - цвет или наложение */
char far *ptr = (char far *) 0xB8000000; /* указатель на
CGA */ bit_mask.i = 0xFF3F; /* 11111111 00111111 в двоичном коде */
/* контроль координат для 4 режима */
if (x<0 || x>199 || y<0 || y>319) return;
xor = color_code & 128; /* проверка установки режима xor */
color_code = color_code & 127; /* маска 7 старших бит */
/* установка bit_mask и color_code в правильное положение */
bit_position = y%4;
color_code <<= 2*(3-bit_position);
bit_mask.i >>= 2*bit_position;
/* поиск соответствующего байта в памяти экрана */
index = x*40 + (y>>2);
if (x%2) index+=8152; /* если нечетный, использовать второй
банк */
/* запись цвета */
if (!xor) /* режим наложения */
t = *(ptr + index) & bit_mask.c[0];
*(ptr + index) = t | color_code;
else /* режим xor */
t = *(ptr + index) | (char)0;
*(ptr + index) = t ^ color_code;
/* установка видеорежима */
void mode(mode_code)
int mode_code;
union REGS r;
r.h.al = mode_code;
r.h.ah = 0;
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);
/* установка цветов диаграмм */
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 save_pic()
char fname[80];
FILE *fp; register int i,j;
char far *ptr = (char far *) 0xB8000000; /* указатель на CGA память */
char far *temp;
unsigned char buf[14][80]; /* для размещения содержимого
экрана */
temp = ptr;
/* сохранение верхней части текущего экрана */
for (i=0;i<14;i++)
for (j=0;j<80;j+=2)
buf[i][j] = *temp; /* четный байт */
buf[i][j+1] = *(temp+8152); /* нечетный байт*/
*temp = 0;
*(temp+8152) = 0; /* чистка верхней части
экрана */
temp++;
goto_xy(0,0);
printf(" Имя файла : ");
gets(fname);
if (!(fp=fopen(fname,"wb")))
printf(" Невозможно открыть файл \n");
return;
temp = ptr;
/* восстановление верхней части экрана */
for (i=0;i<14;i++)
for (j=0;j<80;j+=2)
*temp = buf[i][j];
*(temp+8152) = buf[i][j+1];
temp++;
/* сохранение рисунка в файле */
for (i=0;i<8152;i++)
putc(*ptr,fp); /* четный байт */
putc(*(ptr+8152),fp); /* нечетный байт */
ptr++;
fclose(fp);
Программируемый таймер 8253.
Генерация звуков в компьютере PC выполняется с помощью программируемого таймера 8253, который применяется для управления колебаниями динамика. Управление колебаниями динамика определяется частотой, которая, в свою очередь, определяется содержимым различных внутренних регистров. Значения этих регистров устанавливаются при записи в определенные порты. Порт 66 используется для спецификации счетчика, который использует таймер при определении интервала колебаний динамика. Таймер работает в строгом соответствии с частотой системного таймера и специфицированным значением счетчика, определяющим колебания динамика. Затем, после обнуления счетчика происходит установка нового значения счетчика, и весь цикл функционирования программируемого таймера повторяется сначала. Значение счетчика определяется по следующей формуле:count = 1,193,180/требуемая частота
где 1,193,180 есть тактовая частота системного таймера.
Регистр-счетчик таймера 8253 устанавливается в следующей последовательности (значение счетчика задается двухбайтным числом):
1. Выдать в порт 67 значение 182 (означающее, что будет устанавливаться счетчик).
2. Выдать в порт 66 младший байт числа, определяющего значение счетчика.
3. Выдать в порт 66 старший байт числа, определяющего значение счетчика.
Динамики большинства компьютеров класса PC не позволяют воспроизводить полный спектр частот, воспринимаемых человеческим слухом (от 20 Гц до 18.000 Гц). Однако динамик позволяет воспроизводить ноты лучше, чем динамики других компьютеров в пределах 12000 Гц и даже выше. В основном же динамик используется в пределах 100-5000 Гц.
Итак, таймер установлен. Однако динамик еще не будет воспроизводить звук, так как не включен. Таймер 8253 активен постоянно, а динамик требует дополнительной команды включения. Активизация динамика осуществляется путем установки значений битов 0 и 1 регистра программируемого периферийного интерфейса, задание значений которого выполняется через порт 97. Если значения этих двух битов установлены (равны 1), то динамик издает звук частотой, установленной счетчиком 8253. Если значения этих битов равны 0, то никакой звук генерироваться не будет. Остальные биты этого байта используются другими устройствами, поэтому интерпретация значения левых битов не может быть изменена. Таким образом, для установки значений управляющих динамиком бит
необходимо выполнить следующую последовательность действий:
1. Получить текущее значение регистра из порта 97.
2. Сравнить это значение с 3 или установить равным 3.
3. Записать результат в порт 97.
Для того, чтобы выключить динамик, необходимо переслать в порт значение 253.
Простейшим приемом, позволяющим читать и писать байт из или в порт, в Си является использование соответствующих функций. В Турбо Cи - это функции inportb() и outportb(). В Microsoft Cи - это функции inp() и outp(). Они имеют следующий общий формат:
int inportb(int port);
void outportb(int port, char value);
int inp(unsigned port);
int outp(unsigned port, int value);
В других компиляторах Си эти функции могут иметь иные названия, но обязательно будут присутствовать в вашей библиотеке, так как являются одними из базовых функций версий Си для ПЭВМ. В программах, приведенных в этом параграфе, используются функции Турбо Cи.
Программное подтверждение связи
Когда аппа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иема данных.
send()
while ( есть байты для пеpедачи )
send( байт );
wait();
receive()
do
receive_byte();
send( квитиpующй байт );
while( пока все байты не считаны );
Пpи этом подходе пеpедача данных не вызовет никогда пеpеполнения pегистpа в поpте-пpиемнике независимо от того, насколько велика pазница в скоpости выполнения опеpаций компьютеpов, между котоpыми установлена связь.
Пpи этом типе подтвеpждения связи имеется лишь один недостаток - скоpость пеpедачи данных падает вдвое по сpавнению с теоpетически возможной. Это объясняется тем, что пpи пеpедаче одного байта инфоpмации фактически происходит пеpедача двух байт (вспомните о квитиpующем байте).
наверх
Простая программа, использующая процедуру pulldown
Все функции для иерархических меню показаны здесь вместе с простой программой-образцом и их можно прямо вводить в ваш компьютер./* процедура иерархического меню для текстового режима
и простая программа-пример */
#include "dos.h"
#include "stdlib.h"
#define ESC 27
void save_video(),restore_video();
void display_menu(),draw_border();
char far *vid_mem;
struct menu_frame
int startx,endx,starty,endy;
unsigned char *p; /* указатель на информацию экрана */
char **menu; /* указатель на строки меню */
int border; /* рамка включено/выключено */
int count; /* число альтернатив */
int astive; /* активно ли меню сейчас */
frame[MAX_FRAME];
;
;
char *grape_type[]=
"Конкорд",
"кАнадский",
"Томпсон",
"кРасное пламя"
main()
/* во-первых создадим фреймы меню */
make_menu(1,color,"кжоз",4,9,28,BORDER);
int choice1,choice2,selection;
/* активизация окон по мере надобности */ while((choice1=pulldown(0)) != -1)
switch ( choice1 )
case 0 : /* яблоко */
while((choice2=pulldown(1)) != -1)
if(choice2 ==0) selection=pulldown(2);/*красное яблоко */
restore_video(2);
restore_video(1);
break;
case 1 :
case 2 : goto_xy(1,0);
printf("неправильный выбор");
break;
case 3 : /* грейпфрут */
selection=pulldown(3);
restore_video(3);
break;
case 4 :
case 5 : goto_xy(1,0);
printf("неправильный выбор");
break;
restore_video(0);
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); /* возвратить выбор */
/* вычисление размеров */
for(i=0;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); /* Вы можете здесь сами обработать ошибку */
frame[num].startx=x;
frame[num].starty=y;
frame[num].p = p;
frame[num].border = border;
frame[num].count = count;
return 1;
void display_menu(num)
int num;
for(i=0;i
write_string(x,frame[num].starty+1,m[i],NORM_VID);
int num ;
write_char(frame[num].endx ,frame[num].endy ,217,NORM_VID);
goto_xy(frame[num].endx ,frame[num].starty); putchar(192);
get_resp(num)
union inkey
char ch[2];
int i;
c;
x=frame[num].startx+1;
y=frame[num].starty+1;
goto_xy(x,y);
write_string(x,y,frame[num].menu[0],REV_VID);
/* вернуть выбор в номальный режим */
write_string(x+arrow_choice,y,
frame[num].menu[arrow_choice],norm_vid);
else /* специальная клавиша */
switch(c.ch[1])
case 72 : arrow_choice--; /* стрелка вниз */
break;
case 80 : arrow_choice++; /* стрелка вверх */
break;
if(arrow_choice==frame[num].count) arrow_choice=0;
if(arrow_choice<0) arrow_choice=frame[num].count-1;
/* подсветить выбранную опцию */ goto_xy(x+arrow_choice,y); write_string(x+arrow_choice,y,
/* вывод строки с определенным атрибутом */
void write_string(x,y,p,attrib)
int x,y;
char *p;
int attrib;
register int i,j;
char far *v;
v=vid_mem;
v += (x*160) + y*2;
for(i=y; *p; i++)
*v++ =*p++; /* запись символа */
*v++ =attrib; /* запись атрибута */
/* запись символа с определенным аттрибутом */
void write_char(x,y,ch,attrib)
int x,y;
char ch;
int attrib;
void save_video(num)
int num;
void restore_video(num)
register int i,j;
char far *v, far *t;
char *buf_ptr;
buf_ptr=frame[num].p;
v=vid_mem;
t=v;
for(i=frame[num].starty;i
for(j=frame[num].startx;j
v = t;
v += (j*160) + i*2; /* вычисляем адрес */
*v++ = *buf_ptr++; /* запись символа */
*v = *buf_ptr++; /* запись атрибута */
frame[num].active= 0;
/* очистка экрана */
/* установка курсора в x,y */
int x,y;
video_mode()
char *s,c;
В этом примере, если пользователь выберет "Яблоко", то он или она будет запрошен о цвете яблока; если выбран "Красный" цвет, то будет высвечен список красных сортов яблок. Если же будет выбран грейпфрут то пользователь будет запрошен о желаемом типе. Меню для выбора яблок показано на рисунке.
|Апельсин |
|Груша |
|гРейпфрут|
|Малин---------
|Клубн|Красный|
-------Желтый |
|Ора-------------------
|Зел|Красный деликатес|
----|Д*ж*о*н*а*т*а*н**|
|Белый налив |
|Антоновка |
1. Создать меню, используя make_menu().
2. Активизировать меню, используя pulldown().
3. Восстановить экран, используя restore_video(), при выходе из каждого меню.
Простейшая демонстрационная программа
Эта программа позволяет продемонстрировать работу функций, рассмотренных в данной главе до настоящего момента. Эти функции позволяют изменить форму курсора, выдать сообщение в цвете, а также использовать скроллинг части экрана. Результат работы этой программы представлен на рис.8-2.-----------------------------------------------------------------
| |
| a) Это - тест |
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
| bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
| cccccccccccccccccccccccccccccccccccccccccccccccccccccc |
| dddddddddddddddddddddddddddddddddddddddddddddddddddddd |
| eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee |
| ffffffffffffffffffffffffffffffffffffffffffffffffffffff |
| gggggggggggggggggggggggggggggggggggggggggggggggggggggg |
| hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh |
| iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii |
| jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj |
| kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk |
Простейшая ЛВС
Локальные вычислительные сети (ЛВС) получают все большую популя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 часто называют файловым сервером (file server). Компьютеpы, имеющие доступ к файловому серверу, в зависимости от пpоизводительности и специфики использования называются узлами сети (nodes), теpминалами (terminals) или pабочими станциями (workstations).
Особенности топологии двух типов сетей иллюстpи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еализации файлового сервера. Вы можете использовать эти пpогpаммы в качестве стаpтовой точки пpи pазpаботке всего пpогpаммного обеспечения ЛВС.
наверх
Простейшая тестовая программа
Приведенные здесь программа иллюстрируют применение и возможности ранее описанных функций поддержки графики./* Программа, иллюстрирующая работу графических
функций */
#include "dos.h"
#include "stdio.h"
void mode(),line(),box(),fill_box();
void mempoint(),palette(),xhairs();
void circle(),plot_circle(),fill_circle();
double asp_ratio;
main()
mode(4);
palette(0);
line(0,0,100,100,1);
box(50,50,80,90,2);
fill_box(100,0,120,40,3);
circle(100,160,30,2);
fill_circle(150,250,20,1);
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 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);
/* Вычерчивание линии заданного цвета с использованием
алгоритма Брезенхама */
void line(startx,starty,endx,endy,color)
int startx,starty,endx,endy,color;
register int t,distance;
int xerr=0,yerr=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);
xerr+=delta_x;
yerr+=delta_y;
if (xerr>distance)
xerr-=distance;
startx+=incx;
if (yerr>distance)
yerr-=distance;
starty+=incy;
/* Закрашивание прямоугольника заданным цветом */
void fill_box(startx,starty,endx,endy,color_code)
int startx,starty,endx,endy,color_code;
register int i,begin,end;
begin=startx
end=startx>endx ? startx:endx;
for (i=begin;i<=end;++i)
line(i,starty,i,endy,color_code);
/* Вычерчивание окружности с использованием алгоритма
Брезенхама */
void circle(x_center,y_center,radius,color_code)
int x_center,y_center,radius,color_code;
register x,y,delta;
asp_ratio=1.0; /* это число может меняется в различных
случаях */
y=radius;
delta=3-2*radius;
for (x=0;x
plot_circle(x,y,x_center,y_center,color_code);
if (delta<0)
delta+=4*x+6;
else
delta+=4*(x-y)+10;
y--;
x++;
x=y;
if (y) plot_circle(x,y,x_center,y_center,color_code);
/* Функция изображает точки, определяющие окружность */
void plot_circle(x,y,x_center,y_center,color_code)
int x,y,x_center,y_center,color_code;
int startx,starty,endx,endy,x1,y1;
starty=y*asp_ratio;
endy=(y+1)*asp_ratio;
startx=x*asp_ratio;
endx=(x+1)*asp_ratio;
for (x1=startx;x1
mempoint(x1+x_center,y+y_center,color_code);
mempoint(x1+x_center,y_center-y,color_code);
mempoint(x_center-x1,y+y_center,color_code);
mempoint(x_center-x1,y_center-y,color_code);
for (y1=starty;y1
mempoint(y1+x_center,x+y_center,color_code);
mempoint(y1+x_center,y_center-x,color_code);
mempoint(x_center-y1,x+y_center,color_code);
mempoint(x_center-y1,y_center-x,color_code);
/* Закрашивание окружности путем повторного вызова
circle() с уменьшением радиуса */
void fill_circle(x,y,r,c)
int x,y,r,c;
while (r)
circle(x,y,r,c);
r--;
/* Запись точки в 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;
Простейший способ проверки слуха.
Вы обладаете возможностью сделать несколько грубый, но эффективный тест слуха, который в состоянии обнаружить некоторые типы дефекта слуха. Как вы ранее узнали, динамик большинства компьютеров серии PC не воспроизводит звуки выше 12000 Гц. Однако ряд людей, у которых отмечены некоторые отклонения слуха, не могут услышать звук даже такой частоты. Фактически, тестируя свой слух, вы будете несколько удивлены тем, насколько высоким окажется звук с частотой 12000 Гц. (Предупреждение: тестирование слуха с помощью этого теста можно производить лишь ради шутки. Он, естественно, не позволяет действительно оценить слух испытуемого. Поэтому, если вы заметили у себя дефекты слуха или хотите действительно проверить свой слух, обратитесь лучше к своему врачу).Для получения звука в тесте используется функция sound(), которая генерирует непродолжительное звучание специфицированной ноты. Как показано ниже, эта функция содержит все необходимое для того, чтобы сгенерировать любой звук с помощью динамика компьютера.
/* Звучание динамика на заданной частоте */
void sound(freq)
int freq;
unsigned i;
union
long divisor;
unsigned char c[2];
count;
unsigned char p;
count.divisor = 1193280 / freq; /* вычисление небходимого
значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после
установки счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */ p = inportb(97); /* чтение существующего шаблона бит */ outportb(97,p|3); /* установка битов 0 и 1 */
for (i=0;i<64000;++i); /* цикл задержки */
outportb(97,p); /* восстановление первоначального значения
шаблона бит для отключения динамика */
Заметим, что частота звучания ноты специфицирована как
аргумент функции. Цикл задержки необходим, так как без него вы бы
услышали только мгновенный "щелчок" или "писк". Вы можете
изменить частоту работы системного таймера процессора вашего
компьютера. При этом, оформив его как параметр функции, вы
добьетесь определенной эффективности вашей программы. Функция
sound() может использоваться и для получения банального
"пищания" компьютера.
Управляющая функция для программы теста слуха представлена ниже.
/* Простейший тест слуха */
#include "dos.h"
void sound();
main()
int freq;
do
printf(" Введите частоту ( 0 - выход ): ");
scanf("%d",&freq);
if ( freq ) sound(freq);
while(freq);
При использовании теста, в возрастающем порядке указывайте частоту звука до тех пор, пока звук воспринимается на слух. Для выхода введите 0.
Работа адаптеров CGA/EGA в графическом режиме
Адаптер CGA всегда располагается в видеопамяти по адресу 8000000h. Адаптер EGA имеет аналогичное расположение для тех режимов, которые совместимы с режимами CGA (более полную информацию о аппаратных средствах поддержки графики вы можете получить в руководстве "IBM Technical Reference"). В 4 графическом режиме каждый байт содержит информацию о цвете для 4 точек растра (для каждой точки растра по 2 бита). Следовательно, для работы с экраном размерностью 320 на 200 требуется 16К памяти. Так как два бита могут содержать только 4 различных значения, в 4 видеорежиме поддерживаются только 4 цвета. Значение каждого двухбитового блока определяет цвет в соответствии с таблицей, приведенной ниже:2
3
желтый
красный
зеленый
голубой
пурпурный
булый
Так как каждый байт кодирует значение четырех точек растра, вы должны сохранять значение трех точек при изменении одной из них. Лучший способ сделать это - создание битовой маски со всеми битами, установленными в 1, кроме тех, которые будут изменяться. Значение битовой маски складывается по схеме "И" с действительным значением байта, а затем полученное значение складывается по схеме "ИЛИ" с новым значением. Однако ситуация несколько меняется, если вы хотите сложить по схеме "НЕ-ИЛИ" новое и старое значения. В этом случае вы просто складываете по схеме "ИЛИ" старое значение байта с 0, а затем складываете по схеме "НЕ-ИЛИ" новое двоичное представление цвета и получаете результат. Адрес требуемого байта определяется путем умножения значения координаты X на 40, а затем добавляется значение координаты Y, деленное на
4. Для того, чтобы определить, находится ли точка растра в четном или нечетном блоке памяти, используется остаток от деления значения координаты Х на 2. Если остаток равен 0, блок является четным (используется первый блок), в противном случае - блок нечетный (используется второй блок). Требуемые биты внутри байта вычисляются путем выполнения деления по модулю 4. Остаток
определяет номер двухбитового пакета, который содержит информацию о требуемых точках растра. Для установки байта режима цвета и битовой маски используются операторы побитового сдвига. Хотя манипулирование битами в функции mempoint() несколько запутано, вы, однако, без труда разберетесь в ней, если тщательно изучите что именно и как она делает.
/* Запись точки в 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;
Заметим, что тип указателя видеопамяти объявлен как far; это необходимо, если вы транслируете в модели маленькой (small) памяти. Вы так же должны заметить, что специальный маркер режима записи XOR, определенный в функциях ROM-BIOS, используется и в функции mempoint().
Работа видеоадаптеров.
Из-за того, что создание исчезающих и иерархических меню требует прямого управления экраном, важно понимание адаптеров дисплея. Три основных типа адаптеров - это одноцветный адаптер, цветной/графический адаптер (CGA) и усовершенствованный графический адаптер (EGA). CGA и EGA могут иметь несколько режимов работы, включая 40- или 80- символьный текст или графические операции. Эти режимы показаны в таблице 1-1. Программы меню, разработанные в этой главе, разработаны для использования режима 80-символьного текста, который является наиболее общим режимом для общецелевых применений. Это значит, что видео режим системы должен быть 2, 3 или 7. Независимо от используемого режима - координаты левого верхнего угла - 0,0.Таблица 1-1.
Режим
1
2
3
4
5
6
7
8
9
10
13
14
15
-------
текст 16 цветов
текст ч/б
текст 16 цветов
графика 4 цвета
графика 4 серых тона
графика ч/б
текст ч/б
графика 16 цветов
графика 16 цветов
графика 4 или 16 цв.
графика 16 цветов
графика 16 цветов
графика 4 цвета
----------------------
40*25
80*25
80*25
320*200
320*200
640*200
80*25
160*200
320*200
640*200
320*200
640*200
640*350 -------------
CGA,EGA
CGA,EGA
CGA,EGA
CGA,EGA
CGA,EGA
CGA,EGA монохромный
PCjr
PCjr
PCjr,EGA
EGA
EGA
EGA ------
Символы, выводимые на экран, содержатся в некоторой зарезервированной области памяти на адаптере дисплея. Адрес одноцветной информации В0000000H. И CGA, и EGA хранят информацию, начиная с B80000000H. (Они различны для того, чтобы позволить использовать раздельно текстовый и графический экран - но на практике это делается редко.) Хотя функции CGA и EGA различны в разных режимах, они одинаковы в режимах 2 и 3.
Каждый символ, выводимый на экран, требует два байта видео памяти. Первый байт содержит собственно символ, второй содержит аттрибуты экрана. Для цветного экрана байт аттрибутов интерпретируется так, как показано в таблице 1-2. Если у вас EGA или CGA, то по умолчанию принимается режим 3, и символы выводятся с байтом аттрибутов 7. Это значение включает три основных цвета, производя для символа белый цвет. Для переключения в инверсный
Раздел инициализации
Раздел инициализации программы резидентного калькулятора очень небольшой и целиком помещается в нижеследующей функции main().void interrupt tsr_ap(); /* вход в прикладную программу */
main()
struct address
char far *p;
;
/* адрес прерывания печати экрана */
struct address far *addr = (struct address far *) 20;
addr->p = (char far *) tsr_ap;
set_vid_mem();
tsr(2000);
TSR-программа первым делом должна заменить адрес программы обработки прерывания 5 указателем функции, определенной в самой TSR-программе. Есть несколько способов изменения адреса в таблице векторных прерываний. Один из способов состоит в использовании системного вызова DOS. Однако неудобство использования функции DOS заключается в том, что она требует задания значения адресного сегмента в регистре ЕS, который недоступен при использовании функции int86(). Некоторые компиляторы, как например Турбо Си, включают специальные функции, предназначенные для установки адреса в таблице прерываний. Однако способ, предлагаемый здесь, будет работать при использовании практически любого компилятора. Функция tsr_ap() является точкой входа в прикладную часть TSR-программы. Она использует указатель на содержимое таблицы векторов, соответствующее прерыванию 5. (Напоминаем, что вектор 5 расположен по адресу 20(4х5) в таблице, поскольку каждый вектор имеет размер 4 байта. Некоторые TSR-программы восстанавливают исходное значение адреса. Но при использовании приводимых здесь программ вы должны будете перезагружать систему, чтобы восстановить исходные значения векторов прерываний.
В предыдущих разделах, проверка режима работы видеосистемы производилась динамически теми программами, которые с ней работали. Однако в данном случае это неприменимо, поскольку требует использования системных вызовов DOS. Вместо этого значение глобального указателя vid_mem устанавливается с помощью функции set_vid_mem, приводимой ниже.
set_vid_mem()
int vmode;
vmode = video_mode();
if((vmode!=2) && (vmode!=3) && (vmode!=7))
printf(" video must be in 80 column text mode");
exit (1);
/* установить соответствующий адрес видеопамяти */
if(vmode==7) vid_mem = (char far *) 0xB0000000;
else vid_mem = (char far *) 0xB8000000;
Наконец, выход из функции main() ocyществляется путем обращения к функции tsr(), приведенной ниже.
/* завершить выполнение, но оставить резидентной */
tsr(size)
unsigned size;
union REGS r;
r.h.ah = 49; /* завершить и оставить резидентной */
r.h.al = 0; /* код возврата */
r.x.dx = size;
int86(0x21, &r, &r);
Параметр size, определяемый в регистре DX, используется для того, чтобы сообщить DOS, сколько памяти требуется для размещения ТSR-программы. Размер памяти определяется в 16-байтных параграфах. Иногда бывает трудно определить, сколько памяти необходимо для размещения программы. И если в этом случае вы разделите размер загрузочного модуля вашей программы (файла с расширением .EXE) на 16, а полученную величину умножите на 2, то будете застрахованы от ошибки. Точно определить размер необходимой памяти трудно, поскольку загрузочные модули частично накладываются друг на друга при загрузке и необязательно размещаются в непрерывной области. (Если вы намереватесь продавать свои программы, то наверняка хотели бы знать точно, сколько потребуется памяти, чтобы не оказаться слишком расточительным. Наиболее просто это можно определить экспериментальным путем). Код возврата, устанавливаемый в регистре AL, передается системе.
После завершения выполнения функции маin() программа остается в памяти, и никакая другая программа не может быть загружена на ее место. Это значит, что прикладная часть программы в любой момент времени готова быть запущенной нажатием клавиши PT SCR.
Разработка функций построения диаграмм.
Прежде, чем разрабатывать функцию, рисующую диаграммы, необходимо точно определить, что она будет делать. Во-первых, она должна выполнять свою главную задачу - выводить данные в виде диаграмм. Функция должна допускать использование в качестве входного параметра массива чисел с плавающей точкой и преобразовывать их в нормализованные целые эквиваленты. Программа должна быть реентерабельной и позволять рисовать несколько диаграмм одновременно. Функция должна также содержать аргумент, определяющий расстояние между диаграммами, соответствующими разным последовательностям данных, и, наконец, она должна позволять определять толщину линий диаграммы.Программа функции bargraph(), приведенная ниже,
удовлетворяет этим требованиям.
/* Вывод диаграммы */
void bargraph(data,num,offset,min,max,width)
double *data; /* массив данных */
int num; /* количество элементов в массиве */
int offset; /* расстояние между диаграммами */
int min,max; /* мин. и мак. выводимые значения */
int width; /* толщина линий */
int y,t,incr;
double norm_data,norm_ratio,spread;
char s[80];
static int color = 0;
int tempwidth;
/* всегда используйте различные цвета */
color++;
if ( color > 3 ) color = 1;
/* определение нормирующего множителя */
spread = (double)max-min;
norm_ratio = 180/spread;
incr = 280/num;/* определение промежутка между значениями */
tempwidth = width;
for (t=0;t
/* подгонка отрицательных значений */
norm_data = norm_data-(double)min;
norm_data *= norm_ratio; /* нормирование */
y = (int)norm_data; /* преобразование типа */
do
Line(179,((t*incr)+20+offset+width),179-y,
((t*incr)+20+offset+width),color);
width--;
while(width);
width = tempwidth;
Давайте тщательно разберем данную программу. Функция bargraph() получает через входные параметры: массив данных, число элементов в массиве, расстояние между диаграммами (для случая одновременного вывода нескольких диаграмм), минимальное и максимальное значения данных и ширину линий диаграмм (ширина линии задается в единицах растра). Статическая переменная color определяет новый цвет при повторных обращениях к bargraph(). Таким образом, различные последовательности данных при их одновременном выводе будут изображены диаграммами различного цвета. При вычислении нормирующего множителя вместо максимальной высоты экрана (200 для 4-го видеорежима) использовано меньшее число - 180, что в последующем позволит использовать две строки экрана для вывода поясняющей информации. Обычно удобнее, если диаграмма полностью занимает экран независимо от количества выводимых чисел. Например, диаграмма, отражающая малые наборы данных, выглядит более привлекательной, если она занимает весь экран, а не совокупность сбившихся в кучу вертикальных полос в одном из углов экрана. Для размещения диаграммы относительно ширины экрана последняя (здесь также целесообразнее использовать меньшее число 280 вместо 300) делится на количество выводимых элементов, полученный результат затем используют при определении горизонтальных координат стержней диаграммы. В конце программы выполняется циклическая нормализация данных и вычерчивание линий заданной толщины с указанным смещением.
Функция bargraph() - ключевая функция, но это только одно из многих средств, позволяющих вам рисовать диаграммы почти любого вида. Основные из этих средств вы узнаете в процессе дальнейшего изложения материала.
Вычерчивание линии нулевого уровня.
Диаграмма будет выглядеть более привлекательной и наглядной, если вдоль нижнего края вычертить линию нулевого уровня. Программа функции grid(), представленная в данном разделе, служит именно для этих целей.
/* Вывод линии нулевого уровня */
void grid(min,max)
int min,max;
register int t;
goto_xy(22,0); printf("%d",min);
goto_xy(0,0); printf("%d",max);
line(180,10,180,300,1);
Вы видите, что функция grid() так же, как и bargraph() оставляет внизу две строки для вывода поясняющих меток и другой справочной информации.
Вывод меток элементов диаграмм.
Часто пользователю необходимо помечать значения, выводимые диаграммой. Например, на диаграмме, показывающей изменение прибыли корпорации за пять лет, целесообразно каждый стержень диаграммы пометить соответствующим годом. Конечно, вы всегда можете это сделать вручную, используя функции goto_xy() и printf(); функция label(), представленная ниже, освободит вас от этой рутинной работы, она автоматически выводит необходимые метки в нужном месте экрана. Входными параметрами функции label() являются: массив меток и их количество. Длина каждой метки ограничена 20 символами ( включая указатель конца ), но это не является жестким ограничением и при небходимости вы можете легко изменить максимальную длину меток.
/* Вывод меток на экран */
void label(str,num)
char str[][20]; /* массив меток */
int num; /* количество меток */
int i,j,inc;
inc = 38/num;
i = 2; /* определение начальной точки */
for (j=0;j
goto_xy(23,i);
printf(str[j]);
i += inc;
Вычерчивание вспомогательных линий.
В некоторых случаях полезно выводить горизонтальные полосы для сравнения высот стержней диаграммы. Так как сплошные линии могут отвлекать пользователя, то для этой цели лучше использовать пунктирные линии. Функция hashlines(), приведенная ниже, рисует требуемые пунктирные линии.
/* Вывод пунктирных линий */
void hashlines()
int i,j;
for (i=10;1<180;i+=10)
for (j=10;j<300;j+=5)
mempoint(i,j,3); /* одна точка на каждые 5 единиц
растра */
Вывод надписей.
При одновременном выводе нескольких наборов в виде диаграмм полезно определить цвет диаграммы, соответствующий каждому набору. Это можно сделать, например, выдав надпись, содержащую наименование набора и используемый для него цвет диаграммы. Функция legend(), приведенная здесь, выводит наименования наборов и прямоугольник соответствующего цвета, в качестве входных параметров она использует список наименований и их количество. Функция legend() использует функцию fill_box(), описанную ранее, для вывода цветного прямоугольника.
/* Вывод надписи */
void legend(names,num)
char names[][20];
int num; /* количество наименований */
int color = 1,i,j;
goto_xy(24,0); /* надпись производится в последней строке */
j = 0;
for (i=0;i
/* вывод наименования */
printf("%s ",names[i]);
/* определение координаты цветного прямоугольника. В 4
режиме каждому литерному символу отводится 8 единиц
растра (в ширину) */
j += strlen(names[i]) * 8 + 4;
fill_box(192,j,198,j+12,color);
j += 28; /* продвижение к следующему полю вывода */
color ++;
if ( color>3 ) color = 1;
_________________________________________________________________
Графический рисунок на стр 355 не может быть воиспроизведен имеющимися средствами. (Ред. перевода И.Бычковский.)
_________________________________________________________________
Рис.10-1. Результат работы программы построения диаграмм
Простейшая программа вывода диаграмм.
Следующая программа показывает все описанные функции в действии. Результат ее работы представлен на рис.10-1. Программа выводит среднюю стоимость акций трех мнимых корпораций за пять лет.
/* Программная демонстрация построения диаграмм */
#include "dos.h"
void bargraph(),mode(),mempoint();
void line(),goto_xy(),grid(),label();
void hashlines(),legend(),read_cursor_xy();
void palette(),color_puts(),fill_box();
main()
double widget[] =
10.1,20,30,35.34,50
;
double global[] =
19,20,8.8,30,40
;
double tower[] =
25.25,19,17.4,33,29
;
int min,max;
char n[][20] =
"widget",
"global",
"tower"
;
char lab[][20] =
"1983",
"1984",
"1985",
"1986",
"1987"
;
mode(4); /* выбор режима 320*200 */
palette(0);
grid(0,50); /* построение линии нулевого уровня */
hashlines(); /* вывод пунктирных линий */
label(lab,5); /* вывод чисел */
legend(n,3); /* вывод надписей */
/* вывод курса акций трех кампаний */
bargraph(widget,5,0,0,50,4);
bargraph(global,5,10,0,50,4);
bargraph(tower,5,20,0,50,4);
getch();
mode(3);
/* Вывод линии нулевого уровня диаграммы */
void grid(min,max)
int min,max;
register int t;
goto_xy(22,0); printf("%d",min);
goto_xy(0,0); printf("%d",max);
line(180,10,180,300,1);
/* вывод меток на экран */
void label(str,num)
char str[][20]; /* массив меток */
int num; /* количество меток */
int i,j,inc;
inc = 38/num;
i = 2; /* определение начальной точки */
for (j=0;j
goto_xy(23,i);
printf(str[j]);
i += inc;
/* Вывод пунктирных линий на экран */
void hashlines()
int i,j;
for (i=10;1<180;i+=10)
for (j=10;j<300;j+=5)
mempoint(i,j,3); /* одна точка на каждые 5 единиц
растра */
/* вывод надписи */
void legend(names,num)
char names[][20];
int num; /* количество наименований */
int color = 1,i,j;
goto_xy(24,0); /* надпись производится в последней строке */
j = 0;
for (i=0;i
/* вывод наименования */
printf("%s ",names[i]);
/* определение координаты цветного прямоугольника. В 4
режиме каждому литерному символу отводится 8 единиц
растра ( в ширину ) */
j++ = strlen(names[i]*8+4);
fill_box(192,j,198,j+12,color);
j++ = 28; /* продвижение к следующему полю вывода */
color ++;
if ( color>3 ) color = 1;
/* Вычерчивание диаграммы */
void bargraph(data,num,offset,min,max,width)
double *data; /* массив данных */
int num; /* количество элементов в массиве */
int offset; /* расстояние между диаграммами */
int min,max; /* минимальное и максимальное выводимые значения */
int width; /* толщина линий */
int y,t,incr;
double norm_data,norm_ratio,spread;
char s[80];
static int color = 0;
int tempwidth;
/* всегда используйте различные цвета */
color++;
if ( color > 3 ) color = 1;
/* определение нормирующего множителя */
spread = (double)max-min;
norm_ratio = 180/spread;
incr = 280/num; /* определение промежутка между значениями*/
tempwidth = width;
for (t=0;t
norm_data = data[t];
/* подгонка отрицательных значений */
norm_data = norm_data-(double)min;
norm_data *= norm_ratio; /* нормирование */
y = (int)norm_data; /* преобразование типа */
do
line(179,((t*incr)+20+offset+width),179-y,
((t*incr)+20+offset+width),color);
width--;
while(width);
width = tempwidth;
/* Вывод линии заданного цвета, используя базовый алгоритм
Брезенхама */
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;
/* наполнение прямоугольника заданным цветом */
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);
/* запись точки в 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; /* xor - цвет или наложение */
char far *ptr = (char far *) 0xB8000000; /* указатель на
CGA */ bit_mask.i = 0xFF3F; /* 11111111 00111111 в двоичном коде */
/* контроль координат для 4 режима */
if (x<0 || x>199 || y<0 || y>319) return;
xor = color_code & 128; /* проверка установки режима xor */
color_code = color_code & 127; /* маска 7 старших бит */
/* установка bit_mask и color_code в правильное положение */
bit_position = y%4;
color_code <<= 2*(3-bit_position);
bit_mask.i >>= 2*bit_position;
/* поиск соответствующего байта в памяти экрана */
index = x*40 + (y>>2);
if (x%2) index+=8152; /* если нечетный, использовать второй
байт */
/* запись цвета */
if (!xor) /* режим наложения */
t = *(ptr + index) & bit_mask.c[0];
*(ptr + index) = t | color_code;
else /* режим xor */
t = *(ptr + index) | (char)0;
*(ptr + index) = t | color_code;
/* установка видеорежима */
void mode(mode_code)
int mode_code;
union REGS r;
r.h.al = mode_code;
r.h.ah = 0;
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);
/* установка цветов диаграмм */
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);
Разработка видеоигры
В этом параграфе мы опишем разработку видеоигры, которая иллюстрирует многие принципы, описанные в данной главе.Описание игры
Первым шагом в процессе создания видеоигры является определение ее природы и правил, по которым она ведется. Программа, описанная здесь, представляет собой компьютеризованную версию традиционной детской игры "салочки". Игрок и компьютер управляют каждый своим "человеком". Один из них догоняет другого, и, если у них произошел контакт, происходит смена амплуа. Победителем в игре становится тот, кто больший промежуток времени был в положении догоняемого.
Cчет определяется путем фиксации игрового времени: после каждой прошедшей секунды добавляется одно очко тому, кто находится в роли догоняемого. Счет непрерывно отображается в углу экрана. Игра заканчивается, когда один из игроков набирает 999 очков. Для удобства игра может быть закончена путем нажатия клавиши
.
Игрок управляет движением спрайта посредством клавиш управления курсором. Игровое поле в данном случае не создается самой программой игры. Для этих целей используются программы рисования ("программы-художники"), описанные в главе 4. Поэтому, вы можете самостоятельно создавать различную среду игры.
Использование цвета и граничные условия.
Игра "салочки" использует программирование в цвете для идентификации различных объектов. Например, вы можете сделать спрайт игрока зеленым, спрайт компьютера - желтым, а границы поля игры - красными. В соответствии с этим подходом, нет необходимости хранить отдельные массивы данных в разделяемой области программы, т.к. подпрограммы могут просто контролировать содержимое видеопамяти. В данном случае, так же, значительно упрощается процесс ограничения области движения спрайтов красной линией. Для этой цели необходимо немного изменить функцию is_legal(), описанную ранее, как это показано ниже.
/* Определение допустимости перемещения объекта.
Возвращает 1, если перемещение допустимо, 0 - в противном случае
Сборка подпрограмм
В этом, последнем, параграфе, описывается простая программа рисования, использующая подпрограммы графики. Программы рисования часто используют "мышь", позволяющую пользователю удобным способом отображать линии на экране терминала. Однако, "мышью" комплектуются пока не все компьютеры поэтому, описанная здесь "программа-художник" ориентирована на операции с клавишами перемещения курсора.
В "рисующих" программах вам необходимо контролировать текущее положение координат X и Y (в графическом режиме текущие координаты индицируются курсором не совсем обычной формы). Для простоты дальнейшего изложения материала будем называть "графический" курсор "перекрестьем", принимая во внимание и тот факт, что в графическом режиме его форма действительно напоминает крест. Функция xhairs() размещает графический курсор в позиции, специфицированной значениями ее аргументов X и Y.
Напоминаем, что двоичный код цвета складывается по схеме "ИЛИ" со 128 с целью установки 7 бита в 1. Это позволяет функции mempoint() складывать по схеме "исключающего ИЛИ" двоичные коды старого и нового цвета на экране вместо его изменения. Такая возможность позволяет достичь двух важных моментов. Во-первых, графический курсор всегда видим, т.к. всегда имеет цвет, отличный от окружающего. Во-вторых, значительно упрощается процесс возврата точки растра, занимаемой курсором, в исходное положение. Эта операция выполняется путем повторного обращения к этим точкам (напомним, что последовательное выполнение двух операций по схеме "исключающего ИЛИ" всегда приводит к первоначальному значению). Ниже приведен текст функции отображения графического курсора.
/* отображение графического курсора */
void xhairs(x,y)
int x,y;
line(x-4,y,x+3,y,1|128);
line(x,y+4,x,y-3,1|128);
Программа рисования, описанная в данном разделе, позволит вам:
- рисовать линии;
- рисовать прямоугольники;
- закрашивать прямоугольники;
- рисовать окружности;
- закрашивать окружности;
- выбирать цвет;
- выбирать палитру;
- устанавливать скорость изменения параметров;
- сохранять графические изображения;
- загружать графические изображения;
- вращать объекты вокруг любой точки;
- копировать и пересылать графические изображения.
Приведем ниже текст главной программы :
main()
union k
char c[2];
int i;
key ;
int x=10, y=10; /* текущая позиция экрана */
int cc=2; /* текущий цвет */
int on_flag=1; /* признак использования карандаша */
int pal_num=1; /* номер палитры */
/* конечная точка определения линий,
прямоугольников, окружностей */
int startx=0, starty=0, endx=0, endy=0;
int first_point=1;
int inc=1; /* шаг пересылки */
int sides=0; /* количество сторон выбранного объекта */
int i;
mode(4); /* переключатель режима CGA/EGA */
palette(0); /* палитра 0 */
xhairs(x, y); /* указатель курсора */
do
key.i = bioskey(0);
xhairs(x, y); /* графический курсор */
if(!key.c[0]) switch(key.c[1])
case 75: /* влево */
if(on_flag) line(x, y, x, y-inc, cc);
y -= inc;
break;
case 77: /* вправо */
if(on_flag) line(x, y, x, y+inc, cc);
y += inc;
break;
case 72: /* вверх */
if(on_flag) line(x, y, x-inc, y, cc);
x -= inc;
break;
case 80: /* вниз */
if(on_flag) line(x, y, x+inc, y, cc);
x += inc;
break;
case 71: /* вверх и влево */
if(on_flag) line(x, y, x-inc, y-inc, cc);
x -= inc;
y -= inc;
break;
case 73: /* вверх и вправо */
if(on_flag) line(x, y, x-inc, y+inc, cc);
x -= inc;
y += inc;
break;
case 79: /* вниз и влево */
if(on_flag) line(x, y, x+inc, y-inc, cc);
x += inc;
y -= inc;
break;
case 81: /* вниз и вправо */
if(on_flag) line(x, y, x+inc, y+inc, cc);
x += inc;
y += inc;
break;
case 59: /* F1 - медленно */ inc=1;
break;
case 60: /* F2 - быстро */
inc=5;
break;
else switch(tolower(key.c[0]))
case 'o': /* переключение шаблона */
on_flag = !on_flag;
break;
case '1': cc=1; /* цвет 1 */
break;
case '2': cc=2; /* цвет 2 */
break;
case '3': cc=3; /* цвет 3 */
break;
case '0': cc=0; /* цвет 0 */
break;
case 'b': box(startx, starty, endx, endy, cc);
break;
case 'f':
fill_box(startx, starty, endx, endy, cc); break;
case 'l':
line(startx, starty, endx, endy, cc); break;
case 'c':
circle(startx, starty, endy-starty, cc); break;
case 'h':
fill_circle(startx, starty, endy-starty, cc); break; case 's':
save_pic(); break;
case 'r':
load_pic(); break;
case 'm': /* пересылка фрагмента */
move(startx, starty, endx, endy, x, y); break;
case 'x': /* копирование фрагмента */
copy(startx, starty, endx, endy, x, y); break;
case 'd': /* определить поворот(cдвиг) объекта */
/* Внимание!! Во время трансляции программы идентификатор object
был помечен как "неопределенный". Его описание действительно
отсутствует в этой программе. (Ред. пер. И.Бычковский)
*/
sides = define_objekt(object, x, y); break;
case 'a': /* поворот(сдвиг) объекта */
rotate_objekt(object, 0.05, x, y, sides); break;
case '\r': /* набор конечных точек для линий, кругов
или прямоугольников */
if(first_point)
startx = x, starty = y;
else
endx = x, endy = y;
first_point = !first_point;break;
case 'p':
pal_num = pal_num==1 ? 2:1;
palette(pal_num);
xhairs(x, y);
while (key.c[0]!='q');
getchar();
mode(2);
Опишем кратко алгоритм работы программы рисования. Вначале экран терминала устанавливается в 4 графический режим. Затем устанавливается палитра 0, и графический курсор перемещается в верхний левый угол. Шаблон цвета по умолчанию устанавливается в соответствии с кодом 2 (красный в палитре 0). При перемещении графического курсора на экране остается след, который окрашивается в соответствии с текущим цветом шаблона. Если нажимать клавиши перемещения курсора, графический курсор перемещается на одну точку растра в заданном направлении. Такая скорость перемещения может не удовлетворять пользователя, поэтому в программе предусмотрена возможность смещения на 5 точек растра путем нажатия клавиши F2. Отменить режим ускоренного перемещения можно путем нажатия клавиши F1. Изменение цвета осуществляется при нажатии цифровых клавиш от 0 до 3. В палитре 0 цифра 0 зарезервирована, 1 определяет зеленый цвет, 2 - красный, 3 - желтый. Шаблон цвета может быть изменен путем нажатия клавиши 0. Клавиши <Курсор в левый верхний угол> (), <Страница вверх> ( ), <Страница вниз> ( ) и <Кон> ( ) перемещают графический курсор в указанном направлении и под углом в 45 градусов.
Для анализа кодов операций чтения в программах используется функция bioskey(). Порядок подключения этой функции к программе
при компиляции описан в главе 1. В программу включены обращения к
функциям, позволяющим вам рисовать и закрашивать прямоугольники и
окружности, рисовать линии, копировать и перемещать изображение
на экране, сохранять на диске и загружать с него содержимое
экрана, отображать и вращать объекты.
При изображении линий, прямоугольников и окружностей вам необходимо определить координаты двух точек. Для прямоугольников
- это координаты двух противоположных углов. Для линий задается начальная и конечная точки, а для окружности - координаты центра и точки, через которую она будет проходить.
Процесс выбора этих точек выполняется путем нажатия клавиши <ВВОД> в момент, когда графический курсор находится в требуемой области. Например, для изображения линии вы перемещаете графический курсор в точку, где она должна начинаться и нажимаете клавишу <ВВОД> . Затем вы устанавливаете курсор в точку, где линия заканчивается, и нажимаете <ВВОД> снова. При нажатии клавиши <ВВОД> выполняется загрузка переменных startx, starty, endx и endy, которые потом используются в качестве параметров вызываемых функций. После того, как координаты точек будут определены, при нажатии клавиши <В> рисуется квадрат, а- квадрат закрашивается, при нажатии рисуется линия, при нажатии <С> рисуется окружность, а <Н> - окружность закрашивается.
Для копирования или перемещения части экрана вы должны определить верхний левый и нижний правый углы области, которую вы хотите переместить (нажатием клавиши <ВВОД> ). Затем вы перемещаете курсор в верхний левый угол области, куда вы хотите переместить изображение. Для пересылки изображения требуется нажать клавишу <М>, а для копирования - <Х>. Запомните, что старое изображение в области, куда осуществляется копирование, будет уничтожено.
Для вращения объекта вам необходимо определить сам объект, путем нажатия клавиши. Затем, используя клавишу <ВВОД>, вы должны определить начальные и конечные координаты точек для отрезков по периметру выбранного объекта. Процесс выбора объекта вращения и определения его границ реализуется функцией define_object(). Вращение объекта начинается после нажатия клавиши <А>. Для определения направления вращения используются клавиши (по часовой стрелке) или (против часовой стрелки). Остановить процесс вращения можно нажатием любой клавиши, кроме или <А>.
Для остановки работы программы используется клавиша. При желании вы можете включить в программу функции для работы с "мышью". Пример выходных данных программы показан на рисунке 4-5.
_________________________________________________________________
Рисунок 4-5 на стр. 163 не может быть воспроизведен имеющимися средствами. (Ред. пер. И.Бычковский)
_________________________________________________________________
Рис. 4-5. Простейшие результаты работы программы рисования.
А теперь приведем всю программу рисования целиком.
/* Программа для CGA/EGA, позволяющая рисовать линии, прямоугольники и окружности. Вы можете нарисовать какой-либо объект и вращать его по часовой или против часовой стрелки. Вы так же можете копировать графическое изображение на диск и загружать его с диска. */
#define NUM_SIDES 20 /* число сторон объекта;
при необходимости увеличивается */ #include "dos.h"
#include "stdio.h"
#include "math.h"
void mode(), line(), box(), fill_box();
void mempoint(), palette(), xhairs();
void circle(), plot_circle(), fill_circle();
void rotate_point(), rotate_object(), goto_xy();
void display_object(), copy(), move();
void save_pic(), load_pic();
unsigned char read_point();
/* Этот массив содержит динамически меняющиеся
координаты объекта.
*/
double object[NUM_SIDES][4];
double asp_ratio; /* содержит коэффициент сжатия для
окружностей */
main()
union k
char c[2];
int i;
key ;
int x=10, y=10; /* текущая позиция экрана */
int cc=2; /* текущий цвет */
int on_flag=1; /* признак использования карандаша */
int pal_num=1; /* номер палитры */
int startx=0, starty=0, endx=0, endy=0;
int first_point=1;
int inc=1; /* шаг пересылки */
int sides=0; /* количество сторoн выбранного объекта */
int i;
mode(4); /* переключатель режима CGA/EGA */
palette(0); /* палитра 0 */
xhairs(x, y); /* указатель курсора */
do
key.i = bioskey(0);
xhairs(x, y);
if(!key.c[0]) switch(key.c[1])
case 75: /* влево */
if(on_flag) line(x, y, x, y-inc, cc);
y -= inc;
break;
case 77: /* вправо */
if(on_flag) line(x, y, x, y+inc, cc);
y += inc;
break;
case 72: /* вверх */
if(on_flag) line(x, y, x-inc, y, cc);
x -= inc;
break;
case 80: /* вниз */
if(on_flag) line(x, y, x+inc, y, cc);
x += inc;
break;
case 71: /* вверх и влево */
if(on_flag) line(x, y, x-inc, y-inc, cc);
x -= inc;
y -= inc;
break;
case 73: /* вверх и вправо */
if(on_flag) line(x, y, x-inc, y+inc, cc);
x -= inc;
y += inc;
break;
case 79: /* вниз и влево */
if(on_flag) line(x, y, x+inc, y-inc, cc);
x += inc;
y -= inc;
break;
case 81: /* вниз и вправо */
if(on_flag) line(x, y, x+inc, y+inc, cc);
x += inc;
y += inc;
break;
case 59: /* F1 - медленно */
inc=1;
break;
case 60: /* F2 - быстро */
inc=5;
break;
else switch(tolower(key.c[0]))
case 'o': /* переключение шаблона */
on_flag = !on_flag;
break;
case '1': cc=1; /* цвет 1 */
break;
case '2': cc=2; /* цвет 2 */
break;
case '3': cc=3; /* цвет 3 */
break;
case '0': cc=0; /* цвет 0 */
break;
case 'b':
box(startx, starty, endx, endy, cc); break;
case 'f':
fill_box(startx, starty, endx, endy, cc); break;
case 'l':
line(startx, starty, endx, endy, cc); break;
case 'c':
circle(startx, starty, endy-starty, cc); break;
case 'h':
fill_circle(startx, starty, endy-starty, cc); break; case 's':
save_pic(); break;
case 'r':
load_pic(); break;
case 'm': /* пересылка фрагмента */
move(startx, starty, endx, endy, x, y); break;
case 'x': /* копирование фрагмента */
copy(startx, starty, endx, endy, x, y); break;
case 'd': /* определить объект вращения */
sides = define_objekt(object, x, y); break;
case 'a': /* вращение объекта */
rotate_objekt(object, 0.05, x, y, sides); break;
case '\r': /* набор конечных точек для линий, кругов
или прямоугольников */
if(first_point)
startx = x, starty = y;
else
endx = x, endy = y;
first_point = !first_point; break;
case 'p':
pal_num = pal_num==1 ? 2:1;
palette(pal_num);
xhairs(x, y);
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 box(sx, sy, ex, ey, c)
int sx, sy, ex, ey, c;
line(sx, sy, ex, sy, c);
line(sx, sy, sx, ey, c);
line(sx, ey, ex, ey, c);
line(ex, sy, ex, ey, c);
/* изображение линии заданного цвета с использованием
алгоритма Брезенхама */
void line(startx,starty,endx,endy,color)
int startx,starty,endx,endy,color;
register int t,distance;
int xerr=0,yerr=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);
xerr+=delta_x;
yerr+=delta_y;
if (xerr>distance)
xerr-=distance;
startx+=incx;
if (yerr>distance)
yerr-=distance;
starty+=incy;
/* закрашивание прямоугольника в заданный цвет */
void fill_box(startx,starty,endx,endy,color_code)
int startx,starty,endx,endy,color_code;
register int i,begin,end;
begin=startx
end=startx>endx ? startx:endx;
for (i=begin;i<=end;++i)
line(i,starty,i,endy,color_code);
/* изображение окружности с использованием алгоритма
Брезенхама */
void circle(x_center,y_center,radius,color_code)
int x_center,y_center,radius,color_code;
register x,y,delta;
asp_ratio=1.0; /* это число меняется в различных
случаях */
y=radius;
delta=3-2*radius;
for (x=0;x
plot_circle(x,y,x_center,y_center,color_code);
if (delta<0)
delta+=4*x+6;
else
delta+=4*(x-y)+10;
y--;
x++;
x=y;
if (y) plot_circle(x,y,x_center,y_center,color_code);
/* plot_circle печатает точки, определяющие окружность */
void plot_circle(x, y, x_center, y_center, color_code)
int x_center,y_center,radius,color_code;
int x, y, startx, starty, endx, endy, x1, y1;
starty=y*asp_ratio;
endy=(y+1)*asp_ratio;
startx=x*asp_ratio;
endx=(x+1)*asp_ratio;
for (x1=startx;x1
mempoint(x1+x_center,y+y_center,color_code);
mempoint(x1+x_center,y_center-y,color_code);
mempoint(x_center-x1,y+y_center,color_code);
mempoint(x_center-x1,y_center-y,color_code);
for (y1=starty;y1
mempoint(y1+x_center,x+y_center,color_code);
mempoint(y1+x_center,y_center-x,color_code);
mempoint(x_center-y1,x+y_center,color_code);
mempoint(x_center-y1,y_center-x,color_code);
Семь или восемь бит данных
Если вы соби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едаче двоичных файлов: EOF (символ End-Of-File) не используeтся для сигнализации об окончании файла. Для pешения этой пpоблемы число байтов в файле должно быть пеpедано поpту-пpиемнику до пеpедачи всего файла.
наверх
Синтаксический анализатор выражений
Полный простой синтаксический анализатор рекурсивного спуска для целых числовых выражений включает в себя ряд функций. Вы должны взять тексты этих функций и сохранить их в своем файле (когда тексты анализатора и интерпретатора объединятся получится довольно большой файл, поэтому рекомендуется откомпилировать файлы отдельно). Смысл использования глобальных переменных будет кратко описан, в процессе обсуждения интерпретатора.
Исходный текст простейшего синтаксического анализатора рекурсивного спуска для целочисленных выражений приведен ниже.
/* Синтаксический анализатор рекурсивного спуска
для целочисленных выражений, который содержит
ряд включаемых переменных
*/
#include "setjmp.h"
#include "math.h"
#include "ctype.h"
#include "stdlib.h"
#define DELIMITER 1
#define VARIABLE 2
#define NUMBER 3
#define COMMAND 4
#define STRING 5
#define QUOTE 6
#define EOL 9
#define FINISHED 10
extern char *prog; /* буфер анализируемого выражения */
extern jmp_buf e_buf; /* буфер среды функции longjmp() */
extern int variables[26]; /* переменные */
extern struct commands
char command[20];
char tok;
table[];
extern char token[80]; /* внешнее представление лексемы */
extern char token_type; /* тип лексемы */
extern char tok; /* внутреннее представление лексемы */
void get_exp(),level2(),level3(),level4(),level5();
void level6(),primitive(),arith(),unary();
void serror(), putback();
/* Точка входа в анализатор. */
void get_exp(result)
int *result;
get_token();
if(!*token)
serror(2);
return;
level2(result);
putback(); /* возвращает последнюю считаную
лексему обратно во входной поток */
/* Сложение или вычитание двух термов */
void level2(result)
int *result;
register char op;
int hold;
level3(result);
while((op=*token) == '+' || op == '-')
get_token();
Синтаксический разбор выражений
Наиболее важной частью интерпретатора языка является синтактический анализатор выражений, который преобразует числовые выражения, такие как (10-X)/23, в такую форму, чтобы компьютер мог понять ее и вычислить. В книге по языку Cи: The Complete Reference (Osborne/McGraw-uill, 1987) вступительная глава посвящена синтаксическому анализу выражений. Подобного же рода синтаксический анализ, основанный на принципах, изложенных в вышеупомянутой книге, (правда, с небольшими изменениями) будет использоваться для построения интерпретатора SMALL BASIC в данной главе нашей книги. (Так как эта глава содержит только краткие сведения о синтаксическом анализе выражений, то для более детального изучения этой проблемы советуем вам обратиться к источнику: The Compelete Reference.
Синтаксический анализ выражений является довольно сложной задачей, однако в некоторых случаях она облегчается тем, что в процессе синтаксического анализа используются довольно строгие правила алгебры. Синтаксический анализатор, описанный в этой главе, в общем может быть классифицирован как синтаксический анализатор рекурсивного спуска.
Перед тем, как приступить к детальной разработке синтаксического анализатора, вы должны иметь представление о выражениях. Поэтому наш следующий параграф посвящен именно этому вопросу.
Скроллинг части экрана
Две совместно используемые функции ROM-BIOS-прерывания позволяют осуществлять скроллинг вперед и назад части экрана. Эти функции были включены в ROM-BIOS для поддержки многооконных интерфейсов. Как вы знаете, когда курсор расположен в двадцать пятой строке и вы нажали клавишу <ВВОД>, то автоматически осуществляется перемещение текста на одну строку вверх с целью отображения новой строки в нижней части экрана. Точно так же, с помощью функций 6 и 7 прерывания ROM-BIOS 10Н, можно осуществить скроллинг лишь части экрана. Функция 6 позволяет выполнить скроллинг в окне вниз (вперед), а функция 7 - вверх (назад).
Обе функции при вызове используют информацию, хранимую в определенных регистрах. Занесите количество строк, на которые будет "прокручиваться" текст (мощность скроллинга) в регистр AL. Номер верхней левой строки, ограничивающей ваше "окно", занесите в регистр CH, а номер верхнего левого столбца - в регистр CL. Номер нижней левой строки занесите в регистр DH, а номер нижнего правого столбца - в регистр DL. В конце запомните в регистре BH атрибуты режима отображения, которые будут определять, как именно будут отображаться в процессе скроллинга новые строки. Функция scroll_window() представлена ниже.
/* Скроллинг в окне вперед и назад */
void scroll_window(startx,starty,endx,endy,lines,direct)
char startx,starty;/* верхний левый угол */
char endx,endy; /* нижний правый угол */
char lines; /* число строк прокрутки */
char direct; /* вперед или назад */
union REGS r;
if ( direct == UP ) r.h.ah = 6; /* скроллинг вперед */
else r.h.ah = 7; /* скроллинг вниз (назад) */
r.h.al = lines;
r.h.ch = starty;
r.h.cl = startx;
r.h.dh = endy;
r.h.dl = endx;
r.h.bh = 0; /* режим отображения */
int86(0x10,&r,&r);
Вы можете определить макрос UP как имеющий некоторое значение. Вы также можете определить макрос DOWN, значение которого будет отлично от UP, а затем использовать при необходимости осуществления скроллинга в окне эти макроопределения. Такой прием значительно упростит вашу программу. Функция scroll_window() присваивает регистру ВН значение 0 для сохранения пустых строк, однако вы можете изменить это значение по своему усмотрению.
Сохранение части экрана.
Для сохранения содержимого экрана, должно быть прочитано и запомнено текущее значение каждой позиции экрана. Для считывания символа с определенной позиции экрана, используется прерывание 16, функция 8, которая возвращает символ и связанный с ним аттрибут текущей позиции курсора. Для считывания символа с определенного места экрана, вы должны иметь способ установки курсора. Хотя некоторые компиляторы Си поддерживают эту функцию, многие ее не имеют. Тем не менее показанная ниже функция goto_xy() может быть использована. Она использует прерывание 16, функцию 2 с координатой столбца в DL и координатой ряда в DH. Видеостраница задается в ВН (используется страница 0 по умолчанию).
/* установка курсора в x,y */
int x,y;
Прерывание 16, функция 8 возвращает символ из текущей позиции курсора в AL и его атрибут в AH. Функция save_video(), показанная здесь, считывает часть экрана, сохраняет информацию в буфер, и очищает эту часть экрана.
/* сохранение части экрана */
int startx,endx,starty,endy;
union REGS r;
register int i,j;
for(i=starty;ifor(j=startx;j goto_xy(j,i);
r.h.ah=8; /* функция чтения символа */
r.h.bh=0; /* видео страница */
*buf_ptr++ = int86(0x10,&r,&r);
putchar(' '); /* очистка экрана */
верхнего левого и правого нижнего угла сохраняемой области.
Параметр buf_ptr это целый указатель на область памяти, которая содержит информацию. Она должна быть достаточно большой, чтобы разместить всю информацию, считанную с экрана.
Программы в этой главе размещают буфер динамически, но вы можете использовать любую другую схему, если это важно для ваших приложений. Не забудьте, однако, что буфер должен существовать, до тех пор, пока экран не вернется в исходное состояние. Эта функция также чистит область, записывая пробел в каждой позиции.
Сохранение и загрузка графических изображений
Сохранение и загрузка графических изображений является довольно простым делом, т.к. образ изображений находится в видеопамяти дисплея, а ее содержимое легко копировать на дисковый файл. Главной проблемой является необходимость введения пользователем имени файла, ввиду того, что запрос о вводе и введенное имя файла сотрут часть информации на экране. Для того, чтобы избежать этого, разработаны функции save_pic() и load_pic(), тексты которых приводятся в данном разделе. Первая функция сохраняет 14 верхних строк изображения, чистит эту область, запрашивает имя файла и, после того, как оно будет введено, восстанавливает изображение.
/* сохранение графического изображения */
void save_pic()
char fname[80];
FILE *fp; register int i,j;
char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */
char far *temp;
unsigned char buf[14][80]; /* содержит образ экрана */
temp=ptr;
/* сохранение верхних строк текущего содержимого экрана */
for (i=0;i<14;++i)
for (j=0;j<80;++j)
buf[i][j]=*temp; /* четный байт */
buf[i][j+1]=*(temp+8152); /* нечетный байт */
*temp=0; *(temp+8152)=0; /* чистка позиций экрана */
temp++;
goto_xy(0,0);
printf("Имя файла:");
gets(fname);
if (!(fp=fopen(fname,"wb")))
printf("Фвайл не может быть открыт\n");
return;
temp=ptr;
/* восстановление содержимого экрана */
for (i=0;i<14;++i)
for (j=0;j<80;++j)
*temp= buf[i][j]; /* четный байт */
*(temp+8125)=buf[i][j+1]; /* нечетный байт */
*temp=0; *(temp+8152)=0; /* чистка позиций экрана */
temp++;
/* копирование изображения в файл */
for (i=0;i<8152;i++)
putc(*ptr,fp); /* четный байт */
putc(*(ptr+8125),fp); /* нечетный байт */
ptr++;
fclose(fp);
/* загрузка изображения */
void load_pic()
char fname[80];
FILE *fp; register int i,j;
char far *ptr=(char far *) 0xB8000000; /* точка в памяти CGA */
char far *temp;
unsigned char buf[14][80]; /* содержит образ экрана */
temp=ptr;
/* сохранение верхних строк текущего содержимого экрана */
for (i=0;i<14;++i)
for (j=0;j<80;j+=2)
buf[i][j]=*temp;
buf[i][j+1]=*(temp+8152);
*temp=0; *(temp+8152)=0; /* чистка позиций экрана */
temp++;
goto_xy(0,0);
printf("Имя файла:");
gets(fname);
if (!(fp=fopen(fname,"rb")))
goto_xy(0,0);
printf("Файл не может быть открыт\n");
temp=ptr;
/* восстановление содержимого экрана */
for (i=0;i<14;++i)
for (j=0;j<80;j+=2)
*temp= buf[i][j];
*(temp+8125)=buf[i][j+1];
temp++;
return;
/* загрузка изображения из файла */
for (i=0;i<8152;i++)
*ptr=getc(fp); /* четный байт */
*(ptr+8125)=getc(fp); /* нечетный байт */
ptr++;
fclose(fp);
Подпрограммы начинают обработку видеопамяти, начиная с адреса, содержащегося в указателе temp считывая или записывая каждый четный и нечетный байты в порядке возрастания их адресов. Такой подход позволяет добиться простоты и наглядности функционирования рассмотренных выше подпрограмм. В случае обработки видеопамяти в порядке возрастания адресов, вначале будет отображаться четная точка растра, а затем - нечетная.
Сохранение копии экрана в дисковом файле.
Ни в DOS, ни в OS/2 нет утилит, сходных с утилитой печати копии экрана, позволяющей сохранять текущее содержимое экрана дисплея в дисковом файле. В этом разделе вы найдете пояснение, как можно создать программу, которая выполняла бы именно эту функцию.
Эта программа использует ROM-BIOS-прерывание 10Н, функцию 8 для чтения символа из текущей позиции курсора, после чего этот символ записывается в файл на диске. Как и в предыдущем разделе, вы опять встретитесь с функцией goto_xy(), которая в этом случае используется для перемещения курсора последовательно по всем строкам экрана, начиная с левого верхнего угла экрана до правого нижнего угла.
Имя файла, в котором будет храниться копия экрана, указываетя в качестве аргумента программы. Если, к примеру, вы назовете свою программу, копирующую экран на диск, screen, то представленная ниже командная строка приведет к созданию копии экрана в файле с именем scr.sav:
C> screen scr.sav
А вот исходный текст самой программы копирования:
/* Эта программа копирует содержимое экрана вашего
дисплея в файл, имя которого указано в командной
строке
*/
#include "dos.h"
#include "stdio.h"
void save_screen(),goto_xy();
main(argc,argv)
int argc;
char *argv[];
if ( argc != 2 )
printf(" используйте формат : screen <имя файла>");
exit(1);
save_screen(argv[1]);
/* сохранение содержимого экрана в дисковом файле */
void save_screen(fname)
char *fname;
FILE *fp; union REGS r;
register char x,y;
if ( !( fp=fopen(fname,"w")))
printf(" Файл не может быть открыт ");
exit(1);
for (y=0;y<25;y++)
for (x=0;x<80;x++)
goto_xy(x,y);
r.h.ah = 8; /* чтение символа */
r.h.bh = 0; /* видеостраница */
int86(0x10,&r,&r);
putc(r.h.al,fp); /* выдача (печать) символа */
fclose(fp);
/* Перемещение курсора в позицию (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);
Создаваемый файл представляет собой стандартный ASCII файл, который может быть отредактирован и распечатан как обычный текстовый файл. Программа позволяет записывать лишь символы, отображенные на экране, но не позволяет сохранить соответствующие атрибуты отображения символов. Однако дополнить программу для того, чтобы она записывала и атрибуты символов несложно, и вы при желании можете это сделать самостоятельно.
Совершенствование ЛВС
Одним из пеpвых усовеpшенствований описанной здесь
пpостейшей ЛВС является обеспечение дополнительной возможности для pабочих станций сети опеpиpовать с каталогом файловой системы центpального компьютеpа. Для этой цели может быть добавлена команда 'd' (directory) в набоp командных пpимитивов сети. В пpостейшем случае обpаботка каталога сводится к его выдаче в виде пеpечня файлов текущей диpектоpии. Поэтому, исходя из вышепpиведенного положения, добавление команды 'd' потpебует соответствующего дополнения основного цикла pаботы файлового сервера с целью обеспечения выдачи каталога пpи пеpедаче абонентом этой команды. Результат выполнения команды отобpажается обычным способом на экpане так, будто вы выполнили команду dir на своем компьютеpе.
Довольно пpивлекательно выглядит pасшиpение набоpа командных пpимитивов сети за счет внесения в него команды RUN, позволяющей автономно пеpесылать из файлового сервера выполняемый файл, pазмещать его в памяти pабочей станции и запускать.
Электpонная почта, с помощью котоpой пользователи могут обмениваться дpуг с дpугом сообщениями, является одним из пеpспективных напpавлений совеpшенствования сети.
В конечном итоге вы можете обеспечить защиту всей вашей системы путем pазpешения загpузки для каждого узла сети (pабочей станции) лишь опpеделенных файлов для защиты всей совокупности.
Создание фрейма меню
Перед использованием меню для этого должен быть создан фрейм. Функция make_menu(), показанная здесь, создает фрейм меню.
/* создание фрейма иерархического меню.
1 если фрейм может быть создан
в противном случае 0 */ make_menu(num,menu,keys,count,x,y,border) int num; /* номер меню */ char *menu; /* текст меню */ char *keys; /* горячие клавиши */ int count; /* число альтернатив */ int x,y; /* левый верхний угол */ int border; /* рамка */
register int i,len;
int endx,endy,choice,vmode;
unsigned char *p;
if(num>MAX_FRAME)
printf("Слишком много меню");
return 0;
if((x>24)||(x<0)||(y>79)||(y<0))
printf(" выход за пределы экрана");
return 0;
len=0;
endy=len+2+y;
if((endx+1>24) || (endy+1>79))
printf(" выход за пределы экрана");
return 0;
/* размещение памяти для видео буфера */
/* создание фрейма */
frame[num].endx=endx;
frame[num].endy=endy;
frame[num].menu = (char **) menu;
frame[num].count = count;
return 1;
Вы вызываете make_menu с теми же аргументами, какие используются в popup() кроме номера меню, который должен быть определен в первом аргументе. Этот номер используется для идентификации меню.
Создание иерархических окон
Иерархические окна фундаментально отличаются от простых исчезающих меню тем, что два или более исчезающих меню могут быть активными одновременно. Вообще иерархические меню позволяют пользователю выбирать режимы непосредственно из режимов и используются для поддержки системы меню. В отличие от функции popup(), которая сохраняет экран, высвечивает меню, и восстанавливает экран, функция pulldown(), разработанная в этом разделе только сохраняет экран (если это нужно), высвечивает меню и возвращает выбор пользователя. Восстановление экрана обрабатывается как отдельная задача в любом месте программы. Перед тем как вы сможете создать иерархическое меню, вы должны изменить свое представление о меню.
Создание исчезающих меню
Функции, создающей исчезающее меню, должна быть передана некоторая информация. Во-первых, это список предоставляемых меню режимов. Поскольку в меню передаются высвечиваемые строки, то простейший путь передачи списка строк в функцию - помещение их в двумерный массив и передача указателя на массив. Как утверждалось ранее, значение меню может быть выбрано либо передвижением освещенной области на нужное поле и нажатием ВК или нажатием клавиши, указывающей на это поле. Для того, чтобы функция знала, какие клавиши "горячие" и что они обозначают, ей должны быть переданы их имена. Лучший путь для этого - передать строку, которая содержит символы "горячих" клавиш в том же порядке, что и строки меню.
Функция popup() должна также знать как много режимов в меню, и поэтому это число должно быть передано ей. Она должна также знать где расположить меню, то есть нужны координаты X и Y. Наконец, в некоторых ситуациях может быть желательным помещать меню в рамку, а в других - нет. Поэтому должно быть передано значение рамка включена/выключена. Для того, чтобы начать разработку функции popup() нам нужно описание :
/* высветить исчезающее меню и возвратить выбор */
# Сохраняет область экрана под меню
# Высвечивает меню
Создание "космической музыки".
Соединив воедино произвольное количество стандартных функций Си rend() и sound(), вы создадите "космическую" музыку. Звук, получаемый при выполнении программы, представленной ниже, напоминает "музыку звезд" в старых научно-фантастических фильмах. Несмотря на то, что все звуки генерируются произвольным образом, ритм и рисунок мелодии, возникающие время от времени, действительно оставляют впечатление "небесной музыки".
/* Космическая музыка звезд */
#define DELAY 64000
#include "dos.h"
void sound();
main()
int freq;
do
do
freq = rand();
while (freq>5000); /* после персонального
прослушивания */ sound(freq);
while (!kbhit());
/* звучание динамика на специфицированной частоте */
void sound(freq)
int freq;
unsigned i;
union
long divisor;
unsigned char c[2];
count;
unsigned char p;
count.divisor = 1193280 / freq; /* вычисление нужного
значения счетчика */ outportb(67,182); /* обращение к таймеру 8253 после
определения значения счетчика */ outportb(66,count.c[0]); /* пересылка младшего байта */ outportb(66,count.c[1]); /* пересылка старшего байта */
p = inportb(97); /* чтение существующего шаблона бит */
outportb(97,p|3); /* установка бит 0 и 1 */
for (i = 0;iкомпьютеров
32000 для 6 МГц PC/AT
20000 для стандарта PC и XT */
outportb(97,p); /* восстановление начального вида
шаблона бит для отключения динамика */
Эта программа генерирует звуки частотой менее 5000 Гц, так как звуки именно в пределах этой частоты наиболее мягко воспринимаются слухом и не выходят за границы, воспринимаемые ухом человека.
Рекомендуем вам поэкспериментировать с этой программой, установив произвольную длину интервала времени между звуками или фильтруя значения, передаваемые в sound(). Возможны и другие варианты развития вашего творчества.
Спрайты
Многие видеоигры, в которых игрок управляет объектами, атакующими другие объекты, управляемые программой или защищающимися от них, включают два класса активных объектов: среду (представляющую для нас маломеняющееся поле игры) и спрайты. СПРАЙТ - это небольшой подвижный объект, который движется по полю видеоигры по определенным правилам с заданной целью. Например, когда космический корабль стреляет фотонными торпедами, изображение торпеды реализуется спрайтом. В рамках данной главы под спрайтом будем понимать фигуру, определенную некоторыми замкнутыми отрезками (многоугольник). Хотя, в общем случае спрайт может изображаться любым образом, например, в виде окружности. В примерах, рассматриваемых в данной главе, определять спрайт будем в виде двумерного массива целых чисел. Например, спрайт, состоящий из 4 отрезков может быть описан следующим массивом
int sprite [4][4];
Первая размерность массива определяет количество отрезков спрайта, а вторая - координаты конечных точек отрезков (подобный способ описания объектов подробно рассмотрен в главе 4). Начальные и конечные координаты отрезков задаются в следующей последовательности:
start_x, start_y, end_x, end_y
Отрезок, входящий в спрайт, с координатами конечных точек 0,0 и 0,10 может быть описан следующим массивом:
sprite[0][0] = 0; /* start_x */
sprite[0][1] = 0; /* start_y */
sprite[0][2] = 0; /* end_x */
sprite[0][3] = 10; /* end_y */
Несмотpя на то, что изучение
Несмотpя на то, что изучение стандаpта RS-232 не имеет большого влияния на понимание pаботы асинхpонного последовательного поpта в целом, ознакомление читателя со стандаpтом асинхpонного последовательного интеpфейса RS-232 (аналог в СССР - стык С-2) является целью настоящей главы. Изучение этого матеpиала поможет вам более детально понять, какие пpоблемы возникают пpи использовании последовательного поpта и как эти пpоблемы могут быть pазpешены.
Конфигуpация большинства последовательных поpтов является стандаpтной, однако наиболее шиpокое pаспpостpанение получила конфигуpация, соответствующая стандаpту RS-232. По этому стандаpту pазъем содеpжит 25 контактов. (В компьютеpе IBM PC AT используется 9-ти контактный pазъем). Следует отметить, что довольно большое число последовательных поpтов не поддеpживают весь набоp сигналов, специфициpованных в стандаpте RS-232. Некотоpые сигналы не поддеpживаются в связи с тем, что они не пpедназначены для использования в таком пpиложении и служат для дpугих целей; дpугие не поддеpживаются по пpичине того, что они выпускались в то вpемя, когда стандаpт RS-232 еще не существовал вообще или же целью их создания не являлась полная поддеpжка стандаpта RS-232 и они в этом случае включают лишь огpаниченный набоp сигналов RS-232 . Наиболее общими сигналами стандаpта RS-232 являются:
Сигнал Аббpевиатуpа Штыpь pазъема
------ ------------ ---------------
Запpос на посылку данных RTS 4
Очистка для посылки CTS 5
Набоp данных готов DSR 6
Набоp данных завеpшен DTR 20
Пеpедача данных TxD 2
Пpием данных RxD 3
Земля GRD 7
На самом деле сигналов намного больше и это обусловлено тем, что последовательный поpт пеpвоначально pазpабатывался как устpойство поддеpжки модема. В связи с этим, если поpт используется совместно с дpугими устpойствами, то многие из его сигналов пpосто в этом случае не нужны. Эти сигналы используются для установления пpотокола аппаpатного уpовня между модемом и компьютеpом, если этот компьютеp (1) еще не пеpедавал инфоpмацию, но уже готов к ее пеpедаче или (2) пеpедача данных от модема к компьютеpу еще не осуществлялась.
Ошибка кадpирования (т.е. ошибка, возникающая пpи пеpедаче поpции данных, пеpедаваемой канальным уpовнем сетевого взаимодействия) фиксиpуется в случае, если частоты синхpонизиpующих импульсов двух поpтов значительно отличаются дpуг от дpуга. Как вы можете догадаться, последовательный поpт
после того, как он обнаpужил стаpтовый бит, выделяет pегистp
ввода, котоpый за каждый цикл считывает один бит. Длина этого
цикла опpеделяется скоpостью пеpедачи данных. Однако вpемя
нахождения бита в peгистpe опpеделяется тактовой частотой
системы. Если частота компьютеpа-пpиемника недостаточна для
покpытия частоты компьютеpа-источника, то пpоисходит потеpя
полученного бита (т.к. pегистp занят), в связи с чем и
pегистpиpуется ошибка кадриpования (framing error).
наверх
Табло счета активного противника
Роль компьютера в игре во многом зависит от того, является ли эта игра для одного человека или для двоих. Если в игре участвуют два человека, компьютеру отводится роль арбитра и функции табло для отображения счета. Однако, в игре, где участвует один игрок, компьютер становится активным противником. С точки зрения программирования разработка игр, где компьютер выступает в роли противника, значительно интересней.
Тайна 28-го прерывания
Есть одно средство DOS, которое не описывается в документации, и которое может сделать TSR-программы более надежными в тот период времени, когда они используют много системных ресурсов. Вообще говоря, если прикладная часть вашей TSR-программы занимается обменом только с консолью, то вы застрахованы от неприятностей. Неприятности могут возникнуть при использовании таких объектов, как дисковые файлы или порты вводавывода. Хотя это и не описано в технических руководствах по операционной системе, но DOS вызывает прерывание 28Н, когда она находится в "безопасном", т.е. холостом состоянии. Как вам наверное известно, при выполнении определенных функций DOS, которые относятся к критическим участкам, после начала их выполнения прерывания должны быть запрещены. Прерывание 28Н никогда не вызывается DOS во время выполнения критического участка. Вы можете использовать это средство для защиты от сбоев вашей TSR-программы. Хотя здесь не представлено никаких примеров программ, но предлагаются следующие общие соображения по этому вопросу.
Главное отличие, которое вы должны иметь ввиду при использовании прерывания 28Н, заключается в способе активации прикладной части вашей TSR-программы. Когда вызывается прерывание 28Н, прикладная часть TSR-программы не может больше активироваться через программу обработки нажатий клавиш. Вместо этого программа обработки нажатий клавиш при нажатии "горячей клавиши" просто устанавливает флаг (в дальнейшем именуемый is-hotkey). Перед тем, как прикладная часть вашей TSR-программы может быть вызвана, вы должны создать новый обработчик прерывания 28Н, который будет проверять, установлен флаг is-hotkey или нет. Если установлен, то прикладная часть активируется, сбрасывая при этом флаг is-hotkey. При этом вы обязательно должны не просто изменить первоначальное содержимое вектора прерывания 28Н, а напротив, сохранить его и вызывать исходное прерывание 28Н из вашего обработчика 28-го прерывания.
Если вы собираетесь продавать ваши TSR-программы, то должны обязательно использовать прерывание 28Н (хотя по нему и нет документации), поскольку оно позволяет избежать случайных прерываний DOS во время выполнения критических участков.
Тело главной программы
После того как вы научитесь разрабатывать собственные видеоигры, вы поймете, что все они имеют одну главную общую деталь - программу, управляющую игрой. Алгоритм таких программ довольно-таки сходен для различных видеоигр. Главная программа генерирует движение объектов по экрану, контролирует нажатие клавиш пользователем и реагирует на них, проверяет допустимость заданных перемещений, подсчитывает набранные очки и отображает счет, последовательно вызывает функции отображения объектов на экране.
int directx,directy; /* направление */
main()
union k
char c[2];
int i;
key;
int deltax=0,deltay=0;
int swaph=0,swapc=0;
int it=COMPUTER;
long htime,ctime,starttime,curtime; /* таймер счета */
int count;
mode(4); /* установка 4 режима графики CGA/EGA */
palette(0); /* палитра 0 */
load_pic(); /* ввод игрового поля */
time(&starttime); /* установка времени */
htime=ctime=0;
display_object(human,4,1);
display_object(computer,4,3);
count=0;
/* главный цикл игры */
do
/* вычисление текущего счета */
time(&curtime);
if (it==COMPUTER) htime+=curtime-starttime;
else ctime+=curtime-starttime;
time(&starttime);
show_score(it,htime,ctime);
if (bioskey(1)) /* если нажата клавиша */
directx=directy=IDLE; /* устанавливает
направление перемещения */
key.i = bioskey(0);
deltax=0;deltay=0;
if(!key.c[0]) switch(key.c[1])
case 75: /* влево */
deltay= -1;
directy=LEFT;
break;
case 77: /* вправо */
deltay=1;
directy=RIGHT;
break;
case 72: /* вверх */
deltax= -1;
directx=UP;
deltax= -1;
directx=UP;
break;
case 80: /* вниз */
deltax=1;
directx=DOWN;
break;
case 71: /* вверх и влево */
deltay= -1;
directy=LEFT;
deltax= -1;
directx=UP;
break;
case 73: /* вверх и вправо */
deltay=1;
directy=RIGHT;
deltax= -1;
directx=UP;
break;
case 79: /* вниз и влево */
deltay= -1;
directy=LEFT;
deltax=1;
directx=DOWN;
break;
case 81: /* вниз и вправо */
deltay=1;
directy=RIGHT;
deltax=1;
directx=DOWN;
break;
/* смена типа спрайта игрока */
if (!swaph) display_object(human,4,1);
else display_object(human2,4,1);
if (is_legal(human,deltax,deltay,4))
update_object(human,deltax,deltay,4);
update_object(human2,deltax,deltay,4);
/* проверяет: попался ли убегающий */
if (!count && tag(human,computer))
it= !it; /* смена амплуа */
count=6;
swaph= !swaph; /* смена фигуры имитирующей бег */
/* изображение "человека" в новой позиции */
if (!swaph) displey_object(human,4,1);
else displey_object(human2,4,1);
if (!swapc) display_object(computer,4,3);
else display_object(computer2,4,3);
/* генерация движения спрайта компьютера */
if (it==COMPUTER)
it_comp_move(computer,computer2,human,4);
else not_it_comp_move(computer,computer2,directx,directy,4);
if (!count && tag(human,computer))
it= !it;
count=6;
/* компьютер догоняет; изменение координаты Х на 2
так, чтобы быстрей стать догоняемым
*/
if (is_legal(computer, 2, 0, 4))
update_object(computer, 2, 0, 4); update_object(computer2, 2, 0, 4);
else
update_object(computer, -2, 0, 4);
update_object(computer2, -2, 0, 4);
swapc = !swapc; /* заменить тип спрайта */
/* вывод на экран спрайта компьютера */
if(!swapc) display_object(computer, 4, 3);
else display_object(computer2, 4, 3);
if(count) count--;
while (key.c[0] !='q' && htime<999 && ctime<999);
mode(2);
if(ctime>htime)
printf("Компьютер выиграл!");
else
printf("Вы победили!");
В теле главной программы экран терминала устанавливается в 4-й графический режим, выбирается палитра 0 и инициализируются переменные счета игры. После этого оба спрайта отображаются в своих исходных позициях.
Переменная htime содержит значение счета игрока, а ctime - компьютера. Переменные swapc и swaph предназначены для указания типа спрайта. Переменные deltax и deltay содержат изменения значений координат после очередного нажатия клавиш игроком. Глобальные переменные directx и directy содержат координаты спрайта, управляемого игроком. Значения этих величин используются компьютером для генерации перемещения своего спрайта. Переменнаяпризнак it содержит информацию о том, кто в данный момент находится в режиме догоняющего. Она может принимать одно из двух значений, описанных в макроопределении #define: COMPUTER или HUMAN.
Главная программа работает циклически. На экране
отображается текущий счет. После этого проверяется, была ли
нажата какая-либо клавиша. Если клавиша была нажата, то
определяется ее код и производится заданное перемещение спрайта
игрока. Обратите внимание на то, что в данной программе нет
режима ожидания нажатия клавиши игроком. Поэтому, не смотря на
то, что игрок не нажимает клавиш, компьютер продолжает свою
работу, и спрайт игрока продолжает указанное перемещение до тех
пор, пока не будет нажата другая клавиша. Такое движение спрайта
обеспечивает достаточно высокую динамичность игры.
После очередного перемещения спрайта игрока, компьютер генерирует движение собственного спрайта, если это необходимо. Обратите внимание на то, что для генерации движения спрайта используются различные функции в зависимости от того в режиме догоняющего или догоняемого находится спрайт. При реализации очередного перемещения собственного спрайта, компьютер так же проверяет его корректность.
Рассмотрим некоторые программы, используемые в этой игре.
Теория всплывающих окон.
Всплывающее окно представляет собой часть экрана, используемую для специальных целей. Перед появлением окна текущее содержимое экрана сохраняется и лишь после этого производится отображение окна.
При завершении программы, использующей данное окно, это окно удаляется, а первоначальное содержимое экрана восстанавливается. (Данный процесс аналогичен появлению всплывающих меню). Вполне возможно одновременное отображение на экране нескольких окон.
Хотя это и не обязательно, но все хорошие программы, работающие с окнами, позволяют интерактивно изменять размеры и позицию окна на экране. Следовательно, оконные функции допускают, что окно не всегда будет находиться в одном и том же месте и иметь один и тот же размер.
Разработка функций, управляющих окнами, является сложной задачей из-за необходимости обеспечения запрета для прикладной программы осуществлять вывод за границы окна. Поскольку размеры окна могут изменяться без "сообщения" об этом прикладной программе, то именно функции управления окнами, а не прикладная программа, должны предохранить от выхода за границы. Следовательно, все обычные функции Си, осуществляющие ввод/вывод на консоль (например, printf() и lets() ) , не могут быть использованы и должны быть заменены на функции, ориентированные на ввод/вывод с использованием окон.
Теория использования окон крайне проста. Каждая отдельная задача программы использует свое собственное окно. При запуске задачи активируется и ее окно. При завершении работы задачи - ее окно удаляется. Если же задача прерывается, то, хотя ее работа приостанавливается, но ее окно не удаляется, а инициируемая прерыванием задача, просто создает свое окно поверх предыдущего. (Обычно те задачи, которые не используют окон, очищают экран. Это приводит к рассеиванию внимания пользователя. В то же время при использовании окон подобные прерывания выглядят как временные паузы).
Чтобы понять, как окна могут быть наиболее эффективно использованы, предположим, что вы разработали текстовый редактор, включающий ряд дополнительных функций, таких как "записная книжка", калькулятор с четырьмя математическими операциями и конвертер чисел из десятичного в шестнадцатиричное представление. Так как все эти функции в действительности не относятся к операциям редактирования текста, то их реализация тесно переплетается с концепцией всплывающих окон. Таким образом получается, что использование какой-либо из вспомогательных функций лишь приостанавливает основную задачу (редактирование), а не прерывает ее.
наверх
Видеорежимы и цветовая палитра
Перед использованием каких-либо функций графики компьютер должен быть переведен в соответствующий видеорежим. Для компьютеров типа IBM PC это означает, что должны быть выбраны подходящие режим и палитра.
В таблице 4-1 приведены различные видеорежимы, в которых могут работать компьютеры IBM PC. Для функций, приведенных в этой главе, требуется 4 видеорежим, предполагающий использование цветного графического дисплея с размерностью экрана 320 на 200. Хотя адаптер EGA поддерживает и режимы с расширенной разрешающей способностью дисплея, 4 видеорежим выбран в качестве базового для разработки и использования функций графики в связи с тем, что он поддерживается как адаптером EGA, так и CGA. Использование особых режимов EGA требует только изменения функций записи точки (смотри книгу по использованию графики в EGA "Advance Grafics in C" Nelson Jobson Osborn/McGrow-Hall, 1987). Вам необходимо запомнить, что во всех кодах верхний левый угол имеет координаты 0,0.
BIOS-прерывание 16, функция 0, устанавливает видеорежим и используется в функции mode(), текст которой приведен ниже. Но прежде чем вы ознакомитесь с ней, предлагаем вам внимательно изучить все видеорежимы, поддерживаемые соответствующими адаптерами. Для этого обратитесь к таблице 4-1.
Таблица 4-1 Режимы терминала для машин IBM PC
Режим
Тип Размерность Адаптер экрана
0
1
2
3
4
5
6
7
8
9
10
13
14
15
-----
алфавитно-цифровой, ч/б 40х25 CGA, EGA
алфавитно-цифровой, 16 цв. 40х25 CGA, EGA
алфавитно-цифровой, ч/б 80х25 CGA, EGA
алфавитно-цифровой, 16 цв. 80х25 CGA, EGA
графический, 4 цвета 320х200 CGA, EGA
графический, 4 серых тона 320х200 CGA, EGA
графический, ч/б 640х200 CGA, EGA
алфавитно-цифровой, ч/б 80х25 монохромный
графический, 16 цветов 160х200 PCjr
графический, 16 цветов 320х200 PCjr
графический, 4 цвета-PCjr 640х200 PCjr, EGA
16 цветов-EGA
графический, 16 цветов 320х200 EGA
графический, 16 цветов 640х200 EGA
графический, 4 цвета 640х350 EGA
----------------------------------------------------------
А
теперь приведем текст функции mode().
/* Установка видеорежима */
void mode(mode_code)
int mode_code;
union REGS r;
r.h.al = mode_code;
r.h.ah = 0;
int86(0x10,&r,&r);
В 4- ом режиме доступны две палитры (набора цветов). Каждая палитра определяет четыре цвета, отображаемые на экране терминала. В IBM PC палитра 0 определяет желтый, зеленый и красный цвета, палитра 1 определяет белый, ярко-красный (пурпурный) и голубой цвета. Для каждой палитры четвертым цветом является цвет фона, который обычно черный. BIOS-прерывание 16, функция 11, устанавливает палитру.
Функция pallet(), приведенная ниже, устанавливает номер палитры, который задается в качестве значения ее аргумента:
/* Установка палитры */
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);
Виртуализация и реальный экран
Библиотека подпрограмм поддержки "мыши" фирмы Microsoft работает с виртуальным экраном в виде массива точек растра (массива из единиц минимального изображения, цвет и яркость которых можно задать независимо от остального изображения), который может отличаться от реального экрана. При перемещении "мыши" счетчики местоположения курсора изменяют свое значение. Перед отображением курсора виртуальные координаты курсора преобразуются в координаты реального экрана. В видеорежимах 6, 14, 15 и 16 это преобразование осуществляется один к одному. В режимах 4 и 5 не каждая точка виртуальной горизонтальной позиции преобразуется в координты реального экрана, а через одну. вы должны обратить внимание на этот факт, так как программа рисования, к которой будет добавлен интерфейс с "мышью" и которая будет рассматриваться в данной главе, работает именно в 4 графическом режиме.
<
Восстановление экрана
Восстановление экрана после сделанного выбора из меню, заключается просто в записи предварительно запомненной информации назад в видео память. Для того, чтобы сделать это, используйте прерывание 16, функцию 9, которая требует, чтобы символ был в AL, аттрибут в BL, видео страница в ВН, а количество записываемых символов в CX (в нашем случае 1). Функция restore_video(), описанная здесь, помещает информацию из буфера, на который указывает buf_ptr, на экран, заданный начальными и конечными координатами X и Y.
/* восстановление части экрана */
int startx,endx,starty,endy;
union REGS r;
register int i,j;
for(i=starty;ifor(j=startx;j goto_xy(j,i);
r.h.ah=9; /* функция записи символа */
r.h.bh=0; /* видео страница */
r.x.cx=1; /* число повторений символа */
r.h.al=*buf_ptr++; /* символ */
r.h.bl=*buf_ptr++; /* атрибут */
*buf_ptr++ = int86(0x10,&r,&r);
Как и другие функции поддержки меню, измененная restore_video показанная здесь, преобразована для работы с фреймами. Поэтому функции restore_video() теперь передается только номер меню, что делает интерфейс более очевидным.
/* восстановление части экрана */
int num;
Вращение обьекта
Хотя функция rotate_point(), вычисляющая требуемые значения координат X и Y при вращении точки, уже была нами рассмотрена. Однако, она не может быть использована для вращения (поворотов) объектов. Для этого необходима другая функция. Под объектом здесь и далее будем понимать набор сегментов прямых отрезков. Координаты крайних точек каждого отрезка содержатся в двумерном массиве чисел с плавающей точкой. Каждая строка массива содержит начальные и конечные координаты данного отрезка. Это означает, что первая размерность массива представляет собой количество отрезков, входящих в состав объекта, а вторая размерность будет равна 4 (число координат крайних точек отрезка). Например, массив, приведенный ниже
double object [10][4];
определяет объект, состоящий из 10 отрезков.
Как правило, массив организуется так, как показано на рисунке 4-3.
Первый Второй -------->
индекс индекс
| 0 1 2 3
|
|
V
0 start_X1 start_Y1 end_X1 end_Y1
1 start_X2 start_Y2 end_X2 end_Y2
2 start_X3 start_Y3 end_X3 end_Y3
3 start_X4 start_Y4 end_X4 end_Y4 . .
. .
. .
n start_Xn start_Yn end_Xn end_Yn
Рис. 4-3. Условная организация массива.
Определить объект - это значит разместить в массиве координаты начальных и конечных точек отрезков, составляющих объект. Например, если объект представляет собой прямоугольник вида:
0.0-------------------0.10
| |
| |
| |
| |
10.0-------------------10.10
то в массив, определяющий данный прямоугольник, заносятся
следующие числа:
object[0][0] = 0; object[0][1] = 0;
object[0][0] = 0; object[0][3] = 10;
object[1][0] = 0; object[1][1] = 10;
object[1][0] = 10; object[1][3] = 10;
object[2][0] = 10; object[2][1] = 10;
object[2][0] = 10; object[2][3] = 0;
object[3][0] = 10; object[3][1] = 0;
object[3][0] = 0; object[3][3] = 0;
После того, как объект определен, вы можете вращать его, используя функцию rotate_object(), приведенную ниже, по часовой стрелке (клавиша) или в противоположную сторону (клавиша ).
/* Вращение заданных объектов */
void rotate_object(ob, theta, x, y, sides)
double ob[][4]; /* описание объекта */
double theta; /* угол поворота в радианах */
int x, y;
int sides;
register int i, j;
double tempx, tempy;
char ch;
for(;;)
ch = getch(); /* ввод признака направления вращения */
switch(tolower(ch))
case 'l': /* вращение против часовой стрелки */
theta = theta < 0 ? -theta : theta;
break;
case 'r': /* вращение по часовой стрелке */
theta = theta > 0 ? -theta : theta;
break;
default: return;
for(j=0; j<=sides; j++) /* стирание старых линий */
line((int) ob[j][0], (int) ob[j][1],
(int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0],
&ob[j][1], x, y);
rotate_point(theta, &ob[j][2], &ob[j][3], x, y);
line((int) ob[j][0], (int) ob[j][1],
(int) ob[j][2], (int) ob[j][3], 2);
Как показано в описании параметров функции rotate_object(), вращение осуществляется вокруг центра, заданного координатами X и Y, на угол, величина которого задана параметром theta в радианах. Минимальное значение параметра theta равно 0.01 радиан. Заметим, что объект сначала стирается из старой области размещения, а затем перерисовывается вновь. Если это условие не может быть выполнено, то экран окрашивается в голубой цвет. Необходимым условием выполнения программы rotate_object() является обязательное задание параметра sides.
Приведенная ниже функция display_object() не имеет отношения к вращению объектов, но она может быть полезна при работе с объектами. Она рисует на экране объекты, определенные в массиве ob.
/* отображение объекта на экране */
void display_object(ob, sides)
double ob[][4];
int sides;
register int i;
for(i=0; i
line((int)ob[i][0], (int)ob[i][1],
(int)ob[i][2], (int)ob[i][3], 2);
В качестве иллюстрации удобства использования функций вращения объектов ниже приводятся программы вращения изображения дома. На рисунке 4-4 показано изображение на экране терминала дома при различных углах поворота вокруг собственного центра. Прямоугольник, обрамляющий изображение вращаемого дома, поможет вам правильно оценить масштаб и перспективу.
_________________________________________________________________
Прим. пер. Рисунок 4- 4 не может быть воспроизведен имеющимися средствами.
_________________________________________________________________
Рис. 4-4. Вращение объекта.
/* Пример вращения изображения объекта с использованием
адаптера CGA/EGA в 4 графическом режиме
*/
#include "dos.h"
#include "stdio.h"
#include "math.h"
void mode(), line(), mempoint(), palette();
void rotate_point(), rotate_object(), display_object();
/* массив house определяет изображение дома */
double house[][4] =
/* startx, starty, endx, endy */
120, 120, 120, 200, /* дом */
120, 200, 80, 200,
80, 120, 80, 200,
80, 120, 120, 120,
60, 160, 80, 120, /* крышa*/
60, 160, 80, 200,
120, 155, 100, 155, /* двери*/
100, 155, 100, 165,
100, 165, 120, 165,
90, 130, 100, 130, /* окна */
90, 130, 90, 140,
100, 130, 100, 140,
90, 140, 100, 140,
90, 180, 100, 180,
90, 180, 90, 190,
100, 180, 100, 190
;
main()
union k
char c[2];
int i;
key;
mode(4); /* режим = 4 */
palette(0); /* палитра = 0 */
/* рисунок рамки,обрамляющей дом */
line (30, 70, 30, 260, 2);
line (160, 70, 160, 260, 2);
line (30, 70, 160, 70, 2);
line (30, 260, 160, 260, 2);
display_object(house, 17);
getchar();
rotate_object(house, 0.025, 90, 160, 17);
mode(3);
/* Выбор палитры */
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(start_x, start_y, endx, endy, color)
int start_x, start_y, endx, endy, color;
register int t, distance;
int x=0, y=0, delta_x, delta_y;
int incx, incy;
/* вычисление приращений по x и по y */
delta_x = endx-start_x;
delta_y = endy-start_y;
/* вычисление признаков направлений отрезка */
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; t++)
mempoint(start_x, start_y, color);
x+=delta_x;
y+=delta_y;
if(x>distance)
x-=distance;
start_x+=incx;
if(y>distance)
y-=distance;
start_y+=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;
/* вращение точки вокруг центра с координатами
в x_org и y_org, на угол theta */
void rotate_point(theta,x,y,x_org,y_org)
double theta,*x,*y;
int x_org,y_org;
double tx,ty;
/* нормализация X и Y к начальному адресу */
tx=*x-x_org;
ty=*y-y_org;
/* вращение */
*x=tx*cos(theta)-ty*sin(theta);
*y=tx*sin(theta)-ty*cos(theta);
/* возвращение значений координат */
*x+=x_org;
*y+=y_org;
/* Вращение заданных объектов */
void rotate_object(ob, theta, x, y, sides)
double ob[][4]; /* описание объекта */
double theta; /* угол поворота в радианах */
int x, y;
int sides;
register int i, j;
double tempx, tempy;
char ch;
for(;;)
ch = getch(); /* ввод признака направления вращения */
switch(tolower(ch))
case 'l': /* вращение против часовой стрелки */
theta = theta < 0 ? -theta : theta;
break;
case 'r': /* вращение по часовой стрелке */
theta = theta > 0 ? -theta : theta;
break;
default: return;
for(j=0; j<=sides; j++) /* стирание старых линий */
line((int) ob[j][0], (int) ob[j][1],
(int) ob[j][2], (int) ob[j][3], 0); rotate_point(theta, &ob[j][0],
&ob[j][1], x, y);
rotate_point(theta, &ob[j][2], &ob[j][3], x, y);
line((int) ob[j][0], (int) ob[j][1],
(int) ob[j][2], (int) ob[j][3], 2);
/* отображение объекта на экране */
void display_object(ob, sides)
double ob[][4];
int sides;
register int i;
for(i=0; i
line((int) ob[i][0], (int) ob[i][1],
(int) ob[i][2], (int) ob[i][3], 2);
Вращение точки в плоскости экрана
Вращение точки в плоскости экрана (двумерном пространстве) представляет собой довольно простую задачу в декартовой системе координат. Вы можете вспомнить из курса аналитической геометрии, что вращение точки вокруг центра на угол theta, описывается формулой:
new_x = old_x * cos(theta) - old_y * sin(theta)
new_y = old_x * sin(theta) - old_y * cos(theta)
Единственной сложностью при употреблении этих формул для графических дисплеев будет являтся тот факт, что экран дисплея не является декартовым пространством. Декартовы оси определяют 4 квадранта, как это показано на рисунке 4-2. Однако экран терминала представляет собой один из квадрантов, оси X и Y в котором перевернуты. Для решения этой проблемы необходимо определить новый центр и привести в соответствие координаты X и Y экрана и координаты осей декартова пространства. Любая точка на экране может быть использована в качестве центра, но обычно центр определяется как можно ближе к центру объекта, который мы собираемся вращать. Функция rotate_point(), приведенная ниже, вычисляет величину новых значений X и Y для заданного угла вращения.
/* Вращение точки вокруг центра с координатами
x_org и y_org на угол theta */
void rotate_point(theta,x,y,x_org,y_org)
double theta,*x,*y;
int x_org,y_org;
double tx,ty;
/* нормализация X и Y к начальному адресу */
tx=*x-x_org;
ty=*y-y_org;
/* вращение */
*x=tx*cos(theta)-ty*sin(theta);
*y=tx*sin(theta)-ty*cos(theta);
/* возвращение значений координат */
*x+=x_org;
*y+=y_org;
Заметим, что rotate_point() изменяет параметры X и Y путем присвоения им требуемого значение для получения угла вращения, заданного переменной theta. Угол вращения задается в радианах.
Декартова система координат Y
II ^ I
+,- | +,+
|
|0.0
------------------- X
|
-,- | -,+
|
III ј IV
0.0 Графический экран ----------- Y
|
| +,+
|
ј
X
Рис. 4-2. Декартовы координаты на графическом экране.
Ввод информации с помощью "мыши" в программе рисования.
Итак, вы теперь готовы к тому, чтобы разработать подпрограммы, позволяющие с помощью "мыши" управлять программой рисования. Интерфейс с "мышью" может быть добавлен в существующие подпрограммы управления, что, естественно, будет более предпочтительно, чем разработка новых подпрограмм или модификация существующих.
Такой путь выгоден прежде всего тем, что функциональны возможности клавиш управления курсором сохраняются на все 100 процентов, и пользователь в каждой конкретной ситуации может выбрать наиболее подходящее устройство для ввода данных (клавиатура или "мышь").
Прежде, чем "мышь" будет включена как устройство ввода программу рисования, необходимо разработать две подпрограммы,
учитывающие специфику "мыши". Первая подпрограмма - wait_on()
позволяет реализовать процесс ожидания отпускания (освобождения)
специфицированной клавиши пользователем. Анализ подобного рода
имеет весьма большое значение, так как соответствующие прерывания
генерируются постоянно, пока клавиша не нажата. (Однако
невозможно обеспечить такое мгновенное нажатие на клавишу, в
результате которого сформировалось бы лишь одно прерывание). Во
многих подпрограммах наоборот важно избежать такой ситуации и
поэтому в них каждое нажатие на клавишу генерирует (точнее будет
сказать кажется, что генерирует) только одно прерывание за то
время, пока клавиша нажата. В соответствии с этим, ваша программа
должна обращаться к фунции wait_on(), представленной ниже,
непосредственно перед выполнением и после того, когда нажата
соответствующая клавиша.
/* Возвращает 1, если специфицированная клавиша не нажата */
void wait_on(button)
int button;
if(button== LEFTB)
while(leftb_pressed());
else
while(rightb_pressed());
Макросы LEFTB и RIGHTB, представленные ниже, используются при обращении к wait_on().
#define LEFTB 1
#define RIGHTB 2
Второй необходимой вам функцией является mouse_menu(). Эта функция отображает однострочное меню и позволяет пользователю осуществлять выбор из него элементов путем перемещения "мыши" на плоскости (и, соответственно, курсора "мыши" по экрану) и нажатия любой клавиши "мыши". Эта функция может работать только в 4
графическом режиме. Функции передается двумерный массив символов,
который содержит элементы меню (которые может выбрать
пользователь), значение каждого элемента меню (его код),а также
координаты Х и У отображения меню на экране. Массив символов
определяет максимальную длину каждого элемента меню в 19
символов. Функция возвращает в качестве результата номер
выбранного пользователем элемента меню, начиная с 0, или -1, если
пользователь не выбрал ни один из элементов меню. Когда функция
начинает свою работу, то она вначале вычисляет длину в пикселах
(элементах растра) каждого элемента меню, после чего резервирует
пространство по начальной и конечной точке растра для каждого
элемента меню, одновременно запоминая эту информацию в массиве
len. (В четвертом графическом режиме каждый символ имеет высоту в
8 точек растра и ширину в 16 точек растра.) После этих вычислений, функция переходит в состояние ожидаения прерывания от клавиш "мыши". При этом осуществляется анализ нажата или нет клавиша "мыши" в момент нахождения ее курсора в области меню, и, если да, то в месте расположения какого элемента меню. Функция mouse_menu() приведена ниже.
/* Отображает однострочное меню для "мыши" и возвращает
код выбранного пользователем элемента меню */
mouse_menu(count, item, x, y)
int count; /* количество элементов меню */
char item[][20]; /* элементы меню */
int x, y; /* позиции отображения */
int i, len[MENU_MAX][2], t;
int mousex, mousey;
goto_xy(x, y);
t = 0;
for(i=0; i
printf("%s ", item[i]);
len[i][0] = t;
/* каждый символ имеет ширину в 16 точек растра */ len[i][1] = t + strlen(item[i])*16;
t = len[i][1] + 32; /* добавляется два пробела между элементами меню */
/* ожидание выбора пользователем элемента меню */
do
if(rightb_pressed() || leftb_pressed()) break;
while(!kbhit());
/* ожидание нажатия клавиши */
while(rightb_pressed() || leftb_pressed());
/* получить текущую позицию курсора "мыши" */
mouse_position(&mousex, &mousey);
/* анализируется, находится ли курсор в пределах меню */
if(mousey>=0 && mousey<8) /* символ имеет высоту
8 точек растра */ for(i=0; i
if(mousex>len[i][0] && mousex
return i;
return i;
returtn -1; /* выбор из меню не осуществлялся */
Ввод выбора пользователя
Как утверждалось, пользователь может вводить выбор одним из двух способов. Во-первых с помощью клавиш СТРЕЛКА ВНИЗ и СТРЕЛКА ВВЕРХ может переместить освещение на строку и нажать Ввод для ее выбора, (Обычно освещение строки выполняется в инверсном режиме.) Освещенную строку также можно передвигать пробелом. Второй способ это нажатие клавиши, связанной с выбором. Функция get_resp(), показанная здесь, достигает этих целей.
/* ввести выбор пользователя */
int x,y,count;
char *keys;
y++;
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<0) arrow_choice=count-1;
/* подсветить выбранную опцию */
goto_xy(x+arrow_choice,y);
write_video(x+arrow_choice,y,menu[arrow_choice],REV_VID);
Kогда get_resp() начинает выполняться, освещается первое значение меню. Макроопределение REV_VID определяется везде, как 70Н, а NORM_VID как 7Н. Клавиша ESCAPE используется для окончания работы с меню. Значение ESC 27. После этого программа входит в цикл, ожидающий действий пользователя. Она использует функцию bioskey() для того, чтобы дождаться нажатия клавиши, а затем для считывания с этой клавиши. Функция bioskey() специфична для Турбо Си. Если вы используете другой транслятор, вы можете использовать следующую версию функции.
/* эмуляция части функции bioskey Турбо Си */
int c;
get_key()
void write_video(x,y,p,attrib)
char *p;
union REGS r;
register int i,j;
for(i=y; *p; i++)
goto_xy(x,i);
r.h.ah=9; /* функция записи символа */
r.h.bh=0; /* видео страница */
r.x.cx=1; /* число повторений символа */
r.h.al=*p++; /* символ */
r.h.bl=attrib; /* атрибут */
int86(0x10,&r,&r);
Функция is_in() возвращает позицию "горячей" клавиши в строке. Если пользователь нажал не ключевую клавишу, то is_in возвращает 0.
is_in(s,c)
register int i;
for(i=0; *s; i++)
if(*s++ == c) return i+1;
return 0;
Вычерчивание линий
Функции вычерчивания линий являются основными подпрограммами графики и используются для отображения линий в заданном цвете путем задания ее начальных и конечных координат. В то время, как изображение вертикальных и горизонтальных линий не представляет особого труда, более трудной задачей является создание функций, которые рисуют линии вдоль диагоналей. Например, какие точки составляют линию, вычерчиваемую от точки с координатами 0,0 до точки с координатами 80,120?
Один из подходов к разработке функций вычерчивания линий использует отношение между смещением по координатам X и Y. Чтобы показать этот подход в действии, проведем линию между точками с координатами 0,0 и 5,10. Смещение по X равно 5, а по Y - 10. Отношение равно 1/2. Оно будет использоватся при определении коэффициента зависимости, по которому должны меняться координаты X и Y при изображении линий. В данном случае это означает, что приращение координаты X составляет половину величины изменения координаты Y. Начинающий программист часто использует этот метод при разработке функций вычерчивания линий. Хотя такой подход математически верен и прост для понимания, для его правильной работы и для того, чтобы избежать серьезных ошибок округления, необходимо использовать математические операции с числами с плавающей точкой. Это означает, что функции вычерчивания линий будут работать довольно медленно, если в систему не будет включен математический сопроцессор (например - Intel 8087). По этой причине этот метод используется довольно редко.
Наиболее общий метод изображения линий включает
использование алгоритма Брезенхама. Хотя основой в нем служит
также отношение между расстояниями по координатам X и Y, в данном
случае не требуется выполнять деление или вычисление чисел с
плавающей точкой. Вместо этого, отношение между значениями
координат X и Y представляется косвенным образом через серии
сложений и вычитаний. Основной идеей алгоритма Брезенхама,
является регистрация средних значений погрешностей между
идеальным положением каждой точки и той позицией на экране
дисплея, в которой она действительно отображается. Погрешность
между идеальным и действительным положением точки возникает ввиду
ограниченных возможностей технических средств. Фактически не
существует дисплеев с бесконечно большой разрешающей
способностью, и, следовательно, действительное положение каждой
точки на линии требует наилучшей аппроксимации. В каждой итерации
цикла вычерчивания линии вызываются две переменные xerr и yerr,
которые увеличиваются в зависимости от изменения величин
координат X и Y соответственно. Когда значение погрешности
достигает определенного значения, оно вновь устанавливается в
исходное положение, а соответствующий счетчик координат
увеличивается. Этот процесс продолжается до тех пор, пока линия
не будет полностью вычерчена. Функция line(), приведенная ниже,
реализует этот метод. Вы должны изучать ее до тех пор, пока не
поймете механизма выполнения всех ее операций. Заметим, что в ней
используется функция mempoint(), разработанная ранее для
отображения точки на экране терминала.
/* Вычерчивание линии заданного цвета с использованием
алгоритма Брезенхама */
void line(startx,starty,endx,endy,color)
int startx,starty,endx,endy,color;
register int t,distаnce;
int xerr=0,yerr=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);
xerr+=delta_x;
yerr+=delta_y;
if (xerr>distance)
xerr-=distance;
startx+=incx;
if (yerr>distance)
yerr-=distance;
starty+=incy;
Вычерчивание окружностей
Самым быстрым и легким способом вычерчивания окружностей является способ, основанный на использовании опять таки алгоритма Брезенхама, похожего на одноименный алгоритм вычерчивания линий. Данный метод также не требует вычислений чисел с плавающей точкой, кроме вычисления коэффициента сжатия, поэтому он обеспечивает достаточное быстродействие. По существу, алгоритм основан на приращении координат X и Y на величину погрешности между ними. Значение погрешности содержится в переменой delta. Функция plot_circle() выполняет запись точек по окружности. Переменная asp_ratio является глобальной, т.к. она используется как в функции circle(), так и в функции plot_circle(). Эта переменная может быть полезна с точки зрения возможности установления ее значения вне функции circle() и дальнейшего использования внутри функции. Путем изменения значения этой переменной вы можете рисовать эллипсы. Параметрами функции circle() является центр окружности, ее радиус и цвет. Тексты функций приведены ниже.
double asp_ratio;
/* Вычерчивание окружности с использованием алгоритма
Брезенхама */
void circle(x_center,y_center,radius,color_code)
int x_center,y_center,radius,color_code;
register x,y,delta;
asp_ratio=1.0; /* это число может меняется в различных
случаях */
y=radius;
delta=3-2*radius;
for (x=0;xplot_circle(x,y,x_center,y_center,color_code);
if (delta<0)
delta+=4*x+6;
else
delta+=4*(x-y)+10;
y--;
x++;
x=y;
if (y) plot_circle(x,y,x_center,y_center,color_code);
/* Функция печатает точки, определяющие окружность */
void plot_circle(x,y,x_center,y_center,color_code)
int x,y,x_center,y_center,color_code;
int startx,starty,endx,endy,x1,y1;
starty=y*asp_ratio;
endy=(y+1)*asp_ratio;
startx=x*asp_ratio;
endx=(x+1)*asp_ratio;
for (x1=startx;x1mempoint(x1+x_center,y+y_center,color_code);
mempoint(x1+x_center,y_center-y,color_code);
mempoint(x_center-x1,y+y_center,color_code);
mempoint(x_center-x1,y_center-y,color_code);
for (y1=starty;y1mempoint(y1+x_center,x+y_center,color_code);
mempoint(y1+x_center,y_center-x,color_code);
mempoint(x_center-y1,x+y_center,color_code);
mempoint(x_center-y1,y_center-x,color_code);
Закрашивать окружность можно путем повторного вызова функции circle() с заданием все более и более меньшего радиуса. Этот способ используется в функции fill_circle(), текст которой приведен ниже.
/* Закрашивание окружности путем повторного вызова
circle() с уменьшением радиуса */
void fill_circle(x,y,r,c)
int x,y,r,c;
while (r)
circle(x,y,r,c);
r--;
Выражения
Хотя выражения могут быть составлены в принципе из любых типов данных, в этой главе мы будем иметь дело только с числовыми выражениями. Будем считать, что для наших целей числовые выражения могут строится из следующих элементов:
- числа
- операторы + - / * ^ % = () <> ; ,
- скобки
- переменные
Символ '^' означает экспоненту, а символ '=' используется как оператор присваивания, а также как знак равенства в операциях сравнения. Элементы выражения можно комбинировать согласно правилам алгебры.
Вот некоторые примеры выражений:
7-8 (100-5)*14/6
a+b-c
10^5
a=7-b
Символы '=', '>', '<', ',', ';' являются операторами, они не могут использоватся в выражениях функций и конструкциях типа IF, PRINT и операторах присваивания. (Заметим, что анализатор языка Cи должен обрабатывать и эти операторы в различных их комбинациях).
Что касается языка BASIC, старшинство операторов не определено. В процессе работы синтаксический анализатор присваивает операторам следующие приоритеты:
высший ()
^
* / %
+ -
низший =
Операторы равного приоритета выполняются слева направо.
Синтаксис языка SMALL BASIC предполагает, что все переменные обозначаются одной буквой. Это позволяет оперировать в программе двадцати шестью переменными (буквы от A до Z). Хотя интерпретаторы языка BASIC поддерживают обычно большее число символов в определении переменной, (например, переменные типа Х27), но для простоты изложения основных принципов построения интерпретаторов наш интерпретатор языка SMALL BASIC этого делать не будет. Будем считать также, что переменные разных регистров не отличаются друг от друга и, поэтому, переменные "a" и "A" будут трактоваться как одна и та же переменная. Условимся, что все числа являются целыми, хотя вы без особого труда можете написать
программы для манипулирования другими типами чисел. Хотя
символьные переменные в нашей версии языка и не поддерживаются,
будем считать, что возможен вывод ограниченных символьных
констант на экран в виде различных сообщений.
Итак, будем строить синтаксический анализатор исходя из перечисленных выше допущений. Теперь давайте рассмотрим такое базовое понятие теории синтаксического анализа как лексема.
Высвечивание меню.
Для того, чтобы высветить меню, необходимо помнить, что popup получает указатель на массив указателей на строки. Для высвечивания отдельных строк вы просто индексируете указатель, как массив. Каждый элемент в массиве является указателем на соответствующий элемент меню. Следующая функция display_menu() высвечивает каждый элемент меню.
/* высвечивание меню на своем месте */
char *menu[];
register int i;
"первая строка",
Высвечивание рамки
Если нужна рамка, то можно воспользоваться нижеприведенной программой для вывода рамки вокруг меню с заданными координатами левого верхнего и правого нижнего углов. Она использует символы, которые являются частью стандартного набора символов на машинах, совместимых с IBM. Если вы хотите, вы можете выбрать другие.
void draw_border(startx,starty,endx,endy)
register int i;
for(i=startx+1;igoto_xy(i,starty);
putchar(179);
goto_xy(i,endy);
putchar(179);
for(i=starty+1;igoto_xy(startx,i);
putchar(196);
goto_xy(endx,i);
putchar(196);
goto_xy(startx,starty); putchar(218);
goto_xy(startx,endy ); putchar(191);
goto_xy(endx ,starty); putchar(192);
goto_xy(endx ,endy ); putchar(217);
Загрузка удаленных файлов в узел сети
Для того, чтобы pабочая станция иницииpовала тpебования на получение файла из файлового сервера и его загpузку, тpебуется вызов специальной пpогpаммы. Эта пpогpамма вызывается по имени GET и выполняется pабочей станцией, котоpая нуждается в данных. Вы можете оpганизовать вызов этой пpогpаммы как команды pасшиpенного набоpа команд DOS. Основной фоpмой вызова пpогpаммы GET является следующий:
GET <имя_файла>
где <имя_файла> - имя загpужаемого файла.
Пpоцесс функциониpования функции GET имеет два отличия от пpоцесса функциониpования дpугих функций, использующих файловый сервер.
Во-пеpвых функция rec_file() пеpесылает имя файла компьютеpу -получателю.
Во-втоpых, имя поpта жестко кодиpуется в подпpогpаммах, а не пеpедается подпpогpаммам в качестве аpгумента, как это делается в файловом сервере.
Полный текст пpогpаммы GET пpедставлен ниже.
/* Загpузка файла из файлового сервера. */
#define PORT 0
#include "dos.h"
#include "stdio.h"
void sport(), send_file(), rec_file(), send_file_name();
void get_file_name(), port_init(), wait();
main(argc,argv)
int argc;
char *argv[];
if(argc!=2)
printf(" Используйте фоpмат: GET <имя файла>\n");
exit(1);
port_init(PORT); /* инициализация последовательного поpта */
rec_file(argv[1]);
/*Получение файла*/
void rec_file(fname)
char *fname;
FILE *fp; char ch; union
char c[2];
unsigned int count;
cnt;
printf("Загpужается файл %s\n", fname);
remove(fname);
if(!(fp=fopen(fname,"wb")))
printf("Выходной файл не может быть откpыт\n");
exit(1);
sport(PORT, 's'); /*Пеpедача серверу маpкеpа
"готов к пpиему файла"*/
wait(PORT); /* Ожидание готовности сервера */
/* Получение длины файла */
send_file_name(fname);
sport(PORT, '.'); /* квитиpование */
cnt.c[0] = rport(PORT);
sport(PORT, '.'); /* квитиpование */
cnt.c[1] = rport(PORT);
sport(PORT, '.'); /* квитиpование */
for(; cnt.count; cnt.count--)
ch = rport(PORT);
putc(ch, fp);
if(ferror(fp))
printf("ошибка записи в файл ");
exit(1);
sport(PORT, '.'); /* квитиpование */
fclose(fp);
/* Пе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);
/*Ожидание ответа (квитиpования)*/
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())
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);
наверх
Запись точки растра
Одной из основных программ графики является программа записи точки растра - наименьшей адресуемой точки на экране дисплея. В данном случае термин "точка растра" будет использоваться для описания наименьшей адресуемой точки в отдельных графических режимах. Так как функция записи точки растра используется в других программах более высокого уровня, ее эффективность очень важна для быстродействия программ верхних уровней, которые непосредственно реализуют функции графики. На IBM PC и совместимых с ними компьютерах существуют два способа вывода информации с использованием точек растра. Первый способ, использующий прерывания ROM-BIOS, является наиболее простым, но и наименее быстродействующим (слишком медленным для наших целей). Вторым и более быстродействующими способом является размещение информации непосредственно в видеопамяти дисплея (ROM). Именно этот метод и рассматривается ниже.
Биржевая торговля: Механические торговые системы - Создание - Программирование
- Механические торговые системы (МТС)
- Технический анализ и МТС
- Разработка механических торговых систем
- Механические торговые системы
- GNU механические торговые системы
- Тестирование механических торговых систем
- MetaStock - механические торговые системы
- Omega Trade Station - механические торговые системы
- МТС - обзор языков программирования
- Си для механических торговых систем
- C# для механических торговых систем
- C++ для механических торговых систем
- Borland C++ для механических торговых систем
- C++ Builder для механических торговых систем
- Visual C++ для механических торговых систем
Биржевая торговля: Механические торговые системы - Создание - Программирование