Передача байтов
П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<sides; i++)
line((int)ob[i][0], (int)ob[i][1],
(int)ob[i][2], (int)ob[i][3], cc|128);
/* Смещение (параллельный перенос) объекта в направлении,
определенном x и y
*/
void update_object(ob, x, y, sides)
int ob[][4]; /* объект */
int x, y; /* направление смещения */
register int sides; /* количество сторон объекта */
sides--;
for(; sides>=0; sides--)
ob[sides][0] += x;
ob[sides][1] += y;
ob[sides][2] += x;
ob[sides][3] += y;
/* Определение допустимости перемещения объекта. Возвращает
1, если перемещение допустимо, 0 - в противном случае */ 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]<ob1[0][0])
x = -1;
else
if(human[0][0]>ob1[0][0])
x = 1;
if(human[0][1]<ob1[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<frame[num].endx; 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<frame[num].endy; 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<frame[num].endy-1; 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<frame[num].endx-frame[num].startx-1 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<frame[num].endy+1; i++)
for(j=frame[num].startx; j<frame[num].endx+1; 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<frame[num].endy+1; i++)
for(j=frame[num].startx; j<frame[num].endx+1; 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<tos)
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<tos)
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<MAX_NOTE; i++)
*notes[i] = '\0 ';
first = !first;
window(0);
/* вывести существующие записи */
for(i=0; i<MAX_NOTE; 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<MAX_NOTE-1)
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]<ob1[0][0])
x = -1;
else
if(human[0][0]>ob1[0][0])
x = 1;
if(human[0][1]<ob1[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);
Заметим, что эта функция меняет положение спрайта в 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]<ob1[0][0])
x = -1;
else
if(human[0][0]>ob1[0][0])
x = 1;
if(human[0][1]<ob1[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<num_sets;i++)
bargraph(v[i],num_entries,i*offset,min,max,lines);
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<count))
printf("Набор данных %d\n",j+1);
for (i=0;i<num;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<sets;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<num;++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<num;++t)
if (data[t]<min) min = (int)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<num_sets;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<count))
printf(" Набор данных %d\n",j+1);
for (i = 0;i<num;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<num;i++)
printf(" Введите имя: ");
gets(n[i]);
/* Ввод метки каждого входа */
void get_labels(l,num)
char l[][20]; /* массив для меток */
int num; /* число входов */
int i;
for (i=0;i<num;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<num;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<num;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<num;++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<sets;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<num;++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<num;++t)
if (data[t]<min) min = (int)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<count;i++)
if(strlen(menu[i]) > len) len=strlen(menu[i]);
endx=count+1+x;
p=(unsigned int *)malloc((endx-x+1)*(endy-y+1));
if(!p) exit(1); /* Вы можете здесь сами обработать ошибку */
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<frame[num].count;i++,x++)
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<frame[num].endy;i++)
for(j=frame[num].startx;j<frame[num].endx;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<endx ? startx:endx;
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<y; )
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<endx;++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<endy;++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 цвета. Значение каждого двухбитового блока определяет цвет в соответствии с таблицей, приведенной ниже:
Значение
Цвет в палитре
0
Цвет в палитре
1
1
2
3
фон
желтый
красный
зеленый
фон
голубой
пурпурный
булый
Особенность адаптера CGA заключается в том, что четные точки растра будут располагаться по адресу B8000000h, а нечетные - на 2000h (8152 - в десятичном виде) байтов выше, т.е. по адресу B8002000h. Следовательно, каждая строка точек растра требует 80 байтов (40 для четных точек растра, 40 - для нечетных). Внутри каждого байта точки располагаются слева направо, как они появляются на экране терминала. Это означает, что точка растра с номером 0 занимает 6 и 7 биты, в то время, как точка растра с номером 3 - 0 и 1 биты.
Так как каждый байт кодирует значение четырех точек растра, вы должны сохранять значение трех точек при изменении одной из них. Лучший способ сделать это - создание битовой маски со всеми битами, установленными в 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.
Режим
Тип
Размеры
Адаптеры
0
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
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
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<num;++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;
Давайте тщательно разберем данную программу. Функция 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<num;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<num;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<num;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<num;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<num;++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 очков. Для удобства игра может быть закончена путем нажатия клавиши <Q>.
Игрок управляет движением спрайта посредством клавиш управления курсором. Игровое поле в данном случае не создается самой программой игры. Для этих целей используются программы рисования ("программы-художники"), описанные в главе 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. Клавиши <Курсор в левый верхний угол> (<HOME>), <Страница вверх> (<PGUP>), <Страница вниз> (<PGDN>) и <Кон> (<END>) перемещают графический курсор в указанном направлении и под углом в 45 градусов.
Для анализа кодов операций чтения в программах используется функция bioskey(). Порядок подключения этой функции к программе
при компиляции описан в главе 1. В программу включены обращения к
функциям, позволяющим вам рисовать и закрашивать прямоугольники и
окружности, рисовать линии, копировать и перемещать изображение
на экране, сохранять на диске и загружать с него содержимое
экрана, отображать и вращать объекты.
При изображении линий, прямоугольников и окружностей вам необходимо определить координаты двух точек. Для прямоугольников
- это координаты двух противоположных углов. Для линий задается начальная и конечная точки, а для окружности - координаты центра и точки, через которую она будет проходить.
Процесс выбора этих точек выполняется путем нажатия клавиши <ВВОД> в момент, когда графический курсор находится в требуемой области. Например, для изображения линии вы перемещаете графический курсор в точку, где она должна начинаться и нажимаете клавишу <ВВОД> . Затем вы устанавливаете курсор в точку, где линия заканчивается, и нажимаете <ВВОД> снова. При нажатии клавиши <ВВОД> выполняется загрузка переменных startx, starty, endx и endy, которые потом используются в качестве параметров вызываемых функций. После того, как координаты точек будут определены, при нажатии клавиши <В> рисуется квадрат, а <F> - квадрат закрашивается, при нажатии <L> рисуется линия, при нажатии <С> рисуется окружность, а <Н> - окружность закрашивается.
Для копирования или перемещения части экрана вы должны определить верхний левый и нижний правый углы области, которую вы хотите переместить (нажатием клавиши <ВВОД> ). Затем вы перемещаете курсор в верхний левый угол области, куда вы хотите переместить изображение. Для пересылки изображения требуется нажать клавишу <М>, а для копирования - <Х>. Запомните, что старое изображение в области, куда осуществляется копирование, будет уничтожено.
Для вращения объекта вам необходимо определить сам объект, путем нажатия клавиши <D>. Затем, используя клавишу <ВВОД>, вы должны определить начальные и конечные координаты точек для отрезков по периметру выбранного объекта. Процесс выбора объекта вращения и определения его границ реализуется функцией define_object(). Вращение объекта начинается после нажатия клавиши <А>. Для определения направления вращения используются клавиши <L> (по часовой стрелке) или <R> (против часовой стрелки). Остановить процесс вращения можно нажатием любой клавиши, кроме <L> или <А>.
Для остановки работы программы используется клавиша <Q>. При желании вы можете включить в программу функции для работы с "мышью". Пример выходных данных программы показан на рисунке 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<endx ? startx:endx;
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<y; )
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<endx;++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<endy;++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;i<endy;i++)
for(j=startx;j<endx;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<DELAY;++i); /* задержка 64000 для 10+ Мгц
компьютеров
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
----------------------------------------------------------
/* Установка видеорежима */
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;i<endy;i++)
for(j=startx;j<endx;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(), приведенную ниже, по часовой стрелке (клавиша <R>) или в противоположную сторону (клавиша <L>).
/* Вращение заданных объектов */
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<sides; 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<sides; 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<count; 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<count; i++)
if(mousex>len[i][0] && mousex<len[i][1])
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;x<y; )
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<endx;++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<endy;++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() с заданием все более и более меньшего радиуса. Этот способ используется в функции 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;i<endx;i++)
goto_xy(i,starty);
putchar(179);
goto_xy(i,endy);
putchar(179);
for(i=starty+1;i<endy;i++)
goto_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). Именно этот метод и рассматривается ниже.