新浪博客

推箱子解法演算

2019-08-16 12:28阅读:
第一部分
推箱子是个古老的游戏,几乎都忘了。近日偶然间从电视中看到,也不知怎地就萌发弄个求解的算法。想想无非就是个搜索,采用广度搜索,应该更好,可得到最佳走法。说干就干,大约化了3-4小时搞了个初稿。执行吧,当然首次就能通过那是神仙,这在情理之中,不过调试时还是发现想简单了。
箱子限定在狭小的空间内推动,通常也就在10*10方阵甚至更小的范围内,再加上墙的限制复杂度应该不大,时间和空间应该都不是问题。然调试发现,队列数量竟然近万,快照近20万,这比开始想的差太远了。当然后来发现,这还不是最大的,所以暂不管是否通过,先考虑优化设计免得以后作较大调整,毕景还只刚开始。
首先是地图的设计,起初单元格采用建立一个类,内设若干属性,如空地、墙壁和箱子是一个属性,是否目标位又是一个属性等等。这样的考虑用来设计推箱子游玩是没问题的,但用来求解显然不是最佳的。经思考最终觉得每个单元格完全可以采用一个字符来表示,约定如下:
0 表示空地 Space
1 表示墙壁 Wall
2 表示箱子 Box
3 表示人 People
4 表示目标 Desti
6 表示箱子和目标重叠 Box_Desti
7 表示人和目标重叠 People_Desti
此一来,地图占用空间得到了很大压缩。
其次快照的实现,快照记录人的位置、方向和当前地图。其实人的位置不同、方向不同,但地图可能是相同的,所以完全可以将地图独立出来贮存,快照只须对其引用即可。
经过以上两步,空间上得到了很大优化。当然还有进一步优化的空间,因为在演算时,地图实际仅用到SpaceWallBox,即012,所以用2个位Bit即可。其实内存条便宜得很,没必要这样斤斤计较。事实上c#语言的字符char16位而不是8位,节省的空间也没你想象的那么多。所以内存空间优化就此打住。
推箱子求解设计存在着4种地图,1是设计图,2是起始图,3是目标图,4是演算过程中产生的中间地图。其实从类型上讲只有2种,设计图和其他图。设计图中可能包含前面约定的6个属性值,而其他图仅包含SpaceWallBox3个属性值,可以从设计图中分离出起始图和目标图,演算的过程就是从起始图走到目标图的过程。
快照中记录了人的位置和方向,用于确定走过的路就不要走了。
下面是核心代码:
public void ProMain()
{
MapSet.Group.Add(StartMap); //起始图
Snapshot player = new Snapshot() //Player 起始位置
{
Map = StartMap,
Player = new Player()
{
Pos = DesignMap.Player().Position() //人的开始位置
}
};
PhotoSet.Add(player); //首个快照

if (BreadthSearch(player) == false) //false 没有完成,继续
{
while (Queues.Count > 0) //队列存在时处理
{
if (Cancel == true) //强行结束
break;
player = Queues.Dequeue(); //出队列
if (BreadthSearch(player) == true) //指定处理
break;
}
}
}

private bool ExploreForward(Snapshot parent, People direct)
{
parent.Player.DirectSet.Add(direct); //记录方向
People front = parent.Player.People + direct;
int fv = parent.Map[front]; //获得当前位置值
if (fv >= 0)
{
if (fv == Map.BOX) // Box
{
#region 前面是 Box 时处理
People next = new People(front)+direct; //前面的前面next
int nv = parent.Map[next];
if (nv == Map.SPACE) //可以往前推
{
#region 前面的前面是 Space 处理
Map map = parent.Map.Clone(); //采用复件
map[front] = Map.SPACE;
map[next] = Map.BOX;
if (map == DestiMap) //当前图已经和目标图相同
{
Result.Clear(); //清除以前内容
Result.Add(front);
Player player = parent.Player;
while (player != null)
{
Result.Add(player.People);
player = player.Parent;
}
Result.Reverse(); //结果取反
Finish = true; //正常结束
return true;
}
ProSnapshot(parent.Player, map, front.Position(), direct);
#endregion 前面的前面是 Space 时处理
}
#endregion 前面是 Box 时处理
}
else if (fv == Map.SPACE)
{
#region 前面是 Space 处理
ProSnapshot(parent.Player, parent.Map, front.Position(), direct);
#endregion 前面是 Space 处理
}
}
return false;
}
private void ProSnapshot(Player parent, Map map, ushort pos, People direct)
{
int index = MapSet[map]; //存在旧地图时引用旧地图
if (index >= 0)
map = MapSet.Group[index];
else
MapSet.Group.Add(map); //不存在添加

index = PhotoSet.FindSameIndex(map, pos); //继续找
if (index >= 0) //找到位置和图皆相同的
return; //出现重复,没必要继续

Snapshot snapshot = new Snapshot() //生成一个全新的快照
{
Main = this,
Map = map,
Player = new Player()
{
Parent = parent,
Pos = pos
}
};
Queues.Enqueue(snapshot); //入队列
PhotoSet.Add(snapshot); //加入快照
}
广度搜索需要借助队列,出现新情况时生成新快照加入队列Queues

推箱子算法实现链接(百度盘):
https://pan.baidu.com/s/1r_kEV4nllcfzNE69i-2M0Q




我的更多文章

下载客户端阅读体验更佳

APP专享