在开始下一节内容之前,我们先来讲解一下重复局面,因为下一节要用到。

重复局面

如果棋局面(同一方走的情况下)重复三次,就可以宣布和棋。如果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

本文为原创文章,转载请注明出处,欢迎访问作者网站(和而不同)

发表评论

error: Content is protected !!