package game import "strings" import "chessstars/internal/engine" var pieceLetters = map[uint8]string{ engine.PieceKnight: "N", engine.PieceBishop: "B", engine.PieceRook: "R", engine.PieceQueen: "Q", engine.PieceKing: "K", } func squareLabel(sq uint8) string { file := sq % 8 rank := sq / 8 return string(rune('a'+file)) + string(rune('1'+rank)) } func GenerateSAN(entry *Entry) ([]string, error) { entry.Lock() variant, fen, rules := entry.Variant, entry.StartFEN, entry.RuleFlags count := entry.Engine.HistoryCount() moves := make([]engine.Move, 0, count) for i := int32(0); i < count; i++ { mv, ok := entry.Engine.GetMoveAt(i) if !ok { break } moves = append(moves, mv) } entry.Unlock() fresh, err := engine.New(variant, fen, rules, "san-scratch") if err != nil { return nil, err } defer fresh.Close() out := make([]string, 0, len(moves)) for _, mv := range moves { san := sanForMove(fresh, mv) fresh.AddMove(mv) if fresh.InCheck() { if fresh.Status() != engine.StatusOngoing { san += "#" } else { san += "+" } } out = append(out, san) } return out, nil } func sanForMove(e *engine.Engine, mv engine.Move) string { board := e.Board() from := mv.From & 0x3F to := mv.To & 0x3F piece := board[from] pieceType := piece & 0x07 color := 0 if piece&engine.PieceColorBit != 0 { color = 1 } target := board[to] isCapture := target != 0 if pieceType == engine.PieceKing { df := int(to%8) - int(from%8) if df == 2 { return "O-O" } if df == -2 { return "O-O-O" } } if pieceType == engine.PiecePawn && (from%8) != (to%8) && target == 0 { isCapture = true } var sb strings.Builder if pieceType == engine.PiecePawn { if isCapture { sb.WriteByte("abcdefgh"[from%8]) } } else { sb.WriteString(pieceLetters[pieceType]) sb.WriteString(disambiguation(e, board, pieceType, color, from, to)) } if isCapture { sb.WriteByte('x') } sb.WriteString(squareLabel(to)) if pieceType == engine.PiecePawn { toRank := to / 8 if toRank == 0 || toRank == 7 { letter, ok := pieceLetters[mv.Promo&0x07] if !ok { letter = "Q" } sb.WriteString("=" + letter) } } return sb.String() } func disambiguation(e *engine.Engine, board [64]byte, pieceType uint8, color int, from, to uint8) string { var others []uint8 for sq := 0; sq < 64; sq++ { if uint8(sq) == from { continue } p := board[sq] if p == 0 || (p&0x07) != pieceType { continue } pc := 0 if p&engine.PieceColorBit != 0 { pc = 1 } if pc != color { continue } if e.IsLegal(engine.Move{From: uint8(sq), To: to, Promo: engine.PieceQueen}) { others = append(others, uint8(sq)) } } if len(others) == 0 { return "" } sameFile, sameRank := false, false for _, o := range others { if o%8 == from%8 { sameFile = true } if o/8 == from/8 { sameRank = true } } if !sameFile { return string("abcdefgh"[from%8]) } if !sameRank { return string(rune('1' + from/8)) } return squareLabel(from) }