Files
2026-08-14 13:17:03 +08:00

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
}