如果你认为无法因为玩一个电脑游戏而达到精神的顿悟,你可能是正确的。不过你完全可以试着解决一个禅宗花园发生的难题,从而达到静心的精神状态。


  这个游戏是获得2003年独立游戏节提名的精品游戏,在注重游戏画面和特效的今天,很多人无法接触到和了解这个游戏深刻内涵,特别推荐小游戏玩家来挑战这个禅宗花园的难题。

如 图,这就是那个2003年的电脑游戏的截图,其挂卡的设计我在具体的AI实现中会说到,其实该游戏的关卡已经被证明是NP完全的(我在Round 14中也阐述过类似的关卡设计问题,也就是推箱子的关卡设计,一个有挑战的推箱子关卡往往被设计成指数级别的复杂度)。由于当时的智能手机并没有发达到如 今的地步,所以该游戏最初是在电脑端进行的单机游戏。

如今,该游戏已经被安置到手机游戏的银屏上,比如这款基于ipad平台开发的小游戏,大有昔日的魂斗罗重登PS2之感啊!

这里,注意到下角有一些辅助工具,这些都是一些AI小应用,自动退回,自动步进,以及让“智慧老人”指引你找到一条路径等等。可以看到,在手机端的改进版中,形状已经不是规则的矩形了,这也给AI的设计者提出了更大的挑战。

关于游戏挂卡的NP完全证明,我在道客巴巴找到了一篇还没有被翻译出来的英文文献,我会和同学一起翻译,并在这一期Round给出。

 

  游戏的规则

我 这里截取费恩曼在他的《费恩曼物理学讲义》中的一段话:研究物理学就好比研究两个绝世高手下棋,往往先要破解这个游戏的规则,这其实比较容易做到,但是, 我们在这个基础之上,还要想一想,他们是为什么要那么下棋?这就是要破解他们基于这一规则所制定的策略,这往往就是难上加难了,我们可以理解为模拟算法和 AI算法的区别。而且,往往最简单的规则的游戏会蕴含着最深奥的策略,比如围棋。

如图(a),(b),(c),一个分块的矩形中间夹着几个石头,一个小人在上面行走。准确地说,应该是滑行吧,当他碰到石头的时候,就可以考虑换一个方向 进行滑动。每次滑行道离开这个沙场位置。我们最终的目标(goal)是将整个没有石头的沙子都恰好走过一遍(这里的恰好走过一遍的意思是:经过的沙地就不 允许再经过了),而且,这个小人在执行了这个过程之后,最终应该出现在沙子的外面。

(如图,这是Zen Puzzle Garden目前的官方网站,为一个经典的游戏设置一个官网也是必须的事情)

我们设计一款AI,可以再20s之内至少返回一个合理解(无解的情况暂时不考虑),将游戏的界面大小设计为12*12的模式,输入为一个界面,其中0来标记空地,1标记石子。输出的第一行为需要行走的轮数,后面为每一轮的具体步骤。

该问题可以考虑为“吴昊系列Round 16——龙系道馆”的延伸,我们的主角还是采用“滑动”的模式,只是这一次不同之处在于,每一个格子走过之后就不允许再走了,所以,需要用一个visit 数组进行标记。而且,最后的游戏目标是恰好一次扫描到最有的格子,而不是到达一个目的地,所以,异常麻烦,源码我用的是Pengjiajun(NOI) 的,这里注明一下,有些地方还是木有看懂,他使用了三个函数,进行如下的调用:

bfs()判断每一块小方形所能延拓的空地的总数(除开那个小方块本身)。

bool dfs(int a[],pp nw,int ndeep)判断这个游戏是否存在一个解,如果搜索到了,则输出第一个搜索到的解。

bool t_dfs(int a[],int id1,int id2,int na[],int dir,pp now,int deep,int ndeep)以一个固定的点,固定的步进度进行深度优先搜索,这是一个递归的函数,直到找到一个满足条件的解,该函数是dfs函数的子函数。

