贪吃蛇—C—基于easyx图形库:从画图程序到贪吃蛇自带穿墙术

Posted 於清樂的碎碎念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贪吃蛇—C—基于easyx图形库:从画图程序到贪吃蛇自带穿墙术相关的知识,希望对你有一定的参考价值。

上节我们用方向控制函数写了个小画图程序,它虽然简单好玩,但我们不应该止步于此。革命尚未成功,同志还需努力。

 

 

开始撸代码之前,我们先理清一下思路。和前面画图程序不同,贪吃蛇可以有很多节,可以用一个足够大的结构体数组来储存它。 还需要一个食物坐标。定义如下:

typedef struct Position  //坐标结构
{
    int x;
    int y;
}Pos;

Pos array;                         //移动方向向量
Pos snake[300000] = {};  //蛇的结构体数组,谁能够无聊到吃299999个食物~_~
long len=1; //蛇的长度
Pos egg; //食物坐标

 

之前的画图程序是四个方向都可以走,可蛇是不能倒着走的,所以方向控制函数要改成这样:

void command()                              //获取键盘命令
{
    if (_kbhit())       //如果有键盘消息
        switch (_getch())      /*这里不能用getchar()*/
        {
        case \'a\':
            if (array.x != 1 || array.y != 0) {//如果命令不是倒着走,就修正方向向量,否则不做改变,下同。
                array.x = -1;
                array.y = 0;
            }
            break;
        case \'d\':
            if (array.x != -1 || array.y != 0) {
                array.x = 1;
                array.y = 0;
            }
            break;
        case \'w\':
            if (array.x != 0 || array.y != 1) {
                array.x = 0;
                array.y = -1;
            }
            break;
        case \'s\':
            if (array.x != 0 || array.y != -1) {
                array.x = 0;
                array.y = 1;
            }
            break;
        }
} 

 

蛇可能不止一节,所以移动函数需要做出改变。仔细一想就知道,走了一步之后,除了头结点外,每个节点的下一个坐标为它前一个结点之前的坐标,而头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)

还有个问题是蛇走过的痕迹需要擦除,每走一步,它留下的痕迹应该是走这一步之前蛇的最末一个结点的坐标,我们需要擦除掉它。

结果如下:

void move()    //修改各节点坐标以达到移动的目的
{
    setcolor(BLACK);        //覆盖尾部走过的痕迹
    rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5);

    for (int i = len-1; i >0; i--)    //除了头结点外,每个节点的下一个坐标为它前一个结点当前的坐标
    {
        snake[i].x = snake[i - 1].x;
        snake[i].y = snake[i - 1].y;
    }
    snake[0].x += array.x*10;             //头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)
    snake[0].y += array.y*10;
}

 

另外,我们的蛇是有穿墙术的~~~它的实现方法非常简单:

void break_wall()
{
    if (snake[0].x >= 640)             //如果越界,从另一边出来
        snake[0].x = 0;
    else if (snake[0].x <= 0)
        snake[0].x = 640;
    else if (snake[0].y >= 480)
        snake[0].y = 0;
    else if (snake[0].y <= 0)
        snake[0].y = 480;
}

 

 

接下来是食物相关函数,这个算是重点。

1. 食物生成

我们希望食物每次出现的位置都是随机的, 可以这样实现。

1         srand((unsigned)time(NULL));
2         egg.x = rand() % 80 * 5 + 100;    //头节点位置随机化
3         egg.y = rand() % 50 * 5 + 100;

 而且食物不能与蛇重合,最好也不要离蛇太近。综合起来就是这样:(srand在初始化中会被调用,所以这里略去了)

void creat_egg()
{
    while (true)
    {
        int ok = 0;   //这是个标记,用于判断函数是否进入了某一分支
        egg.x = rand() % 80 * 5 + 100;    //头节点位置随机化
        egg.y = rand() % 50 * 5 + 100;
        for (int i = 0; i < len; i++)     //判断是否离蛇太近
        {
            if (snake[i].x == 0 && snake[i].y == 0)
                continue;
            if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10)
                ok = -1;   //如果,进入此分支,改变标记
                break;
        }
        if (ok == 0)    //如果不重合了,跳出函数
            return;
    }
}

2. 吃到食物

如果吃到食物,那么需要消除被吃掉的食物,生成新食物,蛇也要增长一节。

我觉得这里最麻烦的就是蛇变长的实现:是在蛇头添加一节,还是在蛇尾?添加在蛇头(尾)的上下左右哪一边?

