C語言FlappyBird飛揚(yáng)的小鳥實(shí)現(xiàn)開發(fā)流程
前言
《flappy bird》是一款由來自越南的獨(dú)立游戲開發(fā)者Dong Nguyen所開發(fā)的作品,游戲于2013年5月24日上線,并在2014年2月突然暴紅。2014年2月,《Flappy Bird》被開發(fā)者本人從蘋果及谷歌應(yīng)用商店撤下。2014年8月份正式回歸APP Store,正式加入Flappy迷們期待已久的多人對戰(zhàn)模式。游戲中玩家必須控制一只小鳥,跨越由各種不同長度水管所組成的障礙。
通過游戲開發(fā)可以做到
1)在游戲窗口中顯示從右向左運(yùn)動的障礙物,顯示三根柱子墻;
2)用戶使用空格鍵控制小鳥向上移動,以不碰到障礙物為準(zhǔn),即需要從柱子墻的縫隙中穿行,確保隨機(jī)產(chǎn)生的障礙物之間的縫隙大小可以足夠小鳥通過;
3)在沒有用戶按鍵操作情況下,小鳥受重力影響會自行下落;
4)進(jìn)行小鳥與障礙物的碰撞檢測,如果沒有碰到,則給游戲者加 1 分。
5)如果小鳥碰到障礙物或者超出游戲畫面的上下邊界,則游戲結(jié)束。
?
使用空格鍵控制小鳥向上移動,在沒有用戶按鍵操作情況下,小鳥受重力影響會自行下落。如果小鳥碰到障礙物或者超出游戲畫面的上下邊界,則游戲結(jié)束。
打印上下邊界
Linux 環(huán)境下光標(biāo)定位
學(xué)會在 Linux
環(huán)境中光標(biāo)定位,在屏幕上在不同的位置,打印出不同的內(nèi)容。
光標(biāo)報告的格式是: 0x1B [行坐標(biāo);列坐標(biāo)]。
//x 為行坐標(biāo) ,y 為列坐標(biāo)
printf ( "%c[%d;%df" ,0x1B,y,x);
Windows 環(huán)境下光標(biāo)定位
在 Windows
環(huán)境中,光標(biāo)定位的方法有所不同,引入 windows.h 頭文件,以下所使用的到的結(jié)構(gòu)體或者函數(shù)均在存在于該頭文件。
首先需要使用到 windows.h 中的 COORD
結(jié)構(gòu)體,其定義為,
typedef struct _COORD {
SHORT X; // horizontal coordinate
SHORT Y; // vertical coordinate
} COORD;
再通過 GetStdHandle()
獲得標(biāo)準(zhǔn)輸出設(shè)備句柄 HANDLE
;
HANDLE hp = GetStdHandle(STD_OUTPUT_HANDLE);
最后通過 SetConsoleCursorPosition()
設(shè)置控制臺光標(biāo)位置。
//變量 pos 為 COORD 結(jié)構(gòu)體對象
SetConsoleCursorPosition(hp, pos);
現(xiàn)在,我們可以在不同環(huán)境中,在不同位置進(jìn)行打印輸出。
代碼
#include <stdio.h> #define BOOTEM 26 //下邊界的高度 #define DISTANCE 10 //兩個墻體之間的距離 #define WIDTH 5 //墻體寬度 #define BLANK 10 //上下墻體之間的距離 /**********Begin**********/ //光標(biāo)定位 void gotoxy(int x, int y){ printf("%c[%d;%df", 0x1B, y, x); } //函數(shù)功能:顯示上下邊界和分?jǐn)?shù) 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; //函數(shù)功能:顯示小鳥 void prtBird(BIRD *bird){ /**********Begin**********/ void prtBird(BIRD *bird) { gotoxy(bird->pos.X, bird->pos.Y); printf("O^^0"); fflush(stdout); } /**********End**********/ //linux環(huán)境printf頻繁偶爾會在緩存中不會立即打印,fflush函數(shù)可以清空緩存并立即打印 fflush(stdout); } int main() { BIRD bird = {{10, 13}, 0};//小鳥的初始位置 begin(); prtBird(&bird); return 0; }
小鳥移動
代碼
#include "./head.h" //EVALUATING 宏定義 1 為 評測模式 0 為命令行模式 #define EVALUATING 1 //函數(shù)功能:檢測鍵盤輸入 //有輸入值時,該函數(shù)返回 1 ,沒有輸入時,該函數(shù)返回 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; } //函數(shù)功能:移動小鳥 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;// 請忽略 輔助評測 無實(shí)際意義 while(cnt--){ prtBird(&bird); usleep(400000); moveBird(&bird); } }else{ while(1){ prtBird(&bird); usleep(400000); moveBird(&bird); } } return 0; }
打印墻體
我們使用鏈表來存放墻體,鏈表是一種常用的數(shù)據(jù)結(jié)構(gòu),由若干個結(jié)點(diǎn)組成,每個結(jié)點(diǎn)包括兩個部分:一個是存儲數(shù)據(jù)元素的數(shù)據(jù)域,另一個是存儲下一個結(jié)點(diǎn)地址的指針域。
typedef struct wall{
COORD pos;
struct wall *next;//指向下一鏈表
}WALL;
在這里我們要注意變量的生存周期,如果函數(shù)將變量內(nèi)存地址存放在棧區(qū)的時候,當(dāng)創(chuàng)建該變量的函數(shù)結(jié)束時,其內(nèi)部創(chuàng)建在棧區(qū)的地址將被釋放。
因此我們需要將結(jié)點(diǎn)申請?jiān)诙褏^(qū),在 C 語言中,我們可以通過 malloc()
函數(shù)申請堆區(qū),例如。
WALL *wall = (WALL *)malloc(sizeof(WALL));
當(dāng)該變量不需要使用的時候,使用 free()
函數(shù)將其地址空間釋放。
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**********/ //創(chuàng)建節(jié)點(diǎn) WALL *createWall(int x, int y){ //首先生成一個節(jié)點(diǎn) 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; } //創(chuàng)建鏈表 WALL *createLink(){ //生成一個隨機(jī)數(shù),作為上半部分墻體的高度 srand(0); //生成隨機(jī)值,當(dāng)rd等于0或等于1時,給其賦值2 int rd = rand() % (BOOTEM / 2); if(rd == 1||rd==0) rd = 2; //初始化一個節(jié)點(diǎn) WALL *wall = createWall(20, rd); WALL *temp, *p; for(int i = 1; i <= 2; i ++){ //生成隨機(jī)值,當(dāng)rd等于0或等于1時,給其賦值2 rd = rand() % (BOOTEM / 2); if(rd == 1||rd==0) rd = 2; p = lastNode(wall);//找到了鏈表的尾節(jié)點(diǎn) //創(chuàng)建節(jié)點(diǎn) 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;// 請忽略 輔助評測 無實(shí)際意義 while(cnt--){ prtBird(&bird); usleep(400000); moveBird(&bird); } }else{ while(1){ prtBird(&bird); usleep(400000); moveBird(&bird); } } deleteLink(wall); return 0; }
檢測碰撞
當(dāng) justHead()
函數(shù)沒有檢測到碰撞時,返回 0,當(dāng)檢測到碰撞時,返回 1。
當(dāng)小鳥與上下邊界發(fā)生碰撞時,
//與上下邊界發(fā)生碰撞
if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM)
當(dāng)小鳥與墻體發(fā)生碰撞時,
//小鳥與墻體發(fā)生碰撞
bird->pos.X >= wall->pos.X && bird->pos.X <= wall->pos.X+ WIDTH
代碼
#include "./head.h" //EVALUATING 宏定義 1 為 評測模式 0 為命令行模式 #define EVALUATING 1 //監(jiān)測小鳥碰撞 int justHead(WALL *wall, BIRD *bird){ if(wall == NULL) return -1; //與上下邊界發(fā)生碰撞 if(bird->pos.Y <= 0 || bird->pos.Y >= BOOTEM) return 1; //小鳥與墻體發(fā)生碰撞 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;// 請忽略 輔助評測 無實(shí)際意義 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 實(shí)踐練習(xí)
代碼
#include <stdio.h> #include <stdlib.h> #include <curses.h> #include <time.h> #include <termios.h> #include <unistd.h> #include <fcntl.h> #include <stdbool.h> #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); //設(shè)置顏色 void Gotoxy(int x, int y);//定位光標(biāo) bool SetConsoleColor(int back,int front); //設(shè)置顏色 void CheckWall(COORD wall[]);//顯示柱子墻體 void PrtBird(BIRD *bird);//顯示小鳥 int CheckWin(COORD *wall, BIRD *bird);//檢測小鳥是否碰到墻體或者超出上下邊界 void Begin(BIRD *bird);//顯示上下邊界和分?jǐn)?shù) 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; } //主函數(shù) 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) //游戲循環(huán)執(zhí)行 { Begin(&bird); //清屏并顯示上下邊界和分?jǐn)?shù) 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); //清屏并顯示上下邊界和分?jǐn)?shù) 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.自由體驗(yàn)游戲\n"); int mode; while(scanf("%d", &mode)) { if (mode != 1 && mode != 2) { printf(" 輸入數(shù)據(jù)有誤,請重新輸入!\n\n 請選擇模式:\n 1.評測模式\n 2.自由體驗(yàn)游戲\n"); continue; } else return mode; } return 1; } //函數(shù)功能:定位光標(biāo) void Gotoxy(int x, int y)//void Gotoxy(COORD pos) { printf ( "%c[%d;%df" ,0x1B,y,x); } //函數(shù)功能:設(shè)置顏色 bool SetConsoleColor(int back,int front) { printf("\033[%dm",(front)); return TRUE; } //函數(shù)功能:顯示柱子墻體 void CheckWall(COORD wall[]) { int i; srand(time(0)); COORD temp = {wall[2].X + DIS, rand() % 13 + 5};//隨機(jī)產(chǎn)生一個新的柱子 if (wall[0].X < 10) //超出預(yù)設(shè)的左邊界 { //最左側(cè)的柱子墻消失,第二個柱子變成第一個,第三個柱子變成第二個,新產(chǎn)生的柱子變成第三個 /********** Begin **********/ wall[0] = wall[1];//最左側(cè)的柱子墻消失,第二個柱子變成第一個 wall[1] = wall[2];//第三個柱子變成第二個 wall[2] = temp; //新產(chǎn)生的柱子變成第三個 /********** End **********/ } SetConsoleColor(40,31); //設(shè)置黑色背景,亮紅色前景 for (i=0; i<3; ++i)//每次顯示三個柱子墻 { //顯示上半部分柱子墻 temp.X = wall[i].X + 1;//向右縮進(jìn)一格顯示圖案 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++; //向右縮進(jìn)一格顯示圖案 temp.Y++; //在下一行顯示下面的圖案 for (; (temp.Y)<26; temp.Y++)//一直顯示到第25行 { Gotoxy(temp.X, temp.Y); printf("■■■■■■"); } } Gotoxy(0, 26); printf("\n");//第1行顯示分?jǐn)?shù) } //函數(shù)功能:顯示小鳥 void PrtBird(BIRD *bird) { SetConsoleColor(40,33); //設(shè)置黑色背景,亮黃色前景 Gotoxy(bird->pos.X, bird->pos.Y); printf("o->"); } //函數(shù)功能:檢測小鳥是否碰到墻體或者超出上下邊界,是則返回0,否則分?jǐn)?shù)加1并返回1 int CheckWin(COORD *wall, BIRD *bird) { /********** Begin **********/ if (bird->pos.X >= wall->X) //小鳥的橫坐標(biāo)進(jìn)入柱子坐標(biāo)范圍 { if (bird->pos.Y <= wall->Y || bird->pos.Y >= wall->Y + BLAN) { return 0; //小鳥的縱坐標(biāo)碰到上下柱子,則返回0 } } if (bird->pos.Y < 1 || bird->pos.Y > 26) { return 0; //小鳥的位置超出上下邊界,則返回0 } (bird->score)++; //分?jǐn)?shù)加1 return 1; /********** End **********/ } //函數(shù)功能:顯示上下邊界和分?jǐn)?shù) void Begin(BIRD *bird) { system("clear"); Gotoxy(0, 26); //第26行顯示下邊界 printf("==========================================================" "================================Bottom"); Gotoxy(0, 1); //第1行顯示上邊界 printf("==========================================================" "================================Top"); SetConsoleColor(40,33);//設(shè)置黑色背景,亮黃色前景 printf("\n%4d", bird->score);//第1行顯示分?jǐn)?shù) }
最后在liunx環(huán)境下即可完成游戲
到此這篇關(guān)于C語言FlappyBird飛揚(yáng)的小鳥實(shí)現(xiàn)開發(fā)流程的文章就介紹到這了,更多相關(guān)C語言FlappyBird內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VC下通過系統(tǒng)快照實(shí)現(xiàn)進(jìn)程管理的方法
這篇文章主要介紹了VC下通過系統(tǒng)快照實(shí)現(xiàn)進(jìn)程管理的方法,較為詳細(xì)的講述了VC下通過系統(tǒng)快照實(shí)現(xiàn)進(jìn)程管理的原理與具體實(shí)現(xiàn)方法,非常具有實(shí)用價值,需要的朋友可以參考下2014-10-10C++數(shù)據(jù)結(jié)構(gòu)深入探究棧與隊(duì)列
棧和隊(duì)列,嚴(yán)格意義上來說,也屬于線性表,因?yàn)樗鼈円捕加糜诖鎯壿嬯P(guān)系為 "一對一" 的數(shù)據(jù),但由于它們比較特殊,本章講解分別用隊(duì)列實(shí)現(xiàn)棧與用棧實(shí)現(xiàn)隊(duì)列2022-05-05C/C++最短路徑算法之迪杰斯特拉Dijkstra的實(shí)現(xiàn)詳解
Dijkstra(迪杰斯特拉)算法是典型的單源最短路徑算法,用于計(jì)算一個節(jié)點(diǎn)到其他所有節(jié)點(diǎn)的最短路徑。本文將詳解該算法的圖解與實(shí)現(xiàn),需要的可以參考一下2022-07-07C++實(shí)現(xiàn)二叉樹非遞歸遍歷方法實(shí)例總結(jié)
這篇文章主要介紹了C++實(shí)現(xiàn)二叉樹非遞歸遍歷方法實(shí)例總結(jié),是算法設(shè)計(jì)中比較經(jīng)典的一個遍歷算法,需要的朋友可以參考下2014-08-08static_cast,dynamic_cast,reinterpret_cast,const_cast的區(qū)別及用法詳解
以下是對static_cast,dynamic_cast,reinterpret_cast,const_cast的區(qū)別及用法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-09-09C語言深入探究自定義類型之結(jié)構(gòu)體與枚舉及聯(lián)合
今天我們來學(xué)習(xí)一下自定義類型,自定義類型包括結(jié)構(gòu)體、枚舉、聯(lián)合體,小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考2022-05-05