在 Linux 下实现智能贪吃蛇

本周的任务是在 Linux 环境下编写代码,实现一只智能的贪吃蛇,使其能通过算法具有 “感知 - 决策 - 行动” 的能力。


熟悉 Linux 环境

对于 Linux ,我之前早有耳闻,却没有真正的使用过。大家都说计算机从业者应该学会使用 Linux ,但具体是为什么我也不甚了了。总之,我这次总算有机会在自己的计算机上装一个 Linux ,实际感受一下。

下载安装 Ubuntu

作为一个 MacBook 用户,此前就已经购买了最新版的 Parallel Desktop 软件。而下载安装免费的开源系统Ubuntu Linux 也就非常方便了。

这里写图片描述
点击免费系统下的 下载 Ubuntu Linux 即可

终端与 vim 编辑器

进入到 Ubuntu,可以看到类似这样的桌面。如果你找不到终端在哪里打开,可以输入快捷键ctrl + alt + T打开。

这里写图片描述

然后在终端中键入

sudo apt-get install vim

下载安装 vim 编辑器。

一般系统会 built-in gcc 编译器。你可以在终端中输入gcc -v查看你的 gcc 版本。如果没有,仿照上面方法键入

sudo apt-get install gcc

即可安装 gcc 。

然后就可以用 vim 来编辑代码,或直接用 gcc 编译写好的代码了!

VT 100 终端标准

在字符终端上完成“清屏”“修改光标位置”“设置字符前景和背景色”等操作,是通过输出 esc序列实现的。对于 VT100 终端, printf(“\033[2J”) 就实现了清屏。详细内容可以参考, C语言与VT100控制码编程
然而在实践中我发现,上述的控制码虽然看起来实现了清屏,但是其实之前打印的东西其实还留在上面,并没有真的被清除。想象一下如果写一个贪吃蛇游戏,贪吃蛇的每次移动都会留下一个界面,那一定不是我们想要的效果。

这里写图片描述
可以看到右边是有滚动条的

通过搜索,我了解到把上述printf("\033[2J")改为printf("\033[3J")printf("\e[3J")即可真正清除之前的打印记录。(事实上,在代码里是system("clear && printf '\e[3J'");。)

这里写图片描述
改为system(“printf ‘\e[3J’”);后,之前的记录基本清楚了,但仍保留着一个半界面

这里写图片描述
再改为system(“clear && printf ‘\e[3J’”);后,界面就很完美啦

相关讨论可参考:https://stackoverflow.com/questions/2198377/how-to-clear-previous-output-in-terminal-in-mac-os-x/26615036#26615036

加入 kbhit()

在加入 kbhit() 之前,我们的贪吃蛇游戏还做不到在不输入时使蛇自动向当前方向前进一步,并且还会显示输入的字符,还需要摁回车确认。游戏体验很糟。

我们利用下面这个篇帖子的代码: Linux下非阻塞地检测键盘输入的方法 (整理),就可以解决上述所有问题啦!

(具体实现代码可参阅我下面一篇博客)


编写智能算法

在完成上述的学习后,我们终于可以开始编写我们的人工智能算法,使贪吃蛇能够每秒自动走一步了。

智能算法中最关键的函数是决定智能蛇接下来往哪个方向走的函数——即返回一个决定方向的字符的函数——其余则与正常贪吃蛇没有什么不同。

基础智能算法

这个函数初步的算法如下:

def whereGoNext{
    move[4] = {'a', 'd', 'w', 's'}
    distance[4] = {0, 0, 0, 0}
    FOR i in range(4)
        IF 蛇按照move[i]的方向走一步没有走到' '
            IF 走到的是食物
                return move[i]
            ELSE
                distance[i] = 9999
        ELSE
            distance[i] = |Fx - H'x| + |Fy - H'y|
    ENDFOR
    选出distance中最小的值对应下表p
    IF distance[p] = 9999
        game over
    ELSE
        return move[p]
}

该算法可用如下C语言代码实现:

char whereGoNext(void){
    char move[4] = {'a', 'd', 'w', 's'};
    int minDistIndex = 0;
    int distance[4] = {0, 0, 0, 0};
    int i = snakeLength - 1;
    int dx = 0, dy = 0;
    int moveIndex = 0;
    for (moveIndex = 0; moveIndex < 4; moveIndex ++) {
        switch (move[moveIndex]) {
            case 'a':
                dx = -1;
                dy = 0;
                break;
            case 'd':
                dx = 1;
                dy = 0;
                break;
            case 'w':
                dx = 0;
                dy = 1;
                break;
            case 's':
                dx = 0;
                dy = -1;
                break;
            default:
                break;
        }
        // if next step is eating the food, do it.
        if (map[snakeY[i] - dy][snakeX[i] + dx] == '$') {
            return move[moveIndex];
        }
        // cannot move backward
        else if (snakeX[i] + dx == snakeX[i - 1] && snakeY[i] - dy == snakeY[i - 1]) {
            distance[moveIndex] = 9999;
        }
        // cannot move to somewhere not a blank
        else if (map[snakeY[i] - dy][snakeX[i] + dx] != ' '){ /*have already determined if it is '$'*/
            distance[moveIndex] = 9999;
        }
        else{
            distance[moveIndex] = abs(foodX - (snakeX[i] + dx)) + abs(foodY - (snakeY[i] - dy));
        }
        if (distance[minDistIndex] > distance[moveIndex]) {
            minDistIndex = moveIndex;
        }
    }
    if (distance[minDistIndex] == 9999) {
        isGameOver = 1;
    }
    return move[minDistIndex];
}

最终的效果如图:

这里写图片描述

然而目前的智能算法还有很大的改进空间。比如遇到这样的情况时,
这里写图片描述

智能蛇就会义无反顾的向前。并被这个长度为 5 的障碍物困死。

BFS 广度优先搜索算法

要解决这个问题,我们可以尝试 BFS 算法。(参考阅读:http://blog.csdn.net/raphealguo/article/details/7523411

算法的思路可以大致概括为:探索蛇头周围去到的位置,再探索这些位置周围可能的位置……直到第一次找到食物,即找到了从当前位置到食物的最短路径。

这个算法可以保证蛇绝不会死,然而问题也显而易见,就是如果找不到要找的路径怎么办。比如对于下图的情况来说:

这里写图片描述

蛇无法找到蛇头到食物的路径。然而是不是意味着游戏结束了呢?当然不是,蛇只要再往前走两步就有路径了。所以问题的本质在于,上述算法没有考虑到整个图是动态的,蛇每走一步,蛇尾也发生了变化。

判断蛇头到蛇尾是否联通的算法

通过查阅博客我了解到,只要蛇头和蛇尾保持联通,那蛇就一定不会进入死胡同。所以我们可以定义一条虚拟蛇。在每一次真实蛇去吃食物之前,先让虚拟蛇去吃,如果吃完之后的虚拟蛇蛇头与蛇尾是联通的,则说明这是一条安全路径,可以放心去吃;如果不能联通,则向远离蛇尾的方向徘徊一步——在行动之前先想象一下行动的结果,听起来很有智能的意味。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值