
上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。