![](http://www-900.ibm.com/developerWorks/cn/i/c.gif) |
Curses是为了适应网络上各式各样不同类型的终端,而在UNIX上特别发展出的一套函数库,以专门用来处理UNIX上光标移动及屏幕的显示。本文将为你讲述Curses的编程技巧,并为你提供一个汉诺塔游戏的例程。
汉诺塔游戏 汉诺塔游戏是一种古老的游戏:它有三个木柱和大小各异的盘,这些盘都能套进木柱。木柱的编号分别为1、2和3,要利用2柱把1柱上的盘都放到3柱上为之胜出。移动的规则是这样:每次只能移动柱最上面的一个盘;任何盘都不得放在比它小的盘上。
现在我们用Curses函数在终端上实现这个游戏。
Curses的由来 为了因应网络上各式各样的终端类型,在UNIX上特别发展出一套函数库,专门用来处理UNIX上光标移动及屏幕的显示。Curses最早是由柏克莱大学开发出来的,当时发展此函数库主要原因是为了提高程序对不同终端的兼容性。利用Curses开发出来的程序是与终端无关的,这对于网络上的程序是一件相当重要的事。Curses将所有终端的资料存放在一个资料库中,有了这些记录,Curses程序就能知道遇到的是哪一种终端。 Curses函数介绍 下面介绍一些常用的Curses函数。 WINDOW *initscr(void) initscr()函数通常是Curses程序第一个调用函数,用于判断终端类型和初始化Curses数据结构,同时也对终端进行一次刷新以清除屏幕,为以后的操作做准备。若执行时发生错误则在标准错误输出中输出错误信息并退出,正确执行就返回stdscr这一属于WINDOW类型的变量。 int endwin(void) 每一个Curses程序在退出或要临时脱离Curses模式时都应调用endwin()函数。调用这个函数将恢复tty终端原来的状态,把光标移到屏幕的左下角,重置终端为正确的非虚拟模式. bool has_colors(void) has_colors()例程无需参数。若终端能产生彩色,则返回TRUE;否则返回FALSE。程序员通过调用它去判断终端是否可以使用彩色。 int use_default_colors(void) use_default_colors例程是Curses库的一个扩展,它告诉Curses库把终端设置为缺省的前景/背景,缺省时前景为白色,背景为黑色.成功设置返回OK,否则返回ERR,两者都是整型数值。 int init_pair(short pair, short f, short b) init_pair()例程用于更改一个彩色对的定义。彩色对是Curses的一个概念,它用一个整型数值去标志一对前景/背景彩色。该例程有三个参数:彩色对数值pair,其范围从1到 COLOR_PAIRS-1; f(foreground)指定前景彩色; b(background)指定背景彩色。Curses预定义的彩色有:
- COLOR_BLACK-黑色
- COLOR_WHITE-白色
- COLOR_RED-红色
- COLOR_GREEN-绿色
- COLOR_YELLOW-黄色
- COLOR_BLUE-蓝色
- COLOR_MAGENTA-洋红
- COLOR_CYAN-青色
若你嫌这几种彩色太单调,还可用init_color()来自定义。
int echo(void) 用于回显从键盘输入的字符
int noecho(void) 不在终端中显示从键盘输入的字符 int beep(void) 向终端发出一个声频警告,成功执行则返回OK,否则返回ERR int attrset(int attrs) attrset例程把当前窗口设置为参数attrs所指定的属性,其中常用的有:
- A_UNDERLINE--加底线
- A_REVERSE--反白
- A_BLINK--闪烁
- A_BOLD--高量度
- A_MORMAL--标准模式,也用于恢复
int refresh(void) 当在终端上进行任何操作或修改后,所期望的效果只有在调用refresh()后才能显现出来。 int mvprintw(int y, int x, char *fmt [, arg] ...) mvwprintw()用于在屏幕上y行x列的指定位置输出一格式化的字符串,格式化用法与printf()相同。 int mvaddnstr(int y, int x, const char *str, int n) 在屏幕指定位置输出一字符串,与上述例程相比只缺少格式化输出而已。 int mvaddch(int y, int x, chtype ch) 在屏幕指定位置输出一个字符。 int clrtoeol(void) 删除当前行中从光标到行末的的所有字符 int erase(void) 在屏幕的各个位置复制空白以使屏幕得到清除。 事实上,Curses所提供的函数不下200个,但并没有必要把所有的函数都一一掌握,一切再复杂的函数均可以用一些基本的函数组合而成。Curses函数学得太多,反而有反效果,徒增加自己的困扰。 有了上述的基础,现在就进行实质性的工作。 定义数据结构 把编号为1、2和3的三条柱相比较,发现它们有相同和不同之处:当某一盘均放在所有柱上时,若不理会各柱的编号,将很难分辨这些柱,它们都是相同的,因此如果有一数据结构能描述其中一条柱,那么这一数据结构都适用于其余的柱;由游戏规则可知,一个盘只能在一条柱上,各条柱因有不同的盘而不同,柱上的盘应是该柱的属性,因有它从而能从其他柱中区别出来。根据上述分析,抽象出用于描述柱的数据结构如下:
struct Peg { size_t Length[MAXTILES]; /*自底而上,存放盘的大小,盘的大小应该是各不同的*/ int Count; /*记录柱上的盘的总数*/ };
| 定义数据结构是第一步,也是至关重要的一步。
柱的实现 要在终端上画出文章开头那幅图,使用Curses函数其实并不难。 画出水平直线的算法:
- 设置前景/背景彩色对,
- 从水平直线始端到水平直线末端循环执行mvaddch()输出空格,便得到一水平彩色条。
画出垂直直线的算法与上述算法基本相同,只是从垂直直线始端到垂直直线末端循环执行。 三条柱的实现:
for (Line = TOPLINE; Line < BASELINE; Line++) { mvaddch(Line, LEFTPEG, ' '); mvaddch(Line, MIDPEG, ' '); mvaddch(Line, RIGHTPEG, ' '); }
| 其中LEFTPEG,MIDPEG,RIGHTPEG分别是三条柱的位置,垂直长度为从TOPLINE 到BASELINE
盘的实现 为了实现盘堆叠起来时呈尖塔形,盘的长度要形成等差,例如1,3,5,7……。 在画出盘前必需设置struct Peg结构。
static struct Peg Pegs[NPEGS]; /*为三条柱定义三个结构*/ for (Size = NTiles * 2 + 1, SlotNo = 0; Size >= 3; Size -= 2) Pegs[0].Length[SlotNo++] = Size; /*自底而上设置每个盘,Ntiles为柱上盘总数*/ Pegs[0].Count = Ntiles;/*设置柱上盘总数*/
|
然后,就可以画出三条柱上的盘:
for (peg = 0; peg < NPEGS; peg++) { /*分别画出三条柱上的盘,NPEGS为三*/ for (SlotNo = 0; SlotNo < Pegs[peg].Count; SlotNo++) { memset(TileBuf, ' ', Pegs[peg].Length[SlotNo]); TileBuf[Pegs[peg].Length[SlotNo]] = '/0'; /*设置盘的长度,即空格串长度*/ if (has_colors()) attrset(COLOR_PAIR(LENTOIND(Pegs[peg].Length[SlotNo]))); else attrset(A_REVERSE); /*设置彩色*/ mvaddstr(BASELINE - (SlotNo + 1), (int) (PegPos[peg] - Pegs[peg].Length[SlotNo] / 2),TileBuf); /*画出盘*/ } }
|
接收输入的实现 Curses有一个getch()例程是专门用来捕获键盘输入,返回的是所捕获的字符。 移动盘的实现 实现盘的移动前首先要判断输入是否符合游戏规则。移动的目标是柱上最顶层的盘,输入是有效的柱的编号。假定From起始柱,To终点柱,必须符合以下要求:
- 任何盘都不得放在比它小的盘上,即其长度小于终点柱最顶层的盘的长度。
Pegs[From].Length[Pegs[From].Count - 1] >Pegs[To].Length[Pegs[To].Count - 1] - 输入应是有效的,包括From和To都是柱的编号,在1至3之间;From不等于To,这是没意义的。
符合上述要求的输入,被认为是有效,接着就实现盘的移动。盘的移动实际上是改变标志柱状态的数据结构。首先,要改变起始柱状态:Pegs[From].Count减一,因Length数组是有底而上设置的,当Count减一时柱最顶层的盘就自动丢弃;然后,把目标盘加入到终点柱上,即 Pegs[To].Length[Pegs[To].Count] = Pegs[From].Length[Pegs[From].Count]; Pegs[To].Count++; 再根据数据结构重画一次屏幕。 给出完整的程序如下:
/************************************************* * 正常运行: hanoi -n <步数> * * 示例: hanoi -a <步数> * * 帮助: hanoi -h * *************************************************/ #include <curses.h> #include <string.h> #include <unistd.h> #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 #define NPEGS 3 #define MINTILES 3 #define MAXTILES 6 #define DEFAULTTILES 7 #define TOPLINE 6 #define BASELINE 16 #define STATUSLINE (LINES-3) #define LEFTPEG 19 #define MIDPEG 39 #define RIGHTPEG 59 #define LENTOIND(x) (((x)-1)/2) #define OTHER(a,b) (3-((a)+(b))) struct Peg { size_t Length[MAXTILES]; int Count; }; static struct Peg Pegs[NPEGS]; static int PegPos[] = {LEFTPEG, MIDPEG, RIGHTPEG}; static int TileColour[] = { COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, }; static int NMoves = 0; static void InitTiles(int NTiles); static void DisplayTiles(void); static void MakeMove(int From, int To); static void AutoMove(int From, int To, int Num); static void Usage(void); static int Solved(int NumTiles); static int GetMove(int *From, int *To); static int InvalidMove(int From, int To); int main(int argc, char **argv) { int NTiles, FromCol, ToCol,opt; unsigned char AutoFlag = 0; if(argc<2 || argc>3){ Usage();exit(0);} while( (opt=getopt(argc,argv,"a:n:h") )!=-1) switch (opt) { case 'n': NTiles = atoi(optarg); if (NTiles > MAXTILES || NTiles < MINTILES) { fprintf(stderr, "步数从 %d 自 %d之间/n", MINTILES, MAXTILES); return EXIT_FAILURE; } AutoFlag = FALSE; break; case 'a': NTiles = atoi(optarg); if (NTiles > MAXTILES || NTiles < MINTILES) { fprintf(stderr, "步数从 %d 自 %d之间/n", MINTILES, MAXTILES); return EXIT_FAILURE; } AutoFlag = TRUE; break; case 'h': case '?': case ':': Usage(); exit(0); } initscr(); if (has_colors()) { int i; int bg = COLOR_BLACK; start_color(); if (use_default_colors() == OK) bg = -1; for (i = 0; i < 9; i++) init_pair(i + 1, bg, TileColour[i]); } cbreak(); /*LINES和COLS是Curses内部变量,用于存储当前终端的行数和列数*/ if (LINES < 24 || COLS < 80) /*标准的终端为24行80列*/ { endwin(); fprintf(stderr, "当前小于 24x80 /n"); return EXIT_FAILURE; } if (AutoFlag) { curs_set(0); leaveok(stdscr, TRUE); } InitTiles(NTiles); DisplayTiles(); if (AutoFlag) { do { noecho(); AutoMove(0, 2, NTiles); } while (!Solved(NTiles)); sleep(2); } else { echo(); for (;;) { if (GetMove(&FromCol, &ToCol)) break; if (InvalidMove(FromCol, ToCol)) { mvaddstr(STATUSLINE, 0, "移 动 无 效 !!"); refresh(); beep(); sleep(2); continue; } MakeMove(FromCol, ToCol); if (Solved(NTiles)) { mvprintw(STATUSLINE, 0, "恭喜!! 恭喜!! 你用 %d 步赢了", NMoves); refresh(); sleep(5); break; } } } endwin(); return EXIT_SUCCESS; } static int InvalidMove(int From, int To) { if (From >= NPEGS) return TRUE; if (From < 0) return TRUE; if (To >= NPEGS) return TRUE; if (To < 0) return TRUE; if (From == To) return TRUE; if (!Pegs[From].Count) return TRUE; if (Pegs[To].Count && Pegs[From].Length[Pegs[From].Count - 1] > Pegs[To].Length[Pegs[To].Count - 1]) return TRUE; return FALSE; } static void InitTiles(int NTiles) { int Size, SlotNo; for (Size = NTiles * 2 + 1, SlotNo = 0; Size >= 3; Size -= 2) Pegs[0].Length[SlotNo++] = Size; Pegs[0].Count = NTiles; Pegs[1].Count = 0; Pegs[2].Count = 0; } static void DisplayTiles(void) { int Line, peg, SlotNo; char TileBuf[BUFSIZ]; erase(); init_pair(20,COLOR_MAGENTA,COLOR_BLACK); attrset(COLOR_PAIR(20)|A_BOLD); mvaddstr(1, 25,"汉 诺 塔 游 戏"); attrset(A_NORMAL); mvprintw(18, 30, "当前步数 : "); init_pair(21,COLOR_RED,COLOR_BLACK); attrset(COLOR_PAIR(21)|A_BOLD); mvprintw(18,41,"%d",NMoves); attrset(A_NORMAL); attrset(A_REVERSE); mvaddstr(BASELINE, 8, " "); for (Line = TOPLINE; Line < BASELINE; Line++) { mvaddch(Line, LEFTPEG, ' '); mvaddch(Line, MIDPEG, ' '); mvaddch(Line, RIGHTPEG, ' '); } mvaddch(BASELINE, LEFTPEG, '1'); mvaddch(BASELINE, MIDPEG, '2'); mvaddch(BASELINE, RIGHTPEG, '3'); attrset(A_NORMAL); for (peg = 0; peg < NPEGS; peg++) { for (SlotNo = 0; SlotNo < Pegs[peg].Count; SlotNo++) { memset(TileBuf, ' ', Pegs[peg].Length[SlotNo]); TileBuf[Pegs[peg].Length[SlotNo]] = '/0'; if (has_colors()) attrset(COLOR_PAIR(LENTOIND(Pegs[peg].Length[SlotNo]))); else attrset(A_REVERSE); mvaddstr(BASELINE - (SlotNo + 1), (int) (PegPos[peg] - Pegs[peg].Length[SlotNo] / 2), TileBuf); } } attrset(A_NORMAL); refresh(); } static int GetMove(int *From, int *To) { attrset(A_REVERSE); mvaddstr(LINES-1, 20,"<Q>/<q> 退出 <1>-<3> 移动"); attrset(A_NORMAL); mvaddstr(STATUSLINE, 0, "下一步: 从 "); clrtoeol(); refresh(); *From = getch(); if ((*From == 'q')||(*From == 'Q') )return TRUE; *From -= ('0' + 1); addstr(" 到 "); clrtoeol(); refresh(); *To = getch(); if ((*To == 'q') || (*To == 'Q'))return TRUE; *To -= ('0' + 1); refresh(); napms(500); move(STATUSLINE, 0); clrtoeol(); refresh(); return FALSE; } static void MakeMove(int From, int To) { Pegs[From].Count--; Pegs[To].Length[Pegs[To].Count] = Pegs[From].Length[Pegs[From].Count]; Pegs[To].Count++; NMoves++; DisplayTiles(); } static void AutoMove(int From, int To, int Num) { if (Num == 1) { MakeMove(From, To); napms(500); return; } AutoMove(From, OTHER(From, To), Num - 1); MakeMove(From, To); napms(500); AutoMove(OTHER(From, To), To, Num - 1); } static int Solved(int NumTiles) { int i; for (i = 1; i < NPEGS; i++) if (Pegs[i].Count == NumTiles) return TRUE; return FALSE; } static void Usage() { fprintf(stderr, "/nhanoi/t[n]步数 -- 玩游戏/n/ /t[a]步数 -- 示范/n/t[h] /t-- 帮助 /n"); printf("/e[0;33m其中步数在%d-%d之间/e[0m/n", MINTILES, MAXTILES); } /*----------------the end-----------------*/
|
编译 在Linux上编译Curses程序时,gcc要带 -lcurses参数,即
gcc -o hanoi hanoi.c -lcurses 运行结果如下图:
作者简介 梁俊辉,对Linux的网络应用和程序设计有浓厚兴趣,并且专注于这一方面研究,在IBM developerWorks--Linux专区上发表过多篇文章。 |
| ![](http://www-900.ibm.com/developerWorks/cn/i/c.gif) |