目 录 | |
一 | Curses库简介与基本开发方法 |
二 | 事件驱动编程:编写一个视频游戏 |
三 | 弹球游戏的实现与优化 |
四 | 简单TUI游戏设计实践总结 |
curses其实是一个函数开发包,专门用来进行UNIX下终端环境下的屏幕界面处理以及I/O处理。经过这些函数库,C和C++程序就能够控制终端的视频显示以及输入输出。html
使用curses包中的函数,用户能够很是方便的建立和操做窗口,使用菜单以及表单,并且最为重要的一点是使用curses包编写的程序将独立于各类具体的终端,程序具备良好的移植性。node
库名 | 描述 | 头文件 |
curses | 最先的curses包只包含这一部分,主要控制屏幕的输入和输出,光标的操做,窗口的建立和操做等。 | curses.h |
panel | 相似于窗口堆栈,不一样的窗口能够存放于其中,而且能够在其中进行移动。 | panel.h |
menu | 新增的部分,主要包括建立菜单而且与之交互的函数,主要用来接受用户的选择。 | menu.h |
form | 包括建立表单以及与之进行交互的函数,主要用来接受用户数据输入。 | form.h |
curses包的这种终端独立性归根于终端描述数据库terminfo和termcap。linux
终端描述数据库(Terminal Description Databases) - terminfo(TERMinal INFOrmation database) - termcap(TERMinal CAPabilitie database)
这两个数据库里存放了不一样终端的操做控制码和转义序列以及其他相关信息。ios
使用每个终端:数据库
- curses将首先在终端描述数据库中查找是否存在该类型的终端描述信息 - 若是找到,则进行相应的处理。 - 若是数据库中没有相应信息,则程序没法在该终端上运行,除非用户本身增长新的终端描述。
安装编程
sudo apt-get install libncurses5-dev
若发现找不到这个包,可使用命令 sudo apt-get update 更新下包源。数组
gcc program.c -o program -lcurses
程序中使用curses库函数:引用curses库的头文件curses.h,即:缓存
#include <curses.h>
<stdio.h>
和<unclt.h>
一块儿包含进来,若是对于System V系统,<terminfo.h>
也会包含进来,另外还可能包括<termios.h>
、<termio.h>
、<sgtty.h>
,具体的由系统自己决定。编译时加上-lcurses,用来在连接的时候提示连接程序将curses库连接进去。安全
整型常量 | 定 义 |
OK | curses函数运行成功的返回值,系统定义为0 |
ERR | curses函数运行发生错误时候的返回值,系统定义为-1 |
TRUE | 布尔值,表示为真,系统定义为1 |
FALSE | 布尔值,表示为假,系统定义为0 |
在主函数中设置了信号处理函数以后咱们就调用了initscr(),通常状况下在其他的curses函数被调用以前咱们就必须首先调用initscr()。数据结构
initscr()对curses包进行一些初始化的工做,并且在每个程序里面,这个函数只能调用一次。它的做用主要包括:
- 经过读取TERM环境变量的值来决定当前使用的终端类型,开启终端模式。 - 根据终端的具体状况将终端的一些性能参数读进相关变量中,完成对相关数据结构的初始化工做,例如获取LINES和COLS的值。 - 建立和初始化标准屏幕stdscr和当前屏幕curscr,同时为它们分配必要的存储空间。 - 通知refresh()函数首次调用的时候可以清除屏幕。
若是程序中使用了curses库,那么在程序初始化的时候,系统将自动产生两个默认屏幕。
第一个:标准屏幕,系统定义为stdscr,表明终端的整个屏幕区域。 第二个:当前屏幕,系统定义为curscr,表明用户可以看到的屏幕显示。
curses库的屏幕刷新机制:
若是咱们对当前屏幕进行更改而尚未调用刷新函数,那么标准屏幕就仅是一个虚拟屏幕。
程序使用initscr()进行初始化以后,程序对终端的模式进行了一些设置。终端模式其实是一系列的开关属性,它们直接影响着终端如何处理输入以及输出。例如:
- 当咱们敲入字符的时候,系统将根据模式属性设置来判断字符是否须要当即在屏幕上回显。 - 系统根据模式设置决定读取字符后的处理方法,是当即一一读取仍是暂时先存放在字符缓冲区中。 - 系统判断是否须要将输入Ctrl+D解释为文件结束符。 - 系统判断如何处理功能键F一、F2或者方向键等等,决定是将它们做为普通的键读取仍是做为功能键读取。
用来决定用户的输入是否当即回显。
当ECHO模式设置后,它使得在键盘上输入的每个字符都在终端屏幕上当前光标处显示出来,在调用某些函数如addch()
时字符显示后光标的位置将自动的向后移动一个位置。
在非回显模式下,字符的显示必须由程序自己来完成,不然字符不会显示。
注:非回显模式下按键一般用来控制屏幕的操做而不是用来进行字符输入!
echo() 设置回显模式 noecho() 关闭回显模式 - 成功返回OK,失败返回ERR。 - 默认状况下回显模式是打开的。
字符输入在通常的状况下必须加回车键才能完成,这时候退格键是起做用的,输入的字符能够经过BackSpace键删除掉。可是这种操做并不适合交互操做。
注:中断字符和流控制字符并不受这个模式的影响。
int cbreak() 打开当即输入模式 int nocbreak() 关闭当即输入模式
NEWLINE模式用来决定当输入时回车键是否被对应为新行产生符。
int nl() int nonl()
int keypad(win,buf)
参数:
WINDOW *win 一个WINDOW类型的指针,它指向须要设置功能键模式的窗口 int buf buf为TRUE或者FALSE,用来指定模式的开启和关闭。
功能键定义 | 控制码 | 描 述 |
KEY_MIN | 0401 | Curses中定义的最小的键值 |
KEY_BREAK | 0401 | Break按键 |
KEY_DOWN | 0402 | ↓ |
KEY_UP | 0403 | ↑ |
KEY_LEFT | 0404 | ← |
KEY_RIGHT | 0405 | → |
KEY_HOME | 0406 | Home键 |
KEY_BACKSPACE | 0407 | 退格键backspace |
KEY_F0 | 0410 | 功能键F0 |
KEY_F(n) | KEY_F0+n | 功能键Fn |
KEY_DL | 0510 | 行删除键 |
KEY_IL | 0511 | 行插入键 |
KEY_DC | 0512 | 字符删除键 |
KEY_IC | 0513 | 字符插入键 |
KEY_NPAGE | 0522 | 下一页 |
KEY_PPAGE | 0523 | 上一页 |
KEY_END | 0550 | end按键 |
KEY_MAX | 0777 | 最大的curses键值 |
<tinfo.h>
找到,一般它的目录为/usr/include/
目录下。int raw(); int noraw();
延迟模式包括半延迟模式和无延迟模式。
- int halfdelay(tenth) 设置半延迟模式 - nocbreak() 能够取消终端的半延迟模式 - int nodelay(win,bf) 函数设置终端无延迟模式。
参数:
- int tenth指定半延迟的时间间隔,单位是10毫秒。 - WINDOW *win 指向须要设置无延迟模式的窗口的指针 - bool bf 用来决定开启或者关闭该模式。若bf为TRUE,则设置无延迟模式。
字符和字符串操做是应用程序中使用频率最高的,curses库中的一些基本函数容许咱们从键盘接受输入,而且将结果输出到指定窗口上或者在指定窗口上读写、删除字符和字符串、定位光标位置或者控制字符色彩等。
int addch(ch); int echochar(ch); chtype ch; - 若是函数执行成功,将返回OK常量,不然返回ERR。
参数是一个chtype类型的字符,curses中将chtype声明为无符号长整型,它的低位能够包含字符自己的信息,这部分与char字符类型类似,它的高位部分存储了字符的额外的属性信息,好比字符是否高亮度显示,是否反显以及什么色彩等等。
函数在当前光标处输出一个字符,同时光标将向右移动一个位置。若是移动后光标将超出了当前屏幕的范围,光标将自动换行到下一行的开始位置。
除了经常使用的字符参数之外,addch()函数还可使用的C语言中的转义字符:
转义字符 | 描 述 |
/n | 换行,删除当前到行尾的全部字符,并将字符指针向下移动一行。若设newline标志,addch将删除光标后的全部字符并将字符指针置于下行开始处。 |
/r | 回车,将字符指针置于当前行的开始处。 |
/t | 制表符,将字符指针移动到下一个制表符处。 |
int addstr(str); char *str;
字符串的首字符将在当前光标处输出,它的参数是一个字符指针。
若是字符串的长度超出了屏幕的大小,字符串将被截取掉。
curses中与addstr()相似的函数还包括waddstr()、mvwaddstr()、mvaddstr()。
int printw(fmt [,arg…]) char *fmt;
参数:
- fmt 是一个字符串指针,用来表示打印的格式,好比对于字符串格式为%s,字符为%c,十进制整数为%d,八进制整数为%o等。 - arg 是须要打印的值,若是给出的arg不止一个,每个都必须用逗号隔开,它们必须与fmt的格式相适应。 - 若是fmt为%s格式,则相应的arg参数必须为一个字符串指针, - 若是fmt为%d格式,则相应的arg参数必须为整数。
相似的函数有mvprintw(),mvwprintw(),wprintw()。
getch()函数能够从终端键盘读入一个字符,而且返回字符的整数值。这个函数一般用来从键盘接受输入。
输入模式决定了在程序接受字符以前内核进行处理的过程。若是终端被设置成ECHO模式,getch()接受的字符将当即在屏幕上显示,不然屏幕将保持不变化直到刷新后才显示出来
一般状况下,内核会缓存输入文本,若不须要输入缓存,就必须设置CBRREAK或者raw模式。
- 在raw模式下,内核并不进行缓存处理。 - 在CBREAK模式下,除了^S、^Q、^C、^Y 等控制字符之外,其他的字符都原封不动的发送到系统中被处理。 - 若是终端没有设置成RAW或者NOECHO模式,getch()将自动的将终端设置成CBREAK模式。
对于普通的字符,getch() 将返回与字符自己相同的整数值。但若是想获取功能键和方向键等咱们则必须设置功能键模式。一旦进行设置,getch()将返回curses.h中定义的与这些功能键对应的整数。
int getstr(str) char* str
从终端键盘接受字符串,而且显示在指定的位置上。
与getch()相同,若是终端模式被设置为ECHO,getstr()将终端屏幕上当即更新显示字符。若是终端模式没有设置为RAW或者NOECHO模式,函数将自动将终端设置为CBREAK模式,并在读入字符串之后自动恢复到之前的模式。
int scanw(fmt [,argptr…]) char * fmt;
int insch(ch) chtype ch; int delch();
字符插入之后光标将自动的向右移动一个位置,若是最右边的字符超出了终端屏幕的范围,它将被截取掉。
从当前位置删除一个字符,而且将删除字符右边的全部字符向左移动一个位置。当前行最右边的字符由空格代替。
int insertln(); int deleteln();
A_NORMAL:标准的显示模式 A_BLINK:闪烁属性 A_BOLD:加粗属性 A_DIM: 半透明属性 A_REVERSE:反显属性 A_STANDOUT:高亮度显示属性 A_UNDERLINE:加下划线 A_ALTCHARSET:可代替字符集 COLOR_PAIR(n):字符的背景和前景属性
int attron(attrs) int attrset(attrs) int attroff(attrs) - chtype attrs;
attroff()关闭某个已经存在的属性。
attrset(0)的特殊用法关闭全部的属性。
Curses库中光标分为物理光标和逻辑光标。 - 物理光标是最经常使用的光标,每一个窗体只有一个物理光标。 - 逻辑光标属于curses窗体,每一个窗体可能有多个逻辑光标。
int move(y,x); int y,x;
屏幕的行宽和列宽在curses库中定义为(LINES-1,COLS-1)。
须要注意的是行和列都是从0开始计数。咱们进行的大部分操做在操做以前都须要移动光标到必定的位置,若是这样的话咱们须要分两步进行:移动光标,而后进行相关操做。为了更方便,一些函数将移动光标与显示字符结合起来执行。这种函数的格式通常以下:
mvfunc(y , x, [arg,…]) - func通常为操做函数的名字,好比addch,addstr等等。 - y为操做进行时候光标所在的行数,一般也是移动以后的新的光标位置。 - x为操做进行时候光标所在的列数。
例如:须要将光标移动到(10,5)处而后输出字符‘X’,那么咱们就可使用move()函数与addch()函数结合造成的mvaddch()函数来实现。能够写为:mvaddch(10,5,‘X’);
- 清除整个屏幕 int clear(); int erase(); - 清除指定窗口 wclear(); werase();
这四个函数在标准屏幕上使用空格来代替当前字母从而达到清屏的效果。
clear()清除屏幕上的全部的字符而且将光标移动到屏幕的原点(0,0),继而自动调用clearok()函数,这使得屏幕在下次调用refresh()刷新的时候可以彻底被清除。所以clear()函数可以清除物理屏幕上的那些没法识别的东西,这样下次输出将是基于一个彻底“干净”的屏幕进行的。
erase()函数一样能够用来清除屏幕,可是它不会自动调用clearok()函数,所以与clear()相比,它并非一种最完全的清除方式。
int clrtoeol() int clrtobot()
用空格代替当前的须要清除部分的现有字符。
无论clrtobot()仍是clrtoeol()都会改变当前光标的位置。
操做系统一样要面临上面4个相似的问题:
- 内核将程序载入内存空间并维护每一个程序在内存中所处的位置。 - 在内核的调度下,程序以时间片间隔的方式运行,同时,内核在特定的时间运行特定的任务。 - 内核必须在很短的时间内响应用户和外设在任什么时候刻的输入。 - 同时,作几件事须要一些技巧。内核要保证数据的有序和规整的。
游戏的概要描述:
- 球以必定的速度和方向移动 - 球碰到墙壁或挡板会被弹回 - 用户按按钮来控制挡板移动
#include <stdio.h> #include <curses.h> void main() { int i; initscr(); clear(); for(i=0; i<LINES; i++ ){ move( i, i+i ); if ( i%2 == 1 ) standout(); addstr("Hello, world"); if ( i%2 == 1 ) standend(); sleep(1); refresh(); //比较不一样,而后刷新屏幕 } endwin(); }
Hello,world
这串字符在屏幕上自上而下逐行显示,每秒增长一行,反色和正常交替出现。
如何让上一个例子创造移动的假象?
#include <stdio.h> #include <curses.h> void main() { int i; initscr(); clear(); for(i=0; i<LINES; i++ ){ move( i, i+i ); if ( i%2 == 1 ) standout(); addstr("Hello, world"); if ( i%2 == 1 ) standend(); refresh(); sleep(1); move(i,i+i); //将光标移动到上一条字符串的开头 addstr(" "); //用空串覆盖原有字符串 } endwin(); }
方法:先输出一个字符串,而后sleep一秒,而后在原来的地方写空字符串覆盖掉原有字符串,在下一行输出新的字符串。
sleep函数由3个步骤组成:
- 为SIGALRM设置一个处理函数 - 调用alarm(num_seconds) - 调用pause
- 有更高的精度:能够精确到微秒。 - usleep()将当前进程挂起n微秒或者直到有一个不能被忽略的信号到达。 - 每一个进程都有3个独立的计时器 - 每一个计时器有两个设置:初始间隔和重复间隔设置 - 支持alarm和sleep
- 真实(ITIMER_REAL):进程运行的所有时间 - 虚拟(ITIMER_VIRTUAL):进程在用户态的时间 - 实用(ITIMER_PROF):进程在用户态加内核态的时间
包含初始间隔和重复间隔,每一个间隔由秒数和微秒数组成。
- 初始间隔 it_value - 重复间隔 it_interval
it_interval设为0,再也不重复这一特征。
#include<sys/time.h> int set_ticker(int 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); } - getitimer(int which, struct itimerval *val) - setitimer(int which, const struct itimerval* newval, struct itimerval *oldval); - which指定哪一种计时器:ITMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF。
struct itimerval { struct timeval it_interval; struct timeval it_value; }; struct timeval { time_t tv_sec; suseconds_t tv_usec; };
#include <curses.h> #include <sys/time.h> #include <signal.h> #include <stdlib.h>
#define LEFT 0 //当前屏幕的最左边 #define TOP 0 //当前屏幕的最上边 #define RIGHT COLS-1 //球所能到达的当前屏幕最大水平范围 #define BOTTOM LINES-1 //球所能到达的当前屏幕最大垂直范围 #define WIDE RIGHT-LEFT+1 //宽度 #define BOARD_LENGTH 10 //挡板长度
char BALL = 'O'; //球的形状 char BLANK = ' '; //覆盖球走过的轨迹 int hdir; //控制球水平运动的变量 int vdir; //控制球垂直运动的变量 int pos_X; //球的横坐标 int pos_Y; //球的纵坐标 int left_board; //挡板左端点 int right_board; //挡板右端点 int is_lose=0; //标志:小球是否落在挡板上 int delay=100; //设置速度 int ndelay; //速度倍数
int main() { //初始化curses initscr(); crmode(); //中断模式 noecho(); //关闭回显 control(); endwin(); //结束 curses return 0; }
void init() { int i,j; clear(); //初始球 pos_X =20; //球初始的横坐标 pos_Y = BOTTOM-1; //球初始的纵坐标 //初始化球的运动方向,朝右上方运动 hdir=1; vdir=-1; //初始挡板 left_board=20; right_board=left_board+BOARD_LENGTH; //显示挡板 for(i=left_board;i<=right_board;i++) { move(BOTTOM,i); addch('='); } //初始刷新时间 ndelay=2; signal(SIGALRM,moveBall); set_ticker(delay*ndelay); keypad(stdscr,TRUE); //打开 keypad 键盘响应 attroff(A_BLINK); //关闭 A_BLINK 属性 is_lose=0; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(100000); move(LINES-1,COLS-1); refresh(); }
void moveBall() { if(is_lose) return; signal(SIGALRM,moveBall); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; //改变方向 if(pos_X >= RIGHT) //当球横坐标大于右边边缘时,球反弹朝左运动 hdir = -1; if(pos_X <= LEFT) //当球横坐标大于左边边缘时,球反弹朝右运动 hdir = 1; if(pos_Y <= TOP) //当球纵坐标大于顶部边缘时,球反弹朝下运动 vdir = 1; //当球在底部的时候进行额外的处理 if(pos_Y >= BOTTOM-1) { if(pos_X>=left_board&&pos_X<=right_board) //球在挡板处 vdir=-1; else //球不在挡板处 { is_lose=1; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(delay*ndelay*1000); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); } } //不改变球的方向 move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); }
cmd=getch(); if(cmd=='q') break;//按“Q”键退出游戏 //挡板左移 else if(cmd==KEY_LEFT) { if(left_board>0) { move(BOTTOM,right_board); addch(' '); right_board--; left_board--; move(BOTTOM,left_board); addch('='); move(BOTTOM,RIGHT); refresh(); } } //挡板右移 else if(cmd==KEY_RIGHT) { if(right_board<RIGHT) { move(BOTTOM,left_board); addch(' '); right_board++; left_board++; move(BOTTOM,right_board); addch('='); move(BOTTOM,RIGHT); refresh(); } }
int flag=1; char choice; move(6,10); addstr("Game over! Do you want try again?(y/n):"); refresh(); choice=getch(); while(flag) { if(choice=='y'||choice=='Y'||choice=='n'||choice=='N') flag=0; else choice=getch(); } if(choice=='y'||choice=='Y') { init(); continue; } else { if(choice=='n'||choice=='N') break; }
/* Bounceball Game Version 1.3 - 新增welcome界面 - 可设置游戏难易程度(小球速度,挡板长度) - 记分 */ #include <curses.h> #include <sys/time.h> #include <signal.h> #include <stdlib.h> #define LEFT 0 //当前屏幕的最左边 #define TOP 0 //当前屏幕的最上边 #define RIGHT COLS-1 //球所能到达的当前屏幕最大水平范围 #define BOTTOM LINES-1 //球所能到达的当前屏幕最大垂直范围 #define WIDE RIGHT-LEFT+1 //宽度 char BALL = 'O'; //球的形状 char BLANK = ' '; //覆盖球走过的轨迹 int hdir; //控制球水平运动的变量 int vdir; //控制球垂直运动的变量 int pos_X; //球的横坐标 int pos_Y; //球的纵坐标 int left_board; int right_board; int BOARD_LENGTH; int is_lose=0; int score=0; int delay; int ndelay; void init(); void moveBall(); void control(); int set_ticker(int n_msecs); void start(); void help(); void information(); int welcome(); int main() { //初始化curses initscr(); crmode(); //中断模式 noecho(); //关闭回显 welcome(); endwin(); //结束 curses return 0; } int welcome() { move(6,20); addstr("Hello! Welcome the the Bounceball Game!"); move(8,23); addstr("1.Start the Game"); move(9,23); addstr("2.Help"); move(10,23); addstr("3.About me"); move(11,23); addstr("4.Quit"); int flag = 1; char choice; move(13,23); addstr("Please choose your choices : "); refresh(); choice=getch(); while(flag) { if(choice=='1'||choice=='2'||choice=='3'||choice=='4') { flag = 0; switch(choice) { case '1': start(); break; case '2': help(); welcome(); break; case '3': information(); welcome(); break; case '4': break; } } else choice=getch(); } return 0; } void start() { clear(); move(6,20); addstr("Game Level:"); move(8,23); addstr("1.Easy"); move(9,23); addstr("2.Normal"); move(10,23); addstr("3.Hard"); score=0; int flag = 1; char choice; move(13,23); addstr("Please choose the level : "); refresh(); choice=getch(); while(flag) { if(choice=='1'||choice=='2'||choice=='3') { flag = 0; switch(choice) { case '1': delay=100; BOARD_LENGTH=10; break; case '2': delay=60; BOARD_LENGTH=8; break; case '3': BOARD_LENGTH=5; delay=40; break; } } else choice=getch(); } clear(); move(8,20); addstr("Are you ready?"); refresh(); control(); } void control() { init(); int cmd; while (1) { if(!is_lose) { cmd=getch(); if(cmd==27) break;//退出 //挡板左移 else { if(cmd==KEY_LEFT) { if(left_board>0) { move(BOTTOM,right_board); addch(' '); right_board--; left_board--; move(BOTTOM,left_board); addch('='); move(BOTTOM,RIGHT); refresh(); } } //挡板右移 else { if(cmd==KEY_RIGHT) { if(right_board<RIGHT) { move(BOTTOM,left_board); addch(' '); right_board++; left_board++; move(BOTTOM,right_board); addch('='); move(BOTTOM,RIGHT); refresh(); } } } } } else { //输掉球后的处理 int flag=1; char choice; move(6,20); addstr("Game over!"); move(8,20); addstr("Your score : "); printw("%d",score); move(10,20); addstr("Do you want to try again?(y/n):"); refresh(); choice=getch(); while(flag) { if(choice=='y'||choice=='Y'||choice=='n'||choice=='N') flag=0; else choice=getch(); } if(choice=='y'||choice=='Y') { score=0; init(); continue; } else { if(choice=='n'||choice=='N') { clear(); refresh(); welcome(); break; } } } } } void init() { int i,j; clear(); //初始球 pos_X =20; //球初始的横坐标 pos_Y = BOTTOM-1; //球初始的纵坐标 //初始化球的运动方向,朝右上方运动 hdir=1; vdir=-1; //初始挡板 left_board=20; right_board=left_board+BOARD_LENGTH; //显示挡板 for(i=left_board;i<=right_board;i++) { move(BOTTOM,i); addch('='); } //初始刷新时间 ndelay=2; signal(SIGALRM,moveBall); set_ticker(delay*ndelay); keypad(stdscr,TRUE); //打开 keypad 键盘响应 attroff(A_BLINK); //关闭 A_BLINK 属性 is_lose=0; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(100000); move(LINES-1,COLS-1); refresh(); } void moveBall() { if(is_lose) return; signal(SIGALRM,moveBall); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; //改变方向 if(pos_X >= RIGHT) hdir = -1; if(pos_X <= LEFT) hdir = 1; if(pos_Y <= TOP) vdir = 1; //当球在底部的时候进行额外的处理 if(pos_Y >= BOTTOM-1){ if(pos_X>=left_board&&pos_X<=right_board) { vdir=-1; score++; } else { is_lose=1; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); usleep(delay*ndelay*1000); move(pos_Y,pos_X); addch(BLANK); pos_X += hdir; pos_Y += vdir; move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); } } //不改变球的方向 move(pos_Y,pos_X); addch(BALL); move(LINES-1, COLS-1); refresh(); } int set_ticker(int 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 help() { clear(); move(6,20); addstr("Help Information"); move(8,23); addstr(" <- : Control the baffle left shift"); move(9,23); addstr(" -> : Control the baffle right shift"); move(10,23); addstr(" q : Exit the game "); move(12,40); addstr("Press any key to exit..."); refresh(); int ch=getch(); clear(); refresh(); } void information() { clear(); move(6,20); addstr("About the Game"); move(8,23); addstr("written by 20135317_Han"); move(9,23); addstr("Version 1.3"); move(11,40); addstr("Press any key to exit..."); refresh(); int ch=getch(); clear(); refresh(); }
参考资料1:Unix/Linux编程实践教程(Understanding UNIX/LINUX Programming)
参考资料2:Unix/Linux下的Curses库开发指南——第一章 Curses库开发简介
参考资料3:Unix/Linux下的Curses库开发指南——第二章 curses库I/O处理
参考资料4:Linux下curses函数库安装运行
参考资料5:linux中curses使用
参考资料6:curses编程函数1(三类输出函数)
参考资料7:Linux的sleep()和usleep()的使用
参考资料8:《Unix-Linux编程实践教程》读书笔记(七)
参考资料9:第七章 事件驱动编程(curses库,计时器,信号,异步I/O)
参考资料10:Linux进程的计时器和间隔计时器
参考资料11:时钟编程: alarm和setitimer
参考资料12:信息安全系统设计基础第十周学习总结——第八章 异常控制流