用Go写一个中国象棋(六)| 象棋规则
现在棋子可以移动了,但是并没有遵循象棋规则,接下来的几篇文章,我们将实现象棋规则。
判断步长
打开define.go,增加下面的内容
//MaxGenMoves 最大的生成走法数
const MaxGenMoves = 128
//棋子编号
const (
PieceJiang = 0
PieceShi = 1
PieceXiang = 2
PieceMa = 3
PieceJu = 4
PiecePao = 5
PieceBing = 6
)
// 判断步长是否符合特定走法的数组,1=帅(将),2=仕(士),3=相(象)
var ccLegalSpan = [512]int{
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0}
// 根据步长判断马是否蹩腿的数组
var ccMaPin = [512]int{
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, -16, 0, -16, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 16, 0, 16, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0}
// 帅(将)的步长
var ccJiangDelta = [4]int{-16, -1, 1, 16}
// 仕(士)的步长
var ccShiDelta = [4]int{-17, -15, 15, 17}
// 马的步长,以帅(将)的步长作为马腿
var ccMaDelta = [4][2]int{{-33, -31}, {-18, 14}, {-14, 18}, {31, 33}}
// 马被将军的步长,以仕(士)的步长作为马腿
var ccMaCheckDelta = [4][2]int{{-33, -18}, {-31, -14}, {14, 31}, {18, 33}}
//走法是否符合帅(将)的步长
func jiangSpan(sqSrc, sqDst int) bool {
return ccLegalSpan[sqDst-sqSrc+256] == 1
}
//走法是否符合仕(士)的步长
func shiSpan(sqSrc, sqDst int) bool {
return ccLegalSpan[sqDst-sqSrc+256] == 2
}
//走法是否符合相(象)的步长
func xiangSpan(sqSrc, sqDst int) bool {
return ccLegalSpan[sqDst-sqSrc+256] == 3
}
sqSrc表示走法起点,sqDst表示走法终点
ccLegalSpan这个变量第一眼看上去可能会有点懵,其实只要关注中间的值就可以了:
3, 0, 0, 0, 3 0, 2, 1, 2, 0 0, 1, 0, 1, 0 0, 2, 1, 2, 0 3, 0, 0, 0, 3
1=帅(将):假设正中心的0是将,那它只能上、下、左、右移动一步。
2=仕(士):假设正中心的0是士,那它只能左上、左下、右上、右下移动一步。
3=相(象):假设正中心的0是象,那它只能飞田字。
判断接口
// 判断棋子是否在九宫的数组
var ccInFort = [256]int{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
有了ccInFort数组,我们可以定义出一系列判断函数:
//判断棋子是否在棋盘中
func inBoard(sq int) bool {
return ccInBoard[sq] != 0
}
//判断棋子是否在九宫中
func inFort(sq int) bool {
return ccInFort[sq] != 0
}
//格子水平镜像
func mirrorSquare(sq int) int {
return squareXY(xFlip(getX(sq)), getY(sq))
}
//格子水平镜像
func squareForward(sq, sd int) int {
return sq - 16 + (sd << 5)
}
//相(象)眼的位置
func xiangPin(sqSrc, sqDst int) int {
return (sqSrc + sqDst) >> 1
}
//马腿的位置
func maPin(sqSrc, sqDst int) int {
return sqSrc + ccMaPin[sqDst-sqSrc+256]
}
//是否未过河
func noRiver(sq, sd int) bool {
return (sq & 0x80) != (sd << 7)
}
//是否已过河
func hasRiver(sq, sd int) bool {
return (sq & 0x80) == (sd << 7)
}
//是否在河的同一边
func sameRiver(sqSrc, sqDst int) bool {
return ((sqSrc ^ sqDst) & 0x80) == 0
}
//是否在同一行
func sameX(sqSrc, sqDst int) bool {
return ((sqSrc ^ sqDst) & 0xf0) == 0
}
//是否在同一列
func sameY(sqSrc, sqDst int) bool {
return ((sqSrc ^ sqDst) & 0x0f) == 0
}
//走法水平镜像
func mirrorMove(mv int) int {
return move(mirrorSquare(src(mv)), mirrorSquare(dst(mv)))
}
sqSrc表示走法起点,sqDst表示走法终点
legalMove
有了上面的函数,就可以开始写走法判断的函数,打开rule.go,增加如下代码:
//legalMove 判断走法是否合理
func (p *PositionStruct) legalMove(mv int) bool {
//判断起始格是否有自己的棋子
sqSrc := src(mv)
pcSrc := p.ucpcSquares[sqSrc]
pcSelfSide := sideTag(p.sdPlayer)
if (pcSrc & pcSelfSide) == 0 {
return false
}
//判断目标格是否有自己的棋子
sqDst := dst(mv)
pcDst := p.ucpcSquares[sqDst]
if (pcDst & pcSelfSide) != 0 {
return false
}
//根据棋子的类型检查走法是否合理
tmpPiece := pcSrc - pcSelfSide
switch tmpPiece {
case PieceJiang:
return inFort(sqDst) && jiangSpan(sqSrc, sqDst)
case PieceShi:
return inFort(sqDst) && shiSpan(sqSrc, sqDst)
case PieceXiang:
return sameRiver(sqSrc, sqDst) && xiangSpan(sqSrc, sqDst) &&
p.ucpcSquares[xiangPin(sqSrc, sqDst)] == 0
case PieceMa:
sqPin := maPin(sqSrc, sqDst)
return sqPin != sqSrc && p.ucpcSquares[sqPin] == 0
case PieceJu, PiecePao:
nDelta := 0
if sameX(sqSrc, sqDst) {
if sqDst < sqSrc {
nDelta = -1
} else {
nDelta = 1
}
} else if sameY(sqSrc, sqDst) {
if sqDst < sqSrc {
nDelta = -16
} else {
nDelta = 16
}
} else {
return false
}
sqPin := sqSrc + nDelta
for sqPin != sqDst && p.ucpcSquares[sqPin] == 0 {
sqPin += nDelta
}
if sqPin == sqDst {
return pcDst == 0 || tmpPiece == PieceJu
} else if pcDst != 0 && tmpPiece == PiecePao {
sqPin += nDelta
for sqPin != sqDst && p.ucpcSquares[sqPin] == 0 {
sqPin += nDelta
}
return sqPin == sqDst
} else {
return false
}
case PieceBing:
if hasRiver(sqDst, p.sdPlayer) && (sqDst == sqSrc-1 || sqDst == sqSrc+1) {
return true
}
return sqDst == squareForward(sqSrc, p.sdPlayer)
default:
}
return false
}
象飞田和马走日的情况,需要判断象眼和马腿是否有棋子,如果有就不能走。
车和炮的走法差不多,只要前面没有棋子就可以一直走;惟一不同的是,如果前面碰到了棋子,车要判断这个棋子是不是对方的;而炮要跳过这个棋子,继续往前面走,直到碰到第二个棋子,再判断是不是对方的。
兵的走法与有没有过河联系很大;如果兵没有过河,只能往前面走;如果过河了,除了可以往前面走,还可以往左右走;但兵永远不能往后面走。
在下一节中,我们将学习如何生成棋子的所有走法?
0