C/C++中国象棋程序入门与提高
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

4.6 将军检测

如果你被对手将军,那么你能做的唯一事情就是解除被将军。如果一步棋走后会导致被将军,那么这不是一步好棋,必须避免产生这种状况。如果当前局面所有的走法都会导致被将军,显然你已经被“将死”或者“困毙”。

还有一种情况也是要避免发生的,那就是将帅照面。将与帅在一条纵线上且中间没有其他棋子时,称为将帅照面。最后走棋导致将帅照面的一方,将被判输棋。

如果对手的棋子能够吃掉本方的将,则被将军。士象不具有攻击性,因此只需要检查对手的车马炮卒是否能攻击本方的将。

假设本方将的位置为q。

算法4-10:卒对将的攻击

      输入:走棋方代码lSide(检测lSide一方是否被对手将军),卒的位置p
      输出:是否将军,将军返回1,不将军返回0
          将军标志r=0
          1兵的现在位置p
            lSide一方将的位置q
          2三个可行方向
              3计算下一位置n = p + PawnDir[side][n];
              4 n是否在棋盘上
                  是,转第5步
                  不是,转第2步
              5 如果n等于q,且n是卒的合法位置
                  r=1,并返回r
            否则
                  转第2步
          返回r

算法4-11:马对将的攻击

      输入:走棋方代码lSide(检测lSide一方是否被对手将军),马的位置p
      输出:是否将军,将军返回1,不将军返回0
          将军标志r=0
          1现在位置p
            lSide一方将的位置q
          2八个可行方向
              3计算下一位置n = p +KnightDir[side][n];
              4 n是否在棋盘上
                  是,转第5步
                  不是,转第2步
              5 如果n等于q,且n是马的合法位置
                      转第6步
            否则
                  转第2步
              6如果马腿位置无棋子
                  r=1,并返回r
            否则转第2步
          返回r

算法4-12:车对将的攻击

      输入:走棋方代码,车的位置p
      输出:是否将军,将军返回1,不将军返回0
          将军标志r=0
          1现在位置p
            lSide一方将的位置q
          2如果p与q在同一纵线上
              如果p与q之间无其他棋子
                  r=1,并返回r
          3如果p与q在同一横线上
              如果p与q之间无其他棋子
                  r=1,并返回r
          返回r

算法4-13:炮对将的攻击

      输入:走棋方代码lSide(检测lSide一方是否被对手将军),炮的位置p
      输出:是否将军,将军返回1,不将军返回0
          将军标志r=0
          1现在位置p
            lSide一方将的位置q
          2如果p与q在同一纵线上
              如果p与q之间隔一子
                  r=1,并返回r
              如果p与q之间无棋子或隔两个以上棋子
                  r=0;
          3如果p与q在同一横线上
              如果p与q之间隔一子
                  r=1,并返回r
              如果p与q之间无棋子或隔两个以上棋子
                  r=0;
          返回r

算法4-13:将帅照面检测

            输入:无
            输出:是否直接照面,照面返回1,不照面返回0
            黑方将的位置bKing
            红方帅的位置wKing
            辅助变量p=wKing
            照面标志r=0
            如果将帅在同一列上
            p=p-16
            如果p!=bKing且r=0
            如果p位置上有棋子
            r=1
            p=p-16
            返回r

