Files
ad_fun/chessstars/internal/game/san.go
T
2026-07-15 13:29:08 +03:00

153 lines
3.0 KiB
Go

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)
}