前言
《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 [行坐标;列坐标]。
//x 为行坐标 ,y 为列坐标
printf ( "%c[%d;�" ,0x1B,y,x);
Windows 环境下光标定位
在 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);
现在,我们可以在不同环境中,在不同位置进行打印输出。
代码
代码语言:javascript复制#include <stdio.h>
#define BOOTEM 26 //下边界的高度
#define DISTANCE 10 //两个墙体之间的距离
#define WIDTH 5 //墙体宽度
#define BLANK 10 //上下墙体之间的距离
/**********Begin**********/
//光标定位
void gotoxy(int x, int y){
printf("%c[%d;�", 0x1B, y, x);
}
//函数功能:显示上下边界和分数
void begin(){
system("clear");
//打印上边界
gotoxy(0, 0);
printf("n==========================n");
//打印下边界
gotoxy(0, BOOTEM);
printf("n==========================");
}
/**********End**********/
int main()
{
begin();
return 0;
}
打印小鸟
代码
代码语言:javascript复制#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;
}
小鸟移动
代码
代码语言:javascript复制#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, "*");
代码
代码语言:javascript复制#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
代码
代码语言:javascript复制#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;
}
Flappy bird 实践练习
代码
代码语言:javascript复制#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); //设置颜色
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("%cn",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(" 输入数据有误,请重新输入!nn 请选择模式:n 1.评测模式n 2.自由体验游戏n");
continue;
}
else return mode;
}
return 1;
}
//函数功能:定位光标
void Gotoxy(int x, int y)//void Gotoxy(COORD pos)
{
printf ( "%c[%d;�" ,0x1B,y,x);
}
//函数功能:设置颜色
bool SetConsoleColor(int back,int front)
{
printf("