目 录 | |
一 | Curses库简介与基本开发方法 |
二 | 事件驱动编程:编写一个视频游戏 |
三 | 弹球游戏的实现与优化 |
四 | 简单TUI游戏设计实践总结 |
curses实际上是一个函数开发包,专门用来进行UNIX下终端环境下的屏幕界面处理以及I/O处理。通过这些函数库,C和C++程序就可以控制终端的视频显示以及输入输出。
使用curses包中的函数,用户可以非常方便的创建和操作窗口,使用菜单以及表单,而且最为重要的一点是使用curses包编写的程序将独立于各种具体的终端,程序具有良好的移植性。
库名 | 描述 | 头文件 |
curses | 最早的curses包只包含这一部分,主要控制屏幕的输入和输出,光标的操作,窗口的创建和操作等。 | curses.h |
panel | 类似于窗口堆栈,不同的窗口可以存放于其中,并且可以在其中进行移动。 | panel.h |
menu | 新增的部分,主要包括创建菜单并且与之交互的函数,主要用来接受用户的选择。 | menu.h |
form | 包括创建表单以及与之进行交互的函数,主要用来接受用户数据输入。 | form.h |
curses包的这种终端独立性归根于终端描述数据库terminfo和termcap。
终端描述数据库(Terminal Description Databases)
- terminfo(TERMinal INFOrmation database)
- termcap(TERMinal CAPabilitie database)
这两个数据库里存放了不同终端的操作控制码和转义序列以及其余相关信息。
使用每一个终端:
- 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解释为文件结束符。
- 系统判断如何处理功能键F1、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();
}
如何让上一个例子创造移动的假象?
#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:信息安全系统设计基础第十周学习总结——第八章 异常控制流