You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
3.5 KiB

package chess
import (
"fmt"
"strconv"
"strings"
)
// Decodes FEN notation into a GameState. An error is returned
// if there is a parsing error. FEN notation format:
// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
func decodeFEN(fen string) (*Position, error) {
fen = strings.TrimSpace(fen)
parts := strings.Split(fen, " ")
if len(parts) != 6 {
return nil, fmt.Errorf("chess: fen invalid notiation %s must have 6 sections", fen)
}
b, err := fenBoard(parts[0])
if err != nil {
return nil, err
}
turn, ok := fenTurnMap[parts[1]]
if !ok {
return nil, fmt.Errorf("chess: fen invalid turn %s", parts[1])
}
rights, err := formCastleRights(parts[2])
if err != nil {
return nil, err
}
sq, err := formEnPassant(parts[3])
if err != nil {
return nil, err
}
halfMoveClock, err := strconv.Atoi(parts[4])
if err != nil || halfMoveClock < 0 {
return nil, fmt.Errorf("chess: fen invalid half move clock %s", parts[4])
}
moveCount, err := strconv.Atoi(parts[5])
if err != nil || moveCount < 1 {
return nil, fmt.Errorf("chess: fen invalid move count %s", parts[5])
}
return &Position{
board: b,
turn: turn,
castleRights: rights,
enPassantSquare: sq,
halfMoveClock: halfMoveClock,
moveCount: moveCount,
}, nil
}
// generates board from fen format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
func fenBoard(boardStr string) (*Board, error) {
rankStrs := strings.Split(boardStr, "/")
if len(rankStrs) != 8 {
return nil, fmt.Errorf("chess: fen invalid board %s", boardStr)
}
m := map[Square]Piece{}
for i, rankStr := range rankStrs {
rank := Rank(7 - i)
fileMap, err := fenFormRank(rankStr)
if err != nil {
return nil, err
}
for file, piece := range fileMap {
m[getSquare(file, rank)] = piece
}
}
return NewBoard(m), nil
}
func fenFormRank(rankStr string) (map[File]Piece, error) {
count := 0
m := map[File]Piece{}
err := fmt.Errorf("chess: fen invalid rank %s", rankStr)
for _, r := range rankStr {
c := fmt.Sprintf("%c", r)
piece := fenPieceMap[c]
if piece == NoPiece {
skip, err := strconv.Atoi(c)
if err != nil {
return nil, err
}
count += skip
continue
}
m[File(count)] = piece
count++
}
if count != 8 {
return nil, err
}
return m, nil
}
func formCastleRights(castleStr string) (CastleRights, error) {
// check for duplicates aka. KKkq right now is valid
for _, s := range []string{"K", "Q", "k", "q", "-"} {
if strings.Count(castleStr, s) > 1 {
return "-", fmt.Errorf("chess: fen invalid castle rights %s", castleStr)
}
}
for _, r := range castleStr {
c := fmt.Sprintf("%c", r)
switch c {
case "K", "Q", "k", "q", "-":
default:
return "-", fmt.Errorf("chess: fen invalid castle rights %s", castleStr)
}
}
return CastleRights(castleStr), nil
}
func formEnPassant(enPassant string) (Square, error) {
if enPassant == "-" {
return NoSquare, nil
}
sq := strToSquareMap[enPassant]
if sq == NoSquare || !(sq.Rank() == Rank3 || sq.Rank() == Rank6) {
return NoSquare, fmt.Errorf("chess: fen invalid En Passant square %s", enPassant)
}
return sq, nil
}
var (
fenSkipMap = map[int]string{
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
}
fenPieceMap = map[string]Piece{
"K": WhiteKing,
"Q": WhiteQueen,
"R": WhiteRook,
"B": WhiteBishop,
"N": WhiteKnight,
"P": WhitePawn,
"k": BlackKing,
"q": BlackQueen,
"r": BlackRook,
"b": BlackBishop,
"n": BlackKnight,
"p": BlackPawn,
}
fenTurnMap = map[string]Color{
"w": White,
"b": Black,
}
)