吴昊品游戏核心算法 Round 14 —— 吴昊教你推箱子(priority_queue容器+BFS)(HDOJ 1254)

在吴昊品游戏核心算法Round 1中,我有介绍一个“企鹅推箱子”的游戏,我们来回顾一下一个经典游戏是如何经过包装,最后成为一个崭新的游戏的。首先是美工,通过3D的画面,给人一种 震撼力,在UI方面,考虑到智能手机的触屏特性,制造出很多的“动态效果”。在算法方面,通过搜索算法的引入,给游戏制造了一种附加功能,就是,你可以通 过搜索让自己告诉你应该如何进行下一步的方向控制(这一点有点像连连看中的放大镜提示)。在这样的经典游戏中,往往就这么一个小插件就会给整个游戏增色不 少。

 

  关于推箱子问题的解法的时间复杂度,最终被证明是NP难的,而且后来又被证明是PSPACE完全的,其具体的证明步骤我会在后面的文章中给出(准确说应该是转载吧,那位大神的名字是杨超老师)。

 推箱子简介:

 推箱子》(倉庫番)是一款经典电子游戏,1982日本Thinking Rabbit公司首次发行。之后其他游戏开发者争相制作仿制或衍生作品。致使推箱子成为此类游戏的代名词。游戏要求玩家在二维地图上把箱子推到指定地点,当中牵涉到大量的空间逻辑推理。

 推箱子的规则:

  第一个《推箱子》的游戏规则,则是扮演工人的玩家,以“推”的方式,推动箱子。可以在没有阻碍物(如墙壁等的阻碍物)的情况下,向的地方移动。将箱子移动到指定点,达到指定数量,即可过关。

 但玩家移动箱子,有下列条件要注意:

  • 推到墙壁的箱子,玩家就不可以背对墙壁,把箱子推回到空处。即箱子只能以被推的方式被移动,不是以被拉的方式被移动。但如果玩家推至墙壁后,垂直墙壁的两侧没有阻碍物,则玩家可以朝这两个不同的方向推移箱子。
  • 一旦箱子被移动到角落,玩家没有任何方法再移动这个被推到角落的箱子。
  • 玩家不可同时推动两个或以上的箱子。假设工人面前有一个箱子,箱子的正前方又有一个箱子,则这两个箱子是不能被推动的(这个假设可以暂时不予以理睬,我以后会说明推箱子的各种变体,会提出推三个箱子游戏的玩法)。

 

 各种推箱子各种变体:

  (A)增加箱子的数目。

  (B)添加数量有限的炸弹破坏墙壁方可达成目标。

  (C)将重力感应添加到游戏中。

  (D)具有可以改变特性的墙壁、机关;可以收集的物品(原本是障碍物)。

 

 简化问题,抽象出模型(Source:HDOJ 1254):

 在一个M*N的房间里有一个箱子和一个搬运工,搬运工的工作就是把箱子推到指定的位置,注意,搬运工只能推箱子而不能拉箱子,因此如果箱子被推到一个角上(如图)那么箱子就不能再被移动了,如果箱子被推到一面墙上,那么箱子只能沿着墙移动。

 现在给定房间的结构,箱子的位置,搬运工的位置和箱子要被推去的位置,请你计算出搬运工至少要推动箱子多少格。

 

  Input:输入数据的第一行是一个整数T(1<=T<=20),代表测试数据的数量.然后是T组测试数据,每组测试数据的第一行是两个正整数M,N(2<=M,N<=7),代表房间的大小,然后是一个MN列的矩阵,代表房间的布局,其中0代表空的地板,1代表墙,2代表箱子的起始位置,3代表箱子要被推去的位置,4代表搬运工的起始位置(这种做法在经典游戏中的地图编辑器中是常见的,比如坦克大战的地图中,不同的道具也是用一个不同整型变量的二维数组来表征的)

  Output:对于每组测试数据,输出搬运工最少需要推动箱子多少格才能帮箱子推到指定位置,如果不能推到指定位置则输出-1

  Solve:

 

  1  /*
  2     Highlights:
  3                (1)定义四维变量flag[N][N][N][N]来标记每一个状态是否被访问过
  4                (2)用一个二维数组来表证地图,并且对不同的道具用整型数组标识
  5                (3)用每一个Node标识一个状态,并且用运算符重载的方式选择一个最少的步数
  6                (4)由于这里存在人和箱子两个实体,所以每次对各个方向进行搜索的时候,要分人是否可以挪动箱子进行分类讨论 
  7    */
  8  
  9  #include<iostream>
 10   // 这里用的是STL中的优先队列容器
 11   #include<queue>
 12   #define N 8
 13   using  namespace std;
 14  
 15   // 定义一个游戏地图,以及地图的尺寸(n,m),和在搜索时需要用到的标记数组(这里是四维的10^4)
 16    int arr[N][N];
 17   // 设置flag四位数组,来对每一个状态是否访问进行标记,如果该状态已经存在,则设置为true
 18    bool flag[N][N][N][N];
 19   int n,m;
 20  
 21   // 每一个Node标识一个状态
 22    struct Node
 23  {
 24     int x;
 25     int y;
 26     int bx;
 27     int by;
 28     int t;
 29     // <运算符重载,这里对每一个结点的步数进行排序,friend友元是为了让结点类外面的对象可以访问这个类的成员
 30     friend  bool  operator < (Node a,Node b)
 31    {
 32       return a.t>b.t;       
 33    }       
 34  };
 35  
 36   int sx,sy,bx,by,ex,ey;
 37   // 这里也可以定义两个一维数组来组成一个二维数组
 38    int dx[]={ 1,- 1, 0, 0};
 39   int dy[]={ 0, 0,- 1, 1};
 40  
 41   void BFS()
 42  {
 43     int i,j,k;
 44     int a,b,c;
 45     // 设置一个装载结点的优先队列Q
 46     priority_queue<Node>Q;
 47    Node curn,nxtn;
 48     // 初始化标志数组
 49     memset(flag, false, sizeof(flag));
 50     // 这里标识搬运工是可以到达箱子的初始位置的
 51     flag[sx][sy][bx][by]= true;
 52     // 标识人的起始位置和箱子的起始位置,并将这个状态下的步数设置为0(初始步数)
 53     curn.bx=bx;
 54    curn.by=by;
 55    curn.t= 0;
 56    curn.x=sx;
 57    curn.y=sy;
 58     // 装载第一个结点
 59     Q.push(curn);
 60     while(!Q.empty())
 61    {
 62      curn=Q.top();
 63      Q.pop();
 64       // 当到达了终点时,将此时的步数记录如下
 65        if(curn.bx==ex&&curn.by==ey)
 66      {
 67        printf( " %d\n ",curn.t);
 68         return;                            
 69      }
 70       // 根据广搜的原理,从四个不同的方向进行尝试        
 71        for(i= 0;i< 4;i++)
 72      {
 73        nxtn.x=curn.x+dx[i];
 74        nxtn.y=curn.y+dy[i];
 75         // 这里先不慌着加步数,要经过一定的判断之后再加入
 76         nxtn.t=curn.t;
 77         // 这里,如果下一步移动到的位置(1)在游戏地图范围内(2)没有墙壁的阻挡
 78          if(nxtn.x>= 0&&nxtn.x<n&&nxtn.y>= 0&&nxtn.y<m&&arr[nxtn.x][nxtn.y]!= 1)
 79        {
 80           // 加入箱子的位置就在人所移动方向的正前方(这里的方位以人所移动的方向为准)
 81            if(curn.bx==nxtn.x&&curn.by==nxtn.y)
 82          {
 83            a=nxtn.x+dx[i];
 84            b=nxtn.y+dy[i];
 85             // 同上,这里再判断一下人的下一步所造成的情况,顺便,判断是否已经经历过,如果没有,则箱子移动,且步数++,设置flag,并进队列
 86              if(a>= 0&&b>= 0&&a<n&&b<m&&arr[a][b]!= 1&&!flag[nxtn.x][nxtn.y][a][b])
 87            {
 88              nxtn.t++;
 89              nxtn.bx=a;
 90              nxtn.by=b;
 91              flag[nxtn.x][nxtn.y][nxtn.bx][nxtn.by]= true;
 92              Q.push(nxtn);                                                                   
 93            }                                    
 94          }                
 95           // 如果不行的话,就当做这次的箱子没有动,将箱子的位置设为不变(人的位置还是变化了),考虑flag,如果没有经历过的话,标记一下状态
 96            else
 97          {
 98            nxtn.bx=curn.bx;
 99            nxtn.by=curn.by;
100             if(!flag[nxtn.x][nxtn.y][nxtn.bx][nxtn.by])
101            {
102              flag[nxtn.x][nxtn.y][nxtn.bx][nxtn.by]= true;
103              Q.push(nxtn);                                           
104            }    
105          }                                                  
106        }                
107      }         
108    }     
109     // 实在是无解,则输出-1
110     printf( " -1\n ");
111  }
112  
113   int main()
114  {
115     int Case;
116     int i,j,k;
117     // 读入案例数
118     scanf( " %d ",&Case);
119     while(Case--)
120    {
121       // 读入地图的尺寸
122       scanf( " %d%d ",&n,&m);
123       // 填充地图的每一个元素
124        for(i= 0;i<n;i++)
125         for(j= 0;j<m;j++)
126        {
127          scanf( " %d ",&arr[i][j]);                
128        }             
129       // 设置初始值
130        for(i= 0;i<n;i++)
131         for(j= 0;j<m;j++)
132        {
133           // 设置箱子的起始点,终止点和搬运工的起始位置,同时注意,将arr[i][j]重新置0,表示又回归为地面
134            if(arr[i][j]== 2)
135          {
136            bx=i;
137            by=j;
138            arr[i][j]= 0;                
139          }             
140           else  if(arr[i][j]== 3)
141          {
142            ex=i;
143            ey=j;
144            arr[i][j]= 0;     
145          }   
146           else  if(arr[i][j]== 4)
147          {
148            sx=i;
149            sy=j;
150            arr[i][j]= 0;     
151          }
152        }
153      BFS();
154    }
155     return  0;    
156  }
157 
158 

 

你可能感兴趣的:(吴昊品游戏核心算法 Round 14 —— 吴昊教你推箱子(priority_queue容器+BFS)(HDOJ 1254))