186 lines
4.6 KiB
Go
186 lines
4.6 KiB
Go
package xiangqi
|
|
|
|
// pseudoMoves 生成指定方的伪合法着法(不考虑送将与对脸,由 LegalMoves 过滤)
|
|
func (b *Board) pseudoMoves(s int) []Move {
|
|
moves := make([]Move, 0, 48)
|
|
// tryAdd 目标格为空或敌子时加入候选
|
|
tryAdd := func(fr, fc, tr, tc int) {
|
|
if !inBoard(tr, tc) {
|
|
return
|
|
}
|
|
if side(b[tr][tc]) == s {
|
|
return
|
|
}
|
|
moves = append(moves, Move{fr, fc, tr, tc})
|
|
}
|
|
for r := 0; r < 10; r++ {
|
|
for c := 0; c < 9; c++ {
|
|
p := b[r][c]
|
|
if side(p) != s {
|
|
continue
|
|
}
|
|
switch abs8(p) {
|
|
case King:
|
|
// 帅/将:九宫内横竖走一步
|
|
for _, d := range [][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
|
|
tr, tc := r+d[0], c+d[1]
|
|
if inPalace(tr, tc, s) {
|
|
tryAdd(r, c, tr, tc)
|
|
}
|
|
}
|
|
case Advisor:
|
|
// 仕/士:九宫内斜走一步
|
|
for _, d := range [][2]int{{1, 1}, {1, -1}, {-1, 1}, {-1, -1}} {
|
|
tr, tc := r+d[0], c+d[1]
|
|
if inPalace(tr, tc, s) {
|
|
tryAdd(r, c, tr, tc)
|
|
}
|
|
}
|
|
case Bishop:
|
|
// 相/象:田字走,塞象眼判定,不能过河
|
|
for _, d := range [][2]int{{2, 2}, {2, -2}, {-2, 2}, {-2, -2}} {
|
|
tr, tc := r+d[0], c+d[1]
|
|
er, ec := r+d[0]/2, c+d[1]/2
|
|
if !inBoard(tr, tc) || b[er][ec] != 0 {
|
|
continue
|
|
}
|
|
// 不能过河:红象只能在 5-9 行,黑象只能在 0-4 行
|
|
if (s == Red && tr < 5) || (s == Black && tr > 4) {
|
|
continue
|
|
}
|
|
tryAdd(r, c, tr, tc)
|
|
}
|
|
case Knight:
|
|
// 马:日字走,蹩马腿判定(马腿在直线相邻位置)
|
|
knightSteps := [][4]int{
|
|
{-2, -1, -1, 0}, {-2, 1, -1, 0}, {2, -1, 1, 0}, {2, 1, 1, 0},
|
|
{-1, -2, 0, -1}, {1, -2, 0, -1}, {-1, 2, 0, 1}, {1, 2, 0, 1},
|
|
}
|
|
for _, st := range knightSteps {
|
|
tr, tc := r+st[0], c+st[1]
|
|
lr, lc := r+st[2], c+st[3]
|
|
if inBoard(tr, tc) && inBoard(lr, lc) && b[lr][lc] == 0 {
|
|
tryAdd(r, c, tr, tc)
|
|
}
|
|
}
|
|
case Rook:
|
|
// 车:直线滑行,遇子停止(敌子可吃)
|
|
for _, d := range [][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
|
|
tr, tc := r+d[0], c+d[1]
|
|
for inBoard(tr, tc) {
|
|
if b[tr][tc] == 0 {
|
|
tryAdd(r, c, tr, tc)
|
|
} else {
|
|
tryAdd(r, c, tr, tc)
|
|
break
|
|
}
|
|
tr += d[0]
|
|
tc += d[1]
|
|
}
|
|
}
|
|
case Cannon:
|
|
// 炮:平移同车(不吃子),吃子需隔一个炮架
|
|
for _, d := range [][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} {
|
|
tr, tc := r+d[0], c+d[1]
|
|
jumped := false
|
|
for inBoard(tr, tc) {
|
|
if !jumped {
|
|
if b[tr][tc] == 0 {
|
|
tryAdd(r, c, tr, tc)
|
|
} else {
|
|
jumped = true // 遇到炮架,之后找吃子目标
|
|
}
|
|
} else {
|
|
if b[tr][tc] != 0 {
|
|
if side(b[tr][tc]) != s {
|
|
tryAdd(r, c, tr, tc)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
tr += d[0]
|
|
tc += d[1]
|
|
}
|
|
}
|
|
case Pawn:
|
|
// 兵/卒:向前一步,过河后可左右
|
|
forward := -1
|
|
if s == Black {
|
|
forward = 1
|
|
}
|
|
tryAdd(r, c, r+forward, c)
|
|
if crossedRiver(r, s) {
|
|
tryAdd(r, c, r, c-1)
|
|
tryAdd(r, c, r, c+1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return moves
|
|
}
|
|
|
|
// kingsFacing 判断双方帅将是否对脸(同列且中间无子,规则禁止)
|
|
func (b *Board) kingsFacing() bool {
|
|
rr, rc := b.findKing(Red)
|
|
br, bc := b.findKing(Black)
|
|
if rr < 0 || br < 0 || rc != bc {
|
|
return false
|
|
}
|
|
for r := br + 1; r < rr; r++ {
|
|
if b[r][rc] != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// InCheck 判断指定方是否被将军(敌方任一伪着法可吃到帅/将)
|
|
func (b *Board) InCheck(s int) bool {
|
|
kr, kc := b.findKing(s)
|
|
if kr < 0 {
|
|
return true // 帅已不在(理论不会发生)视为被将
|
|
}
|
|
for _, m := range b.pseudoMoves(-s) {
|
|
if m.ToR == kr && m.ToC == kc {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// LegalMoves 生成指定方的所有合法着法(过滤走后被将军与帅将对脸)
|
|
func (b *Board) LegalMoves(s int) []Move {
|
|
pseudo := b.pseudoMoves(s)
|
|
legal := make([]Move, 0, len(pseudo))
|
|
for _, m := range pseudo {
|
|
captured := b.Apply(m)
|
|
ok := !b.InCheck(s) && !b.kingsFacing()
|
|
b.Undo(m, captured)
|
|
if ok {
|
|
legal = append(legal, m)
|
|
}
|
|
}
|
|
return legal
|
|
}
|
|
|
|
// IsLegal 校验一步着法是否合法(联机走子校验入口)
|
|
func (b *Board) IsLegal(m Move, s int) bool {
|
|
if !inBoard(m.FromR, m.FromC) || !inBoard(m.ToR, m.ToC) {
|
|
return false
|
|
}
|
|
if side(b[m.FromR][m.FromC]) != s {
|
|
return false
|
|
}
|
|
for _, lm := range b.LegalMoves(s) {
|
|
if lm == m {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GameOver 判断走子方是否已无合法着法(被将死或困毙,即负)
|
|
func (b *Board) GameOver(sideToMove int) bool {
|
|
return len(b.LegalMoves(sideToMove)) == 0
|
|
}
|