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