Linux终端图形库编程
/* *drawWin.c */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/times.h> #include<sys/types.h> #include<unistd.h> #include<ncurses.h> #define TBool int #define True 1 #define False 0 #define SHAPE_FOOD '@' //food type #define SHAPE_SNAKE '#' //snake body #define GAMEWIN_YLEN 20 #define GAMEWIN_XLEN 60 #define LOGWIN_YLEN 7 #define LOGWIN_XLEN (GAMEWIN_XLEN) #define LOGBUF_NUM (LOGWIN_YLEN - 2) #define LOGBUF_LEN (GAMEWIN_XLEN - 2) #define MAXLEVEL 12 #define GetSnakeTail(s) ((s)->head->front) WINDOW *logwin; //declare a windows for displaying message #define INITRUNLOG() logwin = newlogw() #define RUNLOG(str) runlog(logwin, str) #define DESTROYRUNLOG() delwin(logwin) int g_level; //level of player WINDOW* newgamew(); WINDOW* newlogw(); void runlog(WINDOW* win, char *str); void cleanline(WINDOW *win, int y, int x); int main() { initscr(); //初始化,进入ncurses模式 raw(); //禁止行缓冲,可以立刻见到结果 noecho(); //不在终端上显示控制字符,比如Ctrl+c keypad(stdscr, TRUE); //运行用户在终端上使用键盘 curs_set(0); //设置光标是否可见,0不可见,1可见,2完全可见 refresh(); //将虚拟屏幕上的内容写到显示屏上,并刷新 g_level = 1; INITRUNLOG(); //创建log窗口,就是就是调用自定义函数newlogw() //打印游戏提示 RUNLOG(" press 'q' or 'Q' to quit."); RUNLOG(" press 'w/s/a/d' or 'W/S/A/D' to move the snake."); RUNLOG("info:"); WINDOW *gwin = newgamew(); //创建游戏窗口,具体由自定义函数newgamew()实现 mvwprintw(gwin, GAMEWIN_YLEN/2, GAMEWIN_YLEN/4, "%s", "hello world"); wrefresh(gwin); getch(); //getch()和getchar()不一样 delwin(gwin); //清除游戏窗口,并释放存储窗口数据结构的内存和信息 DESTROYRUNLOG(); //清除信息展示窗口 endwin(); //退出ncurses模式 return 0; } WINDOW* newlogw() { //参数依次定义窗口的高,宽,起始位置(y,x) WINDOW *win = newwin(LOGWIN_YLEN, LOGWIN_XLEN, GAMEWIN_YLEN + 2, 3); //参数依次为:已知的窗口指针, (0,0)是字符莫瑞诺的行列起始位置 box(win, 0, 0); mvwprintw(win, 0, 2, " LOG "); wrefresh(win); //刷新指定的窗口 return win; } WINDOW *newgamew() { WINDOW *win = newwin(GAMEWIN_YLEN, GAMEWIN_XLEN, 1, 3); box(win, 0, 0); mvwprintw(win, 0, 2, " GAME "); mvwprintw(win, GAMEWIN_YLEN - 1, 2, " Level: %d ", g_level); mvwprintw(win, GAMEWIN_YLEN - 1, 30, " Speed: %d ", (int)(g_level/3)); wrefresh(win); return win; } void runlog(WINDOW *win, char *str) { static char logbuf[LOGBUF_NUM][LOGBUF_LEN] = {0}; static int index = 0; strcpy(logbuf[index], str); int i = 0; for (; i < LOGBUF_NUM; ++i) { cleanline(win, i+1, 1); mvwprintw(win, i+1, 1, logbuf[(index + i) % LOGBUF_NUM]); wrefresh(win); } index = (index + LOGBUF_NUM - 1) % LOGBUF_NUM; } void cleanline(WINDOW *win, int y, int x) { char EMPTYLINE[LOGBUF_LEN] = {0}; memset(EMPTYLINE, ' ', LOGBUF_LEN-1); mvwprintw(win, y, x, EMPTYLINE); wrefresh(win); }
getch()函数:
getch()是编程中所用的函数,这个函数是一个不回显函数,当用户按下某个字符时,函数自动读取,无需按回车,有的C语言命令行程序会用到此函数做游戏,但是这个函数并非标准函数,要注意移植性!
在keypad(stdscr, TRUE)中,stdscr是指一个虚拟窗口,我们所有的操作会先写在stdscr上,然后通过refresh函数将stdscr里的图像显示到屏幕上。
refresh()函数
在我们使用printw时,实际上这个数据被打印到一个叫作“stdscr”的虚拟窗口上,没有被直接输出到屏幕上。printw()函数的作用是不断将一些显示标记和相关的数据结构写在虚拟显示器上,并将这些数据写入 stdscr 的缓冲区内。所以,为了显示这些缓冲区中的数据我们必须使用 refresh()函数告诉 curses 系统将缓冲区的内容输出到屏幕上。这种机制可以使程序员不断在虚拟屏幕上写入数据,而调用 refresh()函数时让一切看起来似乎是一次完成的。因为 refresh()函数只核查窗口和数据中变动的部分,这种富有弹性的设计提供了一个高效的反馈机制。但是这有时很打击初学者的积极性。因为对于初学者来说忘记在输出后调用refresh()函数是很恼人的错误。不过不用很担心,多人也经常犯这样的错误。
然后我们看看INITRUNLOG(): 它实质上是个宏定义,定义了logwin = newlogw(),这里调用了自定义函数newlogw,创建了游戏的信息展示窗口。
窗口(Window)机制是整个 CURSES 的核心概念。你应该已经通过前面的例子看到了所有的函数都是默认在输出“窗口”(stdscr)操作。即使如果你现在设计一个最简单的图形用户界面(GUI),你都需要用到窗口。使用窗口的一个最主要的原因是:通过窗口机制,你可以将屏幕分割为不同的部分,并且同时在不同的区域内分别操作。这样做的可以提高工作效率。另外一个原因是:你应当始终在你的程序中追求一种更好的、更易于管理的设计方式。如果你要设计一个大型的、复杂的用户界面,事先设计好这些部分将会提高你的办事效率。
newwin()函数和box()函数:
在WINDOW* newlogw()函数中可以看到,一个窗口的建立是从newwin()开始的。虽然我们建立了一个窗口,但无法看见它,这和HTML中的div有点相似,你不给div标签添加样式的话,在网页上也是什么都看不见。所以我们得用box函数给已知的窗口加上边框。
mvwprintw()函数:
在指定的窗口中,指定的坐标(y,x)输出指定的内容。