另外,设置了数组a[],利用30进制进行记录,应该是对空间的一个优化吧,暂时还木有搞得很明白。

  1 #include<iostream>
  2  #include<cstdlib>
  3  #include<algorithm>
  4  #include<cmath>
  5  #include<cstring>
  6  #incude<stdio.h>
  7  #include<map>
  8  #include<vector>
  9  using namespace std;
 10  
 11  vector<int> adj[6][999997];
 12  
 13  int r,c,cnt;
 14  int MOD=999997;
 15  
 16  //这里定义方向向量,便于搜索 
 17  int dirx[]={-1,1,0,0};
 18  int diry[]={0,0,-1,1};
 19  
 20  //定义一个结构体,存储地图 
 21  struct pp
 22  {
 23    bool mat[20][20];       
 24  };
 25  
 26  struct bl
 27  {
 28    int num;
 29    short list[145][2];
 30    bool operator <(const bl &temp) const
 31    {
 32      return num<temp.num;     
 33    }       
 34  };
 35  
 36  pp nw;
 37  
 38  struct qq
 39  {
 40    int id1,id2;       
 41  };
 42  
 43  qq list[200],queue[200];
 44  
 45  //这里标记是否经历过 
 46  bool visit[13][13];
 47  
 48  int f[20][20],temp1,temp2,temp,ans[200][200][2],num[200],cont;
 49  
 50  bool t_dfs(int a[],int id1,int id2,int na[],int dir,pp now,int deep,int ndeep);
 51  bool dfs(int a[],pp nw,int ndeep);
 52  
 53  void bfs(int id1,int id2,pp nw)
 54  {
 55    int i,j,s,p,q;
 56    //将这个空白方块入队列,并标记为已经访问 
 57    queue[0].id1=id1;
 58    queue[0].id2=id2;
 59    visit[id1][id2]=true;
 60    temp1=temp2=0;
 61    temp=1;
 62    while(temp1<=temp2)
 63    {
 64      for(i=temp1;i<=temp2;i++)
 65      {
 66        //分别对四个方向进行BFS 
 67        for(j=0;j<4;j++)       
 68        {
 69          id1=queue[i].id1+dirx[j];
 70          id2=queue[i].id2+diry[j];
 71          //判断搜索的点是否越界
 72          if(id1>=0&&id1<r&&id2>=0&&id2<c)
 73          {
 74            //如果延拓的这一点是空地而且未被访问的话
 75            if(visit[id1][id2]==false&&nw.mat[id1][id2]==0)
 76            {
 77              //标记为已访问,并且入队列
 78              visit[id1][id2]=true;
 79              queue[temp].id1=id1;
 80              queue[temp++].id2=id2;                                               
 81            }                                
 82          }                       
 83        }                  
 84      }            
 85      //这里的含义是,去掉一个已经出队的点,增加新入队的点       
 86      temp1=temp2+1;
 87      temp2=temp-1;
 88    }     
 89  }
 90  
 91  int main()
 92  {
 93    int i,j,ncnt,a[5];
 94    scanf("%d%d",&r,&c);
 95    cnt=0;
 96    memset(f,-1,sizeof(f));
 97    for(i=0;i<r;i++)
 98    {
 99      for(j=0;j<c;j++)
100      {
101        scanf("%d",&nw.mat[i][j]);
102        if(nw.mat[i][j]==0)
103        {
104          list[cnt].id1=i;
105          list[cnt].id2=j;
106          f[i][j]=cnt++;                   
107        }                
108      }                
109    }
110    memset(num,0,sizeof(num));
111    cont=0;
112    //orz为真值,看是为true还是false 
113    int orz=dfs(a,nw,0);
114    if(orz>0)
115    {
116      //总共需要行进几轮 
117      printf("%d\n",cont);
118      for(i=0;i<cont;i++)
119      {
120        //每一轮的行进步数 
121        printf("%d:",num[i]);
122        //加1的原因是,那个数组是从0下标开始计数的 
123        for(j=0;j<num[i];j++)
124          printf(" (%d,%d)",ans[i][j][0]+1,ans[i][j][1]+1);
125        printf("\n");                   
126      }         
127    }
128    return 0;    
129  }
130  
131  bool t_dfs(int a[],int id1,int id2,int na[],int dir,pp now,int deep,int ndeep)
132  {
133    int i,j,x,y,id,b[6];
134    ans[ndeep][deep][0]=id1;
135    ans[ndeep][deep][1]=id2;
136    //将目前的点标记为石头,表明已经走过 
137    now.mat[id1][id2]=1;
138    //对已经选定好的方向进行搜索 
139    x=id1+dirx[dir];
140    y=id2+diry[dir];
141    if(x<0||x>=r||y<0||y>=c)
142    {
143      //如果已经出界,记录答案 
144      ans[ndeep][deep+1][0]=x;
145      ans[ndeep][deep+1][1]=y;
146      num[ndeep]=deep+2;
147      if(num[ndeep]>3)
148      {
149        if(dfs(b,now,ndeep+1))
150          return true;
151      }
152      //不行的话,重新标记为空地 
153      now.mat[id1][id2]=0;
154      return false;                        
155    }
156    //如果没有越界而且这里为空地的话,则可能满足题目条件 
157    if(now.mat[x][y]==0)
158    {
159      //搜索深度每次加深1个单位 
160      if(t_dfs(a,x,y,na,dir,now,deep+1,ndeep)) return true;
161      //否则,退回到原来的状态
162      now.mat[id1][id2]=0;
163      return false;                    
164    }     
165    //如果不是空地的话,朝四个方向搜索 
166    else
167    {
168      for(i=0;i<4;i++)
169      {
170        x=id1+dirx[i];
171        y=id2+diry[i];
172        if(x<0||x>=r||y<0||y>=c)
173        {
174          ans[ndeep][deep+1][0]=x;
175          ans[ndeep][deep+1][1]=y;
176          if(num[ndeep]>3)
177          {
178            if(dfs(b,now,ndeep+1)) return true;                
179          }                        
180        }                
181        else if(now.mat[x][y]==0)
182        {
183          if(t_dfs(a,x,y,na,i,now,deep+1,ndeep)) return true;     
184        }
185      } 
186      //否则,将其重新标为空地,返回false
187      now.mat[id1][id2]=0;
188      return false;   
189    }
190  }
191  
192  bool dfs(int a[],pp nw,int ndeep)
193  {
194    int siz,value=0,i,j,s=0,cou=0,na[5],b[6],id,in=1000000000;
195    pp now;
196    bl block[20];
197    a[0]=a[1]=a[2]=a[3]=a[4]=0;
198    //利用一个数组模拟30进制存储 
199    for(i=0;i<r;i++)
200    {
201      for(j=0;j<c;j++)
202      {
203        if(nw.mat[i][j]==0)
204        {
205          int id=f[i][j];
206          a[id/30]+=(1<<(id%30));                   
207        }                
208      }                  
209    }     
210    if(a[0]==0&&a[1]==0&&a[2]==0&&a[3]==0&&a[4]==0)
211    {
212      cont=ndeep;
213      return true;                                               
214    }
215    //否则,按照键值存储一个状态 
216    for(i=4;i>=0;i--)
217      value=(((long long)((1<<30)%MOD)*(long long)value)%MOD+a[i])%MOD;
218    siz=adj[0][value].size();
219    //这是判断是否越出了五位的界么?这个逻辑实现的功能还没怎么看懂 
220    for(i=0;i<siz;i++)
221    {
222      for(j=0;j<5;j++)
223      {
224        if(adj[j][value][i]!=a[j]) break;
225      }
226      if(j>=5) break;
227    }
228    if(i<siz) return adj[5][value][i];    
229    memset(visit,false,sizeof(visit));
230    for(i=0;i<r;i++)
231      for(j=0;j<c;j++)
232      {
233        //对于每一个还没有遍历,且为空地的地方,进行bfs扫描 
234        if(visit[i][j]==false&&nw.mat[i][j]==0)
235        {
236          bfs(i,j,nw);
237          //每次新增的块 
238          block[cou].num=temp;
239          //将对每一点搜索的新增的块都加入到list中 
240          for(s=0;s<temp;s++)
241          {
242            block[cou].list[s][0]=queue[s].id1;
243            block[cou].list[s][1]=queue[s].id2;
244          }
245          cou++;
246        }
247      }
248    //对每一个块进行分析 
249    for(i=0;i<cou;i++)
250    {
251      //利用变量orz存储由这个方块延拓的遇到边界的次数(神牛就是神牛,变量名都那么精彩) 
252      int orz=0;
253      //对那一块所有新增加的块进行分析 
254      for(j=0;j<block[i].num;j++)
255      {
256        if(block[i].list[j][0]==0)
257          orz++;
258        else if(block[i].list[j][0]==r-1)
259          orz++;
260        else if(block[i].list[j][1]==0)
261          orz++;
262        else if(block[i].list[j][1]==c-1)
263          orz++;
264      }
265      //得到块数最小的点 
266      if(in>block[i].num)
267      {
268        in=block[i].num;
269        id=i;
270      }
271      //如果只能触及到一个边界,那么是不行的 
272      if(orz<=1) return false;
273      }
274    }
275      //这里给出了我们的AI策略,每次选取延拓方块最小的,这样最不容易导致出现"石头阻挡"或者"经历过的点,绕不回来"的毛病 
276      swap(block[id],block[0]);
277      for(i=0;i<block[0].num;i++)
278      {            
279        //对块延拓出的边界点进行处理,广度搜索,并存储在a[]中               
280        if(block[0].list[i][0]==0)
281        {
282          memset(na,0,sizeof(na));
283          now=nw;
284          num[ndeep]=0;
285          ans[ndeep][num[ndeep]][0]=-1;
286          ans[ndeep][num[ndeep]++][1]=block[0].list[i][1];
287          //如果这条路径可达的话 
288          if(t_dfs(a,block[0].list[i][0],block[0].list[i][1],na,1,now,1,ndeep))
289          {
290            for(j=0;j<5;j++)
291              adj[j][value].push_back(a[j]);
292            adj[5][value].push_back(1);
293            return true;
294          }         
295        }
296        if(block[0].list[i][0]==r-1)
297        {                                                                          
298          memset(na,0,sizeof(na));
299          now=nw;
300          num[ndeep]=0;
301          ans[ndeep][num[ndeep]][0]=r;
302          ans[ndeep][num[ndeep]++][1]=block[0].list[i][1];
303          if(t_dfs(a,block[0].list[i][0],block[0].list[i][1],na,0,now,1,ndeep))
304          {
305            for(j=0;j<5;j++)
306              adj[j][value].push_back(a[j]);
307            adj[5][value].push_back(1);
308            return true;
309          }         
310        }
311        if(block[0].list[i][1]==0)
312        {                        
313          memset(na,0,sizeof(na));
314          now=nw;
315          num[ndeep]=0;
316          ans[ndeep][num[ndeep]][0]=block[0].list[i][0];
317          ans[ndeep][num[ndeep]++][1]=-1;
318          if(t_dfs(a,block[0].list[i][0],block[0].list[i][1],na,3,now,1,ndeep))
319          {
320            for(j=0;j<5;j++)
321              adj[j][value].push_back(a[j]);
322            adj[5][value].push_back(1);
323            return true;
324          }
325        }
326        if(block[0].list[i][1]==c-1)
327        {                   
328          memset(na,0,sizeof(na));
329          now=nw;
330          num[ndeep]=0;
331          ans[ndeep][num[ndeep]][0]=block[0].list[i][0];
332          ans[ndeep][num[ndeep]++][1]=c;
333          if(t_dfs(a,block[0].list[i][0],block[0].list[i][1],na,2,now,1,ndeep))
334          {
335            for(j=0;j<5;j++)
336              adj[j][value].push_back(a[j]);
337            adj[5][value].push_back(1);
338            return true;
339          }
340        }
341      }
342      for(j=0;j<5;j++)
343        adj[j][value].push_back(a[j]);
344      adj[5][value].push_back(0);
345      return false;
346  }
347  
348  
349  
350  
351  
352  

