吴昊品游戏核心算法 Round 3(特别篇)—— 吴昊教你玩三国杀(和Round 2一样AI在附加版中呈现)(HDOJ 3378)
在这次Round 3的 特别篇中,我初始地涉及到桌游的算法,和棋牌类的算法不同,桌游往往比较复杂,而且玩家的人数也比棋牌类游戏多,可玩性将会大大增强,模拟起来也越发困 难,尤其是有些玩家的“武将”中除了普通技能之外还会放出一些“必杀技”,在建模和描述的时候更加有挑战性,而三国杀就是这样的一种桌游。
《三国杀》是中国传媒大学动画学院04级游戏专业学生设计,并于2009年6月底《三国杀》被移植至网游平台的一款国内最流行的桌上游戏。在《三国杀》游戏中,玩家将扮演一名三国时期的武将,结合本局身份,合纵连横,经过一轮一轮的谋略和动作获得最终的胜利。
《三国杀》作为一款原创桌上游戏, 有别于其他桌面游戏的最主要特色,就是身份系统,《三国杀》中共有4种身份:主公、反贼、忠臣、内奸。主公和忠臣的任务就是剿灭反贼,清除内奸;反贼的任务则是推翻主公。内奸则要在场上存在除主公以外的其他人物之时先清除其他人物,最后单挑主公。游戏开始时每个玩家随机抽取一张身份牌,抽到主公的玩家,要将自己的身份牌明示。其他人的身份牌不能被其他玩家看到。
《三国杀》游戏采用回合制的出牌顺序,由主公开始依次行动,在自己的回合内,玩家需要完成摸牌、出牌和弃牌的过程。游戏牌共分为三大类:基本牌、锦囊牌和装备牌。每类牌里包含了多种同类型牌。每种牌都有自己的独特用处。
《三国杀》的另一大游戏特色,就是在游戏中加入了武将系统,每名玩家除去自己的身份外,还需要扮演一名三国时期的武将。每名武将都有专属武将技,这些技能从名称到作用都是根据该武将能力、性格而设计。
作为模拟算法,这里我们忽略魔幻卡,生命卡和角色卡,这样,问题会变得更为简单,HDOJ—3378(杭电OJ)就描述过这个问题。其实,如果需要变得更为复杂的话,我们可以直接在HDOJ—3378的基础上进行适当的改进。
作为简化版,我们定义其规则如下:
这里,我们实现一个控制台,其功能是给出条件(比如:A玩家杀死了B玩家),我们可以得到每个玩家的最终得分
获 胜的一些目标:有四种身份:主公(ZG),忠臣(ZC),内奸(NJ)和反贼(FZ)。为了赢得比赛,主公和忠臣的目标是使所有的内奸和反贼都死了,反贼 的目标是使主公死了,内奸的目标是成为最后一位幸存者。如果主公死了,只有一个内奸,那么他就赢了(只有一个内奸的胜利),其他的情况则反贼获胜。
关于源码的注释,我已经给出,作为赏析,我来点出一些亮点:
(1)由于有不同的赢的种类,所以定义一个win来表示赢的种类,然后用case或者种类不多的时候利用一个if—else进行对每个种类的获胜情况进行讨论。
(2)数据结构不完美的地方,设定的node结构体由于只有内奸存在单挑的情况,所以,尽量还是将其孤立出来比较好。
(3)变量的复用,为什么玩家dead时将其值设置为1呢?一开始我也想了好久,直到第三类赢的情况的时候,我终于悟出原因了,一个不错的技巧!
(4)关于输出的,当最后一个输出数据需要附加一个空行的时候,必须与前面的n-1个数据的处理分开。
源代码已经编译通过!
2
3 #include<cstdio>
4
5 #include<cstring>
6
7 using namespace std;
8
9
10
11 int n,m;
12
13
14
15 struct node
16
17 {
18
19 int type;//玩家的类型,是主公,忠诚,内奸还是反贼
20
21 int other;//额外得分
22
23 int dead;//是否死亡
24
25 int tot;//总得分
26
27 int tiao;//如果是内贼,与主公单挑看是否可以赢
28
29 }pe[105]; //最多100个玩家和少于100个的操作"即谁杀死谁"
30
31
32
33 //主公获胜的条件,每一个反贼和内奸都要死亡
34
35 bool zhuwin()
36
37 {
38
39 for(int i=0;i<n;i++)
40
41 {
42
43 if(pe[i].type==2||pe[i].type==3)
44
45 {
46
47 if(pe[i].dead==1) return false;
48
49 }
50
51 }
52
53 return true;
54
55 }
56
57
58
59 //主公死亡,主公只有一个
60
61 bool zhusi()
62
63 {
64
65 for(int i=0;i<n;i++)
66
67 {
68
69 if(pe[i].type==0&&pe[i].dead==0)
70
71 {
72
73 return true;
74
75 }
76
77 }
78
79 return false;
80
81 }
82
83
84
85 //内奸赢了,只有一种情况,就是盘面上只剩下一个内奸
86
87 bool neiwin()
88
89 {
90
91 int nei=0;
92
93 for(int i=0;i<n;i++)
94
95 {
96
97 if(pe[i].type==2&&pe[i].dead==1) nei++;
98
99 if(pe[i].type!=2&&pe[i].dead==1) return false;
100
101 }
102
103 if(nei==1) return true;
104
105 return false;
106
107 }
108
109
110
111 int cntzhong() //对忠臣的数量进行计数
112
113 {
114
115 int cnt=0;
116
117 for(int i=0;i<n;i++)
118
119 {
120
121 if(pe[i].type==1&&pe[i].dead==1) cnt++;
122
123 }
124
125 return cnt;
126
127 }
128
129
130
131 int cntfan() //对反贼的数量进行计数
132
133 {
134
135 int cnt=0;
136
137 for(int i=0;i<n;i++)
138
139 {
140
141 if(pe[i].type==3&&pe[i].dead==1) cnt++;
142
143 }
144
145 return cnt;
146
147 }
148
149
150
151 int main()
152
153 {
154
155 int i,run;//run表示轮次
156
157 char tmp[20];//还是那句话,数据尽量开大先,增强鲁棒性
158
159 scanf("%d",&run);
160
161 while(run--)
162
163 {
164
165 scanf("%d%d",&n,&m);
166
167 for(i=0;i<n;i++)
168
169 {
170
171 scanf("%s",tmp);
172
173 if(tmp[1]=='G') pe[i].type=0;
174
175 else if(tmp[1]=='C') pe[i].type=1;
176
177 else if(tmp[1]=='J') pe[i].type=2;
178
179 else pe[i].type=3;
180
181 pe[i].other=0;
182
183 pe[i].dead=1;//如果已经死了,则标注为0
184
185 pe[i].tot=0;
186
187 pe[i].tiao=0;
188
189 }
190
191 int a,b;//表示两个进行PK的玩家
192
193 int win=0;//赢的种类有不同
194
195 while(m--)
196
197 {
198
199 scanf("%d%d",&a,&b);
200
201 //这里考虑主公和忠臣获胜的额外情况
202
203 if(pe[a].type==0||pe[a].type==1)
204
205 {
206
207 if(pe[b].type==2||pe[b].type==3) pe[a].other++;
208
209 //内奸存在单挑附加分
210
211 if(pe[a].type==0&&pe[b].type==2) pe[b].tiao=n;
212
213 }
214
215 //内奸获胜的额外情况,由于没有所以没有必要考虑
216
217 //反贼获胜的情况,赢主公和忠臣要分开考虑
218
219 if(pe[a].type==3)
220
221 {
222
223 if(pe[b].type==1||pe[b].type==2) pe[a].other++;
224
225 else if(pe[b].type==0) pe[a].other+=2;
226
227 }
228
229 pe[b].dead=0;//死亡,最开始都是活着的
230
231 //反贼获胜的的条件为杀死主公
232
233 if(pe[a].type==3&&pe[b].type==0)
234
235 {
236
237 win=3;
238
239 break;
240
241 }
242
243 if(zhuwin())
244
245 {
246
247 win=1;
248
249 break;
250
251 }
252
253 if(zhusi())
254
255 {
256
257 if(neiwin())
258
259 {
260
261 win=2;
262
263 break;
264
265 }
266
267 else
268
269 {
270
271 win=3;
272
273 break;
274
275 }
276
277 }
278
279 pe[b].tiao=0;//单挑为内奸所属,但是,这里的结构体是不完全正确的
280
281 }
282
283 //对每一种赢的方法进行分别讨论
284
285 //首先是主公和忠臣获胜计分法则
286
287 if(win==1)
288
289 {
290
291 int zhong=cntzhong();
292
293 for(i=0;i<n-1;i++)//这里i<n-1是要将最后一个与前面n-1个分开来
294
295 {
296
297 if(pe[i].type==0) printf("%d ",4+zhong*2+pe[i].other);
298
299 else if(pe[i].type==1) printf("%d ",5+zhong+pe[i].other);
300
301 else if(pe[i].type==2) printf("%d ",pe[i].tiao);
302
303 else printf("0 ");
304
305 }
306
307 if(pe[i].type==0) printf("%d\n",4+zhong*2+pe[i].other);
308
309 else if(pe[i].type==1) printf("%d\n",5+zhong+pe[i].other);
310
311 else if(pe[i].type==2) printf("%d\n",pe[i].tiao);
312
313 else printf("0\n");
314
315 }
316
317 //内奸获胜
318
319 else if(win==2)
320
321 {
322
323 for(i=0; i<n-1; i++)
324
325 {
326
327 if(pe[i].type==0) printf("1 ");
328
329 else if(pe[i].type==1) printf("0 ");
330
331 else if(pe[i].type==2)
332
333 {
334
335 if(pe[i].dead==1) printf("%d ",4+n*2);
336
337 else printf("0 ");
338
339 }
340
341 else printf("0 ");
342
343 }
344
345 if(pe[i].type==0) printf("1\n");
346
347 else if(pe[i].type==1) printf("0\n");
348
349 else if(pe[i].type==2)
350
351 {
352
353 if(pe[i].dead==1) printf("%d\n",4+n*2);
354
355 else printf("0\n");
356
357 }
358
359 else printf("0\n");
360
361 }
362
363 //反贼获胜,在这里我们可以看到为什么死亡设的值是0而不是1了
364
365 else if(win==3)
366
367 {
368
369 int fan=cntfan();
370
371 for(i=0; i<n-1; i++)
372
373 {
374
375 if(pe[i].type==0) printf("0 ");
376
377 else if(pe[i].type==1) printf("0 ");
378
379 else if(pe[i].type==2) printf("%d ",pe[i].dead);
380
381 else printf("%d ",fan*3+pe[i].other);
382
383 }
384
385 if(pe[i].type==0) printf("0\n");
386
387 else if(pe[i].type==1) printf("0\n");
388
389 else if(pe[i].type==2) printf("%d\n",pe[i].dead);
390
391 else printf("%d\n",fan*3+pe[i].other);
392
393 }
394
395 //最后一种情况相当于还没有终局
396
397 else
398
399 {
400
401 for(i=0;i<n-1;i++) printf("0 ");
402
403 printf("0\n");
404
405 }
406
407 }
408
409 return 0;
410
411 }
412
413
414
415