用Go写一个中国象棋(七)| 象棋规则
走法生成器是象棋规则中最重要的部分,它可以解决几乎所有象棋规则的问题。
generateMoves
打开rule.go,增加以下代码:
//generateMoves 生成所有走法
func (p *PositionStruct) generateMoves(mvs []int) 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 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 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 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 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 {
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 {
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 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 pcDst&pcSelfSide == 0 {
mvs[nGenMoves] = move(sqSrc, sqDst)
nGenMoves++
}
}
}
}
break
}
}
return nGenMoves
}
mvs是在外面初始化的,用来保存所有走法。
nGenMoves保存一共有多少走法。
算法的核心思想就是让每个棋子按照自己的走法尽量去走,例如棋子是马,马可以跳8个点,判断这8个点是否符合马的走法;同时对应的马腿位置上是否有棋子。如果符合走法规则就存入mvs中,nGenMoves加1。
checked
象棋中将军是判断胜负的关键。
//checked 判断是否被将军
func (p *PositionStruct) checked() bool {
nDelta, sqDst, pcDst := 0, 0, 0
pcSelfSide := sideTag(p.sdPlayer)
pcOppSide := oppSideTag(p.sdPlayer)
for sqSrc := 0; sqSrc < 256; sqSrc++ {
//找到棋盘上的帅(将),再做以下判断:
if !inBoard(sqSrc) || p.ucpcSquares[sqSrc] != pcSelfSide+PieceJiang {
continue
}
//判断是否被对方的兵(卒)将军
if p.ucpcSquares[squareForward(sqSrc, p.sdPlayer)] == pcOppSide+PieceBing {
return true
}
for nDelta = -1; nDelta <= 1; nDelta += 2 {
if p.ucpcSquares[sqSrc+nDelta] == pcOppSide+PieceBing {
return true
}
}
//判断是否被对方的马将军(以仕(士)的步长当作马腿)
for i := 0; i < 4; i++ {
if p.ucpcSquares[sqSrc+ccShiDelta[i]] != 0 {
continue
}
for j := 0; j < 2; j++ {
pcDst = p.ucpcSquares[sqSrc+ccMaCheckDelta[i][j]]
if pcDst == pcOppSide+PieceMa {
return true
}
}
}
//判断是否被对方的车或炮将军(包括将帅对脸)
for i := 0; i < 4; i++ {
nDelta = ccJiangDelta[i]
sqDst = sqSrc + nDelta
for inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if pcDst != 0 {
if pcDst == pcOppSide+PieceJu || pcDst == pcOppSide+PieceJiang {
return true
}
break
}
sqDst += nDelta
}
sqDst += nDelta
for inBoard(sqDst) {
pcDst = p.ucpcSquares[sqDst]
if pcDst != 0 {
if pcDst == pcOppSide+PiecePao {
return true
}
break
}
sqDst += nDelta
}
}
return false
}
return false
}
判断是否被将军的核心算法是:
假设帅(将)是车,判断它是否能吃到对方的车和将(帅)(中国象棋中有将帅不能对脸的规则)。
假设帅(将)是炮,判断它是否能吃到对方的炮。
假设帅(将)是马,判断它是否能吃到对方的马,需要注意的是,帅(将)的马腿用的数组是 ccShiDelta,而不是 ccJiangDelta。
假设帅(将)是过河的兵(卒),判断它是否能吃到对方的卒(兵)。
undoMovePiece
//undoMovePiece 撤消搬一步棋的棋子
func (p *PositionStruct) undoMovePiece(mv, pcCaptured int) {
sqSrc := src(mv)
sqDst := dst(mv)
pc := p.ucpcSquares[sqDst]
p.delPiece(sqDst)
p.addPiece(sqSrc, pc)
if pcCaptured != 0 {
p.addPiece(sqDst, pcCaptured)
}
}
movePiece的逆操作。
修改Move
//movePiece 搬一步棋的棋子
func (p *PositionStruct) movePiece(mv int) int {
sqSrc := src(mv)
sqDst := dst(mv)
pcCaptured := p.ucpcSquares[sqDst]
p.delPiece(sqDst)
pc := p.ucpcSquares[sqSrc]
p.delPiece(sqSrc)
p.addPiece(sqDst, pc)
return pcCaptured
}
//makeMove 走一步棋
func (p *PositionStruct) makeMove(mv int) bool {
pcCaptured := p.movePiece(mv)
if p.checked() {
p.undoMovePiece(mv, pcCaptured)
return false
}
p.changeSide()
return true
}
isMated
//isMate 判断是否被将死
func (p *PositionStruct) isMate() bool {
pcCaptured := 0
mvs := make([]int, MaxGenMoves)
nGenMoveNum := p.generateMoves(mvs)
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
}
判断所有走法,如果有一个走法没有被将,就表示没有被将死。
在下一节中,我们将学习界面如何去表示胜败?
0