程序代码

          int Check(int lSide)    //检测lSide一方是否被将军,是被将军返回1,否则返回0
          {
              unsigned char wKing,bKing; //红黑双方将帅的位置
              unsigned char p,q;
              int r;  //r=1表示将军,否则为0
              int SideTag = 32- lSide * 16; //此处表示lSide对方的将的值
              int fSide = 1-lSide;   //对方标志
              int i;
              int PosAdd; //位置增量
              wKing = piece[16];
              bKing = piece[32];
              if(!wKing || !bKing)
                return 0;
              //检测将帅是否照面
              r=1;
              if(wKing%16 == bKing%16)
              {
                for(wKing=wKing-16; wKing!=bKing; wKing=wKing-16)
                {
                    if(board[wKing])
                    {
                        r=0;
                        break;
                    }
                }
                if(r)
                    return r;   //将帅照面
              }
              q = piece[48-SideTag]; //lSide方将的位置
              //检测将是否被马攻击
              int k;
              unsigned char n;//下一步可能行走的位置
              unsigned char m;//马腿位置
              for(i=5;i<=6;i++)
              {
                p = piece[SideTag + i];
                if(!p)
                    continue;
                for(k=0; k<8; k++)//8个方向
                {
                    n = p + KnightDir[k];  //n为新的可能走到的位置
                    if(n!=q)
                        continue;
                    if(LegalPosition[fSide][n] & PositionMask[3])  //马将对应下标为3
                    {
                        m = p + KnightCheck[k];
                        if(!board[m])   //马腿位置无棋子占据
                        {
                            return 1;
                        }
                    }
                }
              }
              //检测将是否被车攻击
              r=1;
              for(i=7;i<=8;i++)
              {
                p = piece[SideTag + i];
                if(!p)
                    continue;
                if(p%16 == q%16)    //在同一纵线上
                {
                    PosAdd = (p>q?-16:16);
                    for(p=p+PosAdd; p!=q; p = p+PosAdd)
                    {
                        if(board[p])    //车将中间有子隔着
                        {
                            r=0;
                            break;
                        }
                    }
                    if(r)
                        return r;
                }
                else if(p/16 ==q/16)   //在同一横线上
                {
                    PosAdd = (p>q?-1:1);
                    for(p=p+PosAdd; p!=q; p = p+PosAdd)
                    {
                        if(board[p])
                        {
                            r=0;
                            break;
                        }
                    }
                    if(r)
                        return r;
                }
              }
              //检测将是否被炮攻击
              int OverFlag = 0;   //翻山标志
              for(i=9;i<=10;i++)
              {
                p = piece[SideTag + i];
                if(!p)
                    continue;
                if(p%16 == q%16)    //在同一纵线上
                {
                    PosAdd = (p>q?-16:16);
                    for(p=p+PosAdd; p!=q; p = p+PosAdd)
                    {
                        if(board[p])
                        {
                            if(!OverFlag)   //隔一子
                                OverFlag = 1;
                            else            //隔两子
                            {
                                OverFlag = 2;
                                break;
                            }
                        }
                    }
                    if(OverFlag==1)
                        return 1;
                }
                else if(p/16 ==q/16)   //在同一横线上
                {
                    PosAdd = (p>q?-1:1);
                    for(p=p+PosAdd; p!=q; p = p+PosAdd)
                    {
                        if(board[p])
                        {
                            if(!OverFlag)
                                OverFlag = 1;
                            else
                            {
                                OverFlag = 2;
                                break;
                            }
                        }
                    }
                    if(OverFlag==1)
                        return 1;
                }
              }
              //检测将是否被兵攻击
              for(i=11;i<=15;i++)
              {
                p = piece[SideTag + i];
                if(!p)
                    continue;
                for(k=0; k<3; k++)//3个方向
                {
                    n = p + PawnDir[fSide][k]; //n为新的可能走到的位置
                    if((n==q) && (LegalPosition[fSide][n] & PositionMask[6]))
          //兵士将对应下标为6
                    {
                        return 1;
                    }
                }
              }
              return 0;
          }
          void SaveMove(unsigned char from, unsigned char to)//保存走法
          {
              unsigned char p;
              p = board[to];
              piece[board[from]] = to;
              if(p)
                piece[p]=0;
              board[to] = board[from];
              board[from] = 0;
              int r =Check(side);
              board[from] = board[to];
              board[to] = p;
              piece[board[from]] = from;
              if(p)
                piece[p] = to;
              if(!r)
              {
                MoveArray[MoveNum].from = from;
                MoveArray[MoveNum].to = to;
                MoveNum++;
              }
          }

代码技巧

检测将帅照面时,判断将帅是否在同一列,只需将两个位置对16求余就得到它们的列号,if(wKing%16 == bKing%16)。要获得行号,则直接整除。

检测车能否将军时,与检测将帅是否直接照面相同。即车与对方将直接照面,显然车可以直接吃掉对方将,形成将军。

检测炮能否将军时,直接统计将与炮之间的棋子数,如果炮与将之间有一个棋子(不论是哪方的棋子),都形成将军,无棋子或两个以上棋子,则不构成将军。

在保存走法的时候进行将军检测。将军检测之前先要执行该走法,修改棋盘数组和棋子数组。如果走后被将军,则不保存该走法。检测之后要撤销该走法,把棋盘数组和棋子数组再修改回来。

参见程序4-4.cpp。