C语言版flappy_bird实现

一、实验说明

1. 环境介绍

本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌面上的程序:

  1. LX终端(LXTerminal):Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令。
  2. GVim

2.环境使用

使用GVim编辑器输入实验所需的代码及文件,使用LX终端(LXTerminal)运行所需命令进行操作。 完成实验后可以点击桌面上方的“实验截图”保存并分享实验结果到微博,向好友展示自己的学习进度。实验楼提供后台系统截图,可以真实有效证明您已经完成了实验。 实验记录页面可以在“我的主页”中查看,其中含有每次实验的截图及笔记,以及每次实验的有效学习时间(指的是在实验桌面内操作的时间,如果没有操作,系统会记录为发呆时间)。这些都是您学习的真实性证明。 本课程中的所有源码可以通过以下方式下载:

二、项目介绍

这次我们的项目是flappy bird游戏,这次,准备好砸你的键盘吧。最终效果图是这样的

三、项目实战

1. 基础知识

我们的项目用到了一点数据结构的知识,还涉及到了linux的一些系统调用,有助于我们加深对linux下的程序设计的深入理解。此外,我们还用了一个文本界面的屏幕绘图库ncurses,编译时需要加上-lcurses选项。

1.1.安装ncurses库

在终端输入sudo apt-get install libncurses5-dev

2. 设计思路

我们的flappy bird游戏里最关键的两点就是响应键盘输入和定时绘图。这就需要结合linux提供的系统函数和我们使用的ncurses库来完成了。另一个问题是如何使bird能看起来像是在向前飞一样,如果一直移动bird势必会超出屏幕范围。我们不妨反过来想,让bird一直保持在原地,而让背景一直向bird的方向移动。这样,就造成了好像bird一直向前移动的效果。

3. 开始动手

让我们先来完成一些基础工作,因为我们是终端字符界面,所以一切离不开ASCII字符,我们需要定义一些常量。我们用'*'来表示背景里的柱子,用'O'来表示bird。好了,代码如下:

include 
include 
include 
include 

define CHAR_BIRD 'O'
define CHAR_STONE '*'
define CHAR_BLANK ' '

背景上的柱子用链表结构表示,定义为结构体如下:

typedef struct node {
    int x, y;
    struct node *next;
}node, *Node;

还需要几个全局变量:

Node head, tail;
int bird_x, bird_y;
int ticker;

为了调用起来方便,我们先声明一下我们定义的函数:

void init();
void init_bird();
void init_draw();
void init_head();
void init_wall();
void drop(int sig);
int set_ticker(int n);

4. 定时问题

现在我们来解决如何让背景定时移动的问题。linux系统为我们提供了信号这一概念,可以解决我们的问题。不知道什么是信号?没关系,说白了就是linux内核有个定时器,它每隔一段时间就会向我们的程序发送一个信号,我们的信号接收函数就会被自动执行,我们只要在接受信号的函数里移动背景就行了。因为是内核发送的信号,所以不会因为我们的键盘接受阻塞而阻塞。怎么样,是不是很简单?下面就来写我们的代码:

    int set_ticker(int n_msec)
    {
        struct itimerval timeset;
        long n_sec, n_usec;

        n_sec = n_msec / 1000;
        n_usec = (n_msec % 1000) * 1000L;

        timeset.it_interval.tv_sec = n_sec;
        timeset.it_interval.tv_usec = n_usec;

        timeset.it_value.tv_sec = n_sec;
        timeset.it_value.tv_usec = n_usec;

        return setitimer(ITIMER_REAL, ×et, NULL);
    }

以上代码用来设定内核的定时周期,下面是我们的信号接受函数:

