贪吃蛇是人人皆知的游戏。传统的贪吃蛇非常简单,玩法在这里不详述。但是,有人提出了一个“破解”的方法,必通关:
忽略食物,只要严格按S形绕着走就可以了。
当然,这是一种很好的方法,但这么玩似乎意义不大。为了防止这种情况出现,我们可以改变一下规则:
- 食物限时,超时重新放置
- 游戏整体也限时
这样可玩性无疑提高了,并且杜绝了之前的“破解”方法。
前段时间,AI与人类的下棋比赛引人注目,我们是否也可以设计一个算法,让计算机来完成这个贪吃蛇的游戏呢?
一个朴素的算法是这样的:
// Hx,Hy: 头的位置
// Fx,Fy:食物的位置
// function whereGoNext(Hx,Hy,Fx,Fy) {
// 用数组movable[3]={“a”,”d”,”w”,”s”} 记录可走的方向
// 用数组distance[3]={0,0,0,0} 记录离食物的距离
// 分别计算蛇头周边四个位置到食物的距离。H头的位置,F食物位置
// 例如:假设输入”a” 则distance[0] = |Fx – (Hx-1)| + |Fy – Hy|
// 如果 Hx-1,Hy 位置不是Blank,则 distance[0] = 9999
// 选择distance中存最小距离的下标p,注意最小距离不能是9999
// 返回 movable[p]
}
感兴趣的读者可以试一下这个算法。这个算法的缺陷非常明显,当遇到这种地图,上述算法明显是自寻死路:
我没有采用这种算法,我用的是另一种算法,这个名字很常见,称为“宽度优先搜索”,简称BFS。
算法的原理是这样的:
- 开一个二维数组
distance[20][20]
,存储当前位置离食物的距离。 - 把食物所在位置的
distance[x][y]
设为0
。 - 从食物所在位置开始向四个方向的空白处扩展。
- 反复执行第3步,直到扩展到蛇头为止。蛇头的方向即被确定下来。
这样就可以避免上图的问题。但是,这种算法仍然是有缺陷的。首先,蛇身会运动,因此按照上述4步算出来的最短路并不是真正的最短路,真正的最短路可能穿过当前的蛇身(而走过去的时候就变成空白);其次,当蛇身长到一定程度时,蛇会在食物的引诱下走入死胡同。
这种算法会遇到两种无解的情况,一种是假无解:
这种情况下,算法认为蛇头无法到达食物。其实只要多走两步,路就出来了:
一种是真无解,蛇在食物的引诱下,走入了死胡同:
那么如何解决这个问题呢?留给读者思考。(我才不说我不会!)