Linux下Curses库使用说明
介绍curses库的使用,介绍curses工作原理、常见的函数使用,Linux下定时器、闹钟信号的使用、最后以一个贪吃蛇游戏案例,介绍curses库的综合应用效果。
这份文档是2017年写的,属于硬盘存货,当初编写时curses库的简介、函数使用很多都参考于网络其他博客,但是时间太久了不清楚来源了。
一、 curses简介
|   全局变量 WINDDW* curscr:当前屏幕 WINDOW* stdscr:标准屏幕 int LINES:终端上的行数 int COLS:终端上的列数 bool TRUE:真标志,1 bool FALSE:假标志,0 int ERR:错误标志,-1 int OK:OK标志,0  |  
  
curses是一个在Linux/Unix下广泛应用的图形函数库.,作用是可以绘制在DOS下的用户界面和漂亮的图形。
curses 的命名是来自一个叫做“cursor optimization " (光标最优化)的双关语。curses 构成了一个工作在底层终端代码之上的封装,并向用户提供了一个灵活高效的API ( Application Programming Interface 应用程序接口)。
curses提供了移动光标,建立窗口,产生颜色,处理鼠标操作等功能。
示例
|   #include <curses.h> int main() { initscr(); //初始化curses模式 printw("hello world!\n"); //虚拟屏幕打印数据 refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
如果要调用ncurses 库中的函数,必须在代码中加载ncurses.h 文件,就是要在C程序开头添加“#include <ncurses.h>”,然后在编译链接时标记lncurses参数。(注:ncurses 库已经包含“stdio.h”)。
因为curses库不在标准路径上, 所以我们要加上 -lcurses连接选项, 像这样:gcc -o hello hello.c -lcurses
格式示例:
|   gcc <程序文件> -lncurses  |  
  
二、curses工作原理
curses窗口是由curses系统定义的一个假想的屏幕,即逻辑屏幕。并不像Windows 平台上的窗口那样具有边框。当curses 初始化的时候,它会默认创建一个叫做stdscr 的窗口。这个窗口的屏幕一般是80列,24 行(根据显示环境的不同,可能会出现不同的大小)。
逻辑屏幕的布局是一个字符数组,数组下标为行号和列号组成。位置(0,0)是屏幕的左上角curses函数使用的坐标是y值(行号)在前,x值(列号)在后。
stdscr数据结构指的是“标准屏幕”,它的工作原理和stdio函数库中的标准输出stdout非常相似,在curses程序里,它是缺省的输出窗口。
curscr数据结构指的是“当前屏幕”。在程序调用refresh之前,输出数据是不会出现在标准屏幕上的。refresh被调用时,curses会比较stdscr与curscr的内容,只刷新发生改变的部分。
在一个curses程序里,输出一个字符的过程是:
(1)使用curses函数刷新一个逻辑屏幕(例如: printw("hello world!\n"); )
(2)请求curses用refresh刷新物理屏幕
重要的几个函数功能解析:
initscr()函数将终端屏幕初始化为curses 模式,它用来清除屏幕上所有的字符,并等待下一步处理。在调用其它的curses 函数前,要先调initscr()函数初始化屏幕。initscr()初始化了curses 系统并且为当前屏幕(也就是“stdscr”)和相关的数据结构分配内存
通过endwin()函数退出curses 模式。endwin()函数释放了curses 子系统和相关数据结构占用的内存必须是在完成所有的curses 操作以后才可以调用。
在使用printw 函数打印“Hello World!!!”时,实际上这个消息打印到了一个叫作“stdscr”的虚拟窗口上,没有被直接输出到屏幕上。
printw()函数的作用是不断将一些显示标记和相关的数据结构写在虚拟显示器上,并将这些数据写入stdscr 的缓冲区内。为了显示这些缓冲区中的数据我们必须使用refresh()函数告诉curses系统将缓冲区的内容输出到屏幕上。
三、curses函数说明
3.1 初始化函数系列
|   WINDOW *initscr(void);  |  
     初始化curses库和ttty (在开始curses编程之前,必须使用initscr()这个函数来开启curses模式)  |  
  
|   int endwin(void);  |  
     关闭curses并重置tty(结束curses编程时,最后调用的一个函数)  |  
  
