吴昊品游戏核心算法 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个数据的处理分开。

源代码已经编译通过!

 
  1 #include<iostream>
  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==1return 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==1return false;       
100 
101    }    
102 
103    if(nei==1return 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("");               
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("");
328 
329          else if(pe[i].type==1) printf("");
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("");
338 
339          }
340 
341          else printf("");
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("");
376 
377          else if(pe[i].type==1) printf("");
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("");
402 
403        printf("0\n");    
404 
405      }
406 
407    }
408 
409    return 0;   
410 
411  }
412 
413  
414 
415 

posted on 2013-02-27 20:40  吴昊系列  阅读(412)  评论(0编辑  收藏  举报

导航