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.
167 lines
3.8 KiB
167 lines
3.8 KiB
package chess |
|
|
|
import ( |
|
"bufio" |
|
"fmt" |
|
"io" |
|
"log" |
|
"regexp" |
|
"strings" |
|
) |
|
|
|
// GamesFromPGN returns all PGN decoding games from the |
|
// reader. It is designed to be used decoding multiple PGNs |
|
// in the same file. An error is returned if there is an |
|
// issue parsing the PGNs. |
|
func GamesFromPGN(r io.Reader) ([]*Game, error) { |
|
games := []*Game{} |
|
current := "" |
|
count := 0 |
|
totalCount := 0 |
|
br := bufio.NewReader(r) |
|
for { |
|
line, err := br.ReadString('\n') |
|
if err == io.EOF { |
|
break |
|
} else if err != nil { |
|
return nil, err |
|
} |
|
if strings.TrimSpace(line) == "" { |
|
count++ |
|
} else { |
|
current += line |
|
} |
|
if count == 2 { |
|
game, err := decodePGN(current) |
|
if err != nil { |
|
return nil, err |
|
} |
|
games = append(games, game) |
|
count = 0 |
|
current = "" |
|
totalCount++ |
|
log.Println("Processed game", totalCount) |
|
} |
|
} |
|
return games, nil |
|
} |
|
|
|
func decodePGN(pgn string) (*Game, error) { |
|
tagPairs := getTagPairs(pgn) |
|
moveStrs, outcome := moveList(pgn) |
|
gameFuncs := []func(*Game){} |
|
for _, tp := range tagPairs { |
|
if strings.ToLower(tp.Key) == "fen" { |
|
fenFunc, err := FEN(tp.Value) |
|
if err != nil { |
|
return nil, fmt.Errorf("chess: pgn decode error %s on tag %s", err.Error(), tp.Key) |
|
} |
|
gameFuncs = append(gameFuncs, fenFunc) |
|
break |
|
} |
|
} |
|
gameFuncs = append(gameFuncs, TagPairs(tagPairs)) |
|
g := NewGame(gameFuncs...) |
|
g.IgnoreAutomaticDraws = true |
|
var notation Notation = AlgebraicNotation{} |
|
if len(moveStrs) > 0 { |
|
_, err := LongAlgebraicNotation{}.Decode(g.Position(), moveStrs[0]) |
|
if err == nil { |
|
notation = LongAlgebraicNotation{} |
|
} |
|
} |
|
for _, alg := range moveStrs { |
|
m, err := notation.Decode(g.Position(), alg) |
|
if err != nil { |
|
return nil, fmt.Errorf("chess: pgn decode error %s on move %d", err.Error(), g.Position().moveCount) |
|
} |
|
if err := g.Move(m); err != nil { |
|
return nil, fmt.Errorf("chess: pgn invalid move error %s on move %d", err.Error(), g.Position().moveCount) |
|
} |
|
} |
|
g.Outcome = outcome |
|
return g, nil |
|
} |
|
|
|
func encodePGN(g *Game) string { |
|
s := "" |
|
for _, tag := range g.TagPairs { |
|
s += fmt.Sprintf("[%s \"%s\"]\n", tag.Key, tag.Value) |
|
} |
|
s += "\n" |
|
for i, move := range g.Moves { |
|
pos := g.Positions[i] |
|
txt := g.Notation.Encode(pos, move) |
|
if i%2 == 0 { |
|
s += fmt.Sprintf("%d.%s", (i/2)+1, txt) |
|
} else { |
|
s += fmt.Sprintf(" %s ", txt) |
|
} |
|
} |
|
s += " " + string(g.Outcome) |
|
return s |
|
} |
|
|
|
var ( |
|
tagPairRegex = regexp.MustCompile(`\[(.*)\s\"(.*)\"\]`) |
|
) |
|
|
|
func getTagPairs(pgn string) []*TagPair { |
|
tagPairs := []*TagPair{} |
|
matches := tagPairRegex.FindAllString(pgn, -1) |
|
for _, m := range matches { |
|
results := tagPairRegex.FindStringSubmatch(m) |
|
if len(results) == 3 { |
|
pair := &TagPair{ |
|
Key: results[1], |
|
Value: results[2], |
|
} |
|
tagPairs = append(tagPairs, pair) |
|
} |
|
} |
|
return tagPairs |
|
} |
|
|
|
var ( |
|
moveNumRegex = regexp.MustCompile(`(?:\d+\.+)?(.*)`) |
|
) |
|
|
|
func moveList(pgn string) ([]string, Outcome) { |
|
// remove comments |
|
text := removeSection("{", "}", pgn) |
|
// remove variations |
|
text = removeSection(`\(`, `\)`, text) |
|
// remove tag pairs |
|
text = removeSection(`\[`, `\]`, text) |
|
// remove line breaks |
|
text = strings.Replace(text, "\n", " ", -1) |
|
|
|
list := strings.Split(text, " ") |
|
filtered := []string{} |
|
var outcome Outcome |
|
for _, move := range list { |
|
move = strings.TrimSpace(move) |
|
switch move { |
|
case string(NoOutcome), string(WhiteWon), string(BlackWon), string(Draw): |
|
outcome = Outcome(move) |
|
case "": |
|
default: |
|
results := moveNumRegex.FindStringSubmatch(move) |
|
if len(results) == 2 && results[1] != "" { |
|
filtered = append(filtered, results[1]) |
|
} |
|
} |
|
} |
|
return filtered, outcome |
|
} |
|
|
|
func removeSection(leftChar, rightChar, s string) string { |
|
r := regexp.MustCompile(leftChar + ".*?" + rightChar) |
|
for { |
|
i := r.FindStringIndex(s) |
|
if i == nil { |
|
return s |
|
} |
|
s = s[0:i[0]] + s[i[1]:len(s)] |
|
} |
|
}
|
|
|