📄 xqwl04.cpp
字号:
break;
}
sqDst += nDelta;
}
sqDst += nDelta;
while (IN_BOARD(sqDst)) {
pcDst = ucpcSquares[sqDst];
if (pcDst != 0) {
if ((pcDst & pcOppSide) != 0) {
mvs[nGenMoves] = MOVE(sqSrc, sqDst);
nGenMoves ++;
}
break;
}
sqDst += nDelta;
}
}
break;
case PIECE_PAWN:
sqDst = SQUARE_FORWARD(sqSrc, sdPlayer);
if (IN_BOARD(sqDst)) {
pcDst = ucpcSquares[sqDst];
if (bCapture ? (pcDst & pcOppSide) != 0 : (pcDst & pcSelfSide) == 0) {
mvs[nGenMoves] = MOVE(sqSrc, sqDst);
nGenMoves ++;
}
}
if (AWAY_HALF(sqSrc, sdPlayer)) {
for (nDelta = -1; nDelta <= 1; nDelta += 2) {
sqDst = sqSrc + nDelta;
if (IN_BOARD(sqDst)) {
pcDst = ucpcSquares[sqDst];
if (bCapture ? (pcDst & pcOppSide) != 0 : (pcDst & pcSelfSide) == 0) {
mvs[nGenMoves] = MOVE(sqSrc, sqDst);
nGenMoves ++;
}
}
}
}
break;
}
}
return nGenMoves;
}
// 判断走法是否合理
BOOL PositionStruct::LegalMove(int mv) const {
int sqSrc, sqDst, sqPin;
int pcSelfSide, pcSrc, pcDst, nDelta;
// 判断走法是否合法,需要经过以下的判断过程:
// 1. 判断起始格是否有自己的棋子
sqSrc = SRC(mv);
pcSrc = ucpcSquares[sqSrc];
pcSelfSide = SIDE_TAG(sdPlayer);
if ((pcSrc & pcSelfSide) == 0) {
return FALSE;
}
// 2. 判断目标格是否有自己的棋子
sqDst = DST(mv);
pcDst = ucpcSquares[sqDst];
if ((pcDst & pcSelfSide) != 0) {
return FALSE;
}
// 3. 根据棋子的类型检查走法是否合理
switch (pcSrc - pcSelfSide) {
case PIECE_KING:
return IN_FORT(sqDst) && KING_SPAN(sqSrc, sqDst);
case PIECE_ADVISOR:
return IN_FORT(sqDst) && ADVISOR_SPAN(sqSrc, sqDst);
case PIECE_BISHOP:
return SAME_HALF(sqSrc, sqDst) && BISHOP_SPAN(sqSrc, sqDst) &&
ucpcSquares[BISHOP_PIN(sqSrc, sqDst)] == 0;
case PIECE_KNIGHT:
sqPin = KNIGHT_PIN(sqSrc, sqDst);
return sqPin != sqSrc && ucpcSquares[sqPin] == 0;
case PIECE_ROOK:
case PIECE_CANNON:
if (SAME_RANK(sqSrc, sqDst)) {
nDelta = (sqDst < sqSrc ? -1 : 1);
} else if (SAME_FILE(sqSrc, sqDst)) {
nDelta = (sqDst < sqSrc ? -16 : 16);
} else {
return FALSE;
}
sqPin = sqSrc + nDelta;
while (sqPin != sqDst && ucpcSquares[sqPin] == 0) {
sqPin += nDelta;
}
if (sqPin == sqDst) {
return pcDst == 0 || pcSrc - pcSelfSide == PIECE_ROOK;
} else if (pcDst != 0 && pcSrc - pcSelfSide == PIECE_CANNON) {
sqPin += nDelta;
while (sqPin != sqDst && ucpcSquares[sqPin] == 0) {
sqPin += nDelta;
}
return sqPin == sqDst;
} else {
return FALSE;
}
case PIECE_PAWN:
if (AWAY_HALF(sqDst, sdPlayer) && (sqDst == sqSrc - 1 || sqDst == sqSrc + 1)) {
return TRUE;
}
return sqDst == SQUARE_FORWARD(sqSrc, sdPlayer);
default:
return FALSE;
}
}
// 判断是否被将军
BOOL PositionStruct::Checked() const {
int i, j, sqSrc, sqDst;
int pcSelfSide, pcOppSide, pcDst, nDelta;
pcSelfSide = SIDE_TAG(sdPlayer);
pcOppSide = OPP_SIDE_TAG(sdPlayer);
// 找到棋盘上的帅(将),再做以下判断:
for (sqSrc = 0; sqSrc < 256; sqSrc ++) {
if (ucpcSquares[sqSrc] != pcSelfSide + PIECE_KING) {
continue;
}
// 1. 判断是否被对方的兵(卒)将军
if (ucpcSquares[SQUARE_FORWARD(sqSrc, sdPlayer)] == pcOppSide + PIECE_PAWN) {
return TRUE;
}
for (nDelta = -1; nDelta <= 1; nDelta += 2) {
if (ucpcSquares[sqSrc + nDelta] == pcOppSide + PIECE_PAWN) {
return TRUE;
}
}
// 2. 判断是否被对方的马将军(以仕(士)的步长当作马腿)
for (i = 0; i < 4; i ++) {
if (ucpcSquares[sqSrc + ccAdvisorDelta[i]] != 0) {
continue;
}
for (j = 0; j < 2; j ++) {
pcDst = ucpcSquares[sqSrc + ccKnightCheckDelta[i][j]];
if (pcDst == pcOppSide + PIECE_KNIGHT) {
return TRUE;
}
}
}
// 3. 判断是否被对方的车或炮将军(包括将帅对脸)
for (i = 0; i < 4; i ++) {
nDelta = ccKingDelta[i];
sqDst = sqSrc + nDelta;
while (IN_BOARD(sqDst)) {
pcDst = ucpcSquares[sqDst];
if (pcDst != 0) {
if (pcDst == pcOppSide + PIECE_ROOK || pcDst == pcOppSide + PIECE_KING) {
return TRUE;
}
break;
}
sqDst += nDelta;
}
sqDst += nDelta;
while (IN_BOARD(sqDst)) {
int pcDst = ucpcSquares[sqDst];
if (pcDst != 0) {
if (pcDst == pcOppSide + PIECE_CANNON) {
return TRUE;
}
break;
}
sqDst += nDelta;
}
}
return FALSE;
}
return FALSE;
}
// 判断是否被杀
BOOL PositionStruct::IsMate(void) {
int i, nGenMoveNum, pcCaptured;
int mvs[MAX_GEN_MOVES];
nGenMoveNum = GenerateMoves(mvs);
for (i = 0; i < nGenMoveNum; i ++) {
pcCaptured = MovePiece(mvs[i]);
if (!Checked()) {
UndoMovePiece(mvs[i], pcCaptured);
return FALSE;
} else {
UndoMovePiece(mvs[i], pcCaptured);
}
}
return TRUE;
}
// 检测重复局面
int PositionStruct::RepStatus(int nRecur) const {
BOOL bSelfSide, bPerpCheck, bOppPerpCheck;
const MoveStruct *lpmvs;
bSelfSide = FALSE;
bPerpCheck = bOppPerpCheck = TRUE;
lpmvs = mvsList + nMoveNum - 1;
while (lpmvs->wmv != 0 && lpmvs->ucpcCaptured == 0) {
if (bSelfSide) {
bPerpCheck = bPerpCheck && lpmvs->ucbCheck;
if (lpmvs->dwKey == zobr.dwKey) {
nRecur --;
if (nRecur == 0) {
return 1 + (bPerpCheck ? 2 : 0) + (bOppPerpCheck ? 4 : 0);
}
}
} else {
bOppPerpCheck = bOppPerpCheck && lpmvs->ucbCheck;
}
bSelfSide = !bSelfSide;
lpmvs --;
}
return 0;
}
PositionStruct pos; // 局面实例
// 与图形界面有关的全局变量
static struct {
HINSTANCE hInst; // 应用程序句柄实例
HWND hWnd; // 主窗口句柄
HDC hdc, hdcTmp; // 设备句柄,只在"ClickSquare"过程中有效
HBITMAP bmpBoard, bmpSelected, bmpPieces[24]; // 资源图片句柄
int sqSelected, mvLast; // 选中的格子,上一步棋
BOOL bFlipped, bGameOver; // 是否翻转棋盘,是否游戏结束(不让继续玩下去)
} Xqwl;
// 与搜索有关的全局变量
static struct {
int mvResult; // 电脑走的棋
int nHistoryTable[65536]; // 历史表
} Search;
// MVV/LVA每种子力的价值
static BYTE cucMvvLva[24] = {
0, 0, 0, 0, 0, 0, 0, 0,
5, 1, 1, 3, 4, 3, 2, 0,
5, 1, 1, 3, 4, 3, 2, 0
};
// 求MVV/LVA值
inline int MvvLva(int mv) {
return (cucMvvLva[pos.ucpcSquares[DST(mv)]] << 3) - cucMvvLva[pos.ucpcSquares[SRC(mv)]];
}
// "qsort"按MVV/LVA值排序的比较函数
static int CompareMvvLva(const void *lpmv1, const void *lpmv2) {
return MvvLva(*(int *) lpmv2) - MvvLva(*(int *) lpmv1);
}
// "qsort"按历史表排序的比较函数
static int CompareHistory(const void *lpmv1, const void *lpmv2) {
return Search.nHistoryTable[*(int *) lpmv2] - Search.nHistoryTable[*(int *) lpmv1];
}
// 静态(Quiescence)搜索过程
static int SearchQuiesc(int vlAlpha, int vlBeta) {
int i, nGenMoves;
int vl, vlBest;
int mvs[MAX_GEN_MOVES];
// 一个静态搜索分为以下几个阶段
// 1. 检查重复局面
vl = pos.RepStatus();
if (vl != 0) {
return pos.RepValue(vl);
}
// 2. 到达极限深度就返回局面评价
if (pos.nDistance == LIMIT_DEPTH) {
return pos.Evaluate();
}
// 3. 初始化最佳值
vlBest = -MATE_VALUE; // 这样可以知道,是否一个走法都没走过(杀棋)
if (pos.InCheck()) {
// 4. 如果被将军,则生成全部走法
nGenMoves = pos.GenerateMoves(mvs);
qsort(mvs, nGenMoves, sizeof(int), CompareHistory);
} else {
// 5. 如果不被将军,先做局面评价
vl = pos.Evaluate();
if (vl > vlBest) {
vlBest = vl;
if (vl >= vlBeta) {
return vl;
}
if (vl > vlAlpha) {
vlAlpha = vl;
}
}
// 6. 如果局面评价没有截断,再生成吃子走法
nGenMoves = pos.GenerateMoves(mvs, GEN_CAPTURE);
qsort(mvs, nGenMoves, sizeof(int), CompareMvvLva);
}
// 7. 逐一走这些走法,并进行递归
for (i = 0; i < nGenMoves; i ++) {
if (pos.MakeMove(mvs[i])) {
vl = -SearchQuiesc(-vlBeta, -vlAlpha);
pos.UndoMakeMove();
// 8. 进行Alpha-Beta大小判断和截断
if (vl > vlBest) { // 找到最佳值(但不能确定是Alpha、PV还是Beta走法)
vlBest = vl; // "vlBest"就是目前要返回的最佳值,可能超出Alpha-Beta边界
if (vl >= vlBeta) { // 找到一个Beta走法
return vl; // Beta截断
}
if (vl > vlAlpha) { // 找到一个PV走法
vlAlpha = vl; // 缩小Alpha-Beta边界
}
}
}
}
// 9. 所有走法都搜索完了,返回最佳值
return vlBest == -MATE_VALUE ? pos.nDistance - MATE_VALUE : vlBest;
}
// "SearchFull"的参数
const BOOL NO_NULL = TRUE;
// 超出边界(Fail-Soft)的Alpha-Beta搜索过程
static int SearchFull(int vlAlpha, int vlBeta, int nDepth, BOOL bNoNull = FALSE) {
int i, nGenMoves;
int vl, vlBest, mvBest;
int mvs[MAX_GEN_MOVES];
// 一个Alpha-Beta完全搜索分为以下几个阶段
if (pos.nDistance > 0) {
// 1. 到达水平线,则调用静态搜索(注意:由于空步裁剪,深度可能小于零)
if (nDepth <= 0) {
return SearchQuiesc(vlAlpha, vlBeta);
}
// 1-1. 检查重复局面(注意:不要在根节点检查,否则就没有走法了)
vl = pos.RepStatus();
if (vl != 0) {
return pos.RepValue(vl);
}
// 1-2. 到达极限深度就返回局面评价
if (pos.nDistance == LIMIT_DEPTH) {
return pos.Evaluate();
}
// 1-3. 尝试空步裁剪(根节点的Beta值是"MATE_VALUE",所以不可能发生空步裁剪)
if (!bNoNull && !pos.InCheck() && pos.NullOkay()) {
pos.NullMove();
vl = -SearchFull(-vlBeta, 1 - vlBeta, nDepth - NULL_DEPTH - 1, NO_NULL);
pos.UndoNullMove();
if (vl >= vlBeta) {
return vl;
}
}
}
// 2. 初始化最佳值和最佳走法
vlBest = -MATE_VALUE; // 这样可以知道,是否一个走法都没走过(杀棋)
mvBest = 0; // 这样可以知道,是否搜索到了Beta走法或PV走法,以便保存到历史表
// 3. 生成全部走法,并根据历史表排序
nGenMoves = pos.GenerateMoves(mvs);
qsort(mvs, nGenMoves, sizeof(int), CompareHistory);
// 4. 逐一走这些走法,并进行递归
for (i = 0; i < nGenMoves; i ++) {
if (pos.MakeMove(mvs[i])) {
// 将军延伸
vl = -SearchFull(-vlBeta, -vlAlpha, pos.InCheck() ? nDepth : nDepth - 1);
pos.UndoMakeMove();
// 5. 进行Alpha-Beta大小判断和截断
if (vl > vlBest) { // 找到最佳值(但不能确定是Alpha、PV还是Beta走法)
vlBest = vl; // "vlBest"就是目前要返回的最佳值,可能超出Alpha-Beta边界
if (vl >= vlBeta) { // 找到一个Beta走法
mvBest = mvs[i]; // Beta走法要保存到历史表
break; // Beta截断
}
if (vl > vlAlpha) { // 找到一个PV走法
mvBest = mvs[i]; // PV走法要保存到历史表
vlAlpha = vl; // 缩小Alpha-Beta边界
}
}
}
}
// 5. 所有走法都搜索完了,把最佳走法(不能是Alpha走法)保存到历史表,返回最佳值
if (vlBest == -MATE_VALUE) {
// 如果是杀棋,就根据杀棋步数给出评价
return pos.nDistance - MATE_VALUE;
}
if (mvBest != 0) {
// 如果不是Alpha走法,就将最佳走法保存到历史表
Search.nHistoryTable[mvBest] += nDepth * nDepth;
if (pos.nDistance == 0) {
// 搜索根节点时,总是有一个最佳走法(因为全窗口搜索不会超出边界),将这个走法保存下来
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -