您当前的位置:首页 > 计算机 > 编程开发 > C语言

C语言贪吃蛇游戏源码下载、源码解析和设计思路

时间:12-28来源:作者:点击数:

在《C语言贪吃蛇游戏演示和说明》一节中,我们对贪吃蛇游戏的玩法进行了介绍和演示,这节就来分析一下它的源码。

贪吃蛇源代码下载地址:https://pan.baidu.com/s/1pMk7nlx  密码:2yju

各位读者不妨先将源码下载下来浏览一遍,记住关键的几个函数,整理一下不了解的知识点,做到心中有数。

需要说明的是:贪吃蛇背景地图、食物、贪吃蛇本身都是由特殊字符组成(由 printf() 输出),并不是绘制出来的图形。C语言标准库没有绘图函数,如果绘图的话,就需要使用第三方库,增加了大家的学习成本,所以我们采用了“投机取巧”的办法,用特殊字符来模拟不同的图形。

一. 关键知识点

下面请各位读者先学习一下该游戏中涉及到的几个关键知识点,有了这些必备条件,我们才好讲解贪吃蛇的设计思路。

1) 改变输出文本的颜色

贪吃蛇游戏的背景地图是是绿色的,边框是钻红色的,食物是红色的,贪吃蛇本身是黄色的,这就涉及到如何改变文本的输出颜色,请大家猛击《彩色版的C语言》了解详情。

2) 在任意位置输出文本

在一般的程序中,字符都是依次输出的,例如当前控制台上显示的是“123456”,如果我们希望输出“abcd”,那么“abcd”就位于“123456”之后。在一般的程序中这是没有问题的,但是对于贪吃蛇游戏,我们需要自己来控制字符的输出位置,例如:
  • 输出背景地图后,我们需要在背景地图中间输出贪吃蛇和食物;
  • 要统计贪吃蛇吃掉的食物的数量,就必须不断改变同一位置的数字。
这是如何做到的呢?请大家猛击《C语言在任意位置输出》了解详情。

3) 键盘监听

在贪吃蛇移动过程中,必须能够及时捕获用户按下的方向键,并改变移动方向,这是如何做到的呢?请大家猛击《C语言非阻塞式键盘监听》了解详情。

4) 获取随机数

贪吃蛇的食物会随机出现在背景地图上的任意位置,没有任何规律,这就要求程序生成一对随机数值,来控食物所在的行和列。那么,随机数值是如何产生的呢?请大家猛击《C语言获取随机数》了解详情。

二. 输出贪吃蛇背景地图

贪吃蛇背景地图的最终效果如下图所示:

钻红色空心方框表示边框,绿色实心方框表示贪吃蛇的活动区域。实现代码如下:

#include <stdio.h>
#include <conio.h>
#include <windows.h>
int main(){
    int width = 30, height = width;  //宽度和高度
    int x, y;  //x、y分别表示当前行和列
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  
    //设置窗口大小
    system("mode con: cols=64 lines=32");
    //打印背景,按行输出
    for(x=0; x<width; x++){
        for(y=0; y<height; y++){
            if(y==0 || y==width-1 || x==0 || x==height-1){  //输出边框
                SetConsoleTextAttribute(hConsole, 4 );
                printf("□");
            }else{  //贪吃蛇活动区域
                SetConsoleTextAttribute(hConsole, 2 );
                printf("■");
            }
        }
        printf("\n");
    }
    //暂停
    getch();
    return 0;
}

程序的关键是两层嵌套的循环。x=0 时,内层循环执行30次,输出第0行;x=1 时,内层循环又执行30次,输出1行。以此类推,直到 x=30,外层循环不再执行(内存循环当然也就没机会执行),输出结束。

注意,□和■虽然都是单个字符,但它们不在ASCII码范围内,是宽字符,占用两个字节,用 putchar 等输出ASCII码(一个字节)的函数输出时可能会出现问题,所以作为字符串输出。

三. 让贪吃蛇移动起来

接下来,我们来让一条长度为 n 的贪吃蛇移动起来,而且可以用WASD四个键控制移动方向,如下图所示:

其实,移动贪吃蛇并不需要移动所有节点,只需要添加蛇头、删除蛇尾,就会有动态效果,这样会大大提高程序的效率。

我们可以定义一个结构体来表示贪吃蛇一个节点在控制台上的位置(也即所在行和列):

struct POS{
    int x;  //所在行
    int y;  //所在列
}

然后再定义一个比贪吃蛇长的数组来保存贪吃蛇的所有节点:

struct POS snakes[n+m];

并设置两个变量 headerIndex、tailIndex,分别用来表示蛇头、蛇尾在数组中的下标坐标,这样每次添加蛇头、删除蛇尾时只需要改变两个变量的值就可以。如下图所示:

headerIndex 和 tailIndex 都向前移动,也就是每次减1。如果 headerIndex=0,也就是指向数组的头部,那么下次移动时 headerIndex = arrayLength - 1,也就是指向数组的尾部,就这样一圈一圈地循环,tailIndex 也是如此。这相当于把数组首尾相连成一个圆圈,贪吃蛇在这个圆圈中不停地转圈。

由于这部分的演示代码较长,请大家到百度网盘下载:http://pan.baidu.com/s/1bouZGoZ    提取密码:4g74

move.rar
13ffbdf1d19676d4795800d4f040051f.rar (1.48 KB)

对代码的说明

1) 贪吃蛇的最大长度为绿色方框的个数,所以我们将容纳贪吃蛇的数组 snakes 的长度定义为(HEIGHT-2) * (WIDTH-2)

2) □、■、★ 占用两个字符的宽度,所以在 setPosition() 中该变光标位置时,光标的X坐标应该是:

coord.X = 2*y;

四. 随机生成食物

食物的生成是贪吃蛇游戏的难点,因为食物只能在绿色背景(■)部分生成,它不能占用钻红色边框(□)贪吃蛇本身(★)的位置。

最容易想到的思路是:随机生成一个坐标,然后检测该坐标是不是绿色背景,如果是,那么成功生成,如果不是,继续生成随机数,继续检测。幸运的话,可以一次生成;不幸的话,可能要循环好几次甚至上百次才能生成,这样带来的后果就是程序卡死一段时间,贪吃蛇不能移动。

这种方案的优点就是思路简单,容易实现,缺点就是贪吃蛇移动不流畅,经常会卡顿。

改进的方案

最好的方案是生成的随机数一定会在绿色背景的范围内,这样一次就能成功生成食物。该如何实现呢?

这里我们提供了一种看起来不容易理解却行之有效的方案。

我们不妨将贪吃蛇的活动范围称为“贪吃蛇地图”,而加上边框就称为“全局地图”。首先定义一个二维的结构体数组,用来保存所有的点(也即全局地图):

struct{
    char type;
    int index;
}globalMap[MAXWIDTH][MAXHEIGHT];

MAXWIDTH 为宽度,也即列数;MAXHEIGHT 为高度,也即行数。成员 type 表示点的类型,它可以是食物、绿色背景、边框和贪吃蛇节点。

直观上讲,应该将 type 定义为int类型,不过int占用四个字节,而节点类型的取值范围非常有限,一个字节就足够了,所以为了节省内存才定义为char类型。

然后再定义一个一维的结构体数组,用来保存贪吃蛇的有效活动范围:

struct{
    int x;
    int y;
} snakeMap[ (MAXWIDTH-2)*(MAXHEIGHT-2) ];

x、y 表示行和列,也就是 globalMap 数组的两个下标。globalMap 数组中的 index 成员就是 snakeMap 数组的下标。

globalMap 表示了所有节点的信息,而 snakeMap 只表示了贪吃蛇的活动区域。通过 snakeMap 可以定位 globalMap 中的元素,反过来通过 globalMap 也可以找到 snakeMap 中的元素。它们之间的对应关系请看下图:


图1:globalMap 和 snakeMap 的初始对应关系

贪吃蛇向左移动时,headerIndex 指向 404,tailIndex指向 406。

在 snakeMap 数组中,贪吃蛇占用一部分元素,剩下的元素都是绿色的背景,可以随机选取这些元素中的一个作为食物,然后通过 x、y 确定食物的坐标。而这个坐标,一定在绿色背景范围内。

需要注意的是,在贪吃蛇移动过程中需要维护 globalMap 和 snakeMap 的对应关系。

这种方案的另外一个优点就是,贪吃蛇移动时很容易知道下一个节点的类型,不用遍历数组就可以知道是否与自身相撞。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门