嵌入式小项目——FLAPPYBIRD

本文介绍了如何使用C语言和Ncurses库开发一个简单的FLAPPYBIRD游戏。主要涉及小鸟移动、管道生成与移动、碰撞检测等功能,并详细阐述了Ncurses库的关键函数和游戏逻辑。文章还提出了代码优化的建议,如改进管道移动的效率。
摘要由CSDN通过智能技术生成

简介:

      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.每次移动管道都需要把所有的管道都清除后再重新添加,重复工作太多,有待完善

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值