想来想去,只有在蛇头位置,我们可以根据当前方向向量,在移动方向上新添一节。这对应的代码如下:

        //add snake node
        len += 1;
        for (int i = len - 1; i > 0; i--)    //所有数据后移一个单位,腾出snake[0]给新添的一节
        {
            snake[i].x = snake[i - 1].x;
            snake[i].y = snake[i - 1].y;
        }
        snake[0].x += array.x * 10;             //这就是新添的这一节的位置
        snake[0].y += array.y * 10;

吃到食物的完整代码如下:

void eat_egg()
{
    if (fabs(snake[0].x - egg.x) <= 5 && fabs(snake[0].y - egg.y) <= 5)    //判断是否吃到食物,因为食物位置有点小偏差,只好使用范围判定~~
    {
        setcolor(BLACK);          //hide old egg
        circle(egg.x, egg.y, 5);
creat_egg(); //create new egg
//add snake node len += 1; for (int i = len - 1; i >0; i--) { snake[i].x = snake[i - 1].x; snake[i].y = snake[i - 1].y; } snake[0].x += array.x * 10; //每次移动10pix snake[0].y += array.y * 10; } }

 

 

游戏结束判定

最后,我们还差一个死亡判定,因为自带穿墙术,所以实际的死亡判定只有一个,就是咬到自己,代码如下:

void eat_self()
{
    if (len == 1)             //只有一节当然吃不到自己~~
        return;
    for (int i = 1; i < len; i++)
        if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5)            //如果咬到自己(为了不出bug,使用了范围判定)
        {
            outtextxy(250, 200, "GAME OVER!");  //你的蛇死了~
            Sleep(3000);      //3s时间让你看看你的死相~~
            closegraph();
            exit(0);     //退出
        }
}

当然,你也可以直接丢掉这个函数,然后开心地狂咬自己—_—||

最后:画图函数

画出食物和蛇,其实蛇没必要全部画出来,只要画蛇头就可以了,但这之中有些小问题,谁有兴趣可以自己玩玩,我是懒得动了~

void draw()     //画出蛇和食物
{
    setcolor(BLUE);
    for (int i = 0; i < len; i++)
    {
        rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5);
    }
    setcolor(RED);        //画蛋(怎么感觉怪怪的~)
    circle(egg.x, egg.y, 5);
    Sleep(100);
}

到这里,游戏大功告成~~  什么?你说运行不起来?那是因为少了初始化函数,和游戏循环啦~~这几个都比较简单,就直接放下面了:

void init()              //初始化
{
    initgraph(640, 480);                    //初始化图形界面
    srand((unsigned)time(NULL));            //初始化随机函数
    snake[0].x = rand() % 80 * 5 + 100;    //头节点位置随机化
    snake[0].y = rand() % 50 * 5 + 100;
    array.x = pow(-1,rand());        //初始化方向向量,左或者右
    array.y = 0;
    creat_egg();
}

int main()
{
    init();
    while (true)
    {
        command();      //获取键盘消息
        move();         //修改头节点坐标-蛇的移动
        eat_egg();
        draw();         //作图
        eat_self();
    }

    return 0;
}

好了,这是真的大功告成了。给你们看看死亡方式之自尽:

