package chess import ( "fmt" "strconv" "strings" ) var pieceJPZ = []Piece{ BR, BN, BS, BM, BK, BS, BN, BR, "", "", "", "", "", "", "", "", BP, BP, BP, BP, BP, BP, BP, BP, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", WP, WP, WP, WP, WP, WP, WP, WP, "", "", "", "", "", "", "", "", WR, WN, WS, WK, WM, WS, WN, WR, } const ( MoveWK = 0 MoveWM = 1 MoveBM = 2 MoveBK = 3 ) // RecordJPZ 走棋记录 type RecordJPZ struct { FromTo [2]*PositionPiece CambodianMove [4]bool } // ChessJPZ 棋盘 type ChessJPZ struct { Variant int Width int // 宽度 Height int // 高度 Count int // 格子数量 Act bool // 是否该白字走,false白棋 Round int // 回合数 Buf []Piece // 棋子 Castling [4]bool // 王车易位可行性 [KQkq] EnPassant int // 过路兵位置 CambodianMove [4]bool // 柬埔寨移动可行性(DEde,D白王,E白仕,d黑仕,e黑王) Records []*RecordJPZ // 走棋记录 Check [2]bool // 将军 Checkmate [2]bool // 将死 } func (c *ChessJPZ) GetChess() []string { ret := make([]string, len(c.Buf)) for k, v := range c.Buf { ret[k] = string(v) } return ret } func (c *ChessJPZ) GetAct() string { if c.Act { return "b" } else { return "w" } } func (c *ChessJPZ) GetCastling() []bool { ret := make([]bool, len(c.Castling)) for k, v := range c.Castling { ret[k] = v } return ret } func (c *ChessJPZ) GetEnPassant() int { return c.EnPassant } func (c *ChessJPZ) GetCambodianMove() []bool { ret := make([]bool, len(c.CambodianMove)) for k, v := range c.CambodianMove { ret[k] = v } return ret } func (c *ChessJPZ) GetRound() int { return c.Round } func (c *ChessJPZ) GetPiece(index int) Piece { return c.Buf[index] } func (c *ChessJPZ) GetCheck() []bool { ret := make([]bool, len(c.Check)) for k, v := range c.Check { ret[k] = v } return ret } func (c *ChessJPZ) GetCheckmate() []bool { ret := make([]bool, len(c.Checkmate)) for k, v := range c.Checkmate { ret[k] = v } return ret } func (c *ChessJPZ) NextAct() { // 判断将军 c.Check[0] = c.IsCheck(true) c.Check[1] = c.IsCheck(false) // 判断将死 c.Checkmate[0] = false c.Checkmate[1] = false if !c.Act && c.IsCheckmate(true) { c.Checkmate[0] = true } if c.Act && c.IsCheckmate(false) { c.Checkmate[1] = true } // 下一个玩家操作,记录回合数 if !c.Act { c.Round++ } c.Act = !c.Act } func (c *ChessJPZ) PrintChess() { f := func(n int) string { str := fmt.Sprintf(" %d", n+1) if c.IsWhiteMove() && n == 0 || c.IsBlackMove() && n == -1 { str = strings.Replace(str, " ", "*", 1) } return str } splitStr := " " for y := c.Height - 1; y >= 0; y-- { fmt.Print(f(y)) for x := 0; x < c.Width; x++ { fmt.Print(splitStr) idx, _ := XYToIndex(c.Width, c.Height, x, y) value := c.Buf[idx] if value == "" { fmt.Print("--") } else { fmt.Print(value) } splitStr = " " } fmt.Println() splitStr = " " } fmt.Print(" ") for x := 0; x < c.Width; x++ { fmt.Print(splitStr) fmt.Print(fmt.Sprintf(" %d", x+1)) splitStr = " " } fmt.Println() } func (c *ChessJPZ) GenFenChessBoard() string { var build strings.Builder splitStr := "" emptyCnt := 0 writeEmptyCount := func() { if emptyCnt > 0 { countStr := fmt.Sprintf("%d", emptyCnt) build.WriteString(countStr) emptyCnt = 0 } } for y := c.Height - 1; y >= 0; y-- { build.WriteString(splitStr) for x := 0; x < c.Width; x++ { idx, _ := XYToIndex(c.Width, c.Height, x, y) value := c.Buf[idx] if value == "" { //build.WriteString("") emptyCnt++ } else { writeEmptyCount() t := value[1:] c := value[:1] if c == "W" { build.WriteString(string(t)) } else if c == "B" { build.WriteString(strings.ToLower(string(t))) } } } writeEmptyCount() splitStr = "/" } return build.String() } func (c *ChessJPZ) GenFenCastling() string { return "-" // 客户端需要支持王车易位走位后才可使用 //var build strings.Builder //if this.castling[0] { // build.WriteString("K") //} //if this.castling[1] { // build.WriteString("Q") //} //if this.castling[2] { // build.WriteString("k") //} //if this.castling[3] { // build.WriteString("q") //} //str := build.String() //if len(str) == 0 { // return "-" //} //return build.String() } func (c *ChessJPZ) GenFenCambodianMove() string { var build strings.Builder if c.CambodianMove[MoveWK] { build.WriteString("D") } if c.CambodianMove[MoveWM] { build.WriteString("E") } if c.CambodianMove[MoveBM] { build.WriteString("d") } if c.CambodianMove[MoveBK] { build.WriteString("e") } str := build.String() if len(str) == 0 { return "-" } return build.String() } func (c *ChessJPZ) GenFenEnPassant() string { return "-" } func (c *ChessJPZ) GenHalfRoundCount() string { return "0" } func (c *ChessJPZ) GenRoundCount() string { return fmt.Sprintf("%d", c.Round) } func (c *ChessJPZ) GenFen() string { var build strings.Builder build.WriteString("fen ") build.WriteString(c.GenFenChessBoard()) build.WriteString(" ") build.WriteString(c.GetAct()) build.WriteString(" ") build.WriteString(c.GenFenCambodianMove()) build.WriteString(" ") build.WriteString(c.GenFenEnPassant()) build.WriteString(" ") build.WriteString(c.GenHalfRoundCount()) build.WriteString(" ") build.WriteString(c.GenRoundCount()) return build.String() } func (c *ChessJPZ) UciTwoPosToIdxList(str string) []int64 { result := []int64{-1, -1} result[0] = c.UciOnePosToIdx(str[0:2]) result[1] = c.UciOnePosToIdx(str[2:4]) // 这里出现过g5f6m这样的返回,暂时不清楚这个m表示的意义,是兵进行吃子造成的 return result } func (c *ChessJPZ) UciOnePosToIdx(str string) int64 { x := int(strings.ToLower(str[:1])[0] - 'a') y, _ := strconv.Atoi(str[1:]) y-- idx := y*c.Width + x //fmt.Println("UciOnePosToIdx", str, x, y, idx) return int64(idx) } func (c *ChessJPZ) GetChessNum(piece Piece) int { n := 0 for _, v := range c.Buf { if piece == v { n++ } } return n } func (c *ChessJPZ) GetWhiteNum() int { n := 0 for _, v := range c.Buf { if IsWhite(v) { n++ } } return n } func (c *ChessJPZ) GetBlackNum() int { n := 0 for _, v := range c.Buf { if IsBlack(v) { n++ } } return n } func (c *ChessJPZ) Has(piece Piece) bool { for _, v := range c.Buf { if v == piece { return true } } return false } func (c *ChessJPZ) GetVariant() int { return c.Variant } func (c *ChessJPZ) SetChess(strings []string) { for k, v := range strings { c.Buf[k] = Piece(v) } } func (c *ChessJPZ) Set(bufStr []string, bufPiece []Piece, act string, castling []bool, cambodianMove []bool, enPassant int, round int) { //var buf2 []Piece //if len(bufStr) > 0 { // buf2 = make([]Piece, len(bufStr)) // for x := 0; x < c.Width; x++ { // for y := 0; y < c.Height; y++ { // buf2[(c.Height-1-y)*c.Width+x] = Piece(bufStr[y*c.Width+x]) // } // } //} else { // buf2 = make([]Piece, len(bufPiece)) // for x := 0; x < c.Width; x++ { // for y := 0; y < c.Height; y++ { // buf2[(c.Height-1-y)*c.Width+x] = bufPiece[y*c.Width+x] // } // } //} if len(bufStr) > 0 { for k, v := range bufStr { c.Buf[k] = Piece(v) } } if len(bufPiece) > 0 { for k, v := range bufPiece { c.Buf[k] = v } } if act == "w" { c.Act = false } else if act == "b" { c.Act = true } if len(castling) == 4 { for k, v := range castling { c.Castling[k] = v } } if len(cambodianMove) == 4 { for k, v := range cambodianMove { c.CambodianMove[k] = v } } c.EnPassant = enPassant c.Round = round } func (c *ChessJPZ) Init() { c.Variant = ChessVarCambodian c.Width = 8 c.Height = 8 c.Count = c.Width * c.Height c.Act = false c.Round = 1 if c.Buf == nil || len(c.Buf) != c.Count { c.Buf = make([]Piece, c.Count) } for x := 0; x < c.Width; x++ { for y := 0; y < c.Height; y++ { c.Buf[(c.Height-1-y)*c.Width+x] = pieceJPZ[y*c.Width+x] } } c.Castling = [4]bool{true, true, true, true} c.EnPassant = -1 c.CambodianMove = [4]bool{true, true, true, true} c.Records = c.Records[:0] c.Check = [2]bool{false, false} c.Checkmate = [2]bool{false, false} } // GetWidth 获取棋盘的宽度 func (c *ChessJPZ) GetWidth() int { return c.Width } // GetHeight 获取棋盘的高度 func (c *ChessJPZ) GetHeight() int { return c.Height } // MoveR 获取某个位置的车能走的下一步位置 func (c *ChessJPZ) MoveR(x, y int) []*PositionPiece { // 坐标转换成索引 index, err := XYToIndex(c.Width, c.Height, x, y) if err != nil { return nil } // 判断这个位置是不是车 o := c.Buf[index] if !IsR(o) { return nil } var validMoves []*PositionPiece // 遍历四个方向 for _, dir := range StraightDirections { dx, dy := dir.X, dir.Y newX, newY := x+dx, y+dy // 检查新位置是否在棋盘范围内 for newX >= 0 && newX < c.Width && newY >= 0 && newY < c.Height { // 获取新位置的索引 index, err := XYToIndex(c.Width, c.Height, newX, newY) if err != nil { break } // 获取新位置的棋子 piece := c.Buf[index] if IsEmpty(piece) { // 记录位置 validMoves = append(validMoves, &PositionPiece{Index: index}) } else { if IsWhite(piece) != IsWhite(o) { validMoves = append(validMoves, &PositionPiece{Index: index, P: piece}) } break } // 新位置是空的,继续向前走 newX += dx newY += dy } } return validMoves } // MoveN 获取某个位置的马能走的下一步位置 func (c *ChessJPZ) MoveN(x, y int) []*PositionPiece { // 坐标转换成索引 index, err := XYToIndex(c.Width, c.Height, x, y) if err != nil { return nil } // 判断这个位置是不是马 o := c.Buf[index] if !IsN(o) { return nil } var validMoves []*PositionPiece for _, dir := range KnightDirections { dx, dy := dir.X, dir.Y newX, newY := x+dx, y+dy // 检查新位置是否在棋盘范围内 if newX >= 0 && newX < c.Width && newY >= 0 && newY < c.Height { // 获取新位置的索引 index, err := XYToIndex(c.Width, c.Height, newX, newY) if err == nil { // 获取新位置的棋子 piece := c.Buf[index] if IsEmpty(piece) || (IsWhite(piece) != IsWhite(o)) { validMoves = append(validMoves, &PositionPiece{Index: index, P: piece}) } } } } return validMoves } // MoveS 获取某个位置的象能走的下一步位置 func (c *ChessJPZ) MoveS(x, y int) []*PositionPiece { // 坐标转换成索引 index, err := XYToIndex(c.Width, c.Height, x, y) if err != nil { return nil } // 判断这个位置是不是象 o := c.Buf[index] if !IsS(o) { return nil } var validMoves []*PositionPiece directions := QueenDirections if IsWhite(o) { directions = append(directions, Position{Y: 1}) } else { directions = append(directions, Position{Y: -1}) } // 遍历象的移动方向 for _, dir := range directions { dx, dy := dir.X, dir.Y newX, newY := x+dx, y+dy // 检查新位置是否在棋盘范围内 if newX >= 0 && newX < c.Width && newY >= 0 && newY < c.Height { // 获取新位置的索引 index, err := XYToIndex(c.Width, c.Height, newX, newY) if err == nil { // 获取新位置的棋子 piece := c.Buf[index] if IsEmpty(piece) || (IsWhite(piece) != IsWhite(o)) { validMoves = append(validMoves, &PositionPiece{Index: index, P: piece}) } } } } return validMoves } // MoveM 获取某个位置的士能走的下一步位置 func (c *ChessJPZ) MoveM(x, y int) []*PositionPiece { // 坐标转换成索引 index, err := XYToIndex(c.Width, c.Height, x, y) if err != nil { return nil } // 判断这个位置是不是士 o := c.Buf[index] if !IsM(o) { return nil } var validMoves []*PositionPiece for _, dir := range QueenDirections { dx, dy := dir.X, dir.Y newX, newY := x+dx, y+dy // 检查新位置是否在棋盘范围内 if newX >= 0 && newX < c.Width && newY >= 0 && newY < c.Height { // 获取新位置的索引 index, err := XYToIndex(c.Width, c.Height, newX, newY) if err == nil { // 获取新位置的棋子 piece := c.Buf[index] if IsEmpty(piece) || (IsWhite(piece) != IsWhite(o)) { validMoves = append(validMoves, &PositionPiece{Index: index, P: piece}) } } } } if IsWhite(o) && c.CambodianMove[MoveWM] && index == 4 { index, err = XYToIndex(c.Width, c.Height, x, y+2) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } } if IsBlack(o) && c.CambodianMove[MoveBM] && index == 59 { index, err = XYToIndex(c.Width, c.Height, x, y-2) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } } return validMoves } // MoveK 获取某个位置的王能走的下一步位置 func (c *ChessJPZ) MoveK(x, y int) []*PositionPiece { // 坐标转换成索引 i, err := XYToIndex(c.Width, c.Height, x, y) if err != nil { return nil } // 判断这个位置是不是王 o := c.Buf[i] if !IsK(o) { return nil } var validMoves []*PositionPiece for _, dir := range KingDirections { dx, dy := dir.X, dir.Y newX, newY := x+dx, y+dy // 检查新位置是否在棋盘范围内 if newX >= 0 && newX < c.Width && newY >= 0 && newY < c.Height { // 获取新位置的索引 index, err := XYToIndex(c.Width, c.Height, newX, newY) if err == nil { // 获取新位置的棋子 piece := c.Buf[index] if IsEmpty(piece) || (IsWhite(piece) != IsWhite(o)) { validMoves = append(validMoves, &PositionPiece{Index: index, P: piece}) } } } } // 没有移动过,不能被将军,不能有棋子 if IsWhite(o) && c.CambodianMove[MoveWK] { if !c.IsCheck(false) { //index, err := XYToIndex(c.Width, c.Height, x-1, y+2) //if err == nil && IsEmpty(c.Buf[index]) { // validMoves = append(validMoves, &PositionPiece{Index: index}) //} //index, err = XYToIndex(c.Width, c.Height, x+1, y+2) //if err == nil && IsEmpty(c.Buf[index]) { // validMoves = append(validMoves, &PositionPiece{Index: index}) //} index, err := XYToIndex(c.Width, c.Height, x-2, y+1) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } index, err = XYToIndex(c.Width, c.Height, x+2, y+1) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } } } // 没有移动过,不能被将军,不能有棋子 if IsBlack(o) && c.CambodianMove[MoveBK] { // 不能被将军,不能有棋子 if !c.IsCheck(true) { //index, err := XYToIndex(c.Width, c.Height, x-1, y-2) //if err == nil && IsEmpty(c.Buf[index]) { // validMoves = append(validMoves, &PositionPiece{Index: index}) //} //index, err = XYToIndex(c.Width, c.Height, x+1, y-2) //if err == nil && IsEmpty(c.Buf[index]) { // validMoves = append(validMoves, &PositionPiece{Index: index}) //} index, err := XYToIndex(c.Width, c.Height, x-2, y-1) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } index, err = XYToIndex(c.Width, c.Height, x+2, y-1) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } } } // 移动之后不能被将军 var moves []*PositionPiece for _, v := range validMoves { c.Move(i, v.Index) if IsWhite(o) { if !c.IsCheck(false) { moves = append(moves, v) } } else { if !c.IsCheck(true) { moves = append(moves, v) } } c.MoveBack() } return moves } // MoveP 获取某个位置的兵能走的下一步位置 func (c *ChessJPZ) MoveP(x, y int) []*PositionPiece { // 坐标转换成索引 index, err := XYToIndex(c.Width, c.Height, x, y) if err != nil { return nil } // 判断这个位置是不是兵 o := c.Buf[index] if !IsP(o) { return nil } var validMoves []*PositionPiece newY := y isWhite := IsWhite(o) if isWhite { newY++ } else { newY-- } // 前进一格 index, err = XYToIndex(c.Width, c.Height, x, newY) if err == nil && IsEmpty(c.Buf[index]) { validMoves = append(validMoves, &PositionPiece{Index: index}) } // 斜吃 index, err = XYToIndex(c.Width, c.Height, x-1, newY) if err == nil && !IsEmpty(c.Buf[index]) && IsWhite(c.Buf[index]) != isWhite { validMoves = append(validMoves, &PositionPiece{Index: index, P: c.Buf[index]}) } index, err = XYToIndex(c.Width, c.Height, x+1, newY) if err == nil && !IsEmpty(c.Buf[index]) && IsWhite(c.Buf[index]) != isWhite { validMoves = append(validMoves, &PositionPiece{Index: index, P: c.Buf[index]}) } return validMoves } func (c *ChessJPZ) movePositions(index int, flag bool) []*PositionPiece { x, y, err := IndexToXY(c.Width, c.Height, index) if err != nil { return nil } o := c.Buf[index] if IsEmpty(o) { return nil } var moves []*PositionPiece switch o { case WR, BR: moves = c.MoveR(x, y) case WN, BN: moves = c.MoveN(x, y) case WS, BS: moves = c.MoveS(x, y) case WM, BM: moves = c.MoveM(x, y) case WK, BK: moves = c.MoveK(x, y) case WP, BP: moves = c.MoveP(x, y) } // 被将军必须应将,走子后不能被将军 if !flag { return moves } var moves2 []*PositionPiece for _, v := range moves { c.Move(index, v.Index) if IsWhite(o) && c.IsCheck(false) || IsBlack(o) && c.IsCheck(true) { c.MoveBack() continue } c.MoveBack() moves2 = append(moves2, v) } return moves2 } // GetMovePositions 获取指定索引位置的棋子能走的位置 func (c *ChessJPZ) GetMovePositions(index int) []*PositionPiece { return c.movePositions(index, true) } func (c *ChessJPZ) IsWhiteMove() bool { return !c.Act } func (c *ChessJPZ) IsBlackMove() bool { return c.Act } // GetIndexes 获取指定棋子的位置 func (c *ChessJPZ) GetIndexes(p Piece) []int { var positions []int for k, v := range c.Buf { if v == p { positions = append(positions, k) } } return positions } // IsCheck 判断将军 func (c *ChessJPZ) IsCheck(isWhite bool) bool { for k, v := range c.Buf { if IsEmpty(v) || IsWhite(v) != isWhite { continue } if !IsK(v) { for _, vv := range c.movePositions(k, false) { if IsK(vv.P) { return true } } } else { // 白王是否吃黑王 x, y, _ := IndexToXY(c.Width, c.Height, k) for _, dir := range KingDirections { dx, dy := dir.X, dir.Y newX, newY := x+dx, y+dy // 检查新位置是否在棋盘范围内 if newX >= 0 && newX < c.Width && newY >= 0 && newY < c.Height { // 获取新位置的索引 index, err := XYToIndex(c.Width, c.Height, newX, newY) if err == nil { if IsK(c.Buf[index]) { return true } } } } } } return false } // IsCheckmate 判断将死 func (c *ChessJPZ) IsCheckmate(isWhite bool) bool { if !c.IsCheck(isWhite) { return false } // 走任何一步,都将军 for k, v := range c.Buf { if !IsEmpty(v) && IsWhite(v) != isWhite { for _, vv := range c.movePositions(k, false) { c.Move(k, vv.Index) if !c.IsCheck(isWhite) { c.MoveBack() return false } c.MoveBack() } } } return true } // CanMove 判断是否可以移动 func (c *ChessJPZ) CanMove(fromIndex, toIndex int) bool { if fromIndex < 0 || fromIndex >= len(c.Buf) || toIndex < 0 || toIndex >= len(c.Buf) { return false } if fromIndex == toIndex { return false } from := c.Buf[fromIndex] if IsEmpty(from) { return false } // 检查操作权 if c.Act == IsWhite(from) { return false } list := c.GetMovePositions(fromIndex) for _, v := range list { if v.Index == toIndex { return true } } return false } // Move 移动棋子 func (c *ChessJPZ) Move(fromIndex, toIndex int) *PositionPiece { if fromIndex < 0 || fromIndex >= len(c.Buf) || toIndex < 0 || toIndex >= len(c.Buf) { return nil } if fromIndex == toIndex { return nil } from := c.Buf[fromIndex] if IsEmpty(from) { return nil } fromPiece := &PositionPiece{ Index: fromIndex, P: c.Buf[fromIndex], } to := &PositionPiece{ Index: toIndex, P: c.Buf[toIndex], } c.Records = append(c.Records, &RecordJPZ{ FromTo: [2]*PositionPiece{fromPiece, to}, CambodianMove: c.CambodianMove, }) // 王,士移动 switch fromPiece.P { case WK: if c.CambodianMove[MoveWK] { c.CambodianMove[MoveWK] = false } case BK: if c.CambodianMove[MoveBK] { c.CambodianMove[MoveBK] = false } case WM: if c.CambodianMove[MoveWM] && fromIndex == 4 { c.CambodianMove[MoveWM] = false } case BM: if c.CambodianMove[MoveBM] && fromIndex == 59 { c.CambodianMove[MoveBM] = false } } // 兵升士 if IsP(fromPiece.P) { _, y, err := IndexToXY(c.Width, c.Height, toIndex) if err == nil { if IsBlack(fromPiece.P) { if y == 2 { c.Buf[fromIndex] = BM } } else { if y == 5 { c.Buf[fromIndex] = WM } } } } c.Buf[toIndex] = c.Buf[fromIndex] c.Buf[fromIndex] = "" return to } // MoveBack 悔棋一步 func (c *ChessJPZ) MoveBack() bool { if len(c.Records) == 0 { return false } record := c.Records[len(c.Records)-1] c.Records = c.Records[:len(c.Records)-1] c.CambodianMove = record.CambodianMove c.Buf[record.FromTo[0].Index] = record.FromTo[0].P c.Buf[record.FromTo[1].Index] = record.FromTo[1].P return true } // IsStop 是否无子可走 func (c *ChessJPZ) IsStop(isWhite bool) bool { for k, v := range c.Buf { if !IsEmpty(v) && IsWhite(v) == isWhite { if len(c.GetMovePositions(k)) > 0 { return false } } } return true } // GetMoveAllPositions 所有可移动棋子及位置 func (c *ChessJPZ) GetMoveAllPositions(isWhite bool) [][2]*PositionPiece { var positions [][2]*PositionPiece for k, v := range c.Buf { if !IsEmpty(v) && IsWhite(v) == isWhite { for _, p := range c.GetMovePositions(k) { positions = append(positions, [2]*PositionPiece{{ Index: k, P: v, }, p}) } } } return positions }