简介:
FLAPPYBIRD 就是小鸟穿过管道的一个小游戏,相信大家都玩过,这次我们用之前所学的只是来完成一个他的简易版本。
主要完成的目标:
1.按下空格键小鸟上升,不按小鸟下落
2.搭建小鸟需要穿过的管道
3.管道自动左移和创建
4.小鸟撞到管道游戏结束
想要完成这些目标,我们需要一个新的知识 Ncurses库
Ncurses库简介:
在项目中的作用:创建一个新的窗口,对这个窗口进行一系列的操作
安装命令:sudo apt-get install libncurses5-dev
man手册下载:sudo apt-get install ncurses-doc
注意:为了能够使用Ncurses库,必须在源程序中将#include<curses.h>包括进来,而且在编译的需要与它链接起来. 也就是在编译时需要在命令最后加 -lcurses或者-lncurses
项目所需要的Ncurses库的函数:
1. initscr(void);
是curses模式的入口。将终端屏幕初始化为curses模式,为当前屏幕和相关的数据结构分配内存。简单来说就是创建一个新窗口。
2. int endwin(void);
是curses模式的出口,退出curses模式,释放curses子系统和相关数据结构占用的内存。他与initscr()配对使用,有打开就要有关闭。
3. int curs_set(int visibility);
设置光标是否可见,visibility = 0(不可见),visibility = 1(可见)
4. int move(int new_y, int new_x);
将光标移动到new_y所指定的列和new_x所指定的行,注意,此函数第一个参数代表列,第二个参数代表行,move(10,5)表示将光标移动到第五行第十列的位置。
5. int addch(const chtype char);
在当前光标位置添加字符,一般配合move使用,先移动到想要添加的位置,然后添加想要添加的字符。
扩展:
如果想添加字符串可以使用printw()函数,此函数作用为在光标处添加一个字符串,他的参数就是你想添加的字符串,也有升级版mvprintw()函数,他的作用是移动到你指定的位置后添加字符串,他有三个参数,前两个为你想要吧光标移动到的位置,最后一个参数为你要添加的字符串。
6. int refresh(void);
刷新物理屏幕。将获取的内容显示到显示器上。每次移动都需要刷新一下后才能显示移动之后的现象,否则程序裕兴但是没有现象发生。
7. int keypad(WINDOW *window_ptr, bool key_on);
允许使用功能键。exp:keypad(stdscr,1);
8. int getch(void);
读取键盘输入的一个字符,与getc作用基本相同,读取一个键盘输入的字符,在实现空格控制小鸟上升时使用。
9. chtype inch(void);
获取当前光标位置的字符。
注:curses有自己的字符类型chtype,使用时强制类型转换为char
(char)inch == ' '//判断当前位置字符是否为空格
10. int start_color(void);
启动color机制,初始化当前终端支持的所有颜色,当你想改变你创建的窗口里的元素的颜色时,就需要启动color机制。
11. int init_pair(short pair_number, short foreground, short background);
配置颜色对
COLOR_BLACK 黑色 COLOR_MAGENTA 品红色
COLOR_RED 红色 COLOR_CYAN 青色
COLOR_GREEN 绿色 COLOR_WHITE 白色
COLOR_YELLOW 黄色 COLOR_BLUE 蓝色
init_pair(1,COLOR_RED,COLOR_BLUE)//设置颜色对的编号为 1 ,
//他的字符颜色为RED,背景颜色为BLUE
12. int COLOR_PAIR(int pair_number);
设置颜色属性,设置完颜色对,可以通过COLOR_PAIR实现,他的参数就是上面设置的颜色对编号,COLOR_PAIR一般作为下面两个函数的参数去使用。
13. int attron(chtype attribute);
启用属性设置,他的参数为COLOR_PAIR(pair_number);使用在你需要进行更改颜色的代码之前。
14. int attroff(chtype attribute);
关闭属性设置,在需要更改颜色的代码之后,参数同上。
15.noecho();
关闭输入字符显示,使用后在键盘输入的字符将不会被显示。
代码实现:
因为一时想不起来要写什么,具体就全部写在代码的备注里与代码一起理解。
#include<stdlib.h>
#include<curses.h>
#include <sys/time.h>
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<time.h>
#define BIRD '@'//用 @ 代表小鸟
#define PIPE '+'//用 + 代表管道
#define DIS ' '// 被清除的管道和小鸟用空格代替。
#define LOWSPEED 300//自动移动的速度
int by = 15,bx = 5;//初始化小鸟的位置
void interface();//对创建的新的界面的初始化
void birdfly();//小鸟飞起来
void clean();//清除小鸟上一次所在位置
void birdpos();//显示小鸟当前位置
void birdlow(int speed);//小鸟自动下降的实现
/*
管道我们是用链表的结构来表示
思路是每隔20个位置就创建一个节点
用这个节点来表示管道的初始位置
后面创建管道时只需要在节点后添加管道表示PIPE即可
下面是管道节点的初始化
*/
typedef struct pipe{
int y;
int x;
struct pipe *next;
}pipenode,*pipelist;
pipelist head,tail;
//下面函数就如定义的意思,不做解释
void pipe_create();
void pipe_clean();
void pipe_move();
void pipe_show();
/*
我们要实现小鸟的自动下落和管道的自动移动,
我们的思路是移动管道,小鸟的横坐标不变,
所以我们在这里使用定时器,
让程序每隔LOWSPEED的时间发送一次INTALRM信号,
然后我们捕获这个信号
对其进行操作来实现管道自动左移和小鸟自动下落
*/
void *handler(void *arg){
pipelist p, new;
int i,j;
//三行实现小鸟下降
clean();
by++;
birdpos();
//判断小鸟下降时是否触碰到管道
if((char)inch == PIPE){
birdlow(0);
endwin();
exit(0);
}
//管道清除,当一个管道的第一个节点的x坐标为0时
//清空整个管道,然后再清除该节点,然后再链表的最后新增一个节点
//然后重新经过show函数使新管道出现
p = head->next;
if(p->x == 0)
{
head->next = p->next;
for(i = p->x; i < p->x+10; i++)
{
/*上半部分管道清除*/
for(j=0; j<p->y; j++)
{
move(j,i);
addch(DIS);
}
/*下半部分管道清除*/
for(j = p->y+5; j < 25; j++)
{
birdlow(0);
endwin();
exit(0);
}
refresh();
}
free(p);
//创建新管道并初始化
new = (pipelist)malloc(sizeof(pipenode));
new->x = tail->x + 20;
new->y = rand() % 11 + 5;
new->next = NULL;
tail->next = new;
tail = new;
}
pipe_clean();
pipe_move();
pipe_show();
}
int main(int argc,char **argv){
//先初始化界面,在进行项目的实现
interface();
birdpos();
birdlow(LOWSPEED);
srand(time(0));//使管道的长度每次重新开始时都不一样
pipe_create();
pipe_show();
birdfly();
while(1);
endwin();
}
//显示小鸟当前位置
void birdpos(){
attron(COLOR_PAIR(1));//使用颜色对1
move(by,bx);
addch(BIRD);
refresh();
attroff(COLOR_PAIR(1));
}
//删除前一个位置的小鸟,实现就是把那个位置的字符串替换为空格
void clean(){
move(by,bx);
addch(DIS);
refresh();
}
//新界面的初始化
void interface(){
initscr();
curs_set(0);
noecho();
keypad(stdscr,1);//允许使用功能键,stdscr代表键盘
start_color();
init_pair(1,COLOR_WHITE, COLOR_RED);//定义小鸟所用的颜色对 1
init_pair(2,COLOR_WHITE, COLOR_GREEN);//定义管道所用的颜色对 2
}
//鸟飞
void birdfly(){
char fly;
while(1){
fly = getch();
if(fly == ' '){
/*
当接受到的字符为空格时
删除鸟当前的位置
然后让光标移动到之前位置的上面一个位置
在重新显示小鸟
*/
clean();
by--;
birdpos();
//判断小鸟上升时是否触碰到管道
//若碰到则关闭窗口和程序来代表游戏结束。
if((char)inch == PIPE){
birdlow(0);//将信号器启动时间变为0,即不再发送信号。
endwin();
exit(0);
}
}
}
}
/*
因为开始写函数时没想那么多所以起了个birdlow
他里面是一个定时器,每个多长时间会发送一次INTALRM信号
时间由上面的LOWSPPED决定
实际他代表了整个程序管道的移动和小鸟下落的速度
*/
void birdlow(int speed){
struct itimerval timer;
int s,us;
s = speed / 1000;//将速度变为微秒,让他快一点
us = speed;
timer.it_value.tv_sec = s;//定义第一次启动的时间
timer.it_value.tv_usec = us;
timer.it_interval.tv_sec = s;//定义之后每次重新启动的时间
timer.it_interval.tv_usec = us;
setitimer(ITIMER_REAL,&timer,NULL);//启动定时器
signal(SIGALRM,handler);//信号捕捉函数,也可使用sigaction;
}
//创建管道 ,x代表管道节点的位置
void pipe_create(){
pipelist p,q;
int i;
head = (pipelist)malloc(sizeof(pipenode));
head ->next = NULL;
p = head;
//屏幕上一共五个管道 ,创建五个节点来表示每个管道的起始位置
for(i = 1; i <= 5 ;i++){
q = (pipelist)malloc(sizeof(pipenode));
q -> x = (i) * 20;//每个管道间隔为20
q -> y = rand() % 11 + 5;//随机生成一个5-15的输来表示管道的高度
q ->next = NULL;
p ->next = q;
p = q;
}
tail = p;
}
//管道展示,也可以理解为填充管道
void pipe_show(){
pipelist p;
int i,j;
p = head ->next;
attron(COLOR_PAIR(2));
while(p){
/*
让p指向管道的第一个节点,将管道的宽度设计为10
因为下一个节点的位置在20格之后,所以我们每次填充了一排纵向管道
就让x的位置向后移动一格即可 ,在十格的管道都填充完之后,
就让p指向之前创建的链表的下一个节点。
第一层for循环的作用是移动管道的节点来为他添加纵向的管道
第二层for循环前面的作用是添加上半部分的管道
后面的作用是添加下半部分的管道
下班部分在y的基础上+5,所以管道之间的间隙就是5,
间隙之上是上半部分,间隙之下是下半部分
*/
for(i = p->x;i <p->x +10 ; i++ ){
for(j = 0;j < p->y;j++){
move(j,i);
addch(PIPE);
}
for(j = p->y +5 ; j < 25; j++){
move(j,i);
addch(PIPE);
}
}
refresh();
p = p->next;
}
attron(COLOR_PAIR(2));
}
//管道清除,实现逻辑与展示相同,只是把添加的字符由管道PIPE替换为空格DIS
void pipe_clean(){
pipelist p;
int i,j;
p = head ->next;
while(p){
for(i = p->x;i <p->x +10 ; i++ ){
for(j = 0;j < p->y;j++){
move(j,i);
addch(DIS);
}
for(j = p->y +5 ; j < 25; j++){
move(j,i);
addch(DIS);
}
}
refresh();
p = p->next;
}
}
// 管道移动,只要链表p不空,就一直让x--来实现管道左移
// 实际上也就是更改x的值来使管道展示和清除时的位置发生变换来实现管道左移
void pipe_move(){
pipelist p;
p = head ->next;
while(p){
p ->x--;
p = p->next;
}
}
自认为可以完善的点:
1.代码堆在一块有点多,封装的再好一点
2.每次移动管道都需要把所有的管道都清除后再重新添加,重复工作太多,有待完善