153 lines
3.0 KiB
Go
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)
|
|
}
|