相信对于推箱子这种东西大家都一定很熟悉了吧,本人身为一个宅男最近迷上了一款叫做游戏俱乐部的game,听上去是不是很高大上的样子。。。结果----这TM不就是个推箱子么!!!
当时被这个关卡卡的欲仙欲死。。。所以果断拿出了自己大学的存货,自动推箱子搞起!
-------------------------------------------------------------------无情的分割线-------------------------------------------------------------------
废话说了这么多不会被打死吧。。。
正题开始,首先是思路:
first:做过ACM的一看就知道这TM不就是个图论的题目么,所以一开始自己想到的就是进行DFS(深度优先搜索),存储每次走过的地图肯定是必要的,用于防止进行重复的搜索。然后。。。然后就是让他自己搜吧。。。虽然觉得很不靠谱,走一步算一步是不。
关于地图的存储,因为有一部分元素是可以重叠放置的,所以用了一个类似二进制的存储方式,就是4种物件分别有是否存在状态,使得用一个数字可以表示多个物件。
1:是否存在目的地
2:是否存在箱子
4:是否存在人
8:是否存在墙壁
这样就解决了地图存储问题。使用short[][]就存下了(这里没有做更小的存储了,本来用java不用c++就是怕麻烦不是。。。)
之后用这个思路进行了一系列思考,发现数据量还是太大了,一步一步慢慢走简直不能忍,随随便便就爆表了,所以漫漫优化之路开始了。
1.在不移动箱子的情况下其实无论人在哪里对于map来说是没有影响的,所以填充可移动区域可以让需要存储地图的数量有一个大的下降。例如之前那副地图:
8888888
8103018
8002008
8320238
8012108
8403008
8888888
经过变换之后就成了:
8888888
8103018
8002008
8320238
8452108
8443008
8888888
这样就把存储量缩小了四分之三。至于怎样填充,相信对图论有一点了解的都可以随便想出方案,我这里用的是BFS:
/**
* 填充可移动位置
*/
public void findStart() {
//位置队列
Queue<Part> manList = new LinkedList<Part>();
for (short i = 0; i < H; i++) {
for (short j = 0; j < W; j++) {
//能站人的地方都加入队列
if (map[i][j] == 4 || map[i][j] == 5) {
manList.offer(new Part(i, j));
}
//这里用于同时记录箱子的位置,以后会用到,用于减少遍历次数
if (map[i][j] == 2 || map[i][j] == 3) {
boxList.offer(new Part(i, j));
}
}
}
//提出没一个站立位置并将其周围填充之后加入队列
while (!manList.isEmpty()) {
Part temp = manList.poll();
fillMovable(manList, (short) (temp.x - 1), temp.y);
fillMovable(manList, (short) (temp.x + 1), temp.y);
fillMovable(manList, temp.x, (short) (temp.y - 1));
fillMovable(manList, temp.x, (short) (temp.y + 1));
}
}
一次BFS就是每个箱子往不同可移动位置进行一次移动。
/**
* 获取子节点地图(进行一次BFS搜索)
*
* @return
*/
public LinkedList<Map> getChildMap() {
LinkedList<Map> list = new LinkedList<Map>();
for (Part part : boxList) {
Map temp;
temp = move(part, -1, 0);
if (null != temp)
list.offer(temp);
temp = move(part, 1, 0);
if (null != temp)
list.offer(temp);
temp = move(part, 0, -1);
if (null != temp)
list.offer(temp);
temp = move(part, 0, 1);
if (null != temp)
list.offer(temp);
}
return list;
}
/**
* 移动箱子
*
* @param box
* @param x
* @param y
* @return
*/
private Map move(Part box, int x, int y) {
Map temp = null;
//如果箱子可以向当前方向移动
if ((map[box.x + x][box.y + y] == 0 || map[box.x + x][box.y + y] == 1
|| map[box.x + x][box.y + y] == 4 || map[box.x + x][box.y + y] == 5)
&& (map[box.x - x][box.y - y] == 4 || map[box.x - x][box.y - y] == 5)) {
temp = this.clone();
// TODO待优化
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
if (temp.map[i][j] == 4 || temp.map[i][j] == 5) {
temp.map[i][j] -= 4;
}
}
}
temp.map[box.x][box.y] += 4;
temp.map[box.x][box.y] -= 2;
temp.map[box.x + x][box.y + y] += 2;
//因为箱子已经移动,所以需要重新计算可以站立的位置
temp.fatherMap = this;
temp.findStart();
temp.getCode();
}
return temp;
}
3.至于什么时候结束搜索,基本上就只有三种情况:
所有目的地被填充完毕-------计算完成退出程序。
有箱子被推到角落并且不是在目的地--------说明不是正确的路线,搜索不再往下走。
当前地图在以前已经被达成过--------说明是重复路线,搜索不再往下走。
4.关于地图的存储,因为要保证不能重复,我是用的是hashSet,并重写了equals和hashCode的实现,用来自动判断地图是否重复。
<span style="white-space:pre"> </span>public void getCode() {
hashCode = 0;
int i = 0;
for (short[] temp : map) {
for (short temp2 : temp) {
hashCode += ((int) Math.pow(7, NUM - i)) * temp2;
++i;
}
}
}
<span style="white-space:pre"> </span>@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
Map o = (Map) obj;
if (this.hashCode != o.hashCode)
return false;
boolean flag = true;
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
if (o.map[i][j] != this.map[i][j]) {
flag = false;
break;
}
}
if (!flag)
break;
}
return flag;
}
最后就是完成之后的地图显示问题,我的解决方案就是每个节点存储自己父亲节点的地址,当节点发现自己已经完成之后根据地址向上查找直到树顶。
好了,java版的推箱子就这么完成了~~试了一下效率还不错~基本上java解不出来的c++估计也悬,时间复杂度主要取决于箱子的数量与空地的数量。话说做完这么一个东西还是蛮有成就感的说。
本人第一次写这种博文,有什么意见或者想法欢迎来交流~~
bye~bye~~
源码下载地址:http://download.csdn.net/detail/u010236185/8239077
请原谅我放荡不羁的要了1分。。。