Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion fen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ var (
"3r1rk1/p3qppp/2bb4/2p5/3p4/1P2P3/PBQN1PPP/2R2RK1 w - - 0 1",
"4r1k1/1b3p1p/ppq3p1/2p5/8/1P3R1Q/PBP3PP/7K w - - 0 1",
"5k2/ppp5/4P3/3R3p/6P1/1K2Nr2/PP3P2/8 b - - 1 32",
"rnbqkbnr/pp1ppppp/8/8/1Pp1PP2/8/P1PP2PP/RNBQKBNR b KQkq b3 0 3",
"rnbqkbnr/p1ppppp1/7p/Pp6/8/8/1PPPPPPP/RNBQKBNR w KQkq b6 0 3",
"rnbqkbnr/1pppppp1/7p/pP6/8/8/P1PPPPPP/RNBQKBNR w KQkq a6 0 3",
"rnbqkbnr/1pppppp1/7p/pP6/4P3/8/P1PP1PPP/RNBQKBNR b KQkq e3 0 3",
}

validXFENs = []string{
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
"rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2",
"rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2",
"7k/8/8/8/8/8/8/R6K w - - 0 1",
"7k/8/8/8/8/8/8/2B1KB2 w - - 0 1",
"8/8/8/4k3/8/8/8/R3K2R w KQ - 0 1",
"8/8/8/8/4k3/8/3KP3/8 w - - 0 1",
"8/8/5k2/8/5K2/8/4P3/8 w - - 0 1",
"r4rk1/1b2bppp/ppq1p3/2pp3n/5P2/1P1BP3/PBPPQ1PP/R4RK1 w - - 0 1",
"3r1rk1/p3qppp/2bb4/2p5/3p4/1P2P3/PBQN1PPP/2R2RK1 w - - 0 1",
"4r1k1/1b3p1p/ppq3p1/2p5/8/1P3R1Q/PBP3PP/7K w - - 0 1",
"5k2/ppp5/4P3/3R3p/6P1/1K2Nr2/PP3P2/8 b - - 1 32",
"rnbqkbnr/pp1ppppp/8/8/1Pp1PP2/8/P1PP2PP/RNBQKBNR b KQkq b3 0 3",
"rnbqkbnr/p1ppppp1/7p/Pp6/8/8/1PPPPPPP/RNBQKBNR w KQkq b6 0 3",
"rnbqkbnr/1pppppp1/7p/pP6/8/8/P1PPPPPP/RNBQKBNR w KQkq a6 0 3",
"rnbqkbnr/1pppppp1/7p/pP6/4P3/8/P1PP1PPP/RNBQKBNR b KQkq - 0 3",
}

//nolint:gochecknoglobals // test data
Expand All @@ -40,14 +64,19 @@ var (
)

func TestValidFENs(t *testing.T) {
for _, f := range validFENs {
for idx, f := range validFENs {
state, err := decodeFEN(f)
if err != nil {
t.Fatal("recieved unexpected error", err)
}
if f != state.String() {
t.Fatalf("fen expected board string %s but got %s", f, state.String())
}
xfen := state.XFENString()
if xfen != validXFENs[idx] {
t.Fatalf("xfen for fen %v (%v) was %v but expected %v", idx, f,
xfen, validXFENs[idx])
}
}
}

Expand Down
41 changes: 41 additions & 0 deletions position.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,47 @@ func (pos *Position) String() string {
return fmt.Sprintf("%s %s %s %s %d %d", b, t, c, sq, pos.halfMoveClock, pos.moveCount)
}

// XFENString() is similar to String() except that it returns a string with
// the X-FEN format
func (pos *Position) XFENString() string {
b := pos.board.String()
t := pos.turn.String()
c := pos.castleRights.String()
sq := "-"
if pos.enPassantSquare != NoSquare {
// Check if there is a pawn in a position to capture en passant
var rank Rank
if pos.turn == White {
rank = Rank5
} else {
rank = Rank4
}
// The en passant target square will always be on the rank opposite the current turn's pawns
file := pos.enPassantSquare.File()
potentialPawnFiles := []File{file - 1, file + 1} // Pawns that could capture en passant will be on an adjacent file

for _, f := range potentialPawnFiles {
if f < FileA || f > FileH { // Ensure file is within bounds
continue
}

potentialPawnSquare := NewSquare(f, rank)
potentialPawn := pos.board.Piece(potentialPawnSquare)
if potentialPawn == NoPiece {
continue
}
if potentialPawn.Type() != Pawn {
continue
}
if potentialPawn.Color() == pos.turn {
sq = pos.enPassantSquare.String()
break
}
}
}
return fmt.Sprintf("%s %s %s %s %d %d", b, t, c, sq, pos.halfMoveClock, pos.moveCount)
}

// Hash returns a unique hash of the position.
func (pos *Position) Hash() [16]byte {
b, _ := pos.MarshalBinary()
Expand Down
Loading