Skip to content

Commit

Permalink
Merge pull request #84 from sumnerevans/remove-extraneous-disambiguators
Browse files Browse the repository at this point in the history
notation: remove extraneous disambiguators when decoding (L)AR
  • Loading branch information
notnil authored Dec 18, 2021
2 parents 03fd91c + b230606 commit efbbef8
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 14 deletions.
26 changes: 25 additions & 1 deletion fixtures/valid_notation_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,29 @@
"longAlgText" : "Rd1d2",
"uciText" : "d1d2",
"description" : "From Lichess DB https://lichess.org/editor/3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PP1QPP1/3R3K_w_-_-_2_22"
}
},
{
"pos1": "r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8 b - - 1 35",
"pos2": "r7/1R1nk3/2R1pn2/p4p2/P5p1/4P3/1P2KPP1/8 w - - 2 36",
"algText": "Nf6",
"longAlgText": "Nd5f6",
"uciText": "d5f6",
"description" : "https://lichess.org/analysis/fromPosition/r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8_b_-_-_1_35"
},
{
"pos1": "r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8 b - - 1 35",
"pos2": "r7/1R1nk3/2R1pn2/p4p2/P5p1/4P3/1P2KPP1/8 w - - 2 36",
"algText": "N5f6",
"longAlgText": "Nd5f6",
"uciText": "d5f6",
"description" : "https://lichess.org/analysis/fromPosition/r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8_b_-_-_1_35"
},
{
"pos1": "r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8 b - - 1 35",
"pos2": "r7/1R1nk3/2R1pn2/p4p2/P5p1/4P3/1P2KPP1/8 w - - 2 36",
"algText": "Ndf6",
"longAlgText": "Nd5f6",
"uciText": "d5f6",
"description" : "https://lichess.org/analysis/fromPosition/r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8_b_-_-_1_35"
}
]
62 changes: 49 additions & 13 deletions notation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chess

import (
"fmt"
"regexp"
"strings"
)

Expand Down Expand Up @@ -121,15 +122,58 @@ func (AlgebraicNotation) Encode(pos *Position, m *Move) string {
return pChar + s1Str + capChar + m.s2.String() + promoText + checkChar
}

var pgnRegex = regexp.MustCompile(`^(?:([RNBQKP]?)([abcdefgh]?)(\d?)(x?)([abcdefgh])(\d)(=Q)?|(O-O(?:-O)?))([+#!?]|e\.p\.)*$`)

func algebraicNotationParts(s string) (string, string, string, string, string, string, string, string, error) {
submatches := pgnRegex.FindStringSubmatch(s)
if len(submatches) == 0 {
return "", "", "", "", "", "", "", "", fmt.Errorf("could not decode algebraic notation %s", s)
}

return submatches[1], submatches[2], submatches[3], submatches[4], submatches[5], submatches[6], submatches[7], submatches[8], nil
}

// Decode implements the Decoder interface.
func (AlgebraicNotation) Decode(pos *Position, s string) (*Move, error) {
s = removeSubstrings(s, "?", "!", "+", "#", "e.p.")
piece, originFile, originRank, capture, file, rank, promotes, castles, err := algebraicNotationParts(s)
if err != nil {
return nil, fmt.Errorf("chess: %+v for position %s", err, pos.String())
}

for _, m := range pos.ValidMoves() {
str := AlgebraicNotation{}.Encode(pos, m)
str = removeSubstrings(str, "?", "!", "+", "#", "e.p.")
if str == s {
moveStr := AlgebraicNotation{}.Encode(pos, m)
moveSubmatches := pgnRegex.FindStringSubmatch(moveStr)
moveCleaned := strings.Join(moveSubmatches[1:9], "")

cleaned := piece + originFile + originRank + capture + file + rank + promotes + castles
if cleaned == moveCleaned {
return m, nil
}

// Try and remove the disambiguators and see if it parses. Sometimes they
// get extraneously added.
options := []string{}

if piece != "" {
options = append(options, piece+capture+file+rank+promotes+castles) // no origin
options = append(options, piece+originRank+capture+file+rank+promotes+castles) // no origin file
options = append(options, piece+originFile+capture+file+rank+promotes+castles) // no origin rank
} else {
if capture != "" {
// Possibly a pawn capture. In order to parse things like d4xe5, we need
// to try parsing without the rank.
options = append(options, piece+originFile+capture+file+rank+promotes+castles) // no origin rank
}
if originFile != "" && originRank != "" {
options = append(options, piece+capture+file+rank+promotes+castles) // no origin
}
}

for _, opt := range options {
if opt == moveCleaned {
return m, nil
}
}
}
return nil, fmt.Errorf("chess: could not decode algebraic notation %s for position %s", s, pos.String())
}
Expand Down Expand Up @@ -170,15 +214,7 @@ func (LongAlgebraicNotation) Encode(pos *Position, m *Move) string {

// Decode implements the Decoder interface.
func (LongAlgebraicNotation) Decode(pos *Position, s string) (*Move, error) {
s = removeSubstrings(s, "?", "!", "+", "#", "e.p.")
for _, m := range pos.ValidMoves() {
str := LongAlgebraicNotation{}.Encode(pos, m)
str = removeSubstrings(str, "?", "!", "+", "#", "e.p.")
if str == s {
return m, nil
}
}
return nil, fmt.Errorf("chess: could not decode long algebraic notation %s for position %s", s, pos.String())
return AlgebraicNotation{}.Decode(pos, s)
}

func getCheckChar(pos *Position, move *Move) string {
Expand Down
6 changes: 6 additions & 0 deletions notation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ var (
Pos: unsafeFEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2"),
Text: "nf3",
},
{
// disambiguation should not allow for this since it is not a capture
N: AlgebraicNotation{},
Pos: unsafeFEN("rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2"),
Text: "bf4",
},
}
)

Expand Down

0 comments on commit efbbef8

Please sign in to comment.