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 /* там определено 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 /* 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
/* текущий цвет: белый по синему */
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
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 /* standard input/output */
#include /* system typedefs */
#include /* terminal input/output */
#include /* signals */
#include /* file control */