吴昊品游戏核心算法 Round 4 —— 连连看AI(用BFS+STL实现)(HDOJ 1175)
连连看游戏的历史:
游戏“连连看”顾名思义就是找出相关联的东西,这个连连看在网上基本是用在小游戏中,就是找出相同的两样东西,在一定的规则之内可以做为相关联处理。“连连看”的发展经历了从桌面游戏、在线游戏、社交游戏三个过程。
游 戏“连连看”是源自台湾的桌面小游戏,自从流入大陆以来风靡一时,也吸引众多程序员开发出多种版本的“连连看”。这其中,顾方编写的“阿达连连看”以其精 良的制作广受好评,这也成为顾方“阿达系列软件”的核心产品。并于2004年,取得了国家版权局的计算机软件著作权登记证书。
随着Flash应用的流行,网上出现了多种在线Flash版本“连连看”。如“水晶连连看”、“果蔬连连看”等,流行的“水晶连连看”以华丽界面吸引了一大批的女性玩家。
2008年,随着社交网络的普及和开放平台的兴起,“连连看”被引入了社交网络。“连连看”与个人空间相结合,被快速的传播,成为一款热门的社交游戏,其中以开发者Jonevey在Manyou开放平台上推出的“宠物连连看”最为流行。
规则如下:第 一次使用鼠标点击棋盘中的棋子,该棋子此时为“被选中”,以特殊方式显示;再次以鼠标点击其他棋子,若该棋子与被选中的棋子图案相同,且把第一个棋子到 第二个棋子连起来,中间的直线不超过3根,则消掉这一对棋子,否则第一颗棋子恢复成未被选中状态,而第二颗棋子变成被选中状态。
下面我来品析的这个AI实际上实现的是一个查询功能,属于连连看的一个小“作弊工具”,使用它之后,可以判断当前局面是否还有可以消去的“图形对”(实际上,该小工具还可以搜索到任意的一种这样的“图形对”并提示可以删去,当然,这些都是大同小异了)
我们利用BFS(宽度优先搜索)进行,并且,利用STL里面的队列queue结构来存储每一种结果。(这种或者类似的方法,在推箱子游戏的AI中也是比较常见的)
下面,我来给出HDOJ 1175关于此问题的介绍和一些input和output。
Problem Description——“连连看”相信很多人都玩过。没玩过也没关系,下面我给大家介绍一下游戏规则:在一个棋盘中,放了很多的棋子。如果某两个相同的棋子,可以通过一条线连起来 (这条线不能经过其它棋子),而且线的转折次数不超过两次,那么这两个棋子就可以在棋盘上消去。不好意思,由于我以前没有玩过连连看,咨询了同学的意见, 连线不能从外面绕过去的,但事实上这是错的。现在已经酿成大祸,就只能将错就错了,连线不能从外围绕过(吴昊评注:这个也算是连连看的一个变种吧!)。
玩家鼠标先后点击两块棋子,试图将他们消去,然后游戏的后台判断这两个方格能不能消去。现在你的任务就是写这个后台程序(吴昊评注:前台的绚丽靠UI和美工,后台的绚丽靠数据结构和算法)。
Input—— 输入数据有多组。每组数据的第一行有两个正整数n,m(0<n<=1000,0<m<1000),分别表示棋盘的行数与列数。在 接下来的n行中,每行有m个非负整数描述棋盘的方格分布。0表示这个位置没有棋子,正整数表示棋子的类型。接下来的一行是一个正整数 q(0<q<50),表示下面有q次询问。在接下来的q行里,每行有四个正整数x1,y1,x2,y2,表示询问第x1行y1列的棋子与第 x2行y2列的棋子能不能消去。n=0,m=0时,输入结束。注意:询问之间无先后关系,都是针对当前状态的!
Output——每一组输入数据对应一行输出。如果能消去则输出"YES",不能则输出"NO"。
亮点如下:
(1) 上下左右四个方向的标识,采用一个二维数组,这也是一种常用技巧dir[4][2].
(2) 采用STL队列容器,装载每一个可能的点。
(3) 在BFS中,每相邻两个点放在一起判断,定期更新每个点处拐弯次数的最小值,这一点很不错。
2
3 #include<queue> //利用STL中的queue容器
4
5 using namespace std;
6
7
8
9 const int N=1001;//棋盘的尺度上限
10
11 bool flag;//判断最终是成功了还是失败了
12
13 int n,m,sx,sy,ex,ey;//棋盘的行列以及始点和终点
14
15 int hash[N][N],map[N][N];// 描述整个棋盘
16
17 //标示上,下,左,右四个方向
18
19 int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
20
21
22
23 struct node
24
25 {
26
27 int x,y,turn,d;//每个点的(x,y)坐标,转了几次,以及当前的方向
28
29 }start;
30
31
32
33 queue<node> q;//queue容器的实例q装载结构体node
34
35
36
37 //下述函数可以判断是否越界
38
39 inline bool in(const node &p) //内联函数,在需要调用的地方展开
40
41 {
42
43 if(p.x<0||p.y<0||p.x>=n||p.y>=m)
44
45 {
46
47 return false;
48
49 }
50
51 return true;
52
53 }
54
55
56
57 void bfs()
58
59 {
60
61 node now,t;
62
63 while(!q.empty())
64
65 {
66
67 now=q.front();//队首元素
68
69 q.pop();//出队列
70
71 if(now.x==ex&&now.y==ey&&now.turn<=2)//成功条件
72
73 {
74
75 flag=true;
76
77 return;
78
79 }
80
81 for(int i=0;i<4;i++)//可以让结点分别朝着不同方向前进一步
82
83 {
84
85 t.x=now.x+dir[i][0];
86
87 t.y=now.y+dir[i][1];
88
89 if(now.d==i)
90
91 t.turn=now.turn,t.d=now.d;
92
93 else
94
95 t.turn=now.turn+1,t.d=i;
96
97 //点既在区域内又没有点占据或者说到达了重点,并且其拐点次数由于当前那个点上的拐点次数
98
99 //最后一个判断乃是一个优先的原则
100
101 if(in(t)&&(map[t.x][t.y]==0||t.x==ex&&t.y==ey)&&hash[t.x][t.y]>=t.turn)
102
103 {
104
105 hash[t.x][t.y]=t.turn;
106
107 q.push(t);
108
109 }
110
111 }
112
113 }
114
115 }
116
117
118
119 int main()
120
121 {
122
123 int i,j,t;
124
125 while(scanf("%d%d",&n,&m),(n||m))//如果n和m都是0方可退出
126
127 {
128
129 for(i=0;i<n;i++)
130
131 for(j=0;j<m;j++)
132
133 scanf("%d",&map[i][j]);
134
135 scanf("%d",&t);
136
137 while(t--)
138
139 {
140
141 scanf("%d%d%d%d",&sx,&sy,&ex,&ey);//每一个要查询的首尾点
142
143 //这里有一个处理,将实际的游戏界面和程序中的二维数组匹配
144
145 sx--,sy--,ex--,ey--;
146
147 //以下四种情况,判断是不符合条件的(实际上还有转折超过2的情况)
148
149 if((map[sx][sy]!=map[ex][ey])||map[sx][sy]==0||map[ex][ey]==0||(sx==ex&&sy==ey))
150
151 {
152
153 puts("NO");
154
155 continue;
156
157 }
158
159 for(i=0;i<n;i++)
160
161 for(j=0;j<m;j++)
162
163 hash[i][j]=11;
164
165 for(i=0;i<4;i++)
166
167 {
168
169 start.x=sx,start.y=sy,start.turn=0,start.d=i;
170
171 q.push(start);
172
173 }
174
175 flag=false,hash[sx][sy]=0;
176
177 bfs();//搜索
178
179 puts(flag ? "YES":"NO");
180
181 }
182
183 }
184
185 return 0;
186
187 }
188
189