吴昊品游戏核心算法 Round 8 —— 吴昊教你下国际象棋(复杂模拟)

 

历史】据现有史料记载,国际象棋的发展历史已将近2000年。关于它的起源,曾经有过多种不同的说法,诸如起源于中国、印 度、锡兰、波斯、阿拉伯国家等等。目前世界上多数棋史学家认为国际象棋最早出现在印度。 18世纪时,W.琼斯指出:古印度是国际象棋诞生的摇篮。大约公元 2~4世纪时,印度有一种叫作“恰图兰加”(chaturanga)的棋戏,内有车、马、象、兵 4种棋子,象征着印度古代的军制。在当时流传的印度叙事史诗《摩诃婆罗多》中,有"四军将士已安排"的诗句。四军”就是指军队分为车、象、马、兵 4个兵种。但作为今日国际象棋前身的这种“四方棋”,当时是由掷骰子的方法来进行的。游戏的目的也不是将死对方的王,而是吃掉对方全部棋子。  以梵语“恰 图兰加”命名的“四方棋”在 6世纪时由印度传入波斯,由于语音上的讹误,古波斯人把“恰图兰加”误读为“恰特兰格”(chatrang)。“恰特兰格” 就被阿拉伯人改称为“沙特兰兹” (shatranj),以后这种 “沙特兰兹” 在中亚和阿拉伯国家广泛流传。10世纪前后,阿拉伯国家已经出现了许多闻名一时的棋手。世界名著《一千零一夜》中,就曾提及哈里发何鲁纳·拉施德的宫廷诗 人里有一位著名棋手。公元 819年,在巴格达还举行过几个棋手的比赛。 

国际象棋大约在10世纪以后,经中亚和阿拉伯传到欧洲的各个地区,先传到意大利,然后是西班牙和法国。11世纪末叶,遍及欧洲各国。在当时的文献中,将国际象棋列为骑士教育的 “七艺” 之一。“七艺”是骑术、游泳、射箭、击剑、狩猎、赋诗和下棋。法国的英雄史诗《罗兰之歌》中,也谈及这一点。15、16世纪,国际象棋终于定型成今日的样式和棋制。现存最早的国际象棋谱,出版在1497年。 

国际象棋的着法有过许多次变革。早先,王的走法虽一样,但据说是可以被吃掉的。关于王车易位的特权是从16世纪上半叶才开始有的,这是欧洲人的一项创造发 明。皇后的名称、 性别和威力曾经有过多次奇妙的改变。在沙特拉兹时期,皇后这只棋子表示“律师”、“大臣”或“将军”。后来法国人把皇后一子的形式稍稍变了一下,成了现在 这样的女性。过去,皇后并不是最强的棋子,它只能斜走1格,与中国象棋的士非常类似。双方的皇后只能在不同颜色的格子上斜走,因此不能相遇。皇后具有现在 这样强大的威力,大约是在15世纪中叶以后。兵在过去只能前进1格,第1次走动时也一样,一直到16世纪初,兵的威力才提高到现在这样。兵到达第 8格,过去只能升变为皇后,不能升变成别的棋子。只有车和马的走法一直没有改变。至于国际象棋的棋盘,在13世纪以前是不分黑白格的。 

后汉明帝时(公元 1世纪下半叶),中国与印度开始正式交往,隋唐之后,文化交流更形频繁。中国的象棋与国际象棋着法类似,渊源相近,在过去的发展历史中可能互有影响。直到 今天,这两种象棋的车和马走法相同(只是国际象棋的马不受蹩腿的限制),基本规则也类似。现代国际象棋盘完全一样;河南开封出土的北宋铜质棋子,“士”的图案为身穿戎装的女子,和国际象棋里的皇后性别一样。

 

【规则】基本规定

国际象棋由黑白两棋组成,执白先行,国际象棋的对局目的是把对方的王将死

一方的王受到对方棋子攻击时,称为王被照将,攻击方称为"将军",此时被攻击方必须立即"应将"。如果无法避开将军,王即被将死,攻击方取胜。除"将死"外,还有"超时判负"与"和棋"。

棋盘和棋子

国际象棋棋盘是个正方形,由横纵各8格、颜色一深一浅交错排列的64个小方格组成。深色格称黑格,浅色格称白格,棋子就放在这些格子中移动,右下角是白格。棋子共三十二个,分为黑白两组,各十六个,由对弈双方各执一组,兵种是一样的,分为六种:

