前言

《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 环境下光标定位

学会在 Linux 环境中光标定位,在屏幕上在不同的位置,打印出不同的内容。

光标报告的格式是: 0x1B [行坐标;列坐标]。

  1. //x 为行坐标 ,y 为列坐标
  2. printf ( "%c[%d;%df" ,0x1B,y,x);

Windows 环境下光标定位

Windows 环境中,光标定位的方法有所不同,引入 windows.h 头文件,以下所使用的到的结构体或者函数均在存在于该头文件。

首先需要使用到 windows.h 中的 COORD 结构体,其定义为,

  1. typedef struct _COORD {
  2. SHORT X; // horizontal coordinate
  3. SHORT Y; // vertical coordinate
  4. } COORD;

再通过 GetStdHandle() 获得标准输出设备句柄 HANDLE

  1. HANDLE hp = GetStdHandle(STD_OUTPUT_HANDLE);

最后通过 SetConsoleCursorPosition() 设置控制台光标位置。

  1. //变量 pos 为 COORD 结构体对象
  2. 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 coordinateshort 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 ,没有输入时,该函数返回 0int 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;}

打印墙体

我们使用链表来存放墙体,链表是一种常用的数据结构,由若干个结点组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

  1. typedef struct wall{
  2. COORD pos;
  3. struct wall *next;//指向下一链表
  4. }WALL;

在这里我们要注意变量的生存周期,如果函数将变量内存地址存放在栈区的时候,当创建该变量的函数结束时,其内部创建在栈区的地址将被释放。

因此我们需要将结点申请在堆区,在 C 语言中,我们可以通过 malloc() 函数申请堆区,例如。

  1. WALL *wall = (WALL *)malloc(sizeof(WALL));

当该变量不需要使用的时候,使用 free() 函数将其地址空间释放。

  1. free(wall);

第 1 行和第 BOOTEM 行是上下边界,从第 2 行开始打印墙体,其中上下墙体间隔 BLANK 行。DISTANCE 为相邻两个墙体的间距,WIDTH 为墙体的宽度。

  1. #define BOOTEM 26 //下边界的高度
  2. #define DISTANCE 10 //两个墙体之间的距离
  3. #define WIDTH 5 //墙体宽度
  4. #define BLANK 10 //上下墙体之间的距离

墙体样式为,

  1. prtNode(p, "*");

代码

#include "./head.h"//EVALUATING 宏定义1 为 评测模式0 为命令行模式#define EVALUATING 1typedef 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时,给其赋值2int 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时,给其赋值2rd = 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 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。

当小鸟与上下边界发生碰撞时,

  1. //与上下边界发生碰撞
  2. if(bird->pos.Y pos.Y >= BOOTEM)

当小鸟与墙体发生碰撞时,

  1. //小鸟与墙体发生碰撞
  2. bird->pos.X >= wall->pos.X && bird->pos.X 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 pos.X+ WIDTH){if(bird->pos.Y 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;}

Flappy bird 实践练习

代码

#include #include #include #include #include #include  #include #include #define DIS 22#define BLAN 9 //上下两部分柱子墙之间的缝隙typedef struct COORD {short X; // horizontal coordinateshort 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();//输入的字符存入chprintf("%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();//输入的字符存入chprintf("%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<wall[i].Y; 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并返回1int CheckWin(COORD *wall, BIRD *bird){/********** Begin **********/if (bird->pos.X >= wall->X) //小鸟的横坐标进入柱子坐标范围{if (bird->pos.Y Y || bird->pos.Y >= wall->Y + BLAN) {return 0; //小鸟的纵坐标碰到上下柱子,则返回0}}if (bird->pos.Y < 1 || bird->pos.Y > 26) {return 0; //小鸟的位置超出上下边界,则返回0}(bird->score)++; //分数加1return 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环境下即可完成游戏