吴昊品游戏核心算法 Round 9 —— 黑白棋AI系列之西洋跳棋(第二弹)(双向BFS+STL)(POJ 1198)

 

接上回,如图所示,这是黑白棋的一个变种,Solitaire也是一种在智能手机上普遍存在的一种游戏。和翻转棋(Flip Game)一样,西洋跳棋(Solitaire)也没有正统的黑白棋(奥赛罗,又称Othello)受关注,但毕竟这也属于黑白棋的常见的一个变种,所以 我在这里还是将其收录了。

其规则同样很简单,只是比Flip Game多了一条规则,变为了如下的两条(棋子的颜色相同,假设一个8*8的棋盘上一共只有4个棋子)

(1)        每个棋子可以自由移动一个单位长度。

(2)        任何一个棋子如果相邻位置有棋子而后面一位又没有棋子的话,是可以跳过的。

有了以上两个规则,我们看看此问题的AI吧,还是先说明下Source——(POJ 1198),这里我们依然采用BFS,当然,纯粹的单向BFS当然也是可解的,但是,考虑到该问题的某些对称性,我们在这里采用双向BFS,可以提高对该问题求解的时间效率。

  1   /*
  2      Highlights:
  3     (1)开的空间为什么是1<<24,这个是有解释的,而且bitset也可以装载下来,该容器也可以节省部分空间
  4     (2)开两个不同的方向数组,但是,保持对应的关系,这在判断是否可以跳跃的时候有帮助
  5     (3)关于结构体里面的与结构体名同名的函数的成员变量设置,我会在后面给出说明
  6     (4)两个队列容器共同搜索,不然,搜索八步的话,需要浪费很长的时间以及很大的空间,都不划算
  7     (5)在搜索之前,每次一个变化都用sort进行二维点的排序处理
  8     (6)利用Hash容器来看看最终是否匹配,其实,这一点上看map容器也是可以的,只是Hash容器更好,其原因在(1)中也已经说明了
  9    */
 10  
 11  #include<iostream>
 12  #include<cstdio>
 13  #include<bitset>  // STL容器
 14   #include<cstring>
 15  #include<algorithm>  // C++自带的算法库
 16   #include<queue>  // STL容器
 17    using  namespace std;
 18  
 19   const  int N=(( 1<< 24)+ 10);  // 开这么大一个空间,也是为了增强容错性能
 20   
 21  bitset<N> hash1;
 22  bitset<N> hash2;  // bitset容器是可以节省内存的
 23   
 24   int dir1[ 4][ 2]={{- 1, 0},{ 1, 0},{ 0, 1},{ 0,- 1}}; // 上下左右四个方向的移动
 25    int dir2[ 4][ 2]={{- 2, 0},{ 2, 0},{ 0, 2},{ 0,- 2}}; // 上下左右四个方向的跳跃
 26   
 27   char map1[ 9][ 9],map2[ 9][ 9];  // 数组也尽量开大一点
 28   
 29   struct Dir
 30  {
 31     int x,y;       
 32  };
 33  
 34   struct Node
 35  {
 36    Dir dir[ 4];
 37     int step;
 38     char map[ 9][ 9];
 39    Node()  // 初始化这个结构体的成员变量,其左右相当于类中的构造函数
 40     {
 41       for( int i= 0;i< 9;i++)
 42      {
 43         for( int j= 0;j< 9;j++)
 44        {
 45          map[i][j]= ' A ';        
 46        }        
 47      }      
 48      step= 0// 初始的步数为0
 49     }       
 50  };
 51  
 52  Node start,end;
 53  
 54   // 以下是两个内联函数,其在函数调用的地方直接展开,但是不适合
 55    // 在很长的函数代码中使用,这样会使代码总长度过于冗长
 56   
 57    // 对坐标进行排序
 58   inline  bool cmp( const Dir &a, const Dir &b)
 59  {
 60     if(a.x==b.x)  return a.y<b.y;
 61     else  return a.x<b.x;       
 62  }
 63  
 64   // 将二维坐标转换成一维的
 65   inline  int Hash( const Node &node)
 66  {
 67     int num;
 68     int sum= 0;
 69     for( int i= 0;i< 4;i++)
 70    {
 71       // (0*8+1到7*8+8也就是标注了1到64这64个点)
 72        // 这里我们可以看到为什么对N的设立是1<<24+10了,因为2^6^4=2^24
 73        // int类型是32位的,也完全可以存下来
 74       num=(node.dir[i].x- 1)* 8+node.dir[i].y;
 75      sum= 64*sum+num;        
 76    }       
 77  }
 78  
 79   void BFS()
 80  {
 81     // 设立两个队列容器双向搜索
 82     queue<Node> Q1, Q2;
 83    Q1.push(start);
 84    Q2.push(end);
 85     // 开启一个结点和相邻的结点,并重置hash表
 86     Node now, next;
 87    hash1.reset();
 88    hash2.reset();
 89    hash1[ Hash(start)] =  true;
 90    hash2[ Hash(end)] =  true;
 91     int x, y;
 92    
 93     // 由于这里是最多八步,两个队列分别移动四步就可以了
 94      for( int step =  0; step <  4; step++)
 95    {
 96       while(!Q1.empty())
 97      {
 98        next = now = Q1.front();
 99        Q1.pop();
100         // 搜索到第4步的时候,跳出循环
101          if(next.step >  4)
102           break;
103         for( int i =  0; i <  4; i++)  //  四个点 D表示棋子,A表示空白
104         {
105           for( int j =  0; j <  4; j++)
106          {
107             //  先搜一步的方向
108             next = now;
109            x = now.dir[i].x + dir1[j][ 0];
110            y = now.dir[i].y + dir1[j][ 1];
111             if(x >=  1 && x <=  8 && y >=  1 && y <=  8 && now.map[x][y] !=  ' D ')
112            {
113              next.dir[i].x = x;
114              next.dir[i].y = y;
115              next.step = now.step +  1;
116               // 这里转换过来,移动一步的位置置D而原来的位置置A 
117               next.map[x][y] =  ' D ';
118              next.map[now.dir[i].x][now.dir[i].y] =  ' A ';
119              sort(next.dir, next.dir +  4 , cmp);
120               // 如果已经遍历过,就继续了
121                if(hash1[ Hash(next) ])
122                 continue;
123               if(hash2[ Hash(next) ] )
124              {
125                 // 如果该状态在end搜过来hash过的就说明存在
126                 printf( " YES\n ");
127                 return;
128              }
129               // 将next结点标记,并载入队列中
130               hash1[ Hash(next) ] =  true;
131              Q1.push(next);
132            }
133          }
134           // 再搜索两步,也就是跨越的方向
135            for( int j =  0; j <  4; j++)
136          {
137            next = now;
138            x = now.dir[i].x + dir2[j][ 0];
139            y = now.dir[i].y + dir2[j][ 1];
140             // 跨越的条件
141              if(x >=  1 && x <=  8 && y >=  1 && y <=  8 &&
142                       now.map[now.dir[i].x + dir1[i][ 0] ][now.dir[i].y + dir1[i][ 1] ] ==  ' D '
143                       && now.map[x][y] !=  ' D ')
144            {
145              next.dir[i].x = x;
146              next.dir[i].y = y;
147              next.step = now.step +  1;
148              next.map[x][y] =  ' D ';
149              next.map[now.dir[i].x][now.dir[i].y] =  ' A ';
150              sort(next.dir, next.dir +  4 , cmp);
151               if(hash1[ Hash(next) ])
152                 continue;
153               if(hash2[ Hash(next) ] )
154              {
155                printf( " YES\n ");
156                 return ;
157              }
158              hash1[ Hash(next) ] =  true;
159              Q1.push(next);
160            }
161          }
162        }
163      }
164      
165       // 同理,对Q2队列的妹一种情况采用相似的搜索,这里直接搬砖,如果将相似的功能封装入一个函数的话,代码也许就没有这么长了
166        while(!Q2.empty())
167      {
168        next = now = Q2.front();
169        Q2.pop();
170         // 这里应该是说如果四步都已经搜索完了,则只有退出,但是,这里为什么用next.step==step+1而不与上面的next.step>4相同,现在我还没有弄清楚
171          if(next.step == step +  1)
172           break;
173         for( int i =  0; i <  4; i++)
174         //  四个点 D表示棋子,A表示空白
175         {
176           for( int j =  0; j <  4; j++)
177          {
178            next = now;
179            x = now.dir[i].x + dir1[j][ 0];
180            y = now.dir[i].y + dir1[j][ 1];
181             if(x >=  1 && x <=  8 && y >=  1 && y <=  8 && now.map[x][y] !=  ' D ')
182            {
183              next.dir[i].x = x;
184              next.dir[i].y = y;
185              next.step = now.step +  1;
186              next.map[x][y] =  ' D ';
187              next.map[now.dir[i].x][now.dir[i].y] =  ' A ';
188              sort(next.dir, next.dir +  4 , cmp);
189               if(hash2[ Hash(next)])
190                 continue;
191               if(hash1[ Hash(next)])
192              {
193                printf( " YES\n ");
194                 return ;
195              }
196              hash2[ Hash(next) ] =  true;
197              Q2.push(next);
198            }
199          }
200           for( int j =  0; j <  4; j++)
201          {
202            next = now;
203            x = now.dir[i].x + dir2[j][ 0];
204            y = now.dir[i].y + dir2[j][ 1];
205             if(x >=  1 && x <=  8 && y >=  1 && y <=  8 &&
206                      now.map[now.dir[i].x + dir1[i][ 0] ][now.dir[i].y + dir1[i][ 1] ] ==  ' D '
207                      && now.map[x][y] !=  ' D ')
208            {
209              next.dir[i].x = x;
210              next.dir[i].y = y;
211              next.step = now.step +  1;
212              next.map[x][y] =  ' D ';
213              next.map[now.dir[i].x ][now.dir[i].y ] =  ' A ';
214              sort(next.dir, next.dir +  4 , cmp);
215               if(hash2[ Hash(next)])
216                 continue;
217               if(hash1[ Hash(next) ] )
218              {
219                printf( " YES\n ");
220                 return ;
221              }
222              hash2[ Hash(next) ] =  true;
223              Q2.push(next);
224            }
225          }
226        }
227      }
228    }
229    printf( " NO/n ");
230     return ;
231  }
232  
233   int main()
234  {
235     int x,y;
236     // 每一行的前面两个数和后面六个数是需要区分开来的
237      while(scanf( " %d%d ",&x,&y)!=EOF)  // 读到文件尾
238     {
239      Node ss,ee;  // 收尾结点
240       ss.map[x][y]= ' D ';
241      ss.dir[ 0].x=x;
242      ss.dir[ 0].y=y;
243       for( int i= 1;i< 4;i++)
244      {
245        scanf( " %d%d ",&x,&y);
246        ss.map[x][y]= ' D ';
247        ss.dir[i].x=x;
248        ss.dir[i].y=y;        
249      }               
250       for( int i= 0;i< 4;i++)
251      {
252        scanf( " %d%d ",&x,&y);
253        ee.map[x][y]= ' D ';
254        ee.dir[i].x=x;
255        ee.dir[i].y=y;        
256      }               
257      start=ss;
258      end=ee;
259       // 分别按照cmp函数的规则对这八个点四个四个地排序
260       sort(start.dir,start.dir+ 4,cmp);
261      sort(end.dir,end.dir+ 4,cmp);
262      BFS();  
263    }
264     return  0;   
265  }


 
  如图,以下是解释结构体的名字和其内的函数名同名的情况:

 

你可能感兴趣的:(round)