中文简称

中文全称

国王

皇后

城堡(战车)

主教(传教士)

骑士

近卫军

英文简称

K

Q

R

B

N

P

英文全称

King

Queen

Rook

Bishop

Knight

Pawn

数量

1

1

2

2

2

8

在比赛中,国际象棋棋子采用立体棋子,非正式的下棋可以采用平面图案的棋子。

布子规则

对于初学者,摆棋时记住:

右下角是白格,白后占白格,黑后占黑格。

走子规则

王(K):横、直、斜都可以走,但每次限走一步。不过,王是不可以送吃的,即任何被敌方控制的格子,己方王都不能走进去。否则,算"送王",犯规。三次就要判负。

(1)除易位时外,王可走到不被对方棋子攻击的任何相邻格子,而且只能走一步。

(2)易位是由王和己方任何一个车一起进行的仍被视作王的一着的走法,参见“特殊规则”(王车易位)。

后(Q):横、直、斜都可以走,步数不受限制,但不能越子。

车(R):横、竖均可以走,步数不受限制,不能斜走。除王车易位外不能越子。

象(B):只能斜走。格数不限,不能越子。开局时每方有两象,一个占白格,一个占黑格。

马(N):每步棋先横走或直走一格,然后再往外斜走一格;或者先斜走一格,最后再往外横走或竖走一格(即走“日”字)。可以越子,没有"中国象棋"的"蹩马腿"限制。

兵(P):只能向前直走,每着只能走一格。但走第一步时,可以走一格或两格。兵的吃子方法与行棋方向不一样,它是直走斜吃,即如果兵的斜进一格内有对方棋子,就可以吃掉它而占据该格。

特殊着法

除了上面所有棋子的一般着法外,国际象棋中存在下面三种特殊着法:

· 吃过路兵:如果对方的兵第一次行棋且直进两格,刚好形成本方有兵与其横向紧贴并列,则本方的兵可以立即斜进,把对方的兵吃掉。这个动作必须立刻进行,缓着后无效。记录时记为 “en passant” 或 “en pt”, 法语中表示 “路过”。

·兵的升变:任何一个兵直进达到对方底线时,即可升变为除"王"和"兵"以外的任何一种棋子,可升变为“后”、“车”、“马”、“象”,不能不变。这被视为一步棋。升变后按新棋子的规则走棋。

·王车易位:每局棋中,双方各有一次机会,让王朝车的方向移动两格,然后车越过王,放在与王紧邻的一格上。王车易位根据左右分为"长易位"和"短易位"。

在下面四种情况下,王车易位不允许:

王或对应的车已经移动过;

王和车之间有其他棋子阻隔;

王正被对方"将军";

王经过或到达的位置受对方棋子"将军"。

胜、负、和

· 国际象棋的对局目的是把对方的王将死。比赛规定:一方的王受到对方棋子攻击时,成为王被照将,攻击方称为“将军”,此时被攻击方必须立即“应将”,如果无法避开将军,王即被将死(长将除外)。除“将死”外,还有“超时判负”与“和局”。出现以下情况,算和局:

1.一方轮走时,提议作和,对方同意.

(注 意:一方提和时,必须在自己走棋的时间内提出和棋,同时走出自己的棋并按钟。任何提和都不可以撤回。对方在自己 的时间内思考是否和棋。同意,则口头声明;不同意,则拒绝或直接走棋。任何人都不能连续提和,即自己的提和被对方拒绝后若没有对方再次提和遭拒绝的话,那 么自己是不可以提和的。)

2.双方都无法将死对方王时,称为material或“死局”.

3.一方连续不断地将对方的王,且对方无力避免,这被称为“长将和”.

4.轮到一方走棋,王没有被将军,但却无路可走,称为stalemake或“逼和”.(吴昊评注:这种规则叫无子可动,这中国象棋不同)

5.对局中同一局面出现三次,而且每次都是同一方走的,并且没有任何可走棋步的差别,判为和局,称为"3 folder"或“三次重复”