|   int refresh(void); int wrefresh(WINDOW *win);  |  
     使屏幕按照你的意图显示,刷新物理屏幕显示。  |  
  
3.2 输出函数系列
在curses 函数中有三类输出函数,它们分别是:
addch()系列:将单一的字符打印到屏幕上,可以附加加字符修饰参数的一类函数。
printw()系列:和printf()一样的具有格式化输出的一类函数。
addstr()系列:打印字符串的一类函数。
|   int addch(const chtype ch);  |  
     在光标的当前位置添加给定的字符  |  
  
|   int waddch(WINDOW *win, const chtype ch); int mvaddch(int y, int x, const chtype ch); int mvwaddch(WINDOW *win, int y, int x, const chtypech); int echochar(const chtype ch); int wechochar(WINDOW *win, const chtype ch);  |  
     其他显示字符的函数  |  
  
|   int addstr(const char *str);  |  
     在光标的当前位置添加给定的字符串  |  
  
|   int addnstr(const char *str, int n); int waddstr(WINDOW *win, const char *str); int waddnstr(WINDOW *win, const char *str, int n); int mvaddstr(int y, int x, const char *str); int mvaddnstr(int y, int x, const char *str, int n); int mvwaddstr(WINDOW *win, int y, int x, const char *str); int mvwaddnstr(WINDOW *win, int y, int x, const char *str, int n);  |  
     其他显示字符串的函数  |  
  
|   int printw(const char *fmt, ...);  |  
     和printf()类似,在光标当前位置格式化输出  |  
  
|   int wprintw(WINDOW *win, const char *fmt, ...); int mvprintw(int y, int x, const char *fmt, ...); int mvwprintw(WINDOW *win, int y, int x, const char*fmt, ...); int vwprintw(WINDOW *win, const char *fmt, va_listvarglist); int vw_printw(WINDOW *win, const char *fmt, va_listvarglist);  |  
     其他格式化输出函数  |  
  
示例:
|   #include <curses.h> int main() { initscr(); //初始化curses模式 printw("hello world!\n"); addstr("hello world!\n"); addch('A'); refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
3.3 输入函数系列
输入函数也被分为三种:
getch ()系列:读取一个字符的一类函数。
scanw()系列:按照格式化读取输入的一类函数。
getstr()系列:读取字符串的一类函数。
|   int getch(void);  |  
     从键盘读入一个字符  |  
  
|   int getch(void); int wgetch(WINDOW *win); int mvgetch(int y, int x); int mvwgetch(WINDOW *win, int y, int x);  |  
     其他字符的读取函数  |  
  
|   int scanw(char *fmt, ...);  |  
     和标准io的scanf()类似用法,用来获取用户的输入  |  
  
|   int wscanw(WINDOW *win, char *fmt, ...); int mvscanw(int y, int x, char *fmt, ...); int mvwscanw(WINDOW *win, int y, int x, char *fmt,...);  |  
     其他scanw系列函数  |  
  
|   int getstr(char *str);  |  
     当前刚把位置读取字符串  |  
  
|   int getnstr(char *str, int n); int wgetstr(WINDOW *win, char *str); int wgetnstr(WINDOW *win, char *str, int n); int mvgetstr(int y, int x, char *str); int mvwgetstr(WINDOW *win, int y, int x, char *str); int mvgetnstr(int y, int x, char *str, int n); int mvwgetnstr(WINDOW *, int y, int x, char *str, int n);  |  
     其他系列读取字符串的函数  |  
  
示例:
|   #include <curses.h> int main() { char buff[100]; initscr(); //初始化curses模式 scanw("%s",buff); printw("%s\n",buff); refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
3.4 屏幕光标位置处理
|   void getyx(WINDOW *win, int y, int x);  |  
     用来取得当前光标的位置  |  
  
|   void getmaxyx(WINDOW *win, int y, int x);  |  
     用于取得窗口行,列  |  
  
|   int move(int y, int x); int wmove(WINDOW *win, int y, int x);  |  
     把逻辑光标的位置移动到指定的地点。 如果我们想让光标位置在调用move函数后立刻发生变化,就必须在它的后面立刻跟上一个refresh()调用。  |  
  
