• 贪吃蛇—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留存,欢迎大家指正~~

    甲铁城镇~

  • 相关阅读:
    linux内存管理之数据结构必备
    Script快速入门与查表
    Bash编程linux诸多熟记命令
    NandFlash/NorFlash源码模型和驱动编写方法
    linux内存管理之uboot第一步
    《Magus Night》
    《P2447 [SDOI2010]外星千足虫》
    DFS 树的理解
    《2021CCPC桂林》
    《GRAPH》
  • 原文地址:https://www.cnblogs.com/kirito-c/p/5596160.html
Copyright © 2020-2023  润新知