Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 1992-96 |
|
|
|
|
|
- 301 - |
Си в UNIX™ |
|
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> |
|
|||
#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 */ |
† XENIX - (произносится "зиникс") версия UNIX для IBM PC, первоначально разработанная фирмой
Microsoft и поставляемая фирмой Santa Cruz Operation (SCO).
А. Богатырёв, 1992-96 |
- 305 - |
Си в UNIX™ |
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:
#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();
}
И, наконец, еще удобнее работа с видеопамятью как с двумерным массивом структур:
А. Богатырёв, 1992-96 |
- 306 - |
Си в UNIX™ |
#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();
}
Учтите, что при работе с экраном через видеопамять, курсор не перемещается! Если в обычной работе с экраном текст выводится в позиции курсора и курсор автоматически продвигается, то здесь курсор будет оставаться на своем прежнем месте. Для перемещения курсора в нужное вам место, вы должны его поставить явным образом по окончании записи в видеопамять (например, обращаясь к портам видеоконтроллера).
Обратите внимание, что спецификатор модели памяти 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 при прямой работе с видеопамятью.
А. Богатырёв, 1992-96 |
- 307 - |
Си в UNIX™ |
#include <stdarg.h>
/* текущий цвет: белый по синему */ static char currentColor = 0x1F;
int videoprintf (int x, int y, char *fmt, ...){ char buf[512], *s;
va_list var;
/* 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-96 |
- 308 - |
Си в 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);
}
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_входа); |
|
} |
|
|
|
|
} |
|
|
|
|
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); |
/* вправо */ |
А. Богатырёв, 1992-96 |
|
- 309 - |
Си в UNIX™ |
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(); |
|
|||||
/* Работа с |
описанием терминала |
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' |
*/ |