示例:
|   #include <curses.h> int main() { WINDOW *win=initscr(); //初始化curses模式 printw("hello world!\n"); wmove(win,10,10); addstr("hello world!\n"); int y,x; getyx(win,y,x); printw("y=%d,x=%d\n",y,x); refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
示例:(屏幕最大的行和列)
|   #include <curses.h> int main() { WINDOW *win=initscr(); //初始化curses模式 printw("x=%d,y=%d\n",COLS,LINES); //2:获取打印当前逻辑屏幕最大的行和列 int x,y; getmaxyx(stdscr,y,x); //2:获取当前逻辑屏幕最大的行和列 printw("x=%d,y=%d\n",x,y); refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
3.5 清除屏幕函数
|   int erase(void); int werase(WINDOW *win);  |  
     在每个屏幕空白位置写上空白符  |  
  
|   int clear(void); int wclear(WINDOW *win);  |  
     清除整个屏幕,要配合refresh()使用  |  
  
示例:
|   #include <curses.h> int main() { WINDOW *win=initscr(); //初始化curses模式 printw("hello world!\n"); //虚拟屏幕打印数据 clear(); //清除屏幕 refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
3.6 设置字符的属性
每个curses字符都可以有特定的属性,属性控制着这个字符在屏幕上的显示方式,当然前提是显示设备硬件能够支持要求的属性
|   属性名  |  
     描述  |  
  
|   A_NORMAL  |  
     普通字符输出  |  
  
|   A_STANDOUT  |  
     终端字符最亮  |  
  
|   A_UNDERLINE  |  
     下划线  |  
  
|   A_REVERSE  |  
     字符反白显示  |  
  
|   A_BLINK  |  
     闪动显示  |  
  
|   A_BOLD  |  
     加亮加粗  |  
  
|   A_PROTECT  |  
     保护模式  |  
  
|   A_INVIS  |  
     空白显示模式  |  
  
|   A_DIM  |  
     半亮显示  |  
  
|   A_CHARTEXT  |  
     字符掩盖  |  
  
|   A_ALTCHARSET  |  
     字符交替  |  
  
函数集合:
|   int attroff(int attrs); //关闭指定的属性 int wattroff(WINDOW *win, int attrs); int attron(int attrs); //开启指定的属性 int wattron(WINDOW *win, int attrs); int attrset(int attrs);//设置属性 int wattrset(WINDOW *win, int attrs); int standend(void); //关闭“突出”模式 int wstandend(WINDOW *win); int standout(void);//开启“突出”模式 int wstandout(WINDOW *win);  |  
  
示例:
|   #include <curses.h> int main() { WINDOW *win=initscr(); //初始化curses模式 attron(A_UNDERLINE|A_BOLD); //设置显示属性 printw("hello world!\n"); refresh();//刷新到物理显示器 getch(); //获取用户输入 endwin(); //退出curses模式 return 0; }  |  
  
3.7 窗口创建与边框绘制
窗口(Window)机制是整个curses 系统的核心,可能需要将屏幕分成几个部分并分别处理,然而,将屏幕拆分成各个窗口,然后独立处理每个窗口是比较高效的方法。
WINDOW结构通常定义在ncurses.h头文件里,对它的存取操作必须按照规定的指令进行,程序永远不允许直接访问它标准屏幕stdscr是WINDOW结构的一个特例。
函数:
|   WINDOW *newwin(int nlines, int ncols, int begin_y,int begin_x);  |  
     创建一个新窗口,窗口从屏幕位置 (start_y,start_x)开始,尺寸由分别代表行数和列数的lines和cols参数指定。 创建成功,返回一个指向新窗口的指针 创建失败,返回NULL  |  
  

|   int delwin(WINDOW *win);  |  
     删除一个通过newwin函数创建的窗口。 调用newwin的时候可能已经分配过内存,所以只要某个窗口不再需要使用,最好立刻删掉它,但千万不要去尝试删除curse自己的窗口stdscr和curscr!  |  
  
|   int box(WINDOW *win, chtype verch, chtype horch);  |  
     在已定义的窗口外围画上边框  |  
  

