zoukankan      html  css  js  c++  java
  • 【代码解析】双向链表实现贪吃蛇游戏!简单易学,开发自己第一个游戏!

    如何利用双向链表实现一个简易的 C 语言版贪吃蛇游戏(如下图所示)。


     

    其中,黄色框代表贪吃蛇,红色 ★ 代表食物!

    使用双向链表实现此游戏,有以下几点需要做重点分析。

     

    1) 

    我们知道,双向链表中各个节点的标准构成是一个数据域和 2 个指针域,但对于实现贪吃蛇游戏来说,由于各个节点的位置是随贪吃蛇的移动而变化的,因此链表中的各节点还需要随时进行定位。

    在一个二维画面中,定义一个节点的位置,至少需要所在的行号和列号这 2 个数据。

    由此,我们可以得出构成贪吃蛇的双向链表中各节点的构成:

    -------------------------------

    //创建表示蛇各个节点的结构体
    
    typedef struct SnakeNode {
    
        int x, y;//记录节点所在的行和列
    
        struct SnakeNode *pre;//指向前驱节点的指针
    
        struct SnakeNode *next;//指向后续节点的指针
    
    }Node, *pNode;

    -------------------------------

     

    2) 

    贪吃蛇的移动,本质上就是对链表中各个节点的重新定位。

    换句话说,除非贪吃蛇吃到食物,否则无论怎样移动,都不会对双向链表的整个结构(节点数)产生影响,唯一受影响的就只是各个节点中 (x,y) 这对定位数据。

    由此,我们可以试着设计出实现贪吃蛇移动的功能函数,本节所用的实现思想分为 2 步:

            ▶ 从蛇尾(双向链表尾节点)开始,移动向前遍历,过程中依次将当前节点的 (x,y) 修改为前驱节点的 (x,y),由此可实现整个蛇身(除首元节点外的其它所有节点)的向前移动;

            ▶ 接收用户输入的移动指令,根据用户指示贪吃蛇向左、向右、向上还是向下移动,首元节点中的 (x,y) 分别做 x-1、x+1、y-1 和 y+1 运算。

    如下所示,move() 函数就实现了贪吃蛇的移动:

    -------------------------------

    //贪吃蛇移动的过程,即链表中所有节点从尾结点开始逐个向前移动一个位置
    
    bool Move(pNode pHead, char key) {
    
        bool game_over = false;
    
        pNode pt = pTail;
    
        while (pt != pHead) { // 每个节点依次向前完成蛇的移动
    
            pt->x = pt->pre->x;
    
            pt->y = pt->pre->y;
    
            pt = pt->pre;
    
        }
    
        switch (key) {
    
            case'd': {
    
                pHead->x += 1;
    
                if (pHead->x >= ROW)
    
                    game_over = true;
    
                break;
    
            }
    
            case'a': {
    
                pHead->x -= 1;
    
                if (pHead->x < 0)
    
                    game_over = true;
    
                break;
    
            }
    
            case's': {
    
                pHead->y += 1;
    
                if (pHead->y >= COL)
    
                    game_over = true;
    
                break;
    
            }
    
            case'w': {
    
                pHead->y -= 1;
    
                if (pHead->y < 0)
    
                    game_over = true;;
    
                break;
    
            }
    
        }
    
        if (SnakeDeath(pHead))
    
            game_over = true;
    
        return game_over;
    
    }

    -------------------------------

    注意,此段代码中还调用了 SnakeDeath() 函数,此函数用于判断贪吃蛇移动时是否撞墙、撞自身,如果是则游戏结束。

     

    3) 

    当贪吃蛇吃到食物时,贪吃蛇需要增加一截,其本质也就是双向链表增加一个节点。但是实现这个功能唯一的难点在于:如何为该节点初始化 (x,y)?

    这次所设计的贪吃蛇游戏,针对此问题,提供了最简单的解决方案,就是不对新节点 x 和 y 做初始化。

    要知道,贪吃蛇是时刻移动的,而在上面的 move() 函数中,会时刻修正贪吃蛇每个节点的位置,因此当为双向链表添加新节点后,只要贪吃蛇移动一步,新节点的位置就会自行更正。

    当然,你也可发散思维,设计其他的解决方案。

    也就是说,贪吃蛇吃到食物的实现,就仅是给双向链表添加一个新节点。如下即为实现此功能的代码:

    -------------------------------

    //创建表示食物的结构体,其中只需要记录其所在的行和列
    
    typedef struct Food {
    
        int x;
    
        int y;
    
    }Food, *pFood;
    
    //吃食物,等同于链表中新增一个节点
    
    pNode EatFood(pNode pHead, pFood pFood) {
    
        pNode p_add = NULL, pt = NULL;
    
        if (pFood->x == pHead->x&&pFood->y == pHead->y) {
    
            p_add = (pNode)malloc(sizeof(Node));
    
            score++;
    
            pTail->next = p_add;
    
            p_add->pre = pTail;
    
            p_add->next = NULL;
    
            pTail = p_add;
    
            // 检查食物是否出现在蛇身的位置上
    
            do {
    
                *pFood = CreateFood();
    
            } while (FoodInSnake(pHead, pFood));
    
        }
    
        return pHead;
    
    }

    -------------------------------

    其中,Food 结构体用来表示食物,其内部仅包含能够定位食物位置的 (x,y) 即可。

    另外,此段代码中,还调用了 FoodeInSnake() 函数,由于食物的位置是随机的,因此极有可能会和贪吃蛇重合,所以此函数的功能就是:如果重合,就重新生成食物。

    FoodInSnake() 函数的实现很简单,这里不再赘述:

    -------------------------------

    //判断食物的出现位置是否和蛇身重合
    
    bool FoodInSnake(pNode pHead, pFood pFood) {
    
        pNode pt = NULL;
    
        for (pt = pHead; pt != NULL; pt = pt->next) {
    
            if (pFood->x == pt->x&&pFood->y == pt->y)
    
                return true;
    
        }
    
        return false;
    
    }

    -------------------------------

     

    4) 

    贪吃蛇游戏界面的显示,最简单的制作方法就是:贪吃蛇每移动一次,都清除屏幕并重新生成一次。

    这样实现的问题在于,如果贪吃蛇的移动速度过快,则整个界面在渲染的同时,会掺杂着光标,并且屏幕界面会频繁闪动。

    因此,在渲染界面时,有必要将光标隐藏起来,这需要用到<windows.h>头文件,实现代码如下:

    -------------------------------

    // 隐藏光标
    
    void gotoxy(int x, int y) {
    
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    
        COORD pos;
    
        pos.X = x;
    
        pos.Y = y;
    
        SetConsoleCursorPosition(handle, pos);
    
    }
    
    void HideCursor() {
    
        CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
    
        SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
    
    }

    -------------------------------

    同时,为了给整个界面渲染上颜色,也需要引入<windows.h>头文件,并使用如下函数:

    -------------------------------

    void color(int m) {
    
        HANDLE consolehend;
    
        consolehend = GetStdHandle(STD_OUTPUT_HANDLE);
    
        SetConsoleTextAttribute(consolehend, m);
    
    }

    -------------------------------

    5) 

    需要注意的一点是,由此结束后,一定要手动释放双向链表占用的堆空间:

    -------------------------------

    //退出游戏前,手动销毁链表中各个节点
    
    void ExitGame(pNode *pHead)
    
    {
    
        pNode p_delete = NULL, p_head = NULL;
    
        while (*pHead != NULL) {
    
            p_head = (*pHead)->next;
    
            if (p_head != NULL)
    
                p_head->pre = NULL;
    
            p_delete = *pHead;
    
            free(p_delete);
    
            p_delete = NULL;
    
            *pHead = p_head;
    
        }
    
    }

    -------------------------------

    解决以上问题之后,用双向链表实现贪吃蛇,基本上就没有难点了。家人们可根据本节提供的实现思想,尝试独立实现。

    这次设计实现的贪吃蛇游戏,源码文件有 3 个,分别为 snake.h、snake.c 和 main.c,想要源码可私我!


     

    如果你还想更深入的学习以及其他知识,不管你是转行也好,初学也罢,进阶也可~

    【值得关注】我的编程学习交流俱乐部 !【点击进入】

    C语言入门资料:


     

    C语言必读书籍:


     
  • 相关阅读:
    计算机网络中协议相关的问题(转)
    Vs2013打开项目时,一直处理等待状态,并显示“Microsoft Visual Studio正忙”的提示窗,处理方法(转)
    ASP.NET Core 2.2 十九. Action参数的映射与模型绑定(转)
    ASP.NET Core 2.2 十八.各种Filter的内部处理机制及执行顺序(转)
    ASP.NET Core 2.2 : 十七.Action的执行(Endpoint.RequestDelegate后面的故事)(转)
    ASP.NET Core 2.2 : 十六.扒一扒新的Endpoint路由方案(转)
    【Leetcode】771. Jewels and Stones
    【Leetcode】50. Pow(x, n)
    【Leetcode】 328. Odd Even Linked List
    【leetcode】59.Spiral Matrix II
  • 原文地址:https://www.cnblogs.com/huya-edu/p/14825218.html
Copyright © 2011-2022 走看看