用Go写一个中国象棋(十二)| 象棋AI进阶
在开始下一节内容之前,我们先来讲解一下重复局面,因为下一节要用到。
重复局面
如果棋局面(同一方走的情况下)重复三次,就可以宣布和棋。如果AI领先一个车但是它陷入长将,那将是非常糟糕的,对手会在你即将取得胜利的时候宣布和棋。
要解决这个问题,就必须检测以前出现过的局面,并采取对策。如果AI懂得重复,它就可以根据盘面上局势的需要,来谋求重复或避免重复。如果AI即将输棋,那么它应该试图寻找长将,反之应该避免。
如何检查
检查重复局面的办法很简单,每走一个走法就把当前局面的校验码记录下来,再看看前几个局面的校验码是否与当前值相等。
当重复局面发生时,就要根据双方的将军情况来判定胜负——单方面长将者判负(返回杀棋分数而不必要继续搜索了),双长将或双方都存在非将走法则判和(返回和棋分数)。
//repValue 重复局面分值
func (p *PositionStruct) repValue(nRepStatus int) int {
vlReturn := 0
if nRepStatus&2 != 0 {
vlReturn += p.nDistance - MateValue
}
if nRepStatus&4 != 0 {
vlReturn += MateValue - p.nDistance
}
if vlReturn == 0 {
return -DrawValue
}
return vlReturn
}
//repStatus 检测重复局面
func (p *PositionStruct) repStatus(nRecur int) int {
bSelfSide, bPerpCheck, bOppPerpCheck := false, true, true
lpmvs := [MaxMoves]*MoveStruct{}
for i := 0; i < MaxMoves; i++ {
lpmvs[i] = p.mvsList[i]
}
for i := p.nMoveNum - 1; i >= 0 && lpmvs[i].wmv != 0 && lpmvs[i].ucpcCaptured == 0; i-- {
if bSelfSide {
bPerpCheck = bPerpCheck && lpmvs[i].ucbCheck
if lpmvs[i].dwKey == p.zobr.dwKey {
nRecur--
if nRecur == 0 {
result := 1
if bPerpCheck {
result += 2
}
if bOppPerpCheck {
result += 4
}
return result
}
}
} else {
bOppPerpCheck = bOppPerpCheck && lpmvs[i].ucbCheck
}
bSelfSide = !bSelfSide
}
return 0
}
起初bPerpCheck(本方长将)和bOppPerpCheck(对方长将)都设为true,当一方存在非将走法时就改为false,这样 RepStatus 的返回值有有这几种可能:
1. 返回0,表示没有重复局面。
2. 返回1,表示存在重复局面,但双方都无长将(判和)。
3. 返回3(=1+2),表示存在重复局面,本方单方面长将(判本方负)。
4. 返回5(=1+4),表示存在重复局面,对方单方面长将(判对方负)。
5. 返回7(=1+2+4),表示存在重复局面,双方长将(判和)。
generateMoves
generateMoves修改为根据bCapture生成不同的走法,如果为true只生成吃子走法;如果为false生成所有走法。
//generateMoves 生成所有走法,如果bCapture为true则只生成吃子走法
func (p *PositionStruct) generateMoves(mvs []int, bCapture bool) int {
nGenMoves, pcSrc, sqDst, pcDst, nDelta := 0, 0, 0, 0, 0
pcSelfSide := sideTag(p.sdPlayer)
pcOppSide := oppSideTag(p.sdPlayer)
for sqSrc := 0; sqSrc < 256; sqSrc++ {
if !inBoard(sqSrc) {
continue
}
// 找到一个本方棋子,再做以下判断:
pcSrc = p.ucpcSquares[sqSrc]
if (pcSrc & pcSelfSide) == 0 {
continue
}
// 根据棋子确定走法
switch pcSrc - pcSelfSide {
case PieceJiang:
for i := 0; i < 4; i++ {
sqDst = sqSrc + ccJiangDelta[i]
if !inFort(sqDst) {
continue
}
pcDst = p.ucpcSquares[sqDst]
if (bCapture && (pcDst&pcOppSide) != 0) || (!bCapture && (pcDst&pcSelfSide) == 0) {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
break
case PieceShi:
for i := 0; i < 4; i++ {
sqDst = sqSrc + ccShiDelta[i]
if !inFort(sqDst) {
continue
}
pcDst = p.ucpcSquares[sqDst]
if (bCapture && (pcDst&pcOppSide) != 0) || (!bCapture && (pcDst&pcSelfSide) == 0) {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
break
case PieceXiang:
for i := 0; i < 4; i++ {
sqDst = sqSrc + ccShiDelta[i]
if !(inBoard(sqDst) && noRiver(sqDst, p.sdPlayer) && p.ucpcSquares[sqDst] == 0) {
continue
}
sqDst += ccShiDelta[i]
pcDst = p.ucpcSquares[sqDst]
if (bCapture && (pcDst&pcOppSide) != 0) || (!bCapture && (pcDst&pcSelfSide) == 0) {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
break
case PieceMa:
for i := 0; i < 4; i++ {
sqDst = sqSrc + ccJiangDelta[i]
if p.ucpcSquares[sqDst] != 0 {
continue
}
for j := 0; j < 2; j++ {
sqDst = sqSrc + ccMaDelta[i][j]
if !inBoard(sqDst) {
continue
}
pcDst = p.ucpcSquares[sqDst]
if (bCapture && (pcDst&pcOppSide) != 0) || (!bCapture && (pcDst&pcSelfSide) == 0) {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
}
break
case PieceJu:
for i := 0; i < 4; i++ {
nDelta = ccJiangDelta[i]
sqDst = sqSrc + nDelta
for inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if pcDst == 0 {
if !bCapture {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
} else {
if (pcDst & pcOppSide) != 0 {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
break
}
sqDst += nDelta
}
}
break
case PiecePao:
for i := 0; i < 4; i++ {
nDelta = ccJiangDelta[i]
sqDst = sqSrc + nDelta
for inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if pcDst == 0 {
if !bCapture {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
} else {
break
}
sqDst += nDelta
}
sqDst += nDelta
for inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if pcDst != 0 {
if (pcDst & pcOppSide) != 0 {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
break
}
sqDst += nDelta
}
}
break
case PieceBing:
sqDst = squareForward(sqSrc, p.sdPlayer)
if inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if (bCapture && (pcDst&pcOppSide) != 0) || (!bCapture && (pcDst&pcSelfSide) == 0) {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
if hasRiver(sqSrc, p.sdPlayer) {
for nDelta = -1; nDelta <= 1; nDelta += 2 {
sqDst = sqSrc + nDelta
if inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if (bCapture && (pcDst&pcOppSide) != 0) || (!bCapture && (pcDst&pcSelfSide) == 0) {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
}
}
break
}
}
return nGenMoves
}
PositionStruct方法
//inCheck 是否被将军
func (p *PositionStruct) inCheck() bool {
return p.mvsList[p.nMoveNum-1].ucbCheck
}
//captured 上一步是否吃子
func (p *PositionStruct) captured() bool {
return p.mvsList[p.nMoveNum-1].ucpcCaptured != 0
}
修改isMate中的generateMoves参数:
//isMate 判断是否被将死
func (p *PositionStruct) isMate() bool {
pcCaptured := 0
mvs := make([]int, MaxGenMoves)
nGenMoveNum := p.generateMoves(mvs, false)
for i := 0; i < nGenMoveNum; i++ {
pcCaptured = p.movePiece(mvs[i])
if !p.checked() {
p.undoMovePiece(mvs[i], pcCaptured)
return false
}
p.undoMovePiece(mvs[i], pcCaptured)
}
return true
}
修改makeMove,把走法保存到mvsList里面,而不是返回:
//makeMove 走一步棋
func (p *PositionStruct) makeMove(mv int) bool {
dwKey := p.zobr.dwKey
pcCaptured := p.movePiece(mv)
if p.checked() {
p.undoMovePiece(mv, pcCaptured)
return false
}
p.changeSide()
p.mvsList[p.nMoveNum].set(mv, pcCaptured, p.checked(), dwKey)
p.nMoveNum++
p.nDistance++
return true
}
//undoMakeMove 撤消走一步棋
func (p *PositionStruct) undoMakeMove() {
p.nDistance--
p.nMoveNum--
p.changeSide()
p.undoMovePiece(p.mvsList[p.nMoveNum].wmv, p.mvsList[p.nMoveNum].ucpcCaptured)
}
searchMain
修改searchFull参数:
//searchMain 迭代加深搜索过程
func (p *PositionStruct) searchMain() {
// 清空历史表
for i := 0; i < 65536; i++ {
p.search.nHistoryTable[i] = 0
}
// 初始化定时器
start := time.Now()
// 初始步数
p.nDistance = 0
// 迭代加深过程
vl := 0
rand.Seed(time.Now().UnixNano())
for i := 1; i <= LimitDepth; i++ {
vl = p.searchFull(-MateValue, MateValue, i, false)
// 搜索到杀棋,就终止搜索
if vl > WinValue || vl < -WinValue {
break
}
// 超过一秒,就终止搜索
if time.Now().Sub(start).Milliseconds() > 1000 {
break
}
}
}
在下一节中,我们将学习什么是水平线效应?如何解决水平线效应?
0