示例:
|   #include <curses.h> int main() { initscr(); //初始化curses模式 WINDOW *win=newwin(5,20,5,5); box(win,0,0); //绘制边框 //wprintw(win,"hello world!\n"); wrefresh(win);//刷新到物理显示器   wgetch(win); //获取用户输入 wclear(win); //清空内容 delwin(win); //删除窗口 endwin(); //退出curses模式 return 0; }  |  
  
3.8 刷新窗口
|   int refresh(void); int wrefresh(WINDOW *win);  |  
     刷新指定窗口,同refresh()  |  
  
|   int touchwin(WINDOW *win); int touchline(WINDOW *win, int start, int count);  |  
     告诉curses,指定win发生变化,下次刷新时要刷新该窗口  |  
  
3.9 彩色字体显示
大多数的curses实现都开始支持彩色显示功能,屏幕的每一个字符位置都可以从多种颜色里挑一种写上去,它的背景也可以从多种颜色里挑选,通过has_colors()函数来判断终端是否支持彩色显示功能,如果可以返回true,否则返回false。
|   bool has_colors(void);  |  
     判断当前终端是否支持彩色字体显示  |  
  
|   int start_color(void);  |  
     对curses的彩色例程进行初始化。 要启动彩色机制,必须先调用start_color()函数,之后就可以在终端屏幕上调用其它处理颜色的函数  |  
  
|   int init_pair(short pair, short f, short b);  |  
     对准备使用的颜色进行初始化。 用下面的语句可以把绿色背景,红色前景定义为第一号颜色组合 init_pair(1,COLOR_RED,COLOR_GREEN)  |  
  
|   int COLOR_PAIR (int pair_number);  |  
     调用你已定义的颜色。 如:attron(COLOR_PAIR(1));  |  
  
颜色组合
|   COLOR_BLACK  |  
     黑色  |  
  
|   COLOR_RED  |  
     红色  |  
  
|   COLOR_GREEN  |  
     绿色  |  
  
|   COLOR_YELLOW  |  
     黄色  |  
  
|   COLOR_BLUE  |  
     蓝色  |  
  
|   COLOR_MAGENTA  |  
     洋红色  |  
  
|   COLOR_CYAN  |  
     蓝绿色,青色  |  
  
|   COLOR_WHITE  |  
     白色  |  
  
示例:
|   #include <curses.h> int main() { initscr(); //初始化curses模式 if(has_colors()==0) { printw("NONONO\n"); endwin(); getch(); exit(1); } start_color(); init_pair(1, COLOR_RED, COLOR_BLUE); attron(COLOR_PAIR(1)); mvprintw(LINES/2,2, "yes yes yes\n"); attroff(COLOR_PAIR(1)); getch(); endwin(); //退出curses模式 return 0; }  |  
  
3.10 改变窗口前后背景色wbkgd
|   int bkgd(chtype ch); //用来改变窗口前后背景色 int wbkgd(WINDOW *win, chtype ch);  |  
  
示例:
|   #include <curses.h> int main() { initscr(); //初始化curses模式 if(has_colors()==0) { printw("NONONO\n"); endwin(); getch(); exit(1); } start_color(); init_pair(1,COLOR_RED,COLOR_BLUE); attron(COLOR_PAIR(1)); mvprintw(LINES/2,2, "yes yes yes\n"); attroff(COLOR_PAIR(1)); bkgd(COLOR_PAIR(1)); //背景色 getch(); endwin(); //退出curses模式 return 0; }  |  
  
3.11 设置键盘的工作方式
对工作模式进行设置的函数如下所示:
|   函数名  |  
     描述  |  
  
|   echo(void)  |  
     开启输入字符的回显功能  |  
  
|   noecho()  |  
     关闭输入字符的回显功能  |  
  
|   cbreak()  |  
     关闭“预处理模式”  |  
  
|   nocbreak()  |  
     开启“预处理模式”  |  
  
|   raw()  |  
     关闭“预处理模式”  |  
  
|   noraw()  |  
     开启“预处理模式”  |  
  
raw()和cbreak()的区别
通常情况下,终端驱动程序会缓冲用户输入的字符,直到遇到换行符或回车符后,这些字符才可以被使用。
raw()和cbreak()两个函数都可以禁止行缓冲(line buffering)。
在raw()函数模式下,处理挂起(CTRL+Z)、中断或退出(CTRL+C)等控制字符时,将直接传送给程序去处理而不产生终端信号;
cbreak()模式下,控制字符将被终端驱动程序解释成其它字符。
3.12 获取键盘按键
对各个终端来说,它的每一个功能键发送出来的转义序列通常都被保存在一个terminfo结构里,而头文件curses.h通过一组以“KEY_”为前缀的定义把功能键都预先安排好了.(grep KEY ncurses.h | less)curses在启动时会关闭转义序列与逻辑按键之间的转换功能。
|   int keypad(WINDOW *win, bool bf);  |  
     激活keypad模式,开启转义序列与逻辑按键之间的转换功能  |  
  
示例:
|   #include <curses.h> int main() { int key; initscr(); //初始化curses模式 cbreak(); keypad(stdscr,TRUE); noecho(); clear(); mvprintw(5,5,"key pad demonstration.press 'q' to quit"); move(7,5); refresh(); key = getch(); while(key != ERR && key != 'q') { move(7,5); clrtoeol(); if( (key >= 'A' && key <='Z')||(key>='a' && key<='z')) { printw("key was %c",(char)key); } else { switch(key) { case KEY_END:printw("%s","END_KEY");break; case KEY_LEFT:printw("%s","LEFT KEY");break; case KEY_F(1): printw("%s","F1 Key");break; } } refresh(); key = getch(); } endwin(); //退出curses模式 return 0; }  |  
  
3.13 隐藏光标
|   int curs_set(int visibility);  |  
  
填入的值分别为:0、1、2 对应着不可见,正常,高亮。
四、疑难杂症
4.1 中文显示乱码解决
1、引入本地头文件
在main函数文件中添加#include <locale.h>头文件
2、改变gcc编译链接指令
将gcc ..... -lcurses或gcc ..... -lncurses改为gcc ...... -lncursesw
3、设置本地字符编码(最重要的一点)
一般是在main函数中,在初始化curses库的initscr()函数前先调用setlocale(LC_ALL,"")
五、其他函数说明
5.1 Linux下定时器信号
在linux下如果对定时要求不太精确的话,使用alarm()和signal()就行了,但是如果想要实现精度较高的定时功能的话,就要使用setitimer函数。
示例:
|   #include <stdio.h> #include <signal.h> /*信号处理函数*/ void sighandler(int signal_num) { alarm(1); printf("SIGNAL:%d\n",signal_num); } int main(int argc,char **argv) { /*绑定信号到特定的函数*/ signal(SIGALRM,sighandler); alarm(1);   while(1) {   } return 0; }  |  
  
5.2 精确定时
linux上定时函数 setitimer 的使用介绍:
|   #include <sys/time.h> int getitimer(int which, struct itimerval *curr_value); int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);  |  
  
参数介绍:
 which为定时器类型,setitimer支持3种类型的定时器:
ITIMER_REAL: 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: -以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF: 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。
setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。
setitimer()调用成功返回0,否则返回-1。
形参如下
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
};
struct timeval {
long tv_sec;
long tv_usec;
};
it_interval指定间隔时间,it_value指定初始定时时间。如果只指定it_value,就是实现一次定时;如果同时指定 it_interval,则超时后,系统会重新初始化it_value为it_interval,实现重复定时;两者都清零,则会清除定时器。
tv_sec提供秒级精度,tv_usec提供微秒级精度,以值大的为先,注意1s = 1000000us。
ovalue用来保存先前的值,常设为NULL。
如果是以setitimer提供的定时器来休眠,只需阻塞等待定时器信号就可以了。
|   struct itimerval { struct timeval it_interval; /* 初始定时时间*/ struct timeval it_value; /* 间隔时间 */ }; struct timeval { long tv_sec; /* 秒单位 */ long tv_usec; /* 微秒单位*/ };  |  
  
实现一次定时
|   #include <stdio.h> #include <sys/time.h> #include <signal.h> void sighandler(int signalnum) { printf("%d\n",signalnum); } int main(int argc,char *argv[]) { signal(SIGALRM,sighandler);   struct itimerval time; //时间结构体 time.it_interval.tv_sec=0; time.it_interval.tv_usec=0;   time.it_value.tv_sec=1; //实现一次定时 time.it_value.tv_usec=1000; setitimer(ITIMER_REAL,&time,NULL); while(1) {   } return 0; }  |  
  
实现重复定时
|   #include <stdio.h> #include <sys/time.h> #include <signal.h> void sighandler(int signalnum) { printf("%d\n",signalnum); } int main(int argc,char *argv[]) { signal(SIGALRM,sighandler);   struct itimerval time; //时间结构体 time.it_interval.tv_sec=1; //实现重复定时 time.it_interval.tv_usec=1000;   time.it_value.tv_sec=1; time.it_value.tv_usec=1000; setitimer(ITIMER_REAL,&time,NULL); while(1) {   } return 0; }  |  
  
六、界面编写示例
6.1 贪吃蛇游戏界面示例

6.2 贪吃蛇游戏代码示例
编译时加上: -lncursesw
示例:# gcc test.c -lncursesw
#include <stdio.h>  
#include <stdlib.h>  
#include <curses.h>  
#include <signal.h>  
#include <sys/time.h>  
#include <fcntl.h>  
#include <time.h>  
#include <locale.h>
  
#define INIT_POS_X         12  
#define INIT_POS_Y         40  
#define INIT_SNAKE_LENGTH  5  
#define MOVE_LEFT          1  
#define MOVE_RIGHT         2  
#define MOVE_UP            3  
#define MOVE_DOWN          4  
  
struct Snake  
{  
  int pos_x;  
  int pos_y;  
  struct Snake *pNext;  
  struct Snake *pPre;  
} *pSnakeHead,*pSnakeTail,*pSnakeTrace,*pSnakeFood;  
  
int  move_dir;  
int  is_game_over = 0;  
int  speed[5] = {200,150,120,100,80};  
int  snake_len = INIT_SNAKE_LENGTH;  
int  current_score;  
int  levelup_score[5] = {5,10,15,20,25};  
int  level = 0;  
int  life = 3;  
void init_wall();  
void init_snake();  
void show_snake(struct Snake *pSnake);  
void snake_move(int sig);  
int  set_ticker(long n_msecs);  
void getOrder();  
int  is_snake_dead();  
void put_food();  
void eat_food();  
void levelup();  
void over(int i);  
void reset_game();  
void delete_snake();  
  
int main(void)  
{  
  setlocale(LC_ALL,""); //设置本地编码,用于支持中文
  initscr();   //初始化curses模式
  clear();     //清除屏幕
  curs_set(0); //隐藏光标 
  init_wall();  //初始化界面显示
  init_snake();  
  put_food();  
  set_ticker(speed[level]);  
  noecho();  
  keypad(stdscr, true);  
  getOrder();  
  system("stty echo");  
  system("clear");  
  endwin();  
  exit(0);  
}  
//初始化界面显示
void init_wall()  
{  
  int i;
  addstr(" Linux下C语言版贪吃蛇游戏");   
  for(i = 0; i <COLS ; ++i)  
    {  
      move(3,i);  
      addstr("✪");  
    }  
  
  move(2,0);  
  printw("生命: %d", life);  
  move(2,COLS-10);  
  printw("分数:%2d",current_score);  
  move(2,COLS/2-5);  
  printw("次数:%d",level+1);  
  
  //设置背景颜色
  //start_color();
  //init_pair(1,COLOR_BLACK,COLOR_BLUE);
  //attroff(COLOR_PAIR(1)); //字体颜色
  //bkgd(COLOR_PAIR(1)); //背景色
  refresh();  
}  
  
void init_snake()  
{  
  int i;  
  struct Snake *pSnakePre;  
  int flag=0;  
  pSnakeTrace = (struct Snake *)malloc(sizeof(struct Snake));  
  pSnakeTrace->pos_x = 0;  
  pSnakeTrace->pos_y = 0;  
  for(i = 0; i < INIT_SNAKE_LENGTH; ++i)  
    {  
      struct Snake *pSnakeBody = (struct Snake *)malloc(sizeof(struct Snake));  
      pSnakeBody->pos_x = INIT_POS_X;  
      pSnakeBody->pos_y = INIT_POS_Y+INIT_SNAKE_LENGTH-i;  
      if (flag == 0)  
		{  
		  pSnakeHead = pSnakePre = pSnakeBody;  
		  pSnakeBody->pPre = NULL;  
		  flag++;  
		}  
		else  
		{  
		  pSnakePre->pNext = pSnakeBody;  
		  pSnakeBody->pPre = pSnakePre;  
		  pSnakeBody->pNext = NULL;  
		  pSnakePre = pSnakeBody;  
		}  
    }  
  pSnakeTail = pSnakePre;  
  move_dir = MOVE_RIGHT;  
  signal(SIGALRM,snake_move);  
  show_snake(pSnakeHead);  
}  
  
