贪吃蛇自动寻路的尝试
相信大家都对贪吃蛇这个小游戏不陌生吧,那么怎么才能让贪吃蛇吃满全屏呢,通过什么算法可以做到呢,现在让我们来探讨一下。
前提准备
先通过JavaScript写一个贪吃蛇小游戏,一个非常简单的界面,红色为小蛇的头。效果如下:

过程分析
如果让小蛇吃满屏,大致可以分为以下几种情况:
1、
,小蛇可以吃到食物,并且吃到食物后没有危险,那么就采取最短路径去吃食物。
2、
, 小蛇可以吃到食物,但吃到食物后会有危险,那么就采取最短路径去追尾巴。
3、
,小蛇无法吃到食物,但可以追尾巴,那么就采取最远路径去追尾巴。
4、
,小蛇无法吃到食物,也无法追尾巴,那么就按最远路径填补可以通过的空间(一般这种情况可以避免)。
具体算法
现在来完成如何找到最短路径,对此采用BFS算法搜索最短路径
部分代码如下:
//BFS算法寻找最近路径,吃到食物后才再次计算,取消了每步都计算运算量太大的弊端this.findShortPath = function (snake,aim) {let head = snake.body[0];let queue = [];//记录搜索节点let visited = [];// 记录访问过的节点let visitedPath = [];//记录访问过的路径if (snake.shortPath.length === 0){queue.push(head);//从贪吃蛇的头开始搜索while (queue.length !== 0) {let n = queue.shift();//取出队首if (n.X === aim.X && n.Y === aim.Y) {//如果搜到了食物,开始解析路径let state = aim;while (state !== null && state !== head) {snake.shortPath.push(state);//将目标节点加入最短路径for (let i = 0;i < visitedPath.length;i++){if (state.X === visitedPath[i][0].X && state.Y === visitedPath[i][0].Y){state = visitedPath[i][1];//将目标节点指向上一步节点break;}}}if (snake.shortPath.length === 0){snake.shortPath.push(state);}break;}let posUp = new Position(n.X, n.Y - 1);let posDown = new Position(n.X, n.Y + 1);let posLeft = new Position(n.X - 1, n.Y);let posRight = new Position(n.X + 1, n.Y);let death = Object.assign([],snake.body);death.pop();//判断搜索节点的四周是否可行或已访问if (!snake.contains(death,posUp) && !snake.contains(visited,posUp)&& !snake.knockWall(posUp)) {queue.push(posUp);//将节点加入搜索队列visited.push(posUp);//将节点加入已访问组visitedPath.push([posUp,n]);//将节点与搜索节点的路径加入路径组}if (!snake.contains(death,posDown) && !snake.contains(visited,posDown)&& !snake.knockWall(posDown)) {queue.push(posDown);visited.push(posDown);visitedPath.push([posDown,n]);}if (!snake.contains(death,posLeft) && !snake.contains(visited,posLeft)&& !snake.knockWall(posLeft)) {queue.push(posLeft);visited.push(posLeft);visitedPath.push([posLeft,n]);}if (!snake.contains(death,posRight) && !snake.contains(visited,posRight)&& !snake.knockWall(posRight)) {queue.push(posRight);visited.push(posRight);visitedPath.push([posRight,n]);}}}return this.getDirection(snake,snake.shortPath);};
现在来完成如何找到最远路径,对此采用A*算法搜索最远路径
部分代码如下:
//A*算法寻找最远路径this.findFarPath = function (snake,aim) {let openList = [];let closeList = [];let visitedPath = [];let head = snake.body[0];if (snake.farPath.length === 0){openList.push(head);// 将开始节点放入开放列表;head.F = snake.dis(head,aim);while(openList.length !== 0){let now = new Position();let max = 0;//找F值最大的(说明离目标最远),如果有相同我们选的排在后面的也就是最新添加的。for(let i = 0;i < openList.length;i++){if(openList[i].F >= max){max = openList[i].F;now = openList[i];}}// 当终点节点被加入到开放列表作为待检验节点时, 表示路径被找到,此时终止循环,返回方向;if (now.X === aim.X && now.Y === aim.Y) {let node = aim;while(node != null && node !== head){snake.farPath.push(node);for (let i = 0;i < visitedPath.length;i++){if (node.X === visitedPath[i][0].X && node.Y === visitedPath[i][0].Y){node = visitedPath[i][1];break;}}}break;}// 把当前节点从开放列表删除, 加入到封闭列表;for(let i = 0;i < openList.length;i++){if(openList[i] === now){openList.splice(i,1);}}closeList.push(now);//四个方向的相邻节点let posUp = new Position(now.X, now.Y - 1);let posDown = new Position(now.X, now.Y + 1);let posLeft = new Position(now.X - 1, now.Y);let posRight = new Position(now.X + 1, now.Y);let temp = [];temp.push(posUp);temp.push(posRight);temp.push(posDown);temp.push(posLeft);let death = Object.assign([],snake.body);death.pop();for (let i = 0;i < 4;i++){let n = temp[i];// 如果该相邻节点不可通行或者该相邻节点已经在封闭列表中,则什么操作也不执行,继续检验下一个节点;if (snake.contains(death,n) || snake.contains(closeList,n)|| snake.knockWall(n)) {continue;}// 如果该相邻节点不在开放列表中,则将该节点添加到开放列表中,// 并将该相邻节点的父节点设为当前节点,同时保存该相邻节点的G和H值,F值if(!snake.contains(openList,n)){visitedPath.push([n,now]);n.F = snake.dis(n,aim);openList.push(n);}// 如果该相邻节点在开放列表中,// 则判断若经由当前节点到达该相邻节点的G值是否大于或小于(这里找最远用大于)原来保存的G值,若大于或小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的G和F值.if (snake.contains(openList,n)) {if (this.dis(n,aim) > now.F) {for (let i = 0;i < visitedPath.length;i++){if (n.X === visitedPath[i][0].X && n.Y === visitedPath[i][0].Y){visitedPath[i][1] = now;break;}}n.F = this.dis(n,aim);}}}}}return this.getDirection(snake,snake.farPath);};
结果总结
但由于本人能力不足,寻找最远路径部分还存在缺陷,贪吃蛇无法吃满全屏。最终会出现死循环:
,但也有一定几率可以吃满全屏:
。
总结,算法有待改进、完善,争取做到贪吃蛇每次都可以吃满全屏。
