吴昊品游戏核心算法 Round 9 —— 黑白棋AI系列(第一弹)(BFS+位压缩存储)(POJ 1753)
在历史上,关于黑白棋(官方名称为奥赛罗),有许多个变种,比如这一弹中,我准备给出其中一种(和我在Round 7中介绍的熄灯问题)比较相似的(甚至可以说是几乎一摸一样)的游戏,其名字叫做翻转棋(Flip Game)。这款游戏的规则非常简单,在一个4*4的界面中,随意翻转一个子,其自身以及相邻的棋子都会跟着翻转并变换其颜色。了 解了规则之后,我们可以给出如下的AI,该AI可以翻转最短的次数,来找到目标位置(可以是全黑(All Black)或者是全白(All White))。在“熄灯问题”中,我们说过一种方法叫做BFS+Enum(也就是广度优先搜索配合枚举),这里注意的一点是,我们不能使用STL里面的 容器(包括bitset和queue),因为会产生溢出。这里,我们将数组改变成队列或者栈,在65535个长度以内都不会发生溢出的问题了。
Problem——源自POJ 1753,给出四行四列的16个字符,b代表黑子(black),w代表白子(white),我们的输出需要给出一个整形的数字,来表示最少几次就可以达到我们所需要的目标。如果完全无法达到的话,就输出Impossible。
队列实现:
1 /*
2
3 Highlights:
4
5 (1)为了获得更多的装填量,利用数组模拟队列,而不使用STL中的各种容器
6
7 (2)1<<x表示置x位为黑位,16个格子,正好用short int的短整型数表示16个点
8
9 (3)巧妙运用XOR运算,将每一次变化后的状态存储
10
11 */
12
13
14
15 #include<iostream>
16
17 #include<queue>
18
19 using namespace std;
20
21
22
23 int step[65535]; //利用数组,可以得到比容器更多的装载量(这里表示所有可能的装载量)
24
25 bool flag[65535]; //防止重复搜索
26
27 unsigned short qState[65535]; //搜索的状态,由于65536=2^16,所以可以用每一位的'0'和'1'状态标记是否被搜索
28
29
30
31 int rear=0; //队列尾指针
32
33 int top=0; //队列头指针
34
35
36
37 //初始化,读入棋盘的初始状态并将其转化为16位数存储在头队列中
38
39 //黑为'1'而白为'0'
40
41
42
43 void init()
44
45 {
46
47 unsigned short temp=0;//无字符短整型变量,省空间
48
49 char c;//每次读入一个字符
50
51 for(int i=0;i<4;i++)
52
53 {
54
55 for(int j=0;j<4;j++)
56
57 {
58
59 cin>>c;
60
61 if('b'==c)//注意这样写的妙处!如果写成c=='b'很容易和c='b'混淆
62
63 temp=temp|(1<<(i*4+j));//将黑位置为高位
64
65 }
66
67 }
68
69 qState[rear++]=temp;//载入状态队列
70
71 flag[temp]=true;//标记该位
72
73 }
74
75
76
77 //翻转一个棋子,并按照规则对周围棋子施加影响
78
79 unsigned short move(unsigned short state,int i)
80
81 {
82
83 unsigned short temp=0;
84
85 temp|=(1<<i);
86
87 if((i+1)%4!=0) //不在最右的右
88
89 temp|=(1<<(i+1));
90
91 if(i%4!=0) //不在最左的左
92
93 temp|=(1<<(i-1));
94
95 if(i+4<16) //下
96
97 temp|=(1<<(i+4));
98
99 if(i-4>=0) //上
100
101 temp|=(1<<(i-4));
102
103 return (state^temp);//XOR异或运算
104
105 }
106
107
108
109 //采用BFS将每一次
110
111 bool BFS()
112
113 {
114
115 while(rear>top)//直到队列中无元素
116
117 {
118
119 //这个指令是在模拟qState.pop();
120
121 unsigned short state=qState[top++];
122
123 for(int i=0;i<16;i++)
124
125 {
126
127 unsigned short temp;
128
129 temp=move(state,i);
130
131 if(0==state||65535==state)//上述说过的技巧
132
133 {
134
135 cout<<step[state];
136
137 return true;
138
139 }
140
141 else if(!flag[temp])//防止重复搜索
142
143 {
144
145 //模拟进队列操作qState.push(temp);
146
147 qState[rear++]=temp;
148
149 flag[temp]=true;
150
151 step[temp]=step[state]+1;
152
153 }
154
155 }
156
157 }
158
159 return 0;
160
161 }
162
163
164
165 int main()
166
167 {
168
169 init();
170
171 if(!BFS()) cout<<"Impossible";
172
173 char c;
174
175 cin>>c;
176
177 return 0;
178
179 }
2
3 Highlights:
4
5 (1)为了获得更多的装填量,利用数组模拟队列,而不使用STL中的各种容器
6
7 (2)1<<x表示置x位为黑位,16个格子,正好用short int的短整型数表示16个点
8
9 (3)巧妙运用XOR运算,将每一次变化后的状态存储
10
11 */
12
13
14
15 #include<iostream>
16
17 #include<queue>
18
19 using namespace std;
20
21
22
23 int step[65535]; //利用数组,可以得到比容器更多的装载量(这里表示所有可能的装载量)
24
25 bool flag[65535]; //防止重复搜索
26
27 unsigned short qState[65535]; //搜索的状态,由于65536=2^16,所以可以用每一位的'0'和'1'状态标记是否被搜索
28
29
30
31 int rear=0; //队列尾指针
32
33 int top=0; //队列头指针
34
35
36
37 //初始化,读入棋盘的初始状态并将其转化为16位数存储在头队列中
38
39 //黑为'1'而白为'0'
40
41
42
43 void init()
44
45 {
46
47 unsigned short temp=0;//无字符短整型变量,省空间
48
49 char c;//每次读入一个字符
50
51 for(int i=0;i<4;i++)
52
53 {
54
55 for(int j=0;j<4;j++)
56
57 {
58
59 cin>>c;
60
61 if('b'==c)//注意这样写的妙处!如果写成c=='b'很容易和c='b'混淆
62
63 temp=temp|(1<<(i*4+j));//将黑位置为高位
64
65 }
66
67 }
68
69 qState[rear++]=temp;//载入状态队列
70
71 flag[temp]=true;//标记该位
72
73 }
74
75
76
77 //翻转一个棋子,并按照规则对周围棋子施加影响
78
79 unsigned short move(unsigned short state,int i)
80
81 {
82
83 unsigned short temp=0;
84
85 temp|=(1<<i);
86
87 if((i+1)%4!=0) //不在最右的右
88
89 temp|=(1<<(i+1));
90
91 if(i%4!=0) //不在最左的左
92
93 temp|=(1<<(i-1));
94
95 if(i+4<16) //下
96
97 temp|=(1<<(i+4));
98
99 if(i-4>=0) //上
100
101 temp|=(1<<(i-4));
102
103 return (state^temp);//XOR异或运算
104
105 }
106
107
108
109 //采用BFS将每一次
110
111 bool BFS()
112
113 {
114
115 while(rear>top)//直到队列中无元素
116
117 {
118
119 //这个指令是在模拟qState.pop();
120
121 unsigned short state=qState[top++];
122
123 for(int i=0;i<16;i++)
124
125 {
126
127 unsigned short temp;
128
129 temp=move(state,i);
130
131 if(0==state||65535==state)//上述说过的技巧
132
133 {
134
135 cout<<step[state];
136
137 return true;
138
139 }
140
141 else if(!flag[temp])//防止重复搜索
142
143 {
144
145 //模拟进队列操作qState.push(temp);
146
147 qState[rear++]=temp;
148
149 flag[temp]=true;
150
151 step[temp]=step[state]+1;
152
153 }
154
155 }
156
157 }
158
159 return 0;
160
161 }
162
163
164
165 int main()
166
167 {
168
169 init();
170
171 if(!BFS()) cout<<"Impossible";
172
173 char c;
174
175 cin>>c;
176
177 return 0;
178
179 }