• 用C语言开发入门游戏FlappyBird


    前言

    《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);

    现在,我们可以在不同环境中,在不同位置进行打印输出。

    代码

    1. #include
    2. #define BOOTEM 26 //下边界的高度
    3. #define DISTANCE 10 //两个墙体之间的距离
    4. #define WIDTH 5 //墙体宽度
    5. #define BLANK 10 //上下墙体之间的距离
    6. /**********Begin**********/
    7. //光标定位
    8. void gotoxy(int x, int y){
    9. printf("%c[%d;%df", 0x1B, y, x);
    10. }
    11. //函数功能:显示上下边界和分数
    12. void begin(){
    13. system("clear");
    14. //打印上边界
    15. gotoxy(0, 0);
    16. printf("\n==========================\n");
    17. //打印下边界
    18. gotoxy(0, BOOTEM);
    19. printf("\n==========================");
    20. }
    21. /**********End**********/
    22. int main()
    23. {
    24. begin();
    25. return 0;
    26. }

     


     

    打印小鸟

    代码

    1. #include "./head.h"
    2. typedef struct COORD {
    3. short X; // horizontal coordinate
    4. short Y; // vertical coordinate
    5. } COORD;
    6. typedef struct bird
    7. {
    8. COORD pos;
    9. int score;
    10. } BIRD;
    11. //函数功能:显示小鸟
    12. void prtBird(BIRD *bird){
    13. /**********Begin**********/
    14. void prtBird(BIRD *bird)
    15. {
    16. gotoxy(bird->pos.X, bird->pos.Y);
    17. printf("O^^0");
    18. fflush(stdout);
    19. }
    20. /**********End**********/
    21. //linux环境printf频繁偶尔会在缓存中不会立即打印,fflush函数可以清空缓存并立即打印
    22. fflush(stdout);
    23. }
    24. int main()
    25. {
    26. BIRD bird = {{10, 13}, 0};//小鸟的初始位置
    27. begin();
    28. prtBird(&bird);
    29. return 0;
    30. }

     


     

    小鸟移动

    代码

    1. #include "./head.h"
    2. //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式
    3. #define EVALUATING 1
    4. //函数功能:检测键盘输入
    5. //有输入值时,该函数返回 1 ,没有输入时,该函数返回 0
    6. int kbhit()
    7. {
    8. struct termios oldt, newt;
    9. int ch;
    10. int oldf;
    11. tcgetattr(STDIN_FILENO, &oldt);
    12. newt = oldt;
    13. newt.c_lflag &= ~(ICANON | ECHO);
    14. tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    15. oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    16. fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
    17. ch = getchar();
    18. tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    19. fcntl(STDIN_FILENO, F_SETFL, oldf);
    20. if(ch != EOF) {
    21. ungetc(ch, stdin);
    22. return 1;
    23. }
    24. return 0;
    25. }
    26. //函数功能:移动小鸟
    27. void moveBird(BIRD *bird){
    28. /**********Begin**********/
    29. /**********Begin**********/
    30. char ch;
    31. //下面两行代码 作用是覆盖上次打印出来的小鸟位置,如果不加的话 会显示残影
    32. gotoxy(bird->pos.X, bird->pos.Y);
    33. printf(" ");
    34. if (kbhit()) {
    35. ch = getchar();
    36. if (ch == ' ') {
    37. bird->pos.Y--;//向上移动
    38. }
    39. }
    40. else {
    41. bird->pos.Y++;//向下移动
    42. }
    43. /**********End**********/
    44. }
    45. int main()
    46. {
    47. begin();
    48. BIRD bird = {{10, 13}, 0};//小鸟的初始位置
    49. //EVALUATING 宏定义 1 为评测模式 0 为命令行模式
    50. if(EVALUATING){
    51. int cnt=3;// 请忽略 辅助评测 无实际意义
    52. while(cnt--){
    53. prtBird(&bird);
    54. usleep(400000);
    55. moveBird(&bird);
    56. }
    57. }else{
    58. while(1){
    59. prtBird(&bird);
    60. usleep(400000);
    61. moveBird(&bird);
    62. }
    63. }
    64. return 0;
    65. }

     


     

    打印墙体

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

    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, "*");

    代码

    1. #include "./head.h"
    2. //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式
    3. #define EVALUATING 1
    4. typedef struct wall{
    5. COORD pos;
    6. struct wall *next;
    7. }WALL;
    8. /**********Begin**********/
    9. //创建节点
    10. WALL *createWall(int x, int y){
    11. //首先生成一个节点
    12. WALL *wall = (WALL *)malloc(sizeof(WALL));
    13. if(wall == NULL) return NULL;
    14. wall->pos.X = x;
    15. wall->pos.Y = y;
    16. wall->next = NULL;
    17. return wall;
    18. }
    19. //遍历链表
    20. WALL *lastNode(WALL *wall){
    21. WALL *p = wall;
    22. if(wall == NULL) return NULL;
    23. while(p->next){
    24. p = p->next;
    25. }
    26. return p;
    27. }
    28. //创建链表
    29. WALL *createLink(){
    30. //生成一个随机数,作为上半部分墙体的高度
    31. srand(0);
    32. //生成随机值,当rd等于0或等于1时,给其赋值2
    33. int rd = rand() % (BOOTEM / 2);
    34. if(rd == 1||rd==0) rd = 2;
    35. //初始化一个节点
    36. WALL *wall = createWall(20, rd);
    37. WALL *temp, *p;
    38. for(int i = 1; i <= 2; i ++){
    39. //生成随机值,当rd等于0或等于1时,给其赋值2
    40. rd = rand() % (BOOTEM / 2);
    41. if(rd == 1||rd==0) rd = 2;
    42. p = lastNode(wall);//找到了链表的尾节点
    43. //创建节点
    44. temp = createWall(p->pos.X + DISTANCE + WIDTH * 2, rd);
    45. p->next = temp;//尾插法
    46. }
    47. return wall;
    48. }
    49. //销毁链表
    50. void deleteLink(WALL *wall){
    51. WALL *p, *q;
    52. p = q = wall;
    53. if(wall != NULL){
    54. while(p->next != NULL){
    55. q = p->next;
    56. p->next = q->next;
    57. free(q);
    58. }
    59. }
    60. free(wall);//free(p);
    61. }
    62. //打印单个墙体
    63. void prtNode(WALL *node, char ch[]){
    64. if(node == NULL) return ;
    65. int i, j;
    66. //上半部分墙体,第一行是上边界,从第2行开始打印
    67. for(i = 2; i <= node->pos.Y; i ++){
    68. gotoxy(node->pos.X, i);
    69. for(j = 1; j <= WIDTH; j ++){
    70. printf("%s", ch);
    71. }
    72. }
    73. //下半部分墙体 第BOOTEM行是下边界,此处 BOOTEM -1 避免墙体覆盖边界
    74. for(i = node->pos.Y + BLANK; i < BOOTEM - 1; i ++){
    75. gotoxy(node->pos.X, i);
    76. for(j = 1; j <= WIDTH; j ++){
    77. printf("%s", ch);
    78. }
    79. }
    80. }
    81. //打印整个墙体
    82. void prtWall(WALL *wall){
    83. if(wall == NULL) return ;
    84. WALL *p = wall;
    85. while(p){
    86. prtNode(p, "*");
    87. p = p->next;
    88. }
    89. }
    90. int main()
    91. {
    92. begin();
    93. BIRD bird = {{10, 13}, 0};//小鸟的初始位置
    94. WALL *wall= createLink();//链表生成墙体
    95. prtWall(wall);
    96. //EVALUATING 宏定义 1 为评测模式 0 为命令行模式
    97. if(EVALUATING){
    98. int cnt=3;// 请忽略 辅助评测 无实际意义
    99. while(cnt--){
    100. prtBird(&bird);
    101. usleep(400000);
    102. moveBird(&bird);
    103. }
    104. }else{
    105. while(1){
    106. prtBird(&bird);
    107. usleep(400000);
    108. moveBird(&bird);
    109. }
    110. }
    111. deleteLink(wall);
    112. return 0;
    113. }

     


     

    检测碰撞

    justHead() 函数没有检测到碰撞时,返回 0,当检测到碰撞时,返回 1。

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

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

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

    1. //小鸟与墙体发生碰撞
    2. bird->pos.X >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH

    代码

    1. #include "./head.h"
    2. //EVALUATING 宏定义 1 为 评测模式 0 为命令行模式
    3. #define EVALUATING 1
    4. //监测小鸟碰撞
    5. int justHead(WALL *wall, BIRD *bird){
    6. if(wall == NULL) return -1;
    7. //与上下边界发生碰撞
    8. if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM) return 1;
    9. //小鸟与墙体发生碰撞
    10. if(bird->pos.X >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH){
    11. if(bird->pos.Y <= wall->pos.Y || bird->pos.Y >= wall->pos.Y + BLANK){
    12. return 1;
    13. }
    14. }
    15. return 0;
    16. }
    17. int main()
    18. {
    19. begin();
    20. BIRD bird = {{10, 13}, 0};//小鸟的初始位置
    21. WALL *wall= createLink();//链表生成墙体
    22. prtWall(wall);
    23. //EVALUATING 宏定义 1 为评测模式 0 为命令行模式
    24. if(EVALUATING){
    25. int cnt=3;// 请忽略 辅助评测 无实际意义
    26. while(cnt--&&justHead(wall,&bird)==0){
    27. prtBird(&bird);
    28. usleep(400000);
    29. moveBird(&bird);
    30. wall = moveWall(wall,&bird);
    31. }
    32. }else{
    33. while(justHead(wall,&bird)==0){
    34. prtBird(&bird);
    35. usleep(400000);
    36. moveBird(&bird);
    37. wall = moveWall(wall,&bird);
    38. }
    39. }
    40. deleteLink(wall);
    41. return 0;
    42. }

     

     Flappy bird 实践练习

    代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define DIS 22
    10. #define BLAN 9 //上下两部分柱子墙之间的缝隙
    11. typedef struct COORD {
    12. short X; // horizontal coordinate
    13. short Y; // vertical coordinate
    14. } COORD;
    15. typedef struct bird
    16. {
    17. COORD pos;
    18. int score;
    19. } BIRD;
    20. //bool SetConsoleColor(unsigned int wAttributes); //设置颜色
    21. void Gotoxy(int x, int y);//定位光标
    22. bool SetConsoleColor(int back,int front); //设置颜色
    23. void CheckWall(COORD wall[]);//显示柱子墙体
    24. void PrtBird(BIRD *bird);//显示小鸟
    25. int CheckWin(COORD *wall, BIRD *bird);//检测小鸟是否碰到墙体或者超出上下边界
    26. void Begin(BIRD *bird);//显示上下边界和分数
    27. int SelectMode(); //选择模式
    28. int kbhit()
    29. {
    30. struct termios oldt, newt;
    31. int ch;
    32. int oldf;
    33. tcgetattr(STDIN_FILENO, &oldt);
    34. newt = oldt;
    35. newt.c_lflag &= ~(ICANON | ECHO);
    36. tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    37. oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    38. fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
    39. ch = getchar();
    40. tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    41. fcntl(STDIN_FILENO, F_SETFL, oldf);
    42. printf("%c\n",ch);
    43. if(ch != EOF) {
    44. ungetc(ch, stdin);
    45. return 1;
    46. }
    47. return 0;
    48. }
    49. //主函数
    50. int main(int argc, char* argv[])
    51. {
    52. BIRD bird = {{20, 13}, 0};//小鸟的初始位置
    53. COORD wall[3] = {{40, 10},{60, 6},{80, 8}}; //柱子的初始位置和高度
    54. int i;
    55. char ch;
    56. int gameMode = 1;
    57. gameMode = SelectMode();
    58. if(gameMode==1) //用于评测
    59. {
    60. int count = 0;
    61. while (count < 60) //游戏循环执行
    62. {
    63. Begin(&bird); //清屏并显示上下边界和分数
    64. PrtBird(&bird);//显示小鸟
    65. CheckWall(wall);//显示柱子墙
    66. ch = getchar();//输入的字符存入ch
    67. printf("%c",ch);
    68. if (ch == 'u')//输入的是u
    69. {
    70. bird.pos.Y -= 1; //小鸟向上移动一格
    71. }
    72. if (ch == 'd')//输入的是d
    73. {
    74. bird.pos.Y += 1; //小鸟向下移动一格
    75. }
    76. for (i=0; i<3; ++i)
    77. {
    78. wall[i].X--; //柱子墙向左移动一格
    79. }
    80. if(CheckWin(wall, &bird)==0)
    81. {
    82. printf("Bird Lose!");
    83. return 0;
    84. }
    85. count++;
    86. }
    87. printf("Bird Win!");
    88. return 0;
    89. }
    90. while (CheckWin(wall, &bird))
    91. {
    92. Begin(&bird); //清屏并显示上下边界和分数
    93. PrtBird(&bird);//显示小鸟
    94. CheckWall(wall);//显示柱子墙
    95. usleep(400000);
    96. if (kbhit()) //检测到有键盘输入
    97. {
    98. ch = getchar();//输入的字符存入ch
    99. printf("%c",ch);
    100. if (ch == ' ')//输入的是空格
    101. {
    102. bird.pos.Y -= 1; //小鸟向上移动一格
    103. }
    104. }
    105. else //未检测到键盘输入
    106. {
    107. bird.pos.Y += 1;//小鸟向下移动一格
    108. }
    109. for (i=0; i<3; ++i)
    110. {
    111. wall[i].X--; //柱子墙向做移动一格
    112. }
    113. }
    114. return 0;
    115. }
    116. int SelectMode()
    117. {
    118. printf(" 请选择模式:\n 1.评测模式\n 2.自由体验游戏\n");
    119. int mode;
    120. while(scanf("%d", &mode))
    121. {
    122. if (mode != 1 && mode != 2)
    123. {
    124. printf(" 输入数据有误,请重新输入!\n\n 请选择模式:\n 1.评测模式\n 2.自由体验游戏\n");
    125. continue;
    126. }
    127. else return mode;
    128. }
    129. return 1;
    130. }
    131. //函数功能:定位光标
    132. void Gotoxy(int x, int y)//void Gotoxy(COORD pos)
    133. {
    134. printf ( "%c[%d;%df" ,0x1B,y,x);
    135. }
    136. //函数功能:设置颜色
    137. bool SetConsoleColor(int back,int front)
    138. {
    139. printf("\033[%dm",(front));
    140. return TRUE;
    141. }
    142. //函数功能:显示柱子墙体
    143. void CheckWall(COORD wall[])
    144. {
    145. int i;
    146. srand(time(0));
    147. COORD temp = {wall[2].X + DIS, rand() % 13 + 5};//随机产生一个新的柱子
    148. if (wall[0].X < 10) //超出预设的左边界
    149. { //最左侧的柱子墙消失,第二个柱子变成第一个,第三个柱子变成第二个,新产生的柱子变成第三个
    150. /********** Begin **********/
    151. wall[0] = wall[1];//最左侧的柱子墙消失,第二个柱子变成第一个
    152. wall[1] = wall[2];//第三个柱子变成第二个
    153. wall[2] = temp; //新产生的柱子变成第三个
    154. /********** End **********/
    155. }
    156. SetConsoleColor(40,31); //设置黑色背景,亮红色前景
    157. for (i=0; i<3; ++i)//每次显示三个柱子墙
    158. {
    159. //显示上半部分柱子墙
    160. temp.X = wall[i].X + 1;//向右缩进一格显示图案
    161. for (temp.Y=2; temp.Y//从第2行开始显示
    162. {
    163. Gotoxy(temp.X, temp.Y);
    164. printf("■■■■■■");
    165. }
    166. temp.X--;//向左移动一格显示图案
    167. Gotoxy(temp.X, temp.Y);
    168. printf("■■■■■■■■");
    169. //显示下半部分柱子墙
    170. temp.Y += BLAN;
    171. Gotoxy(temp.X, temp.Y);
    172. printf("■■■■■■■■");
    173. temp.X++; //向右缩进一格显示图案
    174. temp.Y++; //在下一行显示下面的图案
    175. for (; (temp.Y)<26; temp.Y++)//一直显示到第25行
    176. {
    177. Gotoxy(temp.X, temp.Y);
    178. printf("■■■■■■");
    179. }
    180. }
    181. Gotoxy(0, 26);
    182. printf("\n");//第1行显示分数
    183. }
    184. //函数功能:显示小鸟
    185. void PrtBird(BIRD *bird)
    186. {
    187. SetConsoleColor(40,33); //设置黑色背景,亮黄色前景
    188. Gotoxy(bird->pos.X, bird->pos.Y);
    189. printf("o->");
    190. }
    191. //函数功能:检测小鸟是否碰到墙体或者超出上下边界,是则返回0,否则分数加1并返回1
    192. int CheckWin(COORD *wall, BIRD *bird)
    193. {
    194. /********** Begin **********/
    195. if (bird->pos.X >= wall->X) //小鸟的横坐标进入柱子坐标范围
    196. {
    197. if (bird->pos.Y <= wall->Y || bird->pos.Y >= wall->Y + BLAN)
    198. {
    199. return 0; //小鸟的纵坐标碰到上下柱子,则返回0
    200. }
    201. }
    202. if (bird->pos.Y < 1 || bird->pos.Y > 26)
    203. {
    204. return 0; //小鸟的位置超出上下边界,则返回0
    205. }
    206. (bird->score)++; //分数加1
    207. return 1;
    208. /********** End **********/
    209. }
    210. //函数功能:显示上下边界和分数
    211. void Begin(BIRD *bird)
    212. {
    213. system("clear");
    214. Gotoxy(0, 26); //第26行显示下边界
    215. printf("=========================================================="
    216. "================================Bottom");
    217. Gotoxy(0, 1); //第1行显示上边界
    218. printf("=========================================================="
    219. "================================Top");
    220. SetConsoleColor(40,33);//设置黑色背景,亮黄色前景
    221. printf("\n%4d", bird->score);//第1行显示分数
    222. }

    最后在liunx环境下即可完成游戏

  • 相关阅读:
    二维码那点事
    SpringBoot中的日志使用
    达梦数据库整合在springboot的使用教程
    力扣876:链表的中间结点
    PHP 运行 mkdir() Permission Denied 的原因
    “相杀相爱”,从Elastic与AWS的恩仇录看开源许可
    CVE-2017-12149
    TOREX | 单功能充电IC的外置电流通路电路
    无人机和热成像
    k3s 上的 kube-ovn 轻度体验
  • 原文地址:https://blog.csdn.net/qq_64691289/article/details/127680432