int set_ticker( long n_msecs )  
{  
  struct itimerval new_timeset;  
  long    n_sec, n_usecs;  
    
  n_sec = n_msecs / 1000 ;  
  n_usecs = ( n_msecs % 1000 ) * 1000L ;  
    
  new_timeset.it_interval.tv_sec  = n_sec;  
  new_timeset.it_interval.tv_usec = n_usecs;  
  new_timeset.it_value.tv_sec     = n_sec  ;  
  new_timeset.it_value.tv_usec    = n_usecs ;  
    
  return setitimer(ITIMER_REAL, &new_timeset, NULL);  
}  
void show_snake(struct Snake *pSnake)  
{  
  while(pSnake)  
    {  
       move(pSnake->pos_x,pSnake->pos_y);  
       addstr("☯"); //蛇身图案 
       pSnake = pSnake->pNext;  
    }  
  move(pSnakeTrace->pos_x,pSnakeTrace->pos_y);  
  addstr(" ");  
  refresh();  
}  
  
void snake_move(int sig)  
{  
  noecho();  
  pSnakeTrace->pos_x = pSnakeTail->pos_x;  
  pSnakeTrace->pos_y = pSnakeTail->pos_y;  
  struct Snake *ptemp;  
  ptemp = pSnakeTail->pPre;  
  pSnakeTail->pPre->pNext = NULL;  
  pSnakeTail->pPre = NULL;  
  pSnakeTail->pNext = pSnakeHead;  
  pSnakeHead->pPre = pSnakeTail;  
  pSnakeHead = pSnakeTail;  
  pSnakeTail = ptemp;  
  
  switch(move_dir)  
    {  
    case MOVE_LEFT:  
      pSnakeHead->pos_y = pSnakeHead->pNext->pos_y-1;  
      pSnakeHead->pos_x = pSnakeHead->pNext->pos_x;  
      break;  
    case MOVE_RIGHT:  
      pSnakeHead->pos_y = pSnakeHead->pNext->pos_y+1;  
      pSnakeHead->pos_x = pSnakeHead->pNext->pos_x;  
      break;  
    case MOVE_UP:  
      pSnakeHead->pos_x = pSnakeHead->pNext->pos_x-1;  
      pSnakeHead->pos_y = pSnakeHead->pNext->pos_y;  
      break;  
    case MOVE_DOWN:  
      pSnakeHead->pos_x = pSnakeHead->pNext->pos_x+1;  
      pSnakeHead->pos_y = pSnakeHead->pNext->pos_y;  
      break;  
    default:  
      break;  
    }  
  
  if((is_game_over = is_snake_dead()) != 0)  
    {  
      over(is_game_over);  
    }  
  
  if((pSnakeHead->pos_x == pSnakeFood->pos_x)  
      && (pSnakeHead->pos_y == pSnakeFood->pos_y))  
    {  
      eat_food();  
      put_food();  
      current_score++;  
      move(2,COLS-10);  
      printw("分数:%2d",current_score);  
    }  
  show_snake(pSnakeHead);  
  if(current_score == levelup_score[level])  
    {  
      levelup();  
    }  
  signal(SIGALRM,snake_move);  
}  
  
