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

4.5 棋子数组

在前面产生局面全部走法时,为了计算一方所有棋子的走法,采用了遍历全部棋盘来找出一方棋子的方法。一方棋子最多有16个,却要访问全部90个位置,显然太过浪费。如果这个算法只执行一次也就罢了,但它却要在程序中执行上万次,几十万次,因此有必要对此算法进行修改。

最简单的方法就是设置一个棋子数组,保存双方每一个棋子的位置,如果棋子被吃掉,则其值为0。其数据结构为

          short piece[48]

下标16~31保存红方对应棋子的位置,超标32~47保存黑方对应棋子的位置,下标0~15没有实际意义,这样做的目的是为了棋子本身的值保持一致。

如开局时棋子数组各下标的值为

          short piece[48] = {
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
          199,198,200,197,201,196,202,195,203,164,170,147,149,151,153,155,
          55,54,56,53,57,52,58,51,59,84,90,99,101,103,105,107}

计算一方走法的时候,不用再遍历整个棋盘了,只需要访问棋子数组。如果棋子位置大于0,表示棋子还在棋盘上,求出该棋子所有可能位置;如果棋子位置等于0,说明棋子已经被对方吃掉,不用计算其走法。

算法4-9:生成一个局面的全部走法(棋子数组)

      输入:棋子数组,棋盘数组
      输出:当前局面的所有走法
      1 如果side=0
              pos=16
          否则
              pos=32
      2 循环from pos to pos+15
          棋子位置p = piece[pos]
          如果p==0则
              返回第2步
          否则:
              Switch( pos ):
                      Case帅:调用帅的走法生成函数
                      Case仕:调用仕的走法生成函数
                      Case相:调用相的走法生成函数
                      Case马:调用马的走法生成函数
                      Case车:调用车的走法生成函数
                      Case炮:调用炮的走法生成函数
                      Case兵:调用兵的走法生成函数

程序代码

          /---------------变量声明-----------------------------------------------
          int side;                    // 轮到哪方走,0表示红方,1表示黑方
          unsigned char board[256];   // 棋盘数组
          unsigned char piece[48];    // 棋子数组
          char FenString[128];        // 局面的FEN串格式
          void ClearBoard() //清空棋盘数组
          {
              int i;
              side = 0;
              for (i = 0; i < 256; i ++)
              {
                board[i] = 0;
              }

              for (i = 0; i < 48; i ++)
              {
                piece[i] = 0;
              }

          }

          void AddPiece(int pos, int pc) //增加棋子
          {
              board[pos] = pc;
              piece[pc] = pos;
          }

          //将FEN串表示的局面转换成一维数组
          void StringToArray(const char *FenStr)
          {
              int i, j, k;
              int pcWhite[7]={16,17,19,21,23,25,27};
              int pcBlack[7]={32,33,35,37,39,41,43};
              const char *str;
              ClearBoard();
              str = FenStr;
              if (*str == '\0')
              {
                return;
              }
              i = 3;
              j = 3;
              while (*str != ' ')
              {
                if (*str == '/')
                {
                    j = 3;
                    i ++;
                    if (i > 12)
                    {
                        break;
                    }
                }
                else if (*str >= '1' && *str <= '9')
                {
                    for (k = 0; k < (*str - '0'); k ++)
                    {
                        if (j >= 11)
                        {
                            break;
                        }
                        j ++;
                    }
                }
                else if (*str >= 'A' && *str <= 'Z')
                {
                    if (j <= 11)
                    {
                        k = CharToSubscript(*str);
                        if (k < 7)
                        {
                            if (pcWhite[k] < 32)
                            {
                                AddPiece((i<<4)+j,pcWhite[k]);
                                pcWhite[k]++;
                            }
                        }
                        j ++;
                    }
                }
                else if (*str >= 'a' && *str <= 'z')
                {
                    if (j <= 11)
                    {
                        k = CharToSubscript(*str);
                        if (k < 7)
                        {
                            if (pcBlack[k] < 48)
                            {
                                AddPiece((i<<4)+j,pcBlack[k]);
                                pcBlack[k]++;
                            }
                        }
                        j ++;
                    }
                }
                str ++;
                if (*str == '\0')
                {
                    return;
                }
              }
              str ++;
              if (*str == 'b')
                side = 1;
              else
                side = 0;
          }
          void GenAllMove()//生成一个局面的所有走法
          {
              short i;
              unsigned char p;    //p:棋子位置
              int SideTag;    //走棋方,红方16,黑方32
              int pc; //棋子
              SideTag = 16 + 16 * side;
              for(i=0; i<16; i++)
              {
                pc = SideTag +i;
                p = piece[pc];
                if(p==0)
                    continue;
                switch(i)
                {
                case 0: //帅
                    KingMove(p);
                    break;
                case 1: //仕
                case 2:
                    AdvisorMove(p);
                    break;
                case 3: //相
                case 4:
                    BishopMove(p);
                    break;
                case 5: //马
                case 6:
                    KnightMove(p);
                    break;
                case 7: //车
                case 8:
                    RookMove(p);
                    break;
                case 9: //炮
                case 10:
                    CannonMove(p);
                    break;
                case 11://兵
                case 12:
                case 13:
                case 14:
                case 15:
                    PawnMove(p);
                    break;
                }
              }
          }

代码技巧

棋盘数组是主要的数据结构,棋子数组是一个辅助数组,主要用于产生局面全部走法。棋子数组是一个冗余的数据结构,棋手一旦行棋,不仅要修改棋盘数组,而且要修改棋子数组,保持棋子数组与棋盘数组的一致性。

必须小心维护冗余的数据结构,因为很容易产生数据不一致,就这会导致产生一些莫名其妙的走法。

在象棋程序设计中,常常会使用不至一个冗余数据结构,它们在不同的场合有特殊的用处。必须小心的设计和使用这些结构,保持它们的一致性。

与前面程序相比,在字符串转换成数组的函数中,增加了对棋子数组的处理。清空棋盘数组的时候,要同时清空棋子数组。

参见程序4-3.cpp。