《禅宗花园》ipad图标

转载于:https://www.cnblogs.com/tuanzang/archive/2013/04/23/3037979.html

吴昊品游戏核心算法 Round 18 —— 吴昊教你玩Zen Puzzle Garden相关推荐

  1. 吴昊品游戏核心算法 Round 16 ——吴昊教你玩口袋妖怪 第三弹 地洞谜题

    这样的场景我们应该经常遇到的吧,哈哈! 口袋妖怪的地洞要算是最令人讨厌的了,因为,有些地洞是全黑的,你即使用了闪光灯(必选道具#03),你有时也只能用GBA外壳的荧光屏作为道具才能将整个地洞看清楚. ...

  2. 吴昊品游戏核心算法 Round 17 —— 吴昊教你玩拼图游戏 序

    如图所示,此人就是<死亡笔记>中最终击败夜神月的尼亚(当然,他在击败夜神月的过程中,利用了梅洛的一些帮助),尼亚喜欢玩各种玩具,比如乐高啊,多米诺骨牌啊等等,当然,他最喜欢的仍然是拼图.他 ...

  3. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第四弹 拉帝亚斯?!拉帝欧斯?!...

    作为讲述口袋妖怪的Round 16,这里,我会采用"夹叙夹议,夹议夹叙"的模式进行编排.也就是在议论一些口袋妖怪中的小游戏的核心算法的同时,叙述一下我对口袋妖怪的理解以及我与这一系 ...

  4. 吴昊品游戏核心算法 Round 10 —— 吴昊教你下围棋(利用递归来解决吃子的问题)...

    如图所示,此即为日本动漫棋魂中的千年佐为,也就是SAI.众所周知,围棋的规则相比于中国象棋,国际象棋等等都简单许多,真是因为更简单的规则,才诞生 了更复杂的逻辑.目前的围棋AI还很不行,最NB的应该是 ...

  5. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第九弹 冰系道馆

    道馆之战!!!这一次,道馆由电系变为了冰系,和龙系的道馆不一样,冰系的道馆有冰系的规定.如图所示,一共有三层,上面一层总是比下面一层要复杂一些.在冰系道馆中,有如下的两个规则:(1)我们不会在冰面上划 ...

  6. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第六弹 龙系道馆

    道馆之战!!!如图所示,此乃口袋妖怪的道馆战中的龙系道馆,一般情况下,在每一个系列的口袋妖怪中,龙系道馆往往是排列在最后的,我们通过一些滑动可以到达我们所想到达的地方(冰块并不会因此而破碎).但是,我 ...

  7. 吴昊品游戏核心算法 Round 17 —— 吴昊教你玩拼图游戏(8 puzzle)

    如图所示,这是一个九宫格(这倒是让我想起了小时候老师在黑板上教导我们的如何通过一系列的拼凑,将横行,竖行,以及斜行都拼到和相等),格子中有一个格子是空的,另外八个格子分别有数字1--8,我们的任务是将 ...

  8. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第十弹 超能力系道馆

    道馆之战!!!这一次,我们将要挑战的是众人熟知的超能力系道馆,这种道馆不是火箭队的巢穴,也不是冰系道馆的那种踏冰川.何以解释?我说明如下:不同的地板上的机关有不同的颜色,每一个颜色有对应的位置,我们可 ...

  9. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第二弹 777赌博机

    (此图选自口袋妖怪红宝石的某个镇的娱乐场所的赌博机) 这款777赌博机存在于日本的赌场,这里,我不想再像以前一样,通过各种百科COPY一些资料,这样没有意思.我谈谈自己当年玩POKEMON时的感受吧! ...

最新文章

  1. java面向对象基础代码_Java基础语法——面向对象(1)
  2. 怎么看android底层源码,Android 底层按键获取
  3. 做组织机构树状图 spark
  4. POJ 1276 完全背包
  5. npm ERR! command failednpm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c node-gyp rebuild
  6. 重装SPS 2003的一点经验
  7. 专题讲座3 数论+博弈论 学习心得
  8. ADF单位根检验三种形式_读文4:面板数据模型建立步骤、面板数据模型设定检验(F test)、面板协整模型(ADF test)...
  9. 论文写作---Matlab求解偏导数
  10. 山西台达plc可编程控制器_PLC(可编程控制器)的点动控制原理
  11. 极智AI | 目标检测 VOC 格式数据集制作
  12. 【NLP基础理论】10 上下文表示(Contextual Representation)
  13. Win10自定义路径位置安装WSL2 (Ubuntu 20.04) 并配置CUDA
  14. 公链Sui Layer1网络
  15. 风寒感冒和风热感冒 区别
  16. sam卡和sim卡区别_科普拍了拍你~PSAM卡\SIM与SAM卡有什么不同?
  17. 吸波材料衰减常数计算,Excel源代码
  18. xp系统怎么创建新宽带连接服务器地址,XP宽带连接怎么创建?
  19. 3t硬盘用什么软件测试显卡,【转】希捷酷鱼3TB网友试用---玩转3T硬盘,就是这么容易...
  20. 熟悉又陌生的 k8s 字段:finalizers

热门文章

  1. 九大背包问题专题--背包问题求具体方案数
  2. 2017蓝桥杯B组:最长公共子序列(动态规划详解(配图))
  3. 牛客网暑期ACM多校训练营(第四场): B. Interval Revisited(DP)
  4. Netty通信技术(一)
  5. python计算两张图像的L1和L2损失
  6. 使用Git将本地文件夹同步至github
  7. zynq文档学习之向GPIO引脚写数据
  8. shell的简单应用
  9. 2步判断晶体管工作状态
  10. Oracle11g限制ip访问数据库,Oracle11g设置IP访问限制