"f(%d)\n", arg); x = arg; x++; } int main(int ac, char *av[]){ h(); f(1); g(); printf("x=%d y=%d\n", x, y); return 0; } -------- файл b.c -------- #include "header.h" void g(){ y = f(5); } -------- файл c.c -------- #include "header.h" void h(){ f(); } Попытка компиляции: abs@wizard$ cc a.c b.c c.c -o aaa a.c: b.c: "b.c", line 4: operand cannot have void type: op "=" "b.c", line 4: assignment type mismatch: int "=" void cc: acomp failed for b.c c.c: "c.c", line 4: prototype mismatch: 0 args passed, 1 expected cc: acomp failed for c.c А. Богатырев, 1992-95 - 348 - Си в UNIX 8. Экранные библиотеки и работа с видеопамятью. Терминал в UNIX с точки зрения программ - это файл. Он представляет собой два устройства: при записи write() в этот файл осуществляется вывод на экран; при чтении read()-ом из этого файла - читается информация с клавиатуры. Современные терминалы в определенном смысле являются устройствами прямого дос- тупа: - информация может быть выведена в любое место экрана, а не только последовательно строка за строкой. - некоторые терминалы позволяют прочесть содержимое произвольной области экрана в вашу программу. Традиционные терминалы являются самостоятельными устройствами, общающимися с компью- тером через линию связи. Протокол|- общения образует систему команд терминала и может быть различен для терминалов разных моделей. Поэтому библиотека работы с традиционным терминалом должна решать следующие проблемы: - настройка на систему команд данного устройства, чтобы одна и та же программа работала на разных типах терминалов. - эмуляция недостающих в системе команд; максимальное использование предоставлен- ных терминалом возможностей. - мимнимизация передачи данных через линию связи (для ускорения работы). - было бы полезно, чтобы библиотека предоставляла пользователю некоторые логичес- кие абстракции, вроде ОКОН - прямоугольных областей на экране, ведущих себя подобно маленьким терминалам. В UNIX эти задачи решает стандартная библиотека curses (а только первую задачу - более простая библиотека termcap). Для настройки на систему команд конкретного дисп- лея эти библиотеки считывают описание системы команд, хранящееся в файле /etc/termcap. Кроме них бывают и другие экранные библиотеки, а также существуют иные способы работы с экраном (через видеопамять, см. ниже). В задачах данного раздела вам придется пользоваться библиотекой curses. При ком- пиляции программ эта библиотека подключается при помощи указания ключа -lcurses, как в следующем примере: cc progr.c -Ox -o progr -lcurses -lm Здесь подключаются две библиотеки: /usr/lib/libcurses.a (работа с экраном) и /usr/lib/libm.a (математические функции, вроде sin, fabs). Ключи для подключения библиотек должны быть записаны в команде САМЫМИ ПОСЛЕДНИМИ. Заметим, что стандартная библиотека языка Си (содержащая системные вызовы, библиотеку stdio (функции printf, scanf, fread, fseek, ...), разные часто употребляемые функции (strlen, strcat, sleep, malloc, rand, ...)) /lib/libc.a подключается автоматически и не требует указания ключа -lc. В начале своей программы вы должны написать директиву #include <curses.h> подключающую файл /usr/include/curses.h, в котором описаны форматы данных, используе- мых библиотекой curses, некоторые предопределенные константы и.т.п. (это надо, чтобы ваша программа пользовалась именно этими стандартными соглашениями). Посмотрите в этот файл! Когда вы пользуетесь curses-ом, вы НЕ должны пользоваться функциями стандартной библиотеки stdio для непосредственного вывода на экран; так вы не должны пользоваться ____________________ |- Под протоколом в программировании подразумевают ряд соглашений двух сторон (сер- вера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети - "host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех лю- дей состоит из одних и тех же звуков и может быть записана одними и теми же буквами (а данные - байтами). Но если два человека говорят на разных языках - т.е. по- разному конструируют фразы и интерпретируют звуки - они не поймут друг друга! А. Богатырев, 1992-95 - 349 - Си в UNIX функциями printf, putchar. Это происходит потому, что curses хранит в памяти про- цесса копию содержимого экрана, и если вы выводите что-либо на экран терминала обходя функции библиотеки curses, то реальное содержимое экрана и позиция курсора на нем перестают соответствовать хранимым в памяти, и библиотека curses начнет выводить неп- равильное изображение. ПРОГРАММА | | | CURSES---копия экрана | printw,addch,move | | V V библиотека STDIO --printf,putchar----> экран Таким образом, curses является дополнительным "слоем" между вашей программой и стан- дартным выводом и игнорировать этот слой не следует. Напомним, что изображение, создаваемое при помощи библиотеки curses, сначала формируется в памяти программы без выполнения каких-либо операций с экраном дисплея (т.е. все функции wmove, waddch, waddstr, wprintw изменяют только ОБРАЗЫ окон в памяти, а на экране ничего не происходит!). И лишь только ПОСЛЕ того, как вы вызо- вете функцию refresh() ("обновить"), все изменения происшедшие в окнах будут отобра- жены на экране дисплея (такое одновременное обновление всех изменившихся частей экрана позволяет провести ряд оптимизаций). Если вы забудете сделать refresh - экран останется неизменным. Обычно эту функцию вызывают перед тем, как запросить у пользо- вателя какой-либо ввод с клавиатуры, чтобы пользователь увидел текущую "свежую" кар- тинку. Хранение содержимого окон в памяти программы позволяет ей считывать содержи- мое окон, тогда как большинство обычных терминалов не способны выдать в компьютер содержимое какой-либо области экрана. Общение с терминалом через линию связи (или вообще через последовательный прото- кол) является довольно медленным. На персональных компьютерах существует другой спо- соб работы с экраном: через прямой доступ в так называемую "видеопамять" - специаль- ную область памяти компьютера, содержимое которой аппаратно отображается на экране консоли. Работа с экраном превращается для программиста в работу с этим массивом байт (запись/чтение). Программы, пользующиеся этим способом, просты и работают очень быстро (ибо доступ к памяти черезвычайно быстр, и сделанные в ней изменения "проявля- ются" на экране почти мгновенно). Недостаток таких программ - привязанность к конк- ретному типу машины. Эти программы немобильны и не могут работать ни на обычных тер- миналах (подключаемых к линии связи), ни на машинах с другой структурой видеопамяти. Выбор между "традиционной" работой с экраном и прямым доступом (фактически - между мобильностью и скоростью) - вопрос принципиальный, тем не менее принятие решения зависит только от вас. Видеопамять IBM PC в текстовом режиме 80x25 16 цветов имеет следующую структуру: struct symbol{ /* IBM PC family */ char chr; /* код символа */ char attr; /* атрибуты символа (цвет) */ } mem[ 25 ] [ 80 ]; /* 25 строк по 80 символов */ Структура байта атрибутов: ------------------------------------------- | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # бита ------------------|------------------------ |blink| R | G | B | intensity | r | g | b | цвет ------------------|------------------------ background (фон) | foreground (цвет букв) R - red (красный) G - green (зеленый) B - blue (синий) blink - мерцание букв (не фона!) intensity - повышенная яркость А. Богатырев, 1992-95 - 350 - Си в UNIX Координатная система на экране: верхний левый угол экрана имеет координаты (0,0), ось X горизонтальна, ось Y вертикальна и направлена сверху вниз. Цвет символа получается смешиванием 3х цветов: красного, зеленого и синего (электронно-лучевая трубка дисплея имеет 3 электронные пушки, отвечающие этим цве- там). Кроме того, допустимы более яркие цвета. 4 бита задают комбинацию 3х основных цветов и повышенной яркости. Образуется 2**4=16 цветов: I R G B номер цвета BLACK 0 0 0 0 0 черный BLUE 0 0 0 1 1 синий GREEN 0 0 1 0 2 зеленый CYAN 0 0 1 1 3 циановый (серо-голубой) RED 0 1 0 0 4 красный MAGENTA 0 1 0 1 5 малиновый BROWN 0 1 1 0 6 коричневый LIGHTGRAY 0 1 1 1 7 светло-серый (темно-белый) DARKGRAY 1 0 0 0 8 темно-серый LIGHTBLUE 1 0 0 1 9 светло-синий LIGHTGREEN 1 0 1 0 10 светло-зеленый LIGHTCYAN 1 0 1 1 11 светло-циановый LIGHTRED 1 1 0 0 12 ярко-красный LIGHTMAGENTA 1 1 0 1 13 ярко-малиновый YELLOW 1 1 1 0 14 желтый WHITE 1 1 1 1 15 (ярко)-белый Физический адрес видеопамяти IBM PC в цветном алфавитно-цифровом режиме (80x25, 16 цветов) равен 0xB800:0x0000. В MS DOS указатель на эту память можно получить при помощи макроса make far pointer: MK_FP (это должен быть far или huge указатель!). В XENIX|- указатель получается при помощи системного вызова ioctl, причем система пре- доставит вам виртуальный адрес, ибо привелегия работы с физическими адресами в UNIX принадлежит только системе. Работу с экраном в XENIX вы можете увидеть в примере "осыпающиеся буквы". 8.1. /*#! /bin/cc fall.c -o fall -lx * "Осыпающиеся буквы". * Использование видеопамяти IBM PC в ОС XENIX. * Данная программа иллюстрирует доступ к экрану * персонального компьютера как к массиву байт; * все изменения в массиве немедленно отображаются на экране. * Функция nap() находится в библиотеке -lx * Показана также работа с портами IBM PC при помощи ioctl(). */ #include <stdio.h> #include <fcntl.h> /* O_RDWR */ #include <signal.h> #include <ctype.h> #include <sys/types.h> #include <sys/at_ansi.h> #include <sys/kd.h> /* for System V/4 and Interactive UNIX only */ /*#include <sys/machdep.h> for XENIX and SCO UNIX only */ #include <sys/sysmacros.h> ____________________ |- XENIX - (произносится "зиникс") версия UNIX для IBM PC, первоначально разрабо- танная фирмой Microsoft и поставляемая фирмой Santa Cruz Operation (SCO). А. Богатырев, 1992-95 - 351 - Си в UNIX #ifdef M_I386 # define far /* на 32-битной машине far не требуется */ #endif char far *screen; /* видеопамять как массив байт */ /* far - "длинный" (32-битный) адрес(segment,offset) */ int segm; /* сегмент с видеопамятью */ #define COLS 80 /* число колонок на экране */ #define LINES 25 /* число строк */ #define DELAY 20 /* задержка (миллисекунд) */ int ega; /* дескриптор для доступа к драйверу EGA */ /* структура для обмена с портами */ static struct port_io_struct PORT[ 4 /* не более 4 за раз */] = { /* операция номер порта данные */ /* .dir .port .data */ /* Переустановить flip/flop: * заставить порт 0x3C0 ожидать пары адрес/значение * при последовательной записи байтов в этот порт. */ { IN_ON_PORT, 0x3DA, -1 }, /* IN-чтение */ /* Теперь 3c0 ожидает пары адрес/значение */ { OUT_ON_PORT, 0x3C0, -1 /* адрес */ }, { OUT_ON_PORT, 0x3C0, -1 /* значение*/ }, /* OUT-запись */ /* переинициализировать дисплей, установив бит #5 порта 3c0 */ { OUT_ON_PORT, 0x3C0, 0x20 } }; void closescr(nsig){ /* конец работы */ setbgcolor(0); /* установить черный фон экрана */ exit(0); } /* получение доступа к видеопамяти адаптера VGA/EGA/CGA */ void openscr () { static struct videodev { char *dev; int mapmode; } vd[] = { { "/dev/vga", MAPVGA }, { "/dev/ega", MAPEGA }, { "/dev/cga", MAPCGA }, { NULL, -1 } }, *v; /* устройство для доступа к видеоадаптеру */ for(v=vd; v->dev;v++ ) if((ega = open (v->dev, O_RDWR)) >= 0 ) goto ok; fprintf( stderr, "Can't open video adapter\n" ); exit(1); А. Богатырев, 1992-95 - 352 - Си в UNIX ok: /* fprintf(stderr, "Adapter:%s\n", v->dev); */ /* получить адрес видеопамяти и доступ к ней */ #ifdef M_I386 screen = (char *) ioctl (ega, v->mapmode, 0); #else segm = ioctl (ega, v->mapmode, 0); screen = sotofar (segm, 0); /* (segment,offset) to far pointer */ #endif signal( SIGINT, closescr ); } /* макросы для доступа к байтам "символ" и "атрибуты" * в координатах (x,y) экрана. */ #define GET(x,y) screen[ ((x) + (y) * COLS ) * 2 ] #define PUT(x,y, c) screen[ ((x) + (y) * COLS ) * 2 ] = (c) #define GETATTR(x,y) screen[ ((x) + (y) * COLS ) * 2 + 1 ] #define PUTATTR(x,y, a) screen[ ((x) + (y) * COLS ) * 2 + 1 ] = (a) /* символ изображается как черный пробел ? */ #define white(c,a) ((isspace(c) || c==0) && (attr & 0160)==0) /* установить цвет фона экрана */ void setbgcolor( color ){ PORT[1].data = 0; /* регистр номер 0 палитры содержит цвет фона */ /* всего в палитре 16 регистров (0x00...0xFF) */ PORT[2].data = color ; /* новое значение цвета, составленное как битовая маска * RGBrgb (r- красный, g- зеленый, b- синий, RGB- дополнительные * тусклые цвета) */ /* выполнить обмены с портами */ if( ioctl( ega, EGAIO, PORT ) < 0 ){ fprintf( stderr, "Can't out port\n" ); perror( "out" ); } } А. Богатырев, 1992-95 - 353 - Си в UNIX void main(ac, av) char **av;{ void fall(); openscr(); if( ac == 1 ){ setbgcolor(020); /* темно-зеленый фон экрана */ fall(); /* осыпание букв */ } else { if(*av[1] == 'g') /* Установить режим адаптера graphics 640x350 16-colors */ ioctl( ega, SW_CG640x350, NULL); /* Если вы хотите получить адрес видеопамяти в графическом режиме, * вы должны СНАЧАЛА включить этот режим, * ЗАТЕМ сделать screen=ioctl(ega, v->mapmode, NULL); * и ЕЩЕ РАЗ сделать включение графического режима. */ /* Установить режим адаптера text 80x25 16-colors */ else ioctl( ega, SW_ENHC80x25, NULL); } closescr(0); } /* осыпать буквы вниз */ void fall(){ register i, j; int rest; int nextcol; int n; int changed = 1; /* не 0, если еще не все буквы опали */ char mask [ COLS ]; while( changed ){ changed = 0; for( i = 0 ; i < COLS ; i++ ) mask[ i ] = 0; for( i = 0 ; i < COLS ; i++ ){ rest = COLS - i; /* осталось осыпать колонок */ nextcol = rand() % rest; j = 0; /* индекс в mask */ n = 0; /* счетчик */ for(;;){ if( mask[j] == 0 ){ if( n == nextcol ) break; n++; } j++; } changed += fallColumn( j ); mask[j] = 1; } } } А. Богатырев, 1992-95 - 354 - Си в UNIX /* осыпать буквы в одном столбце */ int fallColumn( x ){ register int y; char ch, attr; int firstspace = (-1); int firstnospace = (-1); Again: /* find the falled array */ for( y=LINES-1; y >= 0 ; y-- ){ ch = GET( x, y ); attr = GETATTR( x,y ); if( white(ch, attr)){ firstspace = y; goto FindNoSpace; } } AllNoSpaces: return 0; /* ничего не изменилось */ FindNoSpace: /* найти не пробел */ for( ; y >= 0 ; y-- ){ ch = GET( x, y ); attr = GETATTR( x, y ); if( !white(ch, attr)){ firstnospace = y; goto Fall; } } AllSpaces: /* в данном столбце все упало */ return 0; Fall: /* "уронить" букву */ for( y = firstnospace ; y < firstspace ; y++ ){ /* переместить символ на экране на одну позицию вниз */ ch = GET( x, y ); attr = GETATTR( x, y ); PUT( x, y, 0 ); PUTATTR( x, y, 0 ); PUT( x, y+1 , ch ); PUTATTR( x, y+1, attr ); nap( DELAY ); /* подождать DELAY миллисекунд */ } return 1; /* что-то изменилось */ } 8.2. Для работы может оказаться более удобным иметь указатель на видеопамять как на массив структур. Приведем пример для системы MS DOS: А. Богатырев, 1992-95 - 355 - Си в UNIX #include <dos.h> /* там определено MK_FP */ char far *screen = MK_FP(0xB800 /*сегмент*/, 0x0000 /*смещение*/); struct symb{ char chr; char attr; } far *scr, far *ptr; #define COLS 80 /* число колонок */ #define LINES 25 /* число строк */ #define SCR(x,y) scr[(x) + COLS * (y)] /* x из 0..79, y из 0..24 */ void main(){ int x, y; char c; scr = (struct symb far *) screen; /* или сразу * scr = (struct symb far *) MK_FP(0xB800,0x0000); */ /* переписать строки экрана справа налево */ for(x=0; x < COLS/2; x++ ) for( y=0; y < LINES; y++ ){ c = SCR(x,y).chr; SCR(x,y).chr = SCR(COLS-1-x, y).chr; SCR(COLS-1-x, y).chr = c; } /* сделать цвет экрана: желтым по синему */ for(x=0; x < COLS; x++) for(y=0; y < LINES; y++) SCR(x,y).attr = (0xE | (0x1 << 4)); /* желтый + синий фон */ /* прочесть любую кнопку с клавиатуры (пауза) */ (void) getch(); } И, наконец, еще удобнее работа с видеопамятью как с двумерным массивом структур: #include <dos.h> /* MS DOS */ #define COLS 80 #define LINES 25 struct symb { char chr; char attr; } (far *scr)[ COLS ] = MK_FP(0xB800, 0); void main(void){ register x, y; for(y=0; y < LINES; y++) for(x=0; x < COLS; ++x){ scr[y][x].chr = '?'; scr[y][x].attr = (y << 4) | (x & 0xF); } getch(); } Учтите, что при работе с экраном через видеопамять, курсор не перемещается! Если в А. Богатырев, 1992-95 - 356 - Си в UNIX обычной работе с экраном текст выводится в позиции курсора и курсор автоматически продвигается, то здесь курсор будет оставаться на своем прежнем месте. Для перемеще- ния курсора в нужное вам место, вы должны его поставить явным образом по окончании записи в видеопамять (например, обращаясь к портам видеоконтроллера). Обратите внимание, что спецификатор модели памяти far должен указываться перед КАЖДЫМ указателем (именно для иллюстрации этого в первом примере описан неиспользуе- мый указатель ptr). 8.3. Составьте программу сохранения содержимого экрана IBM PC (видеопамяти) в текс- товом режиме в файл и обратно (в системе XENIX). 8.4. Пользуясь прямым доступом в видеопамять, напишите функции для спасения прямоу- гольной области экрана в массив и обратно. Вот функция для спасения в массив: typedef struct { short xlen, ylen; char *save; } Pict; extern void *malloc(unsigned); Pict *gettext (int x, int y, int xlen, int ylen){ Pict *n = (Pict *) malloc(sizeof *n); register char *s; register i, j; n->xlen = xlen; n->ylen = ylen; s = n->save = (char *) malloc( 2 * xlen * ylen ); for(i=y; i < y+ylen; i++) for(j=x; j < x+xlen; j++){ *s++ = SCR(j,i).chr ; *s++ = SCR(j,i).attr; } return n; } Добавьте проверки на корректность xlen, ylen (в пределах экрана). Напишите функцию puttext для вывода спасенной области обратно; функцию free(buf) лучше в нее не встав- лять. void puttext (Pict *n, int x, int y){ register char *s = n->save; register i, j; for(i=y; i < y + n->ylen; i++) for(j=x; j < x + n->xlen; j++){ SCR(j,i).chr = *s++; SCR(j,i).attr = *s++; } } /* очистка памяти текстового буфера */ void deltext(Pict *n){ free(n->save); free(n); } Приведем еще одну полезную функцию, которая может вам пригодиться - это аналог printf при прямой работе с видеопамятью. #include <stdarg.h> /* текущий цвет: белый по синему */ static char currentColor = 0x1F; int videoprintf (int x, int y, char *fmt, ...){ char buf[512], *s; va_list var; А. Богатырев, 1992-95 - 357 - Си в UNIX /* clipping (отсечение по границам экрана) */ if( y < 0 || y >= LINES ) return x; va_start(var, fmt); vsprintf(buf, fmt, var); va_end(var); for(s=buf; *s; s++, x++){ /* отсечение */ if(x < 0 ) continue; if(x >= COLS) break; SCR(x,y).chr = *s; SCR(x,y).attr = currentColor; } return x; } void setcolor (int col){ currentColor = col; } 8.5. Пользуясь написанными функциями, реализуйте функции для "выскакивающих" окон (pop-up window): Pict *save; save = gettext (x,y,xlen,ylen); // ... рисуем цветными пробелами прямоугольник с // углами (x,y) вверху-слева и (x+xlen-1,y+ylen-1) // внизу-справа... // ...рисуем некие таблицы, меню, текст в этой зоне... // стираем нарисованное окно, восстановив то изображение, // поверх которого оно "всплыло". puttext (save,x,y); deltext (save); Для начала напишите "выскакивающее" окно с сообщением; окно должно исчезать по нажа- тию любой клавиши. c = message(x, y, text); Размер окна вычисляйте по длине строки text. Код клавиши возвращайте в качестве зна- чения функции. Теперь сделайте text массивом строк: char *text[]; (последняя строка - NULL). 8.6. Сделайте так, чтобы "выскакивающие" окна имели тень. Для этого надо сохранить в некоторый буфер атрибуты символов (сами символы не надо!), находящихся на местах $: ########## ##########$ ##########$ $$$$$$$$$$ а затем прописать этим символам на экране атрибут 0x07 (белый по черному). При стира- нии окна (puttext-ом) следует восстановить спасенные атрибуты этих символов (стереть тень). Если окно имеет размер xlen*ylen, то размер буфера равен xlen+ylen-1 байт. 8.7. Напишите функцию, рисующую на экране прямоугольную рамку. Используйте ее для рисования рамки окна. А. Богатырев, 1992-95 - 358 - Си в UNIX 8.8. Напишите "выскакивающее" окно, которое проявляется на экране как бы расширяясь из точки: ############## ###### ############## ### ###### ############## ###### ############## ############## Вам следует написать функцию box(x,y,width,height), рисующую цветной прямоугольник с верхним левым углом (x,y) и размером (width,height). Пусть конечное окно задается углом (x0,y0) и размером (W,H). Тогда "вырастание" окна описывается таким алгоритмом: void zoom(int x0, int y0, int W, int H){ int x, y, w, h, hprev; /* промежуточное окно */ for(hprev=0, w=1; w < W; w++){ h = H * w; h /= W; /* W/H == w/h */ if(h == hprev) continue; hprev = h; x = x0 + (W - w)/2; /* чтобы центры окон */ y = y0 + (H - h)/2; /* совпадали */ box(x, y, w, h); delay(10); /* задержка 10 миллисек. */ } box(x0, y0, W, H); } 8.9. Составьте библиотеку функций, аналогичных библиотеке curses, для ЭВМ IBM PC в ОС XENIX. Используйте прямой доступ в видеопамять. 8.10. Напишите рекурсивное решение задачи "ханойские башни" (перекладывание дисков: есть три стержня, на один из них надеты диски убывающего к вершине диаметра. Требу- ется переложить их на третий стержень, никогда не кладя диск большего диаметра поверх диска меньшего диаметра). Усложнение - используйте пакет curses для изображения перекладывания дисков на экране терминала. Указание: идея рекурсивного алгоритма: carry(n, from, to, by) = if( n > 0 ){ carry( n-1, from, by, to ); перенесиОдинДиск( from, to ); carry( n-1, by, to, from ); } Вызов: carry( n, 0, 1, 2 ); n - сколько дисков перенести (n > 0). from - откуда (номер стержня). to - куда. by - при помощи (промежуточный стержень). n дисков потребуют (2**n)-1 переносов. 8.11. Напишите программу, ищущую выход из лабиринта ("червяк в лабиринте"). Лаби- ринт загружается из файла .maze (не забудьте про расширение табуляций!). Алгоритм имеет рекурсивную природу и выглядит примерно так: #include <setjmp.h> jmp_buf jmp; int found = 0; maze(){ /* Это головная функция */ if( setjmp(jmp) == 0 ){ /* начало */ if( неСтенка(x_входа, y_входа)) GO( x_входа, y_входа); А. Богатырев, 1992-95 - 359 - Си в UNIX } } GO(x, y){ /* пойти в точку (x, y) */ if( этоВыход(x, y)){ found = 1; /* нашел выход */ пометить(x, y); longjmp(jmp, 1);} пометить(x, y); if( неСтенка(x-1,y)) GO(x-1, y); /* влево */ if( неСтенка(x,y-1)) GO(x, y-1); /* вверх */ if( неСтенка(x+1,y)) GO(x+1, y); /* вправо */ if( неСтенка(x,y+1)) GO(x, y+1); /* вниз */ снятьПометку(x, y); } #define пометить(x, y) лабиринт[y][x] = '*' #define снятьПометку(x, y) лабиринт[y][x] = ' ' #define этоВыход(x, y) (x == x_выхода && y == y_выхода) /* можно искать "золото": (лабиринт[y][x] == '$') */ неСтенка(x, y){ /* стенку изображайте символом @ или # */ if( координатыВнеПоля(x, y)) return 0; /*край лабиринта*/ return (лабиринт[y][x] == ' '); } Отобразите массив лабиринт на видеопамять (или воспользуйтесь curses-ом). Вы увидите червяка, ползающего по лабиринту в своих исканиях. 8.12. Используя библиотеку termcap напишите функции для: - очистки экрана. - позиционирования курсора. - включения/выключения режима выделения текста инверсией. 8.13. Используя написанные функции, реализуйте программу выбора в меню. Выбранную строку выделяйте инверсией фона. /*#!/bin/cc termio.c -O -o termio -ltermcap * Смотри man termio, termcap и screen. * Работа с терминалом в стиле System-V. * Работа с системой команд терминала через /etc/termcap * Работа со временем. * Работа с будильником. */ #include <stdio.h> /* standard input/output */ #include <sys/types.h> /* system typedefs */ #include <termio.h> /* terminal input/output */ #include <signal.h> /* signals */ #include <fcntl.h> /* file control */ #include <time.h> /* time structure */ void setsigs(), drawItem(), drawTitle(), prSelects(), printTime(); А. Богатырев, 1992-95 - 360 - Си в UNIX /* Работа с описанием терминала TERMCAP ---------------------------------*/ extern char *getenv (); /* получить переменную окружения */ extern char *tgetstr (); /* получить строчный описатель /termcap/ */ extern char *tgoto (); /* подставить %-параметры /termcap/ */ static char Tbuf[2048], /* буфер для описания терминала, обычно 1024 */ /* Tbuf[] можно сделать локальной автоматической переменной * в функции tinit(), чтобы не занимать место */ Strings[256], /* буфер для расшифрованных описателей */ *p; /* вспомогательная перем. */ char *tname; /* название типа терминала */ int COLS, /* число колонок экрана */ LINES; /* число строк экрана */ char *CM; /* описатель: cursor motion */ char *CL; /* описатель: clear screen */ char *CE; /* описатель: clear end of line */ char *SO, *SE; /* описатели: standout Start и End */ char *BOLD, *NORM; /* описатели: boldface and NoStandout */ int BSflag; /* можно использовать back space '\b' */ void tinit () { /* Функция настройки на систему команд дисплея */ p = Strings; /* Прочесть описание терминала в Tbuf */ switch (tgetent (Tbuf, tname = getenv ("TERM"))) { case -1: printf ("Нет файла TERMCAP (/etc/termcap).\n"); exit (1); case 0: printf ("Терминал %s не описан.\n", tname); exit (2); case 1: break; /* OK */ } COLS = tgetnum ("co"); /* Прочесть числовые описатели. */ LINES = tgetnum ("li"); CM = tgetstr ("cm", &p); /* Прочесть строчные описатели. */ CL = tgetstr ("cl", &p); /* Описатель дешифруется и заносится */ CE = tgetstr ("ce", &p); /* в массив по адресу p. Затем */ SO = tgetstr ("so", &p); /* указатель p продвигается на */ SE = tgetstr ("se", &p); /* свободное место, а адрес расшиф- */ BOLD = tgetstr ("md", &p); /* рованной строки выдается из ф-ции */ NORM = tgetstr ("me", &p); BSflag = tgetflag( "bs" ); /* Узнать значение флажка: 1 - есть, 0 - нет */ } А. Богатырев, 1992-95 - 361 - Си в UNIX /* Макрос, внесенный в функцию. Дело в том, что tputs в качестве третьего аргумента требует имя функции, которую она вызывает в цикле: (*f)(c); Если подать на вход макрос, вроде putchar, а не адрес входа в функцию, мы и не достигнем желанного эффекта, и получим ругань от компилятора. */ void put (c) char c; { putchar (c); } /* очистить экран */ void clearScreen () { if (CL == NULL) /* Функция tputs() дорасшифровывает описатель */ return; /* (обрабатывая задержки) и выдает его */ tputs (CL, 1, put); /* посимвольно ф-цией put(c) 1 раз */ /* Можно выдать команду не 1 раз, а несколько: например если это */ /* команда сдвига курсора на 1 позицию влево '\b' */ } /* очистить конец строки, курсор остается на месте */ void clearEOL () { /* clear to the end of line */ if (CE == NULL) return; tputs (CE, 1, put); } /* позиционировать курсор */ void gotoXY (x, y) { /* y - по вертикали СВЕРХУ-ВНИЗ. */ if (x < 0 || y < 0 || x >= COLS || y >= LINES) { printf ("Точка (%d,%d) вне экрана\n", x, y); return; } /* CM - описатель, содержащий 2 параметра. Подстановку параметров * делает функция tgoto() */ tputs (tgoto (CM, x, y), 1, put); } /* включить выделение */ void standout () { if (SO) tputs (SO, 1, put); } /* выключить выделение */ void standend () { if (SE) tputs (SE, 1, put); /* else normal(); */ } /* включить жирный шрифт */ void bold () { if (BOLD) tputs (BOLD, 1, put); } А. Богатырев, 1992-95 - 362 - Си в UNIX /* выключить любой необычный шрифт */ void normal () { if (NORM) tputs (NORM, 1, put); else standend(); } /* Управление драйвером терминала --------------------------------- */ #define ESC '\033' #define ctrl(c) ((c) & 037 ) int curMode = 0; int inited = 0; struct termio old, new; int fdtty; void ttinit () { /* открыть терминал в режиме "чтение без ожидания" */ fdtty = open ("/dev/tty", O_RDWR | O_NDELAY); /* узнать текущие режимы драйвера */ ioctl (fdtty, TCGETA, &old); new = old; /* input flags */ /* отменить преобразование кода '\r' в '\n' на вводе */ new.c_iflag &= ~ICRNL; if ((old.c_cflag & CSIZE) == CS8) /* 8-битный код */ new.c_iflag &= ~ISTRIP; /* отменить & 0177 на вводе */ /* output flags */ /* отменить TAB3 - замену табуляций '\t' на пробелы */ /* отменить ONLCR - замену '\n' на пару '\r\n' на выводе */ new.c_oflag &= ~(TAB3 | ONLCR); /* local flags */ /* выключить режим ICANON, включить CBREAK */ /* выключить эхоотображение набираемых символов */ new.c_lflag &= ~(ICANON | ECHO); /* control chars */ /* при вводе с клавиш ждать не более ... */ new.c_cc[VMIN] = 1; /* 1 символа и */ new.c_cc[VTIME] = 0; /* 0 секунд */ /* Это соответствует режиму CBREAK */ /* Символы, нажатие которых заставляет драйвер терминала послать сигнал * либо отредактировать набранную строку. Значение 0 означает, * что соответствующего символа не будет */ new.c_cc[VINTR] = ctrl ('C'); /* символ, генерящий SIGINT */ new.c_cc[VQUIT] = '\0'; /* символ, генерящий SIGQUIT */ new.c_cc[VERASE] = '\0'; /* забой (отмена последнего символа)*/ new.c_cc[VKILL] = '\0'; /* символ отмены строки */ /* По умолчанию эти кнопки равны: DEL, CTRL/\, BACKSPACE, CTRL/U */ setsigs (); inited = 1; /* уже инициализировано */ } А. Богатырев, 1992-95 - 363 - Си в UNIX void openVisual () { /* open visual mode (включить "экранный" режим) */ if (!inited) ttinit (); if (curMode == 1) return; /* установить моды драйвера из структуры new */ ioctl (fdtty, TCSETAW, &new); curMode = 1; /* экранный режим */ } void closeVisual () { /* canon mode (включить канонический режим) */ if (!inited) ttinit (); if (curMode == 0) return; ioctl (fdtty, TCSETAW, &old); curMode = 0; /* канонический режим */ } /* завершить процесс */ void die (nsig) { normal(); closeVisual (); /* При завершении программы (в том числе по * сигналу) мы должны восстановить прежние режимы драйвера, * чтобы терминал оказался в корректном состоянии. */ gotoXY (0, LINES - 1); putchar ('\n'); if (nsig) printf ("Пришел сигнал #%d\n", nsig); exit (nsig); } void setsigs () { register ns; /* Перехватывать все сигналы; завершаться по ним. */ /* UNIX имеет 15 стандартных сигналов. */ for (ns = 1; ns <= 15; ns++) signal (ns, die); } А. Богатырев, 1992-95 - 364 - Си в UNIX /* Работа с меню -------------------------------------------- */ struct menu { char *m_text; /* выдаваемая строка */ int m_label; /* помечена ли она ? */ } menuText[] = { /* названия песен Beatles */ { "Across the Universe", 0 } , { "All I've got to do", 0 } , { "All my loving", 0 } , { "All together now", 0 } , { "All You need is love",0 } , { "And I love her", 0 } , { "And your bird can sing", 0 } , { "Another girl", 0 } , { "Any time at all", 0 } , { "Ask me why", 0 } , { NULL, 0 } }; #define Y_TOP 6 int nitems; /* количество строк в меню */ int nselected = 0; /* количество выбранных строк */ char title[] = "ПРОБЕЛ - вниз, ЗАБОЙ - вверх, ESC - выход, \ ENTER - выбрать, TAB - отменить"; # define TIMELINE 1 void main (ac, av) char **av; { char **line; register i; int c; int n; /* текущая строка */ extern char readkey (); /* forward */ extern char *ttyname (); /* имя терминала */