void drop(int sig)
{
    int j;
    Node tmp, p;
//draw bird
    move(bird_y, bird_x);
    addch(CHAR_BLANK);
    refresh();
    bird_y++;
    move(bird_y, bird_x);
    addch(CHAR_BIRD);
    refresh();
    if((char)inch() == CHAR_STONE) {
        set_ticker(0);
        sleep(1);
        endwin();
        exit(0);
    }
//first wall out of screen?
    p = head->next;
    if(p->x < 0) {
        head->next = p->next;
        free(p);
        tmp = malloc(sizeof(node));
        tmp->x = 99;
        do {
            tmp->y = rand() % 16;
        } while(tmp->y < 5);
        tail->next = tmp;
        tmp->next = NULL;
        tail = tmp;
        ticker -= 10;  //speed up!
        set_ticker(ticker);
    }
//draw new walls
    for(p = head->next; p->next != NULL; p->x--, p = p->next) {
        for(j = 0; j < p->y; j++) {
            move(j, p->x);
            addch(CHAR_BLANK);
            refresh();
        }
        for(j = p->y+5; j <= 23; j++) {
            move(j, p->x);
            addch(CHAR_BLANK);
            refresh();
        }

        if(p->x-10 >= 0 && p->x < 80) {
            for(j = 0; j < p->y; j++) {
                move(j, p->x-10);
                addch(CHAR_STONE);
                refresh();
            }
            for(j = p->y + 5; j <= 23; j++) {
                move(j, p->x-10);
                addch(CHAR_STONE);
                refresh();
            }
        }
    }
    tail->x--;
}

我们在信号接受函数里将背景向前移动一列,并且让bird向下掉落一行,而且要检测bird是否撞到柱子,是的话game就over了。

5. main函数

先看看代码:

int main()
{
    char ch;

    init();
    while(1) {
        ch = getch();
        if(ch == ' ' || ch == 'w' || ch == 'W') {
            move(bird_y, bird_x);
            addch(CHAR_BLANK);
            refresh();
            bird_y--;
            bird_y--;
            move(bird_y, bird_x);
            addch(CHAR_BIRD);
            refresh();
            if((char)inch() == CHAR_STONE) {
                set_ticker(0);
                sleep(1);
                endwin();
                exit(0);
            }
        }
        else if(ch == 'z' || ch == 'Z') {
            set_ticker(0);
            do {
                ch = getch();
            } while(ch != 'z' && ch != 'Z');
            set_ticker(ticker);
        }
        else if(ch == 'q' || ch == 'Q') {
            sleep(1);
            endwin();
            exit(0);
        }
    }

    return 0;
}

我们在main里先做好初始化,然后在循环中接受键盘输入。如果是w或空格键被按下,我们的bird就向上飞两行,如果是q被按下就退出游戏,z被按下游戏则会暂停。 下面看一下init函数:

void init()
{
    initscr();
    cbreak();
    noecho();
    curs_set(0);
    srand(time(0));
    signal(SIGALRM, drop);

    init_bird();
    init_head();
    init_wall();
    init_draw();
    sleep(1);
    ticker = 500;
    set_ticker(ticker);
}

init函数首先初始化屏幕,调用了ncurses提供的函数,然后调用各个子函数进行初始化。注意,我们安装了信号接收函数drop,并且设定了定时时间。各个初始化子函数如下。 初始化bird位置:

void init_bird()
{
    bird_x = 5;
    bird_y = 15;
    move(bird_y, bird_x);
    addch(CHAR_BIRD);
    refresh();
    sleep(1);
}

初始化背景里的柱子链表结构:

void init_head()  //with header
{
    Node tmp;

    tmp = malloc(sizeof(node));
    tmp->next = NULL;
    head = tmp;
    tail = head;
}

void init_wall()
{
    int i;
    Node tmp, p;

    p = head;
    for(i = 19; i <= 99; i += 20) {
        tmp = malloc(sizeof(node));
        tmp->x = i;
        do {
            tmp->y = rand() % 16;
        }while(tmp->y < 5);
        p->next = tmp;
        tmp->next = NULL;
        p = tmp;
    }
    tail = p;
}

初始化屏幕:

void init_draw()
{
    Node p;
    int i, j;

    for(p = head->next; p->next != NULL; p = p->next) {
        for(i = p->x; i > p->x-10; i--) {
            for(j = 0; j < p->y; j++) {
                move(j, i);
                addch(CHAR_STONE);
                refresh();
            }
            for(j = p->y+5; j <= 23; j++) {
                move(j, i);
                addch(CHAR_STONE);
                refresh();
            }
        }
        sleep(1);
    }
}

6. 编译

gcc flappy_bird.c -o flappy_bird -lcurses

到此,我们的flappy_bird游戏就完成了

你可能感兴趣的:(C/C++)