完整代码如下:

  1 #include<graphics.h>
  2 #include<conio.h>
  3 #include<time.h>
  4 #include<math.h>
  5 
  6 typedef struct Position  //坐标结构
  7 {
  8     int x;
  9     int y;
 10 }Pos;
 11 
 12 Pos snake[300000] = {};
 13 Pos array;
 14 Pos egg;
 15 long len=1;
 16 
 17 void creat_egg()
 18 {
 19     while (true)
 20     {
 21         int ok = 0;
 22         srand((unsigned)time(NULL));            //初始化随机函数
 23         egg.x = rand() % 80 * 5 + 100;    //头节点位置随机化
 24         egg.y = rand() % 50 * 5 + 100;
 25         for (int i = 0; i < len; i++)
 26         {
 27             if (snake[i].x == 0 && snake[i].y == 0)
 28                 continue;
 29             if (fabs(snake[i].x - egg.x) <= 10 && fabs(snake[i].y - egg.y) <= 10)
 30                 ok = -1;
 31                 break;
 32         }
 33         if (ok == 0)
 34             return;
 35     }
 36 }
 37 
 38 void init()              //初始化
 39 {
 40     initgraph(640, 480);                    //初始化图形界面
 41     srand((unsigned)time(NULL));            //初始化随机函数
 42     snake[0].x = rand() % 80 * 5 + 100;    //头节点位置随机化
 43     snake[0].y = rand() % 50 * 5 + 100;
 44     array.x = pow(-1,rand());        //初始化方向向量
 45     array.y = 0;
 46     creat_egg();
 47 }
 48 
 49 void command()                              //获取键盘命令
 50 {
 51     if (_kbhit())       //如果有键盘消息
 52         switch (_getch()/*这里不能用getchar()*/)
 53         {
 54         case \'a\':
 55             if (array.x != 1 || array.y != 0) {//如果不是反方向
 56                 array.x = -1;
 57                 array.y = 0;
 58             }
 59             break;
 60         case \'d\':
 61             if (array.x != -1 || array.y != 0) {
 62                 array.x = 1;
 63                 array.y = 0;
 64             }
 65             break;
 66         case \'w\':
 67             if (array.x != 0 || array.y != 1) {
 68                 array.x = 0;
 69                 array.y = -1;
 70             }
 71             break;
 72         case \'s\':
 73             if (array.x != 0 || array.y != -1) {
 74                 array.x = 0;
 75                 array.y = 1;
 76             }
 77             break;
 78         }
 79 } 
 80 
 81 void move()    //修改各节点坐标以达到移动的目的
 82 {
 83     setcolor(BLACK);        //覆盖尾部走过的痕迹
 84     rectangle(snake[len-1].x - 5, snake[len-1].y - 5, snake[len-1].x + 5, snake[len-1].y + 5);
 85 
 86     for (int i = len-1; i >0; i--)
 87     {
 88         snake[i].x = snake[i - 1].x;
 89         snake[i].y = snake[i - 1].y;
 90     }
 91     snake[0].x += array.x*10;             //每次移动10pix
 92     snake[0].y += array.y*10;
 93 
 94     if (snake[0].x >= 640)             //如果越界,从另一边出来
 95         snake[0].x = 0;
 96     else if (snake[0].x <= 0)
 97         snake[0].x = 640;
 98     else if (snake[0].y >= 480)
 99         snake[0].y = 0;
100     else if (snake[0].y <= 0)
101         snake[0].y = 480;
102 }
103 
104 void eat_egg()
105 {
106     if (fabs(snake[0].x - egg.x)<=5 && fabs(snake[0].y - egg.y)<=5)
107     {
108         setcolor(BLACK);          //shade old egg
109         circle(egg.x, egg.y, 5);
110         creat_egg();
111         //add snake node
112         len += 1;
113         for (int i = len - 1; i >0; i--)
114         {
115             snake[i].x = snake[i - 1].x;
116             snake[i].y = snake[i - 1].y;
117         }
118         snake[0].x += array.x * 10;             //每次移动10pix
119         snake[0].y += array.y * 10;
120     }
121 }
122 
123 void draw()     //画出蛇和食物
124 {
125     setcolor(BLUE);
126     for (int i = 0; i < len; i++)
127     {
128         rectangle(snake[i].x - 5, snake[i].y - 5, snake[i].x + 5, snake[i].y + 5);
129     }
130     setcolor(RED);
131     circle(egg.x, egg.y, 5);
132     Sleep(100);
133 }
134 
135 void eat_self()
136 {
137     if (len == 1)
138         return;
139     for (int i = 1; i < len; i++)
140         if (fabs(snake[i].x - snake[0].x) <= 5 && fabs(snake[i].y - snake[0].y) <= 5)
141         {
142             Sleep(1000);
143             outtextxy(250, 200, "GAME OVER!");
144             Sleep(3000);
145             closegraph();
146             exit(0);
147         }
148 }
149 
150 int main()
151 {
152     init();
153     while (true)
154     {
155         command();      //获取键盘消息
156         move();         //修改头节点坐标-蛇的移动
157         eat_egg();
158         draw();         //作图
159         eat_self();
160     }
161 
162     return 0;
163 }
snakey

可能还有若干bug留存,欢迎大家指正~~

甲铁城镇~

 

以上是关于贪吃蛇—C—基于easyx图形库:从画图程序到贪吃蛇自带穿墙术的主要内容,如果未能解决你的问题,请参考以下文章

easyx图形库做贪吃蛇游戏

贪吃蛇easyx版本

c语言,Easyx实现贪吃蛇

数据结构大作业-贪吃蛇

Linux环境下,基于Ncurse图形库贪吃蛇小游戏(上)

Linux环境下,基于Ncurse图形库贪吃蛇小游戏(上)