作者:CodeNoob 转载请标明作者和出处
前几天在网上看到一张让人涨姿势的图片,这张图片我很早以前看过,当时就觉得肯定是程序实现的,只是当时还比较渣,不会算法。这次学了java也正在学算法,便打算开始实现它,说做就做,let's do it
Java,虽然好久不用Swing
Make it work
首先肯定是先让程序能跑,再去想算法,开始肯定是在一个矩形里不断的随机出现食物,然后让蛇在不走出矩形的情况下去吃,蛇每吃一个食物就会变长。这时问题基本就是,给你一个起点(蛇头)和一个终点(食物),从起点找到一条可行路到达终点。这个路怎么走呢,最简单的就是走曼哈顿距离了.
如下图,绿色是蛇头蓝色是蛇尾,蛇头在去吃食物的时候是直接穿过身体的,这是最简单的方法,先运行起来。
搜索路径的算法其实有多种,分为盲目式搜索和启发式搜索。其中盲目式搜索有DFS和BFS,启发式搜索有A*和有序搜索(或者最佳优先搜索), 这里我用的BFS,首先将蛇头节点放入队列,然后循环弹出队列直到队列为空,为空返回-1,没找到路径,每弹出一个队列里的节点,判断该节点是不是食物,如果不是食物,把该节点添加到vis表说明已经走过,并且把该节点相邻的上下左右四个节点添加到队列里并记录下节点的父节点(我用的HashMap),添加时如果节点在vis表或者出了墙或者是身体节点,就不添加到队列里。如果是食物,返回去食物的第一步的方向,因为我是线程每刷新一次,蛇走一步。这样每走一步就BFS找最优路径。
调了下速度所以比较快,当蛇吃完食物后发现自己没路去吃另外出现的食物时就会GG了
所以不能出现食物就立马去吃,得先判断吃完后能否吃下一个,这里其实可以在随机食物的位置时把下一个食物的位置提前随机出来,不过这样就有点算作弊的感觉。。
make it right
当我们不知道下一个食物的位置时就只能模拟一条蛇去吃了,我们派一条虚拟蛇(不画在屏幕上)去吃,虚拟蛇吃后生成的食物也是虚拟的所以我们不知道真实食物吃完又会在哪出现,吃完怎么判断是否能去吃下一个呢?
我们可以看最上面的吃完全图的可以发现,当它吃完后能跟着尾巴走就表明是安全的,于是策略是
if(能吃到食物)
派虚拟蛇去吃,
if(吃完能跟着蛇尾走) 真蛇去吃
if(吃完不能跟着蛇尾) 真蛇跟着蛇尾走
else
真蛇跟着蛇尾
if(不能吃食物也不能跟着蛇尾)随便逛逛,
然后就有较高概率出现了这种情况,蛇头会经常围着蛇尾转,这里应该是一直围着转的,由于我后来做了下优化,搞了个计数器大于多少就不跟着队尾了,直接去吃然后就GG了。
解决办法还是得从原图来,(原图不知道被我看了多少遍。。)发现蛇头在追蛇尾时我总是走的最短路径,其实应该还可以不那么快走到蛇尾可以到绕下弯去,这就不能用BFS了。于是本不愿意写A算法的,只好用A算比较远的路径了。(为什么A*不是最远路径因为这个怎么判断多远我是用曼哈顿距离的大小判断远近,实际是可以不停绕弯走才是最远的,不过这样就不好写算法了。)
A*其实本质就是BFS加贪心,怎么贪就是给当前节点周围的四个节点先估计下哪个离目标(食物)远一点或近一点(我们要找最远路肯定想远一点),权值表示这个(远或近的)程度,权值越大越远,越小越近,不用像BFS那样四个都走一遍,我们只走权值最大的那个,就可能离远一点。
具体A*算法步骤如下
//将开始节点放入open表
while(opne表不为空){
0.在open表找F值最大的(说明离目标最远),如果有相同我们选的排在后面的也就是最新添加的。
1.把当前节点从开放列表删除, 加入到封闭列表;
2.遍历四个方向的相邻节点
(0)如果该相邻节点不可通行或者该相邻节点已经在封闭列表中,则什么操作也不执行,继续检验下一个节点;
(1)如果该相邻节点不在开放列表中,则将该节点添加到开放列表中,并将该相邻节点的父节点设为当前节点,同时保存该相邻节点的G和H值
[0]当终点节点被加入到开放列表作为待检验节点时, 表示路径被找到,此时终止循环,返回方向;
(2)如果该相邻节点在开放列表中,则判断若经由当前节点到达该相邻节点的G值是否大于或小于(这里找最远用大于)原来保存的G值,若大于或小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的G和H值
}
//当开放列表为空,表明已无可以添加的新节点,而已检验的节点中没有终点节点则意味着路径无法被找到,此时也结束循环返回-1;
然后我们换个策略
吃食物时走最近路径 追尾巴时走最远路径
我们发现这条小蛇终于会绕弯走啦。。由于GIf软件录制不了那么长,我分了几个录制了,另外三个(卡顿是录制软件的问题)
通过最后一个结果可以看出,由于是随机产生的食物,还是有吃不到的时候,这时就只能优化食物的出现算法或调整寻路算法了。(想要吃满全图,可以一直走S路就可以了,不过这样没意思了)。
博客地址 如果有需要优化的地方的话,我想可能BFS太慢了,还是直接全用A*或许更好 。