《flappy bird》是一款由来自越南的独立游戏开发者Dong Nguyen所开发的作品,游戏于2013年5月24日上线,并在2014年2月突然暴红。2014年2月,《Flappy Bird》被开发者本人从苹果及谷歌应用商店撤下。2014年8月份正式回归APP Store,正式加入Flappy迷们期待已久的多人对战模式。游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍。
通过游戏开发可以做到
1)在游戏窗口中显示从右向左运动的障碍物,显示三根柱子墙;
2)用户使用空格键控制小鸟向上移动,以不碰到障碍物为准,即需要从柱子墙的缝隙中穿
行,确保随机产生的障碍物之间的缝隙大小可以足够小鸟通过;
3)在没有用户按键操作情况下,小鸟受重力影响会自行下落;
4)进行小鸟与障碍物的碰撞检测,如果没有碰到,则给游戏者加 1 分。
5)如果小鸟碰到障碍物或者超出游戏画面的上下边界,则游戏结束。
使用空格键控制小鸟向上移动,在没有用户按键操作情况下,小鸟受重力影响会自行下落。如果小鸟碰到障碍物或者超出游戏画面的上下边界,则游戏结束。
学会在 Linux
环境中光标定位,在屏幕上在不同的位置,打印出不同的内容。
光标报告的格式是: 0x1B [行坐标;列坐标]。
//x 为行坐标 ,y 为列坐标
printf ( "%c[%d;%df" ,0x1B,y,x);
在 Windows
环境中,光标定位的方法有所不同,引入 windows.h 头文件,以下所使用的到的结构体或者函数均在存在于该头文件。
首先需要使用到 windows.h 中的 COORD
结构体,其定义为,
typedef struct _COORD {
SHORT X; // horizontal coordinate
SHORT Y; // vertical coordinate
} COORD;
再通过 GetStdHandle()
获得标准输出设备句柄 HANDLE
;
HANDLE hp = GetStdHandle(STD_OUTPUT_HANDLE);
最后通过 SetConsoleCursorPosition()
设置控制台光标位置。
//变量 pos 为 COORD 结构体对象
SetConsoleCursorPosition(hp, pos);
现在,我们可以在不同环境中,在不同位置进行打印输出。
代码
- #include
- #define BOOTEM 26 //下边界的高度
- #define DISTANCE 10 //两个墙体之间的距离
- #define WIDTH 5 //墙体宽度
- #define BLANK 10 //上下墙体之间的距离
- /**********Begin**********/
- //光标定位
- void gotoxy(int x, int y){
-
- printf("%c[%d;%df", 0x1B, y, x);
- }
-
- //函数功能:显示上下边界和分数
- void begin(){
-
- system("clear");
- //打印上边界
- gotoxy(0, 0);
- printf("\n==========================\n");
- //打印下边界
- gotoxy(0, BOOTEM);
- printf("\n==========================");
-
- }
- /**********End**********/
-
- int main()
- {
-
- begin();
-
- return 0;
-
- }
代码
- #include "./head.h"
- typedef struct COORD {
- short X; // horizontal coordinate
- short Y; // vertical coordinate
- } COORD;
-
-
- typedef struct bird
- {
- COORD pos;
- int score;
- } BIRD;
-
- //函数功能:显示小鸟
- void prtBird(BIRD *bird){
- /**********Begin**********/
- void prtBird(BIRD *bird)
- {
- gotoxy(bird->pos.X, bird->pos.Y);
- printf("O^^0");
- fflush(stdout);
- }
-
- /**********End**********/
- //linux环境printf频繁偶尔会在缓存中不会立即打印,fflush函数可以清空缓存并立即打印
- fflush(stdout);
- }
-
- int main()
- {
- BIRD bird = {{10, 13}, 0};//小鸟的初始位置
- begin();
- prtBird(&bird);
- return 0;
-
- }
代码
- #include "./head.h"
-
- //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式
- #define EVALUATING 1
-
- //函数功能:检测键盘输入
- //有输入值时,该函数返回 1 ,没有输入时,该函数返回 0
- int kbhit()
- {
- struct termios oldt, newt;
- int ch;
- int oldf;
- tcgetattr(STDIN_FILENO, &oldt);
- newt = oldt;
- newt.c_lflag &= ~(ICANON | ECHO);
- tcsetattr(STDIN_FILENO, TCSANOW, &newt);
- oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
- fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
- ch = getchar();
- tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
- fcntl(STDIN_FILENO, F_SETFL, oldf);
- if(ch != EOF) {
- ungetc(ch, stdin);
- return 1;
- }
- return 0;
- }
- //函数功能:移动小鸟
- void moveBird(BIRD *bird){
-
- /**********Begin**********/
- /**********Begin**********/
- char ch;
- //下面两行代码 作用是覆盖上次打印出来的小鸟位置,如果不加的话 会显示残影
- gotoxy(bird->pos.X, bird->pos.Y);
- printf(" ");
- if (kbhit()) {
- ch = getchar();
- if (ch == ' ') {
- bird->pos.Y--;//向上移动
- }
- }
- else {
- bird->pos.Y++;//向下移动
- }
-
-
- /**********End**********/
-
- }
-
- int main()
- {
- begin();
- BIRD bird = {{10, 13}, 0};//小鸟的初始位置
-
- //EVALUATING 宏定义 1 为评测模式 0 为命令行模式
- if(EVALUATING){
- int cnt=3;// 请忽略 辅助评测 无实际意义
- while(cnt--){
-
- prtBird(&bird);
- usleep(400000);
- moveBird(&bird);
- }
- }else{
- while(1){
-
- prtBird(&bird);
- usleep(400000);
- moveBird(&bird);
- }
- }
-
- return 0;
- }
-
-
我们使用链表来存放墙体,链表是一种常用的数据结构,由若干个结点组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
typedef struct wall{
COORD pos;
struct wall *next;//指向下一链表
}WALL;
在这里我们要注意变量的生存周期,如果函数将变量内存地址存放在栈区的时候,当创建该变量的函数结束时,其内部创建在栈区的地址将被释放。
因此我们需要将结点申请在堆区,在 C 语言中,我们可以通过 malloc()
函数申请堆区,例如。
WALL *wall = (WALL *)malloc(sizeof(WALL));
当该变量不需要使用的时候,使用 free()
函数将其地址空间释放。
free(wall);
第 1 行和第 BOOTEM
行是上下边界,从第 2 行开始打印墙体,其中上下墙体间隔 BLANK
行。DISTANCE
为相邻两个墙体的间距,WIDTH
为墙体的宽度。
#define BOOTEM 26 //下边界的高度
#define DISTANCE 10 //两个墙体之间的距离
#define WIDTH 5 //墙体宽度
#define BLANK 10 //上下墙体之间的距离
墙体样式为,
prtNode(p, "*");
代码
- #include "./head.h"
-
- //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式
- #define EVALUATING 1
-
- typedef struct wall{
- COORD pos;
- struct wall *next;
- }WALL;
-
- /**********Begin**********/
- //创建节点
- WALL *createWall(int x, int y){
- //首先生成一个节点
- WALL *wall = (WALL *)malloc(sizeof(WALL));
- if(wall == NULL) return NULL;
- wall->pos.X = x;
- wall->pos.Y = y;
- wall->next = NULL;
- return wall;
- }
- //遍历链表
- WALL *lastNode(WALL *wall){
- WALL *p = wall;
- if(wall == NULL) return NULL;
- while(p->next){
- p = p->next;
- }
- return p;
- }
- //创建链表
- WALL *createLink(){
- //生成一个随机数,作为上半部分墙体的高度
- srand(0);
- //生成随机值,当rd等于0或等于1时,给其赋值2
- int rd = rand() % (BOOTEM / 2);
- if(rd == 1||rd==0) rd = 2;
- //初始化一个节点
- WALL *wall = createWall(20, rd);
- WALL *temp, *p;
- for(int i = 1; i <= 2; i ++){
- //生成随机值,当rd等于0或等于1时,给其赋值2
- rd = rand() % (BOOTEM / 2);
- if(rd == 1||rd==0) rd = 2;
- p = lastNode(wall);//找到了链表的尾节点
- //创建节点
- temp = createWall(p->pos.X + DISTANCE + WIDTH * 2, rd);
- p->next = temp;//尾插法
- }
- return wall;
- }
- //销毁链表
- void deleteLink(WALL *wall){
- WALL *p, *q;
- p = q = wall;
- if(wall != NULL){
- while(p->next != NULL){
- q = p->next;
- p->next = q->next;
- free(q);
- }
- }
- free(wall);//free(p);
- }
- //打印单个墙体
- void prtNode(WALL *node, char ch[]){
- if(node == NULL) return ;
- int i, j;
- //上半部分墙体,第一行是上边界,从第2行开始打印
- for(i = 2; i <= node->pos.Y; i ++){
- gotoxy(node->pos.X, i);
- for(j = 1; j <= WIDTH; j ++){
- printf("%s", ch);
- }
- }
- //下半部分墙体 第BOOTEM行是下边界,此处 BOOTEM -1 避免墙体覆盖边界
- for(i = node->pos.Y + BLANK; i < BOOTEM - 1; i ++){
- gotoxy(node->pos.X, i);
- for(j = 1; j <= WIDTH; j ++){
- printf("%s", ch);
- }
- }
- }
- //打印整个墙体
- void prtWall(WALL *wall){
- if(wall == NULL) return ;
- WALL *p = wall;
- while(p){
- prtNode(p, "*");
- p = p->next;
- }
- }
-
- int main()
- {
- begin();
- BIRD bird = {{10, 13}, 0};//小鸟的初始位置
- WALL *wall= createLink();//链表生成墙体
- prtWall(wall);
-
- //EVALUATING 宏定义 1 为评测模式 0 为命令行模式
- if(EVALUATING){
- int cnt=3;// 请忽略 辅助评测 无实际意义
- while(cnt--){
- prtBird(&bird);
- usleep(400000);
- moveBird(&bird);
- }
- }else{
- while(1){
- prtBird(&bird);
- usleep(400000);
- moveBird(&bird);
- }
- }
- deleteLink(wall);
- return 0;
-
- }
当 justHead()
函数没有检测到碰撞时,返回 0,当检测到碰撞时,返回 1。
当小鸟与上下边界发生碰撞时,
//与上下边界发生碰撞
if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM)
当小鸟与墙体发生碰撞时,
//小鸟与墙体发生碰撞
bird->pos.X >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH
代码
- #include "./head.h"
-
- //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式
- #define EVALUATING 1
-
-
-
- //监测小鸟碰撞
- int justHead(WALL *wall, BIRD *bird){
- if(wall == NULL) return -1;
- //与上下边界发生碰撞
- if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM) return 1;
- //小鸟与墙体发生碰撞
- if(bird->pos.X >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH){
- if(bird->pos.Y <= wall->pos.Y || bird->pos.Y >= wall->pos.Y + BLANK){
- return 1;
- }
- }
- return 0;
- }
-
- int main()
- {
- begin();
- BIRD bird = {{10, 13}, 0};//小鸟的初始位置
- WALL *wall= createLink();//链表生成墙体
- prtWall(wall);
- //EVALUATING 宏定义 1 为评测模式 0 为命令行模式
- if(EVALUATING){
- int cnt=3;// 请忽略 辅助评测 无实际意义
- while(cnt--&&justHead(wall,&bird)==0){
- prtBird(&bird);
- usleep(400000);
- moveBird(&bird);
- wall = moveWall(wall,&bird);
- }
- }else{
- while(justHead(wall,&bird)==0){
-
- prtBird(&bird);
- usleep(400000);
- moveBird(&bird);
- wall = moveWall(wall,&bird);
- }
- }
- deleteLink(wall);
- return 0;
-
- }
代码
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #define DIS 22
- #define BLAN 9 //上下两部分柱子墙之间的缝隙
- typedef struct COORD {
- short X; // horizontal coordinate
- short Y; // vertical coordinate
- } COORD;
- typedef struct bird
- {
- COORD pos;
- int score;
- } BIRD;
- //bool SetConsoleColor(unsigned int wAttributes); //设置颜色
- void Gotoxy(int x, int y);//定位光标
- bool SetConsoleColor(int back,int front); //设置颜色
- void CheckWall(COORD wall[]);//显示柱子墙体
- void PrtBird(BIRD *bird);//显示小鸟
- int CheckWin(COORD *wall, BIRD *bird);//检测小鸟是否碰到墙体或者超出上下边界
- void Begin(BIRD *bird);//显示上下边界和分数
- int SelectMode(); //选择模式
- int kbhit()
- {
- struct termios oldt, newt;
- int ch;
- int oldf;
- tcgetattr(STDIN_FILENO, &oldt);
- newt = oldt;
- newt.c_lflag &= ~(ICANON | ECHO);
- tcsetattr(STDIN_FILENO, TCSANOW, &newt);
- oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
- fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
- ch = getchar();
- tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
- fcntl(STDIN_FILENO, F_SETFL, oldf);
- printf("%c\n",ch);
- if(ch != EOF) {
- ungetc(ch, stdin);
- return 1;
- }
- return 0;
- }
- //主函数
- int main(int argc, char* argv[])
- {
- BIRD bird = {{20, 13}, 0};//小鸟的初始位置
- COORD wall[3] = {{40, 10},{60, 6},{80, 8}}; //柱子的初始位置和高度
- int i;
- char ch;
- int gameMode = 1;
- gameMode = SelectMode();
- if(gameMode==1) //用于评测
- {
- int count = 0;
- while (count < 60) //游戏循环执行
- {
- Begin(&bird); //清屏并显示上下边界和分数
- PrtBird(&bird);//显示小鸟
- CheckWall(wall);//显示柱子墙
- ch = getchar();//输入的字符存入ch
- printf("%c",ch);
- if (ch == 'u')//输入的是u
- {
- bird.pos.Y -= 1; //小鸟向上移动一格
- }
- if (ch == 'd')//输入的是d
- {
- bird.pos.Y += 1; //小鸟向下移动一格
- }
- for (i=0; i<3; ++i)
- {
- wall[i].X--; //柱子墙向左移动一格
- }
- if(CheckWin(wall, &bird)==0)
- {
- printf("Bird Lose!");
- return 0;
- }
- count++;
- }
- printf("Bird Win!");
- return 0;
- }
- while (CheckWin(wall, &bird))
- {
- Begin(&bird); //清屏并显示上下边界和分数
- PrtBird(&bird);//显示小鸟
- CheckWall(wall);//显示柱子墙
- usleep(400000);
- if (kbhit()) //检测到有键盘输入
- {
- ch = getchar();//输入的字符存入ch
- printf("%c",ch);
- if (ch == ' ')//输入的是空格
- {
- bird.pos.Y -= 1; //小鸟向上移动一格
- }
- }
- else //未检测到键盘输入
- {
- bird.pos.Y += 1;//小鸟向下移动一格
- }
- for (i=0; i<3; ++i)
- {
- wall[i].X--; //柱子墙向做移动一格
- }
- }
- return 0;
- }
- int SelectMode()
- {
- printf(" 请选择模式:\n 1.评测模式\n 2.自由体验游戏\n");
- int mode;
- while(scanf("%d", &mode))
- {
- if (mode != 1 && mode != 2)
- {
- printf(" 输入数据有误,请重新输入!\n\n 请选择模式:\n 1.评测模式\n 2.自由体验游戏\n");
- continue;
- }
- else return mode;
- }
- return 1;
- }
- //函数功能:定位光标
- void Gotoxy(int x, int y)//void Gotoxy(COORD pos)
- {
- printf ( "%c[%d;%df" ,0x1B,y,x);
- }
- //函数功能:设置颜色
- bool SetConsoleColor(int back,int front)
- {
- printf("\033[%dm",(front));
- return TRUE;
- }
- //函数功能:显示柱子墙体
- void CheckWall(COORD wall[])
- {
- int i;
- srand(time(0));
- COORD temp = {wall[2].X + DIS, rand() % 13 + 5};//随机产生一个新的柱子
- if (wall[0].X < 10) //超出预设的左边界
- { //最左侧的柱子墙消失,第二个柱子变成第一个,第三个柱子变成第二个,新产生的柱子变成第三个
- /********** Begin **********/
- wall[0] = wall[1];//最左侧的柱子墙消失,第二个柱子变成第一个
- wall[1] = wall[2];//第三个柱子变成第二个
- wall[2] = temp; //新产生的柱子变成第三个
- /********** End **********/
- }
- SetConsoleColor(40,31); //设置黑色背景,亮红色前景
- for (i=0; i<3; ++i)//每次显示三个柱子墙
- {
- //显示上半部分柱子墙
- temp.X = wall[i].X + 1;//向右缩进一格显示图案
- for (temp.Y=2; temp.Y
//从第2行开始显示 - {
- Gotoxy(temp.X, temp.Y);
- printf("■■■■■■");
- }
- temp.X--;//向左移动一格显示图案
- Gotoxy(temp.X, temp.Y);
- printf("■■■■■■■■");
- //显示下半部分柱子墙
- temp.Y += BLAN;
- Gotoxy(temp.X, temp.Y);
- printf("■■■■■■■■");
- temp.X++; //向右缩进一格显示图案
- temp.Y++; //在下一行显示下面的图案
- for (; (temp.Y)<26; temp.Y++)//一直显示到第25行
- {
- Gotoxy(temp.X, temp.Y);
- printf("■■■■■■");
- }
- }
- Gotoxy(0, 26);
- printf("\n");//第1行显示分数
- }
- //函数功能:显示小鸟
- void PrtBird(BIRD *bird)
- {
- SetConsoleColor(40,33); //设置黑色背景,亮黄色前景
- Gotoxy(bird->pos.X, bird->pos.Y);
- printf("o->");
- }
- //函数功能:检测小鸟是否碰到墙体或者超出上下边界,是则返回0,否则分数加1并返回1
- int CheckWin(COORD *wall, BIRD *bird)
- {
- /********** Begin **********/
- if (bird->pos.X >= wall->X) //小鸟的横坐标进入柱子坐标范围
- {
- if (bird->pos.Y <= wall->Y || bird->pos.Y >= wall->Y + BLAN)
- {
- return 0; //小鸟的纵坐标碰到上下柱子,则返回0
- }
- }
- if (bird->pos.Y < 1 || bird->pos.Y > 26)
- {
- return 0; //小鸟的位置超出上下边界,则返回0
- }
- (bird->score)++; //分数加1
- return 1;
- /********** End **********/
- }
- //函数功能:显示上下边界和分数
- void Begin(BIRD *bird)
- {
- system("clear");
- Gotoxy(0, 26); //第26行显示下边界
- printf("=========================================================="
- "================================Bottom");
- Gotoxy(0, 1); //第1行显示上边界
- printf("=========================================================="
- "================================Top");
- SetConsoleColor(40,33);//设置黑色背景,亮黄色前景
- printf("\n%4d", bird->score);//第1行显示分数
- }
最后在liunx环境下即可完成游戏