欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

贪吃蛇C/C++面向过程源码分析(双缓冲区 | 248行代码)

程序员文章站 2022-06-19 09:19:21
贪吃蛇会是C/C++选手学完做的一个小DIY,考察了游戏的设计逻辑和C/C++基础编程能力。由于贪吃蛇的设计过程相对简单,采用面向过程(函数式)编程来实现。一、游戏逻辑设计贪吃蛇的游戏逻辑可以归纳如下:蛇在一个区域中向上下左右四个方向移动吃到食物之后会变长(增长 1 格),在蛇尾巴添加一格蛇头的运动决定蛇的运动方向,只要蛇头不碰墙或者不撞到自己,游戏就能继续二、蛇活动区域设计:蛇的活动区域就是一个方格,本质就是一个字符二维矩阵。在这个矩阵中,有四种方格:墙(’#’)、蛇(’*’...

贪吃蛇会是C/C++选手学完做的一个小DIY,考察了游戏的设计逻辑和C/C++基础编程能力。由于贪吃蛇的设计过程相对简单,采用面向过程(函数式)编程来实现。

一、游戏逻辑设计

贪吃蛇的游戏逻辑可以归纳如下:

  1. 蛇在一个区域中向上下左右四个方向移动

  2. 吃到食物之后会变长(增长 1 格),在蛇尾巴添加一格

  3. 蛇头的运动决定蛇的运动方向,只要蛇头不碰墙或者不撞到自己,游戏就能继续

二、蛇活动区域设计:

蛇的活动区域就是一个方格,本质就是一个字符二维矩阵。在这个矩阵中,有四种方格:墙(’#’)、蛇(’*’)、食物(‘O’)和空白(’ ')。蛇的运动会动态改变这些格子的状态

// 一开始蛇在中间,头朝右运动
// H = 20, W = 26
char mp[H][W] = {
    "#########################",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#          **           #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#                       #",
    "#########################"
};

三、蛇的表示设计:

一条蛇实际上就是“一条链”,在每个“节骨眼”(格子)都会有自己的状态,这些状态分别是上(UP),下(DOWN),左(LEFT),右(RIGHT),而这些状态就决定了蛇的运动方向,间接影响了区域中方格的状态。这四个状态可以使用枚举类型表示:

enum direction {
    UP,
    DOWN,
    LEFT,
    RIGHT,
    null
} ;

//紧接着定义 enum direction status[H][W],表示方格的状态(上下左右,仅对蛇身生效,若非蛇格,则为null),记得main刚开始要初始化:
void init() {
    for (int i = 1; i <= 18; ++i) {
        for (int j = 1; j <= 23; ++j) {
            status[i][j] = null;
        }
    }
    status[9][12] = RIGHT;
    status[9][11] = RIGHT;
    while (true) { // 同时随机生成食物,注意食物不能在蛇身上和墙(外)
        int rx = 1 + rand()%23;
        int ry = 1 + rand()%18;
        if (mp[rx][ry] == ' ') {
            mp[rx][ry] = 'O';
            return ;
        }
    }
}

四、蛇的运动设计

蛇的运动,根源来自于蛇头。所以可以从蛇头开始,循着status[][]的信息,进行DFS,一直更新到蛇尾状态结束。而如果蛇头刚好吃到食物,则在DFS开始之前,在蛇尾巴再加上一格。

// (hx,hy)是蛇头,从此DFS,线性更新蛇,模拟蛇的运动
void dfs(int hx,int hy) {
    if (status[hx][hy] == UP) {
        mp[hx][hy] = mp[hx+1][hy];
        if (mp[hx][hy] == ' ') {
            tail = {hx-1,hy}; // tail是尾巴标记,方便吃到食物增长
            status[hx][hy] = null;
            return ;
        }
        dfs(hx+1,hy);
    }
    else if (status[hx][hy] == DOWN) {
        mp[hx][hy] = mp[hx-1][hy];
        if (mp[hx][hy] == ' ') {
            tail = {hx+1,hy};
            status[hx][hy] = null;
            return ;
        }
        dfs(hx-1,hy);
    }
    else if (status[hx][hy] == LEFT) {
        mp[hx][hy] = mp[hx][hy+1];
        if (mp[hx][hy] == ' ') {
            tail = {hx,hy-1};
            status[hx][hy] = null;
            return ;
        }
        dfs(hx,hy+1);
    }
    else if (status[hx][hy] == RIGHT) {
        mp[hx][hy] = mp[hx][hy-1];
        if (mp[hx][hy] == ' ') {
            tail = {hx,hy+1};
            status[hx][hy] = null;
            return ;
        }
        dfs(hx,hy-1);
    }
}

贪吃蛇还要相应键盘输入,应该添加键盘相应事件,并更新蛇头状态,并调用DFS模块:

void operation() {
    while (true) {
        int hx = head.first; // 蛇头x坐标
        int hy = head.second; // 蛇头y坐标
        // 键盘相应事件
        if (kbhit()) {
            char ch = getch();
            if (ch == 'w' && status[hx][hy] != DOWN) { // 往上
                --hx;
                status[hx][hy] = UP;
            }
            else if (ch == 's' && status[hx][hy] != UP) { // 往下
                ++hx;
                status[hx][hy] = DOWN;
            }
            else if (ch == 'a' && status[hx][hy] != RIGHT) { // 往左
                --hy;
                status[hx][hy] = LEFT;
            }
            else if (ch == 'd' && status[hx][hy] != LEFT) { // 往右
                ++hy;
                status[hx][hy] = RIGHT;
            }
            else {
                goto T1;
            }
        } else {
            T1:
            if (status[hx][hy] == UP) {
                --hx;
                status[hx][hy] = UP;
            }
            if (status[hx][hy] == DOWN) {
                ++hx;
                status[hx][hy] = DOWN;
            }
            if (status[hx][hy] == LEFT) {
                --hy;
                status[hx][hy] = LEFT;
            }
            if (status[hx][hy] == RIGHT) {
                ++hy;
                status[hx][hy] = RIGHT;
            }
        }
        if (mp[hx][hy] == '#' || mp[hx][hy] == '*') {
            printf("\a");
            printf("You lose! Get %d points",score);
            exit(0);
        }
        // ----------------
        if (mp[hx][hy] == 'O') {
            int tx = tail.first;
            int ty = tail.second;
            if (status[tx][ty] == UP) {
                ++tx;
                status[tx][ty] = UP;
                mp[tx][ty] = '*';
            }
            if (status[tx][ty] == DOWN) {
                --tx;
                status[tx][ty] = DOWN;
                mp[tx][ty] = '*';
            }
            if (status[tx][ty] == LEFT) {
                ++ty;
                status[tx][ty] = LEFT;
                mp[tx][ty] = '*';
            }
            if (status[tx][ty] == RIGHT) {
                --ty;
                status[tx][ty] = RIGHT;
                mp[tx][ty] = '*';
            }
            tail = {tx,ty};
            while (true) {
                int rx = 1 + rand()%23;
                int ry = 1 + rand()%18;
                if (mp[rx][ry] == ' ') {
                    mp[rx][ry] = 'O';
                    break;
                }
            }
            delay -= 10; // 运动加速,但不得少于100
            delay = delay > 100 ? delay : 100;
            score += 10; // 得分增加
        }
        dfs(hx,hy); // 新蛇头,进行蛇蠕动
        head = {hx,hy};
        display();
    }
}

五、刷新控制台:

贪吃蛇游戏需要不断地刷新控制台,用System(“cls”)当然可以解决这个问题,但当蛇地活动区域较大时,由于输出缓冲的问题,看到的屏幕是闪烁的。良好的设计应该解决这个问题,这里我们可以使用双缓冲区解决这个问题。

HANDLE hOutput, hOutBuf;//控制台屏幕缓冲区句柄
HANDLE *houtpoint;//显示指针
COORD coord = { 0,0 };
//双缓冲处理显示
DWORD bytes = 0;
bool showcircle = false;

在main模块中引入下列定义:

//创建新的控制台缓冲区
hOutBuf = CreateConsoleScreenBuffer(
    GENERIC_WRITE,//定义进程可以往缓冲区写数据
    FILE_SHARE_WRITE,//定义缓冲区可共享写权限
    NULL,
    CONSOLE_TEXTMODE_BUFFER,
    NULL
);
hOutput = CreateConsoleScreenBuffer(
    GENERIC_WRITE,//定义进程可以往缓冲区写数据
    FILE_SHARE_WRITE,//定义缓冲区可共享写权限
    NULL,
    CONSOLE_TEXTMODE_BUFFER,
    NULL
);
//隐藏两个缓冲区的光标
CONSOLE_CURSOR_INFO cci;
cci.bVisible = 0;
cci.dwSize = 1;
SetConsoleCursorInfo(hOutput, &cci);
SetConsoleCursorInfo(hOutBuf, &cci);

展示活动区域的时候使用双缓冲区输出:

void display() {
    showcircle = !showcircle;
    if (showcircle) {
        houtpoint = &hOutput;
    }
    else {
        houtpoint = &hOutBuf;
    }
    for (int i = 0; i < H; ++i) {
        coord.X = 0;
        coord.Y = i;
        WriteConsoleOutputCharacterA(*houtpoint, (char*)mp[i], W, coord, &bytes);
    }
    //设置新的缓冲区为活动显示缓冲
    SetConsoleActiveScreenBuffer(*houtpoint);
    Sleep(delay);
}

欢迎同步关注:ACMFans_Club微信公众号

本文地址:https://blog.csdn.net/weixin_44026604/article/details/107623402

相关标签: 闲聊