推箱子游戏的自动求解

本文介绍了推箱子游戏的自动求解算法,包括游戏的基础部件如移动和最短路径算法,以及自动求解算法的框架。通过构建状态树和使用哈希表优化搜索效率,解决状态空间的搜索问题。此外,文章提到了死锁检测的重要性,但仅实现了两种简单的死锁判断规则。
摘要由CSDN通过智能技术生成
导读:
   推箱子游戏的自动求解
  
  简介
  推箱子,又称搬运工,是一个十分流行的单人智力游戏。玩家的任务是在一个仓库中操纵一个搬运工人,将N个相同的箱子推到N个相同的目的地。推箱子游戏出现在计算机中最早起源于1994年台湾省李果兆开发的仓库世家,又名仓库番,箱子只可以推, 不可以拉, 而且一次只能推动一个。它的规则如此简单,但是魅力却是无穷的。但是人毕竟思考的深度和速度有限,我们是否可以利用计算机帮助我们求解呢?
  游戏的基础部件
  首先,我选择了C++来做这个程序。这一步并没有太大的难度,不少编程爱好者肯定也写过不少的小游戏。所以这里我只简要的为后面的叙述必要的铺垫。
  我将推箱子游戏的数据和操作封装为一个BoxRoom类,下面是后面的自动求解算法用到的成员函数和数据结构。
  ①
  //移动一格,如果有箱子就推
  short MovePush(Direction);
  //移动到一点,并返回一个移动的路径
  short Goto(Position p, MovePath& path);
  以上两个是用来让搬运工移动的,它们返回人走过的步数,失败则返回-1。其中Goto用到了MovePath结构。
  typedef vector MovePath;
  关于Goto算法,即最短路径算法可以参考《CSDN开发高手》2004年第10期的《PC游戏中的路径搜索算法讨论》。考虑到推箱子的地图规模不大所以我采用了经典的Dijkstra算法。这在数据结构的教科书中应该可以找到,故不多着笔墨。
  ②
  为了记忆已经搜索过的状态,BoxRoom还提供了记录和保存状态的函数:
  void SaveState(BoxRoomState& s)const
  void LoadState(constBoxRoomState& s);
  其中的BoxRoomState结构将在后文中讨论。
  ③
  用来检测是否已经胜利。
  bool IsFinished()const{
  returnm_nbox == std::count(m_map.begin(),m_map.end(),EM_BOX_TARGET);
  }
  自动求解算法的框架
  人工智能的精髓从某种意义上就是穷举,但是如何有效的穷举就是一个好的智能算法所要解决的问题。已知的事实是推箱子问题是NP-Hard的。第一个问题是,我们所要搜索的空间是相当的巨大,“傻傻”的搜索是相当费时的,我们所要做的就是动用各种手段减少不必要的搜索来节省时间。还有一个问题是这么大的搜索空间,我们如何利用有限的空间有效的保存,并且快速的判断出某个状态已经搜索过。
  上面多次提到了搜索空间,那么我们如何来描述推箱子问题的搜索空间呢。
  上面提到BoxRoom类的SaveState和LoadState函数用到了BoxRoomState。它描述的就是问题空间中的节点。首先,它的实现要尽量节省空间,因为自动求解过程中要记录相当数量的状态。我用了boost::dynamic_bitset,因为标准库的bitset的有一个弱点就是不能动态的决定其位数,而我们又不想让BoxRoom模板化。
  class BoxRoomState{
  friend class BoxRoom;
  boost::dynamic_bitset<> m_extracted_map;
  Position m_manpos;
  short m_totlestep;
  //比较状态是否等价
  //等价:如果状态A中能够在箱子保持不动的情况下达到状态状态B,那么A<=>B
  //性质:自反性,传递性,对称性
  //
  //注意:其充要条件比较难表示,所以我们暂时只能用其充分条件!所以严格的说这里不符合==的定义,也就是说!operator == ()不代表!=
  public:
  bool operator==(constBoxRoomState& oth)const{
  returnm_manpos == oth.m_manpos &&m_extracted_map == oth.m_extracted_map;
  }
  inlineint GetTotlestep()const{returnm_totlestep;}
  inlinevoidSetTotlestep(ints){m_totlestep = s;}
  };
  SetTotlestep似乎有些奇怪(你甚至可以把它改为1而不考虑其合理性),提供它纯粹是为了算法的需要。注释已经说明了如何判断两个状态是否等价,特别提到了这只是充分条件而非必要条件。提供一个加强的充分条件(如果是充要条件那将更加完美)将能够进一步减小搜索的空间。
  这些状态之间的转移就是边,这样就构成了一个有向图。对一般的有向图的搜索是十分麻烦的,因为这样容易造成回路。考虑这样一种情况,把一个箱子向左推一格和向右推一格再向左推推两格达到的状态明显是等价的,对后一种情况继续搜索所需要的步数明显大于前者,所以这一支可以去掉。也就是说,我们只保留状态A->状态B所需要的路径中人走过的步数最少的一个(我的算法只解决最优移动,当然也有很多人需要最优推动)。如此一来,我们就得到了更特殊的有向图——树。
  对树的搜索,大家应该相当熟悉。一般可以分为深度优先搜索和广度优先搜索。由于要得到(步数)最优解,我采用的算法的基本思路属于广度优先搜索。
  算法的框架:
  //表示解中的一次有效移动:表示走到一个箱子旁,并推动他
  struct ValidStep{
  Position p;
  Direction d;
  ValidStep():p(-1),d(EAST){}
  ValidStep(intpp, Direction dd):p(pp),d(dd){}
  };
  typedefvector SolveResult;
  
  intSolveBoxRoom(BoxRoom room, SolveResult& path){
  //保存根状态
  SolveState startstate(room);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值