From eeda81d2f5232fee58bace7251e25fc7e3cac110 Mon Sep 17 00:00:00 2001 From: Mike Brown Date: Mon, 29 May 2023 16:27:02 +0000 Subject: [PATCH] Scanner: fix parsing of PGNs w/ starting position PGNs can encode games starting from a specific position rather than the normal starting position. Currently, Scanner will not parse these correctly because it matches move lists only on lines beginning with "1. ". This commit fixes this. --- fixtures/pgns/0014.pgn | 55 ++++++++++++++++++++++++++++++++++++++++++ pgn.go | 4 ++- pgn_test.go | 35 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 fixtures/pgns/0014.pgn diff --git a/fixtures/pgns/0014.pgn b/fixtures/pgns/0014.pgn new file mode 100644 index 0000000..8b76d3c --- /dev/null +++ b/fixtures/pgns/0014.pgn @@ -0,0 +1,55 @@ +[Event "Fantasy Caro-Kann: Starting Position"] +[Result "*"] +[UTCDate "2023.05.29"] +[UTCTime "16:05:07"] +[Variant "Standard"] +[ECO "B12"] +[Opening "Caro-Kann Defense: Maróczy Variation"] + +1. e4 c6 2. d4 d5 3. f3 * + + +[Event "Fantasy Caro-Kann: 3. ... dxe4"] +[Result "*"] +[UTCDate "2023.05.29"] +[UTCTime "16:05:48"] +[Variant "Standard"] +[FEN "rnbqkbnr/pp2pppp/2p5/3p4/3PP3/5P2/PPP3PP/RNBQKBNR b KQkq - 0 3"] +[SetUp "1"] + +3... dxe4 4. fxe4 e5 5. Nf3 Bg4 (5... exd4 6. Bc4) 6. Bc4 Nd7 7. O-O Ngf6 8. c3 * + + +[Event "Fantasy Caro-Kann: 3. ... e6"] +[Result "*"] +[UTCDate "2023.05.29"] +[UTCTime "16:06:13"] +[Variant "Standard"] +[FEN "rnbqkbnr/pp2pppp/2p5/3p4/3PP3/5P2/PPP3PP/RNBQKBNR b KQkq - 0 3"] +[SetUp "1"] + +3... e6 4. Nc3 Bb4 (4... Nf6 5. e5) 5. Bd2 Ne7 6. Bd3 * + + +[Event "Fantasy Caro-Kann: 3. ... g6"] +[Result "*"] +[UTCDate "2023.05.29"] +[UTCTime "16:06:42"] +[Variant "Standard"] +[FEN "rnbqkbnr/pp2pppp/2p5/3p4/3PP3/5P2/PPP3PP/RNBQKBNR b KQkq - 0 3"] +[SetUp "1"] + +3... g6 4. Nc3 Bg7 5. Be3 * + + +[Event "Fantasy Caro-Kann: 3. ... Qb6"] +[Result "*"] +[UTCDate "2023.05.29"] +[UTCTime "16:07:08"] +[Variant "Standard"] +[FEN "rnbqkbnr/pp2pppp/2p5/3p4/3PP3/5P2/PPP3PP/RNBQKBNR b KQkq - 0 3"] +[SetUp "1"] + +3... Qb6 4. Nc3 dxe4 5. fxe4 e5 6. Nf3 exd4 7. Nxd4 * + + diff --git a/pgn.go b/pgn.go index 37b76af..e22c689 100644 --- a/pgn.go +++ b/pgn.go @@ -37,6 +37,8 @@ const ( // a game or EOF was reached. Running scan populates // data for Next() and Err(). func (s *Scanner) Scan() bool { + moveListRe := regexp.MustCompile(`^\d+\.`) + if s.err == io.EOF { return false } @@ -64,7 +66,7 @@ func (s *Scanner) Scan() bool { } line := strings.TrimSpace(s.scanr.Text()) isTagPair := strings.HasPrefix(line, "[") - isMoveSeq := strings.HasPrefix(line, "1. ") + isMoveSeq := moveListRe.MatchString(line) switch state { case notInPGN: if !isTagPair { diff --git a/pgn_test.go b/pgn_test.go index 913796d..beec82d 100644 --- a/pgn_test.go +++ b/pgn_test.go @@ -154,6 +154,41 @@ func TestScanner(t *testing.T) { } } +func TestScannerWithFromPosFENs(t *testing.T) { + finalPositions := []string{ + "rnbqkbnr/pp2pppp/2p5/3p4/3PP3/5P2/PPP3PP/RNBQKBNR b KQkq - 0 3", + "r2qkb1r/pp1n1ppp/2p2n2/4p3/2BPP1b1/2P2N2/PP4PP/RNBQ1RK1 b kq - 0 8", + "rnbqk2r/pp2nppp/2p1p3/3p4/1b1PP3/2NB1P2/PPPB2PP/R2QK1NR b KQkq - 5 6", + "rnbqk1nr/pp2ppbp/2p3p1/3p4/3PP3/2N1BP2/PPP3PP/R2QKBNR b KQkq - 3 5", + "rnb1kbnr/pp3ppp/1qp5/8/3NP3/2N5/PPP3PP/R1BQKB1R b KQkq - 0 7", + } + fname := "fixtures/pgns/0014.pgn" + f, err := os.Open(fname) + if err != nil { + panic(err) + } + defer f.Close() + scanner := NewScanner(f) + games := []*Game{} + for idx := 0; scanner.Scan(); { + game := scanner.Next() + if len(game.moves) == 0 { + continue + } + finalPos := game.Position().String() + if finalPos != finalPositions[idx] { + t.Fatalf(fname+" game %v expected final pos %v but got %v", idx, + finalPositions[idx], finalPos) + } + games = append(games, game) + idx++ + } + if len(games) != len(finalPositions) { + t.Fatalf(fname+" expected %v games but got %v", len(finalPositions), + len(games)) + } +} + func BenchmarkPGN(b *testing.B) { pgn := mustParsePGN("fixtures/pgns/0001.pgn") b.ResetTimer()