字符版进阶贪吃蛇:进阶学习与智能蛇学习记录

  相信许多前行在编程学习之路的同学们都跟我一样有自己写游戏的小梦想。在之前的探索和学习中,我们已经接触过了用诸如Construct2这样的“黑箱”开发平台来创造自己的游戏,但不是自己打出来的代码始终觉得缺少一点感觉。经过半个多学期的编程技术和编程思想的学习,运用较为基本的C语言知识和自顶向下的思想方法,我们已经有能力自己完成一个经典的字符游戏:贪吃蛇。
  上一次博客中我们讨论了字符游戏贪吃蛇初阶的设计和程序实现,在这次的进阶实践中,我们将要讨论贪吃蛇的完整实现和制杖智能蛇的算法设计实现。(实验环境:Ubuntu)


完整的贪吃蛇

  此处只讨论关键处理,篇幅原因不贴上源代码。源代码可见GitHub中我的源码。

初阶的不足与进阶完整化

  在初阶的贪吃蛇实现中,我们的蛇只会“一令一动”,即蛇的移动完全由玩家控制WSAD来实现,没有键盘输入时蛇是静止不动的。这与我们熟悉的贪吃蛇游戏不同,传统的贪吃蛇游戏中,除了接收到键盘指令转向外蛇是会一直前进的,下面我们就要让我们的代码实现这一功能。
  如何在没有键盘输入时一直运行我们原先令贪吃蛇移动的那部分程序,而在有键盘指令时更新指令继续移动呢?kbhit函数很好的满足了我们的需求。kbhit函数功能是检测当前是否有键盘输入,若无则返回0,若有则返回一个非零参数,使用时需包含conio.h头文件。因此我们可以在main函数中执行贪吃蛇的运行过程中利用这个函数,来替换先前的每次执行都要等待键盘输入的条件,从而实现蛇的持续运行。具体代码块如下:

int main(void)
{
    char kbi = 'D', lastmove = 'D';
    srand(time(NULL));
    creatfood();
    printer();
    while (flag)
    {
        if (_kbhit()) { kbi = _getch(); continue; }
        else
        switch (kbi)
        {
        case 'A':
            if (lastmove == 'D') break;
            else move(-1, 0);
            lastmove = kbi;
            break;
        case 'S':
            if (lastmove == 'W') break;
            else move(0, -1);
            lastmove = kbi;
            break;
        case 'D':
            if (lastmove == 'A') break;
            else move(1, 0);
            lastmove = kbi;
            break;
        case 'W':
            if (lastmove == 'S') break;
            else move(0, 1);
            lastmove = kbi;
            break;
        }
        system("cls");
        printer();
        Sleep(200);
    }
    system("cls");
    printf("************\n*          *\n*          *\n*          *\n*          *\n*   GAME   *\n*   OVER   *\n*          *\n*          *\n*          *\n*          *\n************\n");
    return 0;
}

  这样,我们的蛇就可以不依赖于键盘控制而一直沿着原来的方向前进。
  大家可能还注意到了一个Sleep(int)函数,这是windows.h库中的一个控制程序运行快慢的函数。因为在我们的计算机上,整段移动程序的耗时很短,蛇在我们开始游戏还来不及控制方向的时候就已经完全高速冲出了屏幕(好吧因为撞到墙就GameOver了还是没机会冲出屏幕的),所以我们需要用Sleep函数定时挂起,控制一下程序运行的速度,可以这样简单地理解,在适当的地方加上Sleep(200)实现了控制蛇移动的板块程序每0.2秒运行一次,即当没有输入时,蛇每约0.2秒前进一个单位。
  下为游戏运行实录(为方便录制,程序在Windows Visual Studio中运行):
  
  字符版进阶贪吃蛇:进阶学习与智能蛇学习记录_第1张图片


智能蛇设计

  此处只讲关键算法模块,源代码见GitHub我的源码。
  真正贪吃的蛇怎么能自己不会去找食物呢?为了实现蛇的“智能觅食”,老师给我们推荐了一个算法,通过伪代码,我们可以很直观地了解到这种算法的执行原理和过程:
  字符版进阶贪吃蛇:进阶学习与智能蛇学习记录_第2张图片

  参考“贪婪算法”,whereGoNext函数实现:
  

char whereGoNext()
{
    int distance[4] = { 0,0,0,0 };
    char direction[4] = { 'W','S','A','D' };
    if ((map[locationx[length - 1] - 1][locationy[length - 1]] == 0) || (map[locationx[length - 1] - 1][locationy[length - 1]] == 3))
        distance[0] = abs(foodx - locationx[length - 1] + 1) + abs(foody - locationy[length - 1]);
    else distance[0] = 9999;
    if ((map[locationx[length - 1] + 1][locationy[length - 1]] == 0) || (map[locationx[length - 1] + 1][locationy[length - 1]] == 3))
        distance[1] = abs(foodx - locationx[length - 1] - 1) + abs(foody - locationy[length - 1]);
    else distance[1] = 9999;
    if ((map[locationx[length - 1]][locationy[length - 1] - 1] == 0) || (map[locationx[length - 1]][locationy[length - 1] - 1] == 3))
        distance[2] = abs(foodx - locationx[length - 1]) + abs(foody - locationy[length - 1] + 1);
    else distance[2] = 9999;
    if ((map[locationx[length - 1]][locationy[length - 1] + 1] == 0) || (map[locationx[length - 1]][locationy[length - 1] + 1] == 3))
        distance[3] = abs(foodx - locationx[length - 1]) + abs(foody - locationy[length - 1] - 1);
    else distance[3] = 9999;
    int min = 0;
    for (int i = 1; i < 4; i++)
        if (distance[min] > distance[i]) min = i;
    return direction[min];
}

  实现了这个子函数来决策前进方向,再在主函数中稍加改动,加大难度在地图上添加障碍物,就可以把智能蛇拉出来溜溜啦~
  来看看这蛇有多智能(为方便录制,程序在Windows Visual Studio中运行):
  字符版进阶贪吃蛇:进阶学习与智能蛇学习记录_第3张图片
  
  PS:此处笔者删除了一些闪动帧,真实运行时屏幕刷新有些许闪动。
  
  可见智能蛇的“贪婪算法”还是有些许缺陷,就是缺少路径预判,蛇很容易走进自己走出的死路。因为有障碍物的影响极端情况下,蛇还会进入死循环一直原地绕圈而永远吃不到食物。算法当然还可以加以改进,此次不再深入,以后我们还可以继续探索。下提供其他的一些算法思路:

  1.佛系觅食“傻”而万无一失法:按照可循环路径遍历整个地图。
  2.BFS算法,初级算法设计和实现可参考这里。
  3.更多思考参考这里。
  ……


以上便是笔者贪吃蛇进阶学习的学习记录。
谢谢阅读!

你可能感兴趣的:(Tech)