6.双方在连续50回合内都没有吃掉对方任何一子,并且未移动一个兵的,判为和局。

 

  本 Round作为《吴昊品核心算法:中国象棋》的姊妹篇而存在,我这里叙述的时候,运用的方法类似,就是将计算机语言通过组织自然而然地翻译为自然语言,或 者,说是以相反的方式翻译。总之,和中国象棋的模拟类似,只是这次的模拟更复杂,毕竟,国际象棋比中国象棋的规则更多,虽然说它们两者的数据结构大同小 已,但由于其规则不同,算法也不一样。作为本例的Source,是来自中山大学第六届程序设计竞赛的第三题。ACM一般的代码行数不会超过150行 (3KB左右,长代码一般见于计算几何),但是,国际象棋的模拟排除在外,其规则的复杂程度决定了算法的复杂程度(注意了,是复杂程度,而不是复杂度)

  作为模拟算法,该Problem也是一个在后台运作的判胜负的程序,根据给出的双方的走子记录判定出当时的胜,平,负三种状态:

  (如下是我自己画的逻辑关系图)

 

  

  #include<stdio.h>

 #include<stdlib.h>

 #include<string.h>

 

 #define ERROR -1 //对于异常情况的处理

 

 enum TResult //当前局面的状态(如果这盘棋下完,就是结局了)

 {

   WHITEWIN=1,//白赢

   BLACKWIN,//黑赢

   STALEMATE,//僵局

   DRAW,//和局

   DEAD,//主要处理棋谱还没有读完,而胜负已分的情况,这样可以忽略后面的棋谱,为计算机减轻了不少负担

   PUZZLE,//无法决定移子,意思是有两种或两种以上走子,而我们无法得知是哪一种的状态

   ILLEGAL//非法状态    

 };

 

 const char RESULT[8][20]=//对于每一种结局,有对应的输出

 //这里应该考虑到,前面将enum枚举的WHITEWIN定义为1,故第0行数组应该置空

 {

   "",

   "White Win",

   "Black Win",

   "Stalemate",

   "Draw",

   "Dead Moves",

   "Puzzle Move",

   "Illegal Move"       

 };

 

 enum TPieceType

 {

   SPACE=0,//空白,如果不放置棋子的话

   PAWN,//兵

   KING,//王

   QUEEN,//后

   ROOK,//车,城堡

   BISHOP,//象,这个在国际象棋中叫爵士

   KNIGHT,//马,骑士    

 }

 

 enum TSide //玩家的表示

 {

   NONE=0,//设立NONE主要是为了考虑到清除该点的棋子的情况,与棋子类型中的SPACE对应

   WHITE,

   BLACK   

 };

 

 typedef struct //封装以上两个enum结构(类型+所属玩家)

 {

   TSide side;

   TPieceType pt;       

 }TPiece;

 

 const int BOARDSIZE=8;//定义棋盘的大小

 

 typedef TPiece TBoard[8][8]; //这里的棋盘仍然以二维数组装载

 

 int n;//棋谱的总步数

 TResult result; //结局

 /*

 *用来进行王车易位的布尔变量,王车易位这一子问题在本问题中是一个难点,因为如果易位的中间有"攻击点"的话,是无法完成易位的

 *

 * whitecastled:白方是否已经王车易位

 * blackcastled:黑方是否已经王车易位

 * white0rookMoved: 白方0号位的车是否已经移动

 * white7rookMoved: 白方7号位的车是否已经移动

 * black0rookMoved: 黑方0号位的车是否已经移动

 * black7rookMoved: 黑方7号位的车是否已经移动

 * whitekingMoved: 白方王是否已经移动

 * blackkingMoved: 黑方王是否已经移动

 *

 */

 bool whitecastled,blackcastled,white0rookMoved,white7rookMoved,black0rookMoved,black7rookMoved,whitekingMoved,blackkingMoved;

 

 TPieceType ChessType(const string& move)

 {

   switch(move[0])

   {

     case 'K'://王

       return KING;

     case 'Q'://后

       return QUEEN;

     case 'R'://车

       return ROOK;

     case 'B'://象

       return BISHOP;

     case 'N'://由于马为KNIGHT,第一个英文字母也为K,所以取移动记录的第一个字符时,为了和王区分开,故取'N'

       return KNIGHT;

   }

   return PAWN;//兵

 }

 

 TSide Opponent(TSide side)//快捷判断函数

 {

   //获取对手类型

   if (side==WHITE)

     return BLACK;

   return WHITE;

 }

 

 void clear(TBoard b,int x,int y)

 {

   //清空棋盘b的(x,y)位置

   b[x][y].side = NONE;//所属玩家

   b[x][y].pt = SPACE;//棋子类型

 }

 

 void init(TBoard b)//初始化棋盘

 {

   int i,j;

   //清空整个棋盘

   for(i=0;i<BOARDSIZE;++i)

   {

     for(j=0;j<BOARDSIZE;++j)//这里的数组不取8而取BOARDSIZE,也是为了增强程序的鲁棒性

     {

       clear(b,i,j);

     }

   }

   //摆放各个棋子

   for(i=0;i<BOARDSIZE;++i)

   {

     //棋盘前两行是白方

     b[0][i].side = WHITE;

     b[1][i].side = WHITE;

     b[1][i].pt = PAWN;//上面第二行是白方的兵

     //棋盘最后两行是黑方

     b[6][i].side = BLACK;

     b[7][i].side = BLACK;

     b[6][i].pt = PAWN;//倒数第二行是黑方的兵

   }

   b[0][0].pt = b[0][7].pt = b[7][0].pt = b[7][7].pt = ROOK;//初始化车的位置

   b[0][1].pt = b[0][6].pt = b[7][1].pt = b[7][6].pt = KNIGHT;//初始化马的位置

   b[0][2].pt = b[0][5].pt = b[7][2].pt = b[7][5].pt = BISHOP;//初始化象的位置

   b[0][3].pt = b[7][3].pt = QUEEN;//初始化后的位置

   b[0][4].pt = b[7][4].pt = KING;//初始化王的位置

   //初始化王车易位使用的布尔变量,当两个易位的棋子都没有移动的时候,易位才是可以发生的,所以说

   //关于王车易位的判断乃是异常复杂

   whitecastled = false;

   blackcastled = false;

   white0rookMoved = false;

   white7rookMoved = false;

   black0rookMoved = false;

   black7rookMoved = false;

   whitekingMoved = false;

   blackkingMoved = false;

 }   

 

 void SkipInput(int k)//表示是从第k步开始进入"忽略阶段"的

 {

   //棋局已经结束,忽略剩余的输入

   int i;

   char mv[20];

   for(i=k;i<n;++i)

   {

     //ANSI C中没有scanf_s(),只有scanf(),scanf()在读取时不检查边界,所以可能会造成内存泄露。

     scanf_s("%s",mv);

   }

 }

 

 void GetPosition(const string& move,int &x,int &y)

 {

   //从输入的移动步骤中获取棋子的目标位置

   int k = 0;

   if(move[0]<'a')//首字母是大写字母,这说明切换到了另外一个棋子,在第二列和第三列寻找(x,y)坐标

     k = 1;

   x = move[k+1]-'1';//行

   y = move[k]-'a';//列

 }

 

 bool OutOfBoard(int x,int y)

 {

   //棋子是否超出棋盘界限,以上共有四种情况可以讨论

   if (x<0||y<0)

   {

     return true;

   }

   if (x>BOARDSIZE||y>BOARDSIZE)

   {

     return true;

   }

   return false;

 }

 

 bool CanMovePawn(TBoard b,int x,int y,int x2,int y2,int flag)

 {

   //判断能否把兵从(x,y)移动到(x2,y2),当flag=1时,表示(x,y)直接移动到(x2,y2),flag为其他表示从(x,y)吃子到(x2,y2)

   if (flag==1)

   {

     //直接移动,即兵直线前进一格

     if (y!=y2||b[x2][y2].side!=NONE)//如果说前面有棋子或者是要斜着走的话

     {

       //y坐标不能改变,无法前进

       return false;

     }

     if (b[x][y].side==WHITE)

     {

       //下棋的是白方

       if (x==1)

       {

         //白方的兵是第一次移动

         return x2==2 || (x2==3&&b[2][y].side==NONE);//第一次移动兵可以移动1格或2格

       }

       else

       {

         return x2==x+1;//不是第一次移动,就只能向前移动1格

       }

     }

     else

     {

       //下棋的是黑方

       if (x==6)

       {

         //黑方的兵是第一次移动

         return x2==5 || (x2==4&&b[5][y].side==NONE);//第一次移动兵可以移动1格或2格

       }

       else

       {

         return x2==x-1;//不是第一次移动,就只能向前移动格

       }

     }

   }

   else

   {

     //吃子判断,吃子时,x向前格,y坐标改变格

     if (b[x][y].side==WHITE)

     {

       //要吃子的是白方

       return (x2==x+1&&abs(y2-y)==1);//这里调用了abs(int x)绝对值函数

     }

     else

     {

       //要吃子的是黑方

       return (x2==x-1&&abs(y2-y)==1);

     }

   }

   return false;

 }

 

 

 bool CanMoveKing(TBoard b,int x,int y,int x2,int y2)

 {

   //判断能否把王从(x,y)移动到(x2,y2)

   return (abs(x-x2)<=1&&abs(y-y2)<=1);

 }

 

 bool CanMoveRook(TBoard b,int x,int y,int x2,int y2)

 {

   //判断能否把车从(x,y)移动到(x2,y2)

   int dx,dy,i,xx,yy;

   //判断移动是否是直线

   if (x!=x2 && y!=y2)

   {

     return false;

   }

   //直线方向增量,这里采用了微积分中的某些思想,一个一个格子地扫描

   if (x2<x)

     dx = -1;

   else

     dx = 1;

   if (y2<y)

     dy = -1;

   else

     dy = 1;

   //x方向上移动

   for(i=1;i<abs(y-y2);++i)

   {

     yy = y+i*dy;

     if (b[x][yy].side!=NONE)

     {

       //中间有棋子阻挡

       return false;

     }

   }

   //y方向上移动

   for (i=1;i<abs(x-x2);++i)

   {

     xx = x+i*dx;

     if (b[xx][y].side!=NONE)

     {

       //中间有棋子阻挡

       return false;

     }

   }

   return true;

 }

 

 bool CanMoveBishop(TBoard b,int x,int y,int x2,int y2)

 {

   //判断能否把象从(x,y)移动到(x2,y2)

   int dx,dy,i,xx,yy;

   //是否斜向移动,如果斜向移动的话,则要判断是否45度或者是135度斜向移动

   if (abs(x-x2)!=abs(y-y2))

   {

     return false;

   }

   //直线方向增量

   if (x2<x)

     dx = -1;

   else

     dx = 1;

   if (y2<y)

     dy = -1;

   else

     dy = 1;

   for (i=1;i<abs(x-x2);++i)

   {

     xx = x+i*dx;

     yy = y+i*dy;

     if (b[xx][yy].side!=NONE)

     {

       //中间有棋子阻挡

       return false;

     }

   }

   return true;

 }

 

 bool CanMoveQueen(TBoard b,int x,int y,int x2,int y2)

 {

   //判断能否把王从(x,y)移动到(x2,y2)

   return CanMoveRook(b,x,y,x2,y2) || CanMoveBishop(b,x,y,x2,y2);//王后等于车+象,从以上可见,利用函数增强了代码的复用程度

 }

 

 bool CanMoveKnight(int x,int y,int x2,int y2)

 {

   //判断马能否从(x,y)移动到(x2,y2)

   int xx,yy;

   xx = abs(x-x2);

   yy = abs(y-y2);

   return (xx+yy==3 && (xx==1 || yy==1));//马行日,x或者y这两者之一移动格,另一方向移动格

 }

 

 bool CanMove(TBoard b,int x,int y,int x2,int y2,int flag)

 {

   //判断一个棋子能否从(x,y)移动到(x2,y2),当flag=1时,直接移动,flag=2时,表示把(x2,y2)处的棋子给吃掉

   //判断是否越界

   if (OutOfBoard(x,y)||OutOfBoard(x2,y2))

   {

     return false;

   }

   //判断原位置是否有棋子

   if (b[x][y].side==NONE)

   {

     return false;

   }

   //根据原来位置上棋子的不同类型判断是否合法

   switch (b[x][y].pt)

   {

     case PAWN://兵

       return CanMovePawn(b,x,y,x2,y2,flag);

     case KING://王

       return CanMoveKing(b,x,y,x2,y2);

     case QUEEN://后

       return CanMoveQueen(b,x,y,x2,y2);

     case ROOK://车

       return CanMoveRook(b,x,y,x2,y2);

     case BISHOP://象

       return CanMoveBishop(b,x,y,x2,y2);

     case KNIGHT://马

       return CanMoveKnight(x,y,x2,y2);

   }

   return false;

 }

 

 void GetSourcePosition(TBoard b,int x2,int y2,int &x,int &y,TPieceType ct,TSide side)

 {

   /*从给出的位置(x2,y2),类型ct和玩家side,求出移动的棋子的原来位置(x,y),

   * 当x=-2时,表示有重复移动方案(Puzzle),x=-1时表示没有移动可能(illegal)

   */

   int i,j,flag = 1;

   if(b[x2][y2].side!=NONE)//目标位置是对手的棋子,则此步为吃子方案

     flag = 2;

   for (i=0;i<BOARDSIZE;++i)

   {

     for (j=0;j<BOARDSIZE;++j)

     {

       if (b[i][j].side==side&&b[i][j].pt==ct)

       {

         //原位置合法并且是同一个子

         if (CanMove(b,i,j,x2,y2,flag))

         {

           if (x==-1)

           {

             //能够移动并且不重复,找到原来棋子的位置

             x = i;

             y = j;

           }

           else

           {

             //能够移动并且有方案,说明有重复

             x = -2;

             return;

           }

         }

       }

     }

   }

 }

 

 //这里标志了国际象棋棋盘的四个"碉堡"的四个车的移动情况

 void MarkRookMove(TSide side,int x,int y)

 {

   if (side==WHITE)

   {

     if (x==0)

     {

       if (y==0)

       {

         white0rookMoved = true;//白方号车已经移动

       }

       if (y==7)

       {

         white7rookMoved = true;//白方号车已经移动

       }

     }

     return;

   }

   if (x==7)

   {

     if (y==0)

     {

       black0rookMoved = true;//黑方号车已经移动

     }

     if (y==7)

     {

       black7rookMoved = true;//黑方号车已经移动

     }

   }

 }

 

 void ChessMove(TBoard b,int x,int y,int x2,int y2)

 {

   //棋子从(x,y)移动到(x2,y2),相当于数组中的元素交换

   b[x2][y2].side = b[x][y].side;

   b[x2][y2].pt = b[x][y].pt;

   clear(b,x,y);//清空原位置

 }

 

 void MakeMove(TBoard b,const string& move,TSide side)

 {

   //根据输入的步骤mv,玩家side移动棋子

   int x,y,x2,y2;

   GetPosition(move,x2,y2);//目标位置

   if(b[x2][y2].side==side)

   {

     //目标位置处已经有我方的棋子了,此步非法

     result = ILLEGAL;

     return;

   }

   x = -1;

   GetSourcePosition(b,x2,y2,x,y,ChessType(move),side);//尝试寻找原位置

   if (x==-1)

   {

     //非法状态

     result = ILLEGAL;

     return;

   }

   else if (x==-2)

   {

     //重复状态

     result = PUZZLE;

     return;

   }

   //移动的棋子是车时,设置王车易位布尔变量,由于车的情况比较难以分析,故在这里再新增一个函数

   if (b[x][y].pt==ROOK)

   {

     MarkRookMove(side,x,y);

   }

   //移动的棋子是王时,设置王车易位布尔变量

   if (b[x][y].pt==KING)

   {

     if (side==WHITE)//白方王移动了

       whitekingMoved = true;

     else//黑方王移动了

       blackkingMoved = true;

   }

   ChessMove(b,x,y,x2,y2);//移动棋子

 }

 

 bool GridBeAttack(TBoard b,int x,int y,TSide byWho)//这里巧妙地运用了一个Opponent函数

 {

   //判断位置(x,y)的棋子能否被吃掉

   int i,j;

   for (i=0;i<BOARDSIZE;++i)

   {

     for (j=0;j<BOARDSIZE;++j)

     {

       if (b[i][j].side==byWho && CanMove(b,i,j,x,y,2))

       {

         //会被对手吃掉的

         return true;

       }

     }

   }

   return false;

 }

 

 bool CanCastle(TBoard b,TSide side,int flag)

 {

   //判断是否能够进行王车易位

   int row,i;

   if (side==WHITE)

   {

     //白方王车易位

     if (whitekingMoved==true)

     {

       //王已经动了,不能王车易位

       return false;

     }

     if (flag==3&&white7rookMoved==true)

     {

       //目标车已经动了,不能王车易位

       return false;

     }

     if (flag==5&&white0rookMoved==true)

     {

       //目标车已经动了,不能王车易位

       return false;

     }

   }

   else

   {

     //黑方王车易位

     if (blackkingMoved==true)

     {

       //王已经动了

       return false;

     }

     if (flag==3 && black7rookMoved==true)

     {

       //目标车已经动了,不能王车易位

       return false;

     }

     if (flag==5 && black0rookMoved==true)

     {

       //目标车已经动了,不能王车易位

       return false;

     }

   }

   if (side==WHITE)

     row = 0;

   else

     row = 7;

   if (flag==5)//易位有两个条件(1)间隙不能有棋子(2)间隙不能受到攻击

   {

     for(i=1;i<4;++i)

     {

       if (b[row][i].side!=NONE)

       {

         //王车之间是否有棋子,若有则不能易位

         return false;

       }

     }

     for (i=0;i<5;++i)

     {

       if (GridBeAttack(b,row,i,Opponent(side))==true)

       {

         //在目标位置上会被对手吃掉,不能王车易位

         return false;

       }

     }

   }

   else

   {

     for (i=5;i<BOARDSIZE-1;++i)

     {

       if (b[row][i].side!=NONE)

       {

         //王车之间是否有棋子,若有则不能易位

         return false;

       }

     }

     for (i=4;i<BOARDSIZE;++i)

     {

       if (GridBeAttack(b,row,i,Opponent(side)))

       {

         //在目标位置上会被对手吃掉,不能王车易位

         return false;

       }

     }

   }

   return true;//检查符合要求,可以王车易位

 }

 

 void Castle(TBoard b,TSide side,int flag)

 {

   //进行王车易位,flag=3,表示靠近王的车King-side castle,flag=5时,表示Queen-side castle,分为短易位和长易位

   int row;

   if (side==WHITE)

   {

     if (whitecastled==true)

     {

       //白方是否已经易位,已经易过位,不能再易了

       result = ILLEGAL;

       return ;

     }

     else

       whitecastled = true;//设置易位变量

   }

   else

   {

     if (blackcastled==true)

     {

       //黑方是否已经易位,已经易过位,不能再易了

       result = ILLEGAL;

       return;

     }

     else

       blackcastled = true;

   }

   if (CanCastle(b,side,flag)==true)

   {

     //判断是否能够易位

     if (side==WHITE)

     {

       row = 0;

     }

     else

     {

       row = 7;

     }

     if (flag==3)//每次易位需要移动两手棋

     {

       //进行王车易位

       ChessMove(b,row,4,row,6);

       ChessMove(b,row,7,row,5);

     }

     else

     {

       ChessMove(b,row,4,row,2);

       ChessMove(b,row,0,row,3);

     }

   }

   else

   {

     //无法王车易位,此步非法

     result = ILLEGAL;

   }

 }

 

 void GetKingPosition(TBoard b,TSide side,int &x,int &y)

 {

   //寻找国王的位置

   int i,j;

   for (i=0;i<BOARDSIZE;++i)

   {

     for (j=0;j<BOARDSIZE;++j)

     {

       if (b[i][j].pt==KING && b[i][j].side == side)

       {

         //找到指定方的王

         x = i;

         y = j;

         return;

       }

     }

   }

 }

 

 bool BeCheck(TBoard b,TSide side)

 {

   //判断是否被"将军"

   int x,y,i,j;

   TSide oppSide;

   GetKingPosition(b,side,x,y);//寻找玩家side的王

   oppSide = Opponent(side);//对手

   for (i=0;i<BOARDSIZE;++i)

   {

     for (j=0;j<BOARDSIZE;++j)

     {

       if (b[i][j].side==oppSide && CanMove(b,i,j,x,y,2)==true)

       {

         //判断对手是否能否将军,flag=2表示此步是吃子,

         return true;

       }

     }

   }

   return false;

 }

 

 void CopyBoard(TBoard desBoard,TBoard srcBoard)

 {

   //复制棋盘,这个属于一种保留函数

   int i,j;

   for (i=0;i<BOARDSIZE;++i)

   {

     for (j=0;j<BOARDSIZE;++j)

     {

       desBoard[i][j].pt = srcBoard[i][j].pt;

       desBoard[i][j].side = srcBoard[i][j].side;

     }

   }

 }

 

 bool NoMoveAvailable(TBoard b,TSide side)

 {

   //判断是否是僵局

   int x,y,x2,y2;

    TBoard b2;

    for (x=0;x<BOARDSIZE;++x)

    {

        for (y=0;y<BOARDSIZE;++y)

        {

            if (b[x][y].side==side)

            {

                for (x2=0;x2<BOARDSIZE;++x2)

                {

                    for (y2=0;y2<BOARDSIZE;++y2)

                    {//判断side方的王能否从(x,y)走到(x2,y2)

                        if ((x2!=x || y2!=y) && b[x][y].side!=b[x2][y2].side && CanMove(b,x,y,x2,y2,1))

                        {

                            if (b[x][y].pt==KING)

                            {

                                CopyBoard(b2,b);

                                ChessMove(b2,x,y,x2,y2);

                                if (!BeCheck(b2,side))

                                {//判断位置是否被”将军“,没有就不是僵局

                                    return false;

                                }

                            }

                            else

                            {

                                return false;

                            }

                        }

                    }

                }

            }

        }

    }

    return true;

}

 

 bool CheckMate(TBoard b,TSide side) //这里需要调用子函数BeCheck来判断

 {

   //判断是否已经结束(即是否某一方被将死)

   int x,y,x2,y2;

   TBoard b2;

   if (!BeCheck(b,side))//"被将军"不意味着被将死

   {

     //没有被将死

     return false;

   }

   for (x=0;x<BOARDSIZE;++x)

   {

     for (y=0;y<BOARDSIZE;++y)

     {

       if (b[x][y].side==side)

       {

         for (x2=0;x2<BOARDSIZE;++x2)

         {

           for (y2=0;y2<BOARDSIZE;++y2)

           {

             if ((x!=x2 || y!=y2) && b[x2][y2].side!=side)

             {

               //王能否从(x,y)移动到(x2,y2)

               if (CanMove(b,x,y,x2,y2,1)==true)

               {

                 CopyBoard(b2,b);

                 ChessMove(b2,x,y,x2,y2);

                 if (!BeCheck(b2,side)==true)

                 {

                   //移动后没有被"将军"

                   return false;

                 }

               }

               if (b[x][y].pt==PAWN && CanMove(b,x,y,x2,y2,2)==true)//移动后可以吃掉,觉得,做掩护的并不一定非要是兵吧,还是我理解错了?

               {

                 CopyBoard(b2,b);

                 ChessMove(b2,x,y,x2,y2);

                 if (!BeCheck(b2,side))

                 {

                   return false;

                 }

               }

             }

           }

         }

       }

     }

   }

   return true;

 }

 

 void run(TBoard b)

 {

   //下棋过程

   string move;//当前步

   TSide side;

   side = WHITE;//起手是白方先走

   int i;

   for(i=0;i<n;++i)

   {

     if(result==WHITEWIN||result==BLACKWIN||result==STALEMATE)

     {

       //棋局已经结束,但还有输入没处理完,状态为DEAD

       result = DEAD;

       SkipInput(i);//忽略其余输入

       return;

     }

     cin>>move;//输入棋子移动步骤

     if(move[0]!='O')

     {

       //不是王车易位

       MakeMove(b,move,side);//根据输入的步骤mv,玩家side移动棋子

     }

     else

     {

       //王车易位

       Castle(b,side,move.length());

     }

     if (BeCheck(b,side)==true)

     {

       //移动棋子后被对手"将军"

       result = ILLEGAL;

     }

     if (result==PUZZLE || result==ILLEGAL)

     {

       //非法输入或不清楚原来的棋子,则跳过数据

       SkipInput(i+1);

       return;

     }

     if (CheckMate(b,Opponent(side)))

     {

       //被”将死“时,判断获胜方

       if (side==WHITE)

       {

         result = WHITEWIN;

       }

       else

       {

         result = BLACKWIN;

       }

     }

     side = Opponent(side);//换到对方下了

   }

   if (result==DRAW && NoMoveAvailable(b,side)==true) //无子可动的状态

   {

     //是否是僵局

     result = STALEMATE;

   }

 }

 

 int main()

 {

   TBoard board;//棋盘

   //输入输出重定向,采用文件的方式

   freopen("chess.in","r",stdin);

   freopen("chess.out","w",stdout);

   while(cin>>n && n!=0)

   {

     init(board);//初始化棋盘

     result = DRAW;//一开始假设结果为和局

     run(board);//下棋过程

     cout<<RESULT[result]<<endl;

   }

   return 0;

 }


 

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

导航