diff --git a/pkg/fen/fen.go b/pkg/fen/fen.go new file mode 100644 index 0000000..5276165 --- /dev/null +++ b/pkg/fen/fen.go @@ -0,0 +1,124 @@ +package fen + +import ( + "fmt" + "strconv" + "strings" + + "code.c-base.org/gochess/libchess/pkg/board" +) + +// DefaultSetup holds the FEN to the default board setup. +const DefaultSetup = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + +// Import returns +func Import(fen string) (*board.Board, error) { + var ( + parts = strings.Split(fen, " ") + pieces = map[board.Square]board.Piece{} + fullMove uint64 + halfMove uint64 + ep *board.Square + cr *board.CastleRights + turn board.Color + position string + expandedFEN string + err error + ) + if len(parts) != 6 { + return nil, fmt.Errorf("invalid fields in FEN: %s", fen) + } + if fullMove, err = strconv.ParseUint(parts[5], 10, 64); err != nil { + return nil, err + } + if halfMove, err = strconv.ParseUint(parts[4], 10, 64); err != nil { + return nil, err + } + if parts[3] != "-" { + if square, ok := board.StrToSquareMap[strings.ToLower(parts[3])]; ok { + ep = &square + } else { + return nil, fmt.Errorf("unable to parse e.p. square: %s", parts[3]) + } + } + if parts[2] != "-" { + cr, err = board.NewCastleRights(parts[2]) + if err != nil { + return nil, err + } + } + switch strings.ToLower(parts[1]) { + case board.White.String(): + turn = board.White + case board.Black.String(): + turn = board.Black + default: + return nil, fmt.Errorf("unable to parse turn: %s", parts[2]) + } + + position = parts[0] + expandedFEN = expandFEN(position) + parts = strings.Split(expandedFEN, "/") + if len(parts) != 8 { + return nil, fmt.Errorf("unable to parse position: %s", position) + } + for r := len(parts) - 1; r >= 0; r-- { + for f := 0; f < 8; f++ { + square := board.NewSquare(board.File(f), board.Rank(7-r)) + piece := board.NewPieceByFEN(rune(parts[r][f])) + if piece != board.NoPiece { + pieces[square] = piece + } + } + } + + b, err := board.NewBoard(pieces, turn, *cr, ep, halfMove, fullMove) + if err != nil { + return nil, err + } + return b, nil +} + +func expandFEN(s string) string { + out := "" + for _, c := range s { + switch c { + case 'K', 'Q', 'R', 'B', 'N', 'P', 'k', 'q', 'r', 'b', 'n', 'p', '/': + out += string(c) + case '1': + out += "1" + case '2': + out += "11" + case '3': + out += "111" + case '4': + out += "1111" + case '5': + out += "11111" + case '6': + out += "111111" + case '7': + out += "1111111" + case '8': + out += "11111111" + } + } + return out +} + +// Export returns the FEN string of the given *board.Board{}. +func Export(b *board.Board) string { + ep := "-" + if b.EnPassent != nil { + ep = b.EnPassent.String() + } + return fmt.Sprintf( + "%s %s %s %s %d %d", + b.String(), + b.Turn.String(), + b.CastleRights.String(), + ep, + b.HalfMove, + b.FullMove, + ) +}