"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 (); /* имя терминала */