153 lines
3.4 KiB
Go
153 lines
3.4 KiB
Go
// Package xiangqi 实现中国象棋的完整规则:走子合法性、将军/将死判定与极大极小搜索 AI
|
||
package xiangqi
|
||
|
||
// 棋盘约定:10 行 x 9 列,row 0 为黑方底线(上方),row 9 为红方底线(下方)
|
||
// 棋子编码 int8:正数=红方,负数=黑方,绝对值代表兵种:
|
||
// 1=帅/将 2=仕/士 3=相/象 4=马 5=车 6=炮 7=兵/卒
|
||
const (
|
||
King = 1 // 帅/将
|
||
Advisor = 2 // 仕/士
|
||
Bishop = 3 // 相/象
|
||
Knight = 4 // 马
|
||
Rook = 5 // 车
|
||
Cannon = 6 // 炮
|
||
Pawn = 7 // 兵/卒
|
||
)
|
||
|
||
// 红黑双方标识
|
||
const (
|
||
Red = 1 // 红方(下方,先手)
|
||
Black = -1 // 黑方(上方)
|
||
)
|
||
|
||
// Board 棋盘:[行][列],0 表示空位
|
||
type Board [10][9]int8
|
||
|
||
// pieceNames 棋子中文名:[兵种][0]=红名 [1]=黑名
|
||
var pieceNames = map[int][2]string{
|
||
King: {"帅", "将"}, Advisor: {"仕", "士"}, Bishop: {"相", "象"},
|
||
Knight: {"马", "马"}, Rook: {"车", "车"}, Cannon: {"炮", "炮"}, Pawn: {"兵", "卒"},
|
||
}
|
||
|
||
// PieceName 棋子中文名(按红黑区分)
|
||
func PieceName(p int8) string {
|
||
if p == 0 {
|
||
return ""
|
||
}
|
||
kind := int(p)
|
||
idx := 0
|
||
if p < 0 {
|
||
kind = -kind
|
||
idx = 1
|
||
}
|
||
return pieceNames[kind][idx]
|
||
}
|
||
|
||
// Initial 初始棋盘布局(红下黑上)
|
||
func Initial() Board {
|
||
var b Board
|
||
// 黑方(上方,负数)
|
||
back := []int8{Rook, Knight, Bishop, Advisor, King, Advisor, Bishop, Knight, Rook}
|
||
for c, p := range back {
|
||
b[0][c] = -p
|
||
}
|
||
b[2][1], b[2][7] = -Cannon, -Cannon
|
||
for c := 0; c < 9; c += 2 {
|
||
b[3][c] = -Pawn
|
||
}
|
||
// 红方(下方,正数)
|
||
for c, p := range back {
|
||
b[9][c] = p
|
||
}
|
||
b[7][1], b[7][7] = Cannon, Cannon
|
||
for c := 0; c < 9; c += 2 {
|
||
b[6][c] = Pawn
|
||
}
|
||
return b
|
||
}
|
||
|
||
// Move 一步着法:从 (FromR,FromC) 到 (ToR,ToC)
|
||
type Move struct {
|
||
FromR int `json:"from_r"` // 起点行
|
||
FromC int `json:"from_c"` // 起点列
|
||
ToR int `json:"to_r"` // 终点行
|
||
ToC int `json:"to_c"` // 终点列
|
||
}
|
||
|
||
// side 返回棋子所属方(1红 -1黑 0空)
|
||
func side(p int8) int {
|
||
if p > 0 {
|
||
return Red
|
||
}
|
||
if p < 0 {
|
||
return Black
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// Apply 执行着法并返回被吃的棋子(调用方需自行保证合法性)
|
||
func (b *Board) Apply(m Move) int8 {
|
||
captured := b[m.ToR][m.ToC]
|
||
b[m.ToR][m.ToC] = b[m.FromR][m.FromC]
|
||
b[m.FromR][m.FromC] = 0
|
||
return captured
|
||
}
|
||
|
||
// Undo 撤销着法(配合 Apply 的返回值恢复现场,搜索用)
|
||
func (b *Board) Undo(m Move, captured int8) {
|
||
b[m.FromR][m.FromC] = b[m.ToR][m.ToC]
|
||
b[m.ToR][m.ToC] = captured
|
||
}
|
||
|
||
// findKing 找到指定方的帅/将位置
|
||
func (b *Board) findKing(s int) (int, int) {
|
||
for r := 0; r < 10; r++ {
|
||
for c := 3; c <= 5; c++ {
|
||
if int(b[r][c])*s > 0 && abs8(b[r][c]) == King {
|
||
return r, c
|
||
}
|
||
}
|
||
}
|
||
return -1, -1
|
||
}
|
||
|
||
// abs8 int8 绝对值
|
||
func abs8(v int8) int8 {
|
||
if v < 0 {
|
||
return -v
|
||
}
|
||
return v
|
||
}
|
||
|
||
// inBoard 坐标是否在棋盘内
|
||
func inBoard(r, c int) bool { return r >= 0 && r < 10 && c >= 0 && c < 9 }
|
||
|
||
// inPalace 坐标是否在指定方的九宫内
|
||
func inPalace(r, c, s int) bool {
|
||
if c < 3 || c > 5 {
|
||
return false
|
||
}
|
||
if s == Red {
|
||
return r >= 7 && r <= 9
|
||
}
|
||
return r >= 0 && r <= 2
|
||
}
|
||
|
||
// crossedRiver 兵卒是否已过河
|
||
func crossedRiver(r, s int) bool {
|
||
if s == Red {
|
||
return r <= 4
|
||
}
|
||
return r >= 5
|
||
}
|
||
|
||
// MoveDesc 着法的中文描述(如 "红车 (9,0)→(5,0)",用于聊天与记录展示)
|
||
func (b *Board) MoveDesc(m Move) string {
|
||
p := b[m.FromR][m.FromC]
|
||
color := "红"
|
||
if p < 0 {
|
||
color = "黑"
|
||
}
|
||
return color + PieceName(p)
|
||
}
|