void getOrder()  
{  
   while(1)  
   {  
     char c = getch();  
     switch(c)  
	 {  
	 case 'a':  
	   move_dir=MOVE_LEFT;  
	   break;  
	 case 'd':  
	   move_dir=MOVE_RIGHT;  
	   break;  
	 case 'w':  
	   move_dir=MOVE_UP;  
	   break;  
	 case 's':  
	   move_dir=MOVE_DOWN;  
	   break;  
	 case 'q':  
	   system("stty echo");  
	   system("clear");  
	   endwin();  
	   exit(0);  
	   break;  
	 default:  
	   break;  
	 }  
   }  
}  
int is_snake_dead()  
{  
  struct Snake *pSnakeBody = pSnakeHead->pNext;  
  int k=0;  
  while(pSnakeBody)  
    {  
      if((pSnakeHead->pos_x == pSnakeBody->pos_x)  
     && (pSnakeHead->pos_y == pSnakeBody->pos_y) )  
    {  
      return 2;  
    }  
      pSnakeBody = pSnakeBody->pNext;  
      k++;  
    }  
  if ((pSnakeHead->pos_x == 3) || (pSnakeHead->pos_x == LINES)  
      || (pSnakeHead->pos_y == 0) || (pSnakeHead->pos_y == COLS) )  
    {  
      return 1;  
    }  
  return 0;  
}  
  
void put_food()  
{  
  int food_pos_x,food_pos_y;  
  int rangex,rangey;  
  int flag=0;  
  srand(time(NULL));  
  rangex = LINES-4;  
  rangey = COLS-1;  
  struct Snake *pTemp = pSnakeHead;  
  while(1)  
  {  
      flag = 0;  
      pTemp = pSnakeHead;  
      food_pos_x = rand()%rangex + 4;  
      food_pos_y = rand()%rangey + 1;  
    while(pTemp)  
    {  
      if((pTemp->pos_x == food_pos_x)&&(pTemp->pos_y == food_pos_y) )  
		{  
          flag++;  
        }  
      pTemp = pTemp->pNext;  
    }  
    
	if(flag ==0)  
    {  
      break;  
    }  
    }  
  struct Snake *pPutFood = (struct Snake *)malloc(sizeof(struct Snake));  
  pSnakeFood = pPutFood;  
  pSnakeFood->pos_x = food_pos_x;  
  pSnakeFood->pos_y = food_pos_y;  
  pSnakeFood->pPre = NULL;  
  pSnakeFood->pNext = NULL;  
  move(pSnakeFood->pos_x,pSnakeFood->pos_y);  
  addstr("❤");  //食物图案 
}  
  
void eat_food()  
{  
  pSnakeFood->pNext = pSnakeHead->pNext;  
  pSnakeHead->pNext->pPre = pSnakeFood;  
  pSnakeHead->pNext = NULL;  
  pSnakeTail->pNext = pSnakeHead;  
  pSnakeHead->pPre = pSnakeTail;  
  pSnakeTail = pSnakeHead;  
  pSnakeHead = pSnakeFood;  
}  
  
void levelup()  
{  
  if (level == 4)  
    {  
      move(0, 1);  
      addstr("完成游戏,按任意键重启游戏");  
      refresh();  
      set_ticker(0);  
      char c = getchar();  
      level = 0;  
      life = 3;  
      reset_game();  
    }  
  else  
    {  
      move(0, 1);  
      addstr("按任意键继续     ");  
      refresh();  
      set_ticker(0);  
      char c = getchar();  
      level++;  
      reset_game();  
  }  
}  
  
void delete_snake()  
{  
  struct Snake *pSnake;  
  pSnake = pSnakeHead;  
  while(pSnake)  
    {  
      free(pSnake);  
      pSnake = pSnake->pNext;  
    }  
  free(pSnakeFood);  
}  
  
void reset_game()  
{  
  current_score = 0;  
  delete_snake();  
  initscr();  
  clear();  
  init_wall();  
  init_snake();  
  put_food();  
  set_ticker(speed[level]);  
}  
  
void over(int i)  
 {  
   life--;  
   move(2,COLS/2-5);  
   printw("次数: %d", life);  
   if (life == 0)  
     {  
       move(0, 1);  
       addstr("游戏结束后,按任意键重启游戏               ");  
       refresh();  
       life = 3;  
       set_ticker(0);  
       char c = getchar();  
       level = 0;  
       reset_game();  
     }  
   else  
     {  
       move(0, 1);  
       if(1 == i)  
       {  
         addstr("撞墙,按任意键继续");  
       }  
       else if(2 == i)  
       {  
         addstr("咬尾事故,按任意键继续   ");  
       }  
       refresh();  
       set_ticker(0);  
       char c = getchar();  
       reset_game();  
     }  
}  
        - 点赞
 - 收藏
 - 关注作者
 
            
           
评论(0)