Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Zobrist/Polyglot and improve tests #10

Merged
merged 8 commits into from
Dec 18, 2024
Merged

Add Zobrist/Polyglot and improve tests #10

merged 8 commits into from
Dec 18, 2024

Conversation

CorentinGS
Copy link
Owner

@CorentinGS CorentinGS commented Dec 18, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a streamlined method for move processing with PushMove, replacing previous methods.
    • Added a new function to convert string representations of colors to the corresponding Color type.
    • Implemented a comprehensive polyglot opening book management system for chess engines.
    • Added functionality for generating Zobrist hashes for chess positions.
  • Bug Fixes

    • Enhanced error handling for various scenarios in PGN data processing and FEN string validation.
  • Tests

    • Expanded test coverage for PGN handling and polyglot book functionality.
    • Introduced unit tests for the new hashing functionality and validation of chess positions.
  • Chores

    • Updated import statements to include necessary packages for new functionalities.

This commit introduces three tests to ensure the correctness of PGN parsing. The tests validate behavior for valid PGN files, invalid PGN data, and handling of empty inputs. These additions improve the robustness and reliability of the PGN parsing functionality.
Replaced all instances of MoveStr with PushMove in tests for consistency and to use the updated method. Removed deprecated methods Move and MoveStr, as well as MakeMove, which are now redundant. This simplifies the codebase and aligns with the updated API.
Replaced the placeholder "TODO" with a call to g.FEN() to ensure the Game's String method returns the FEN representation. This aligns with expected behavior and improves the code's utility.
The moveSlice type and its associated find method were unused and redundant. This cleanup improves code readability and reduces unnecessary complexity.
Implemented a new test function to validate the Bytes method for various chess piece types. This ensures the method produces the correct byte representations for all supported piece types. Also updated imports to include the "bytes" package.
Introduce ChessHasher to compute Zobrist hashes for chess positions, supporting operations like piece placement, en passant, castling, and side to move. Add comprehensive unit tests to validate functionality, error handling, and known position cases. Include polyglot hash constants for hashing computations.
Introduced functionality to handle Polyglot opening books, including loading from various sources (file, memory, reader), parsing moves, finding moves by position, and weighted random move selection. Extensive tests were added to verify behavior across scenarios such as invalid data, move decoding, and position-based lookups.
Replaced the placeholder random number generator with a cryptographically secure implementation using crypto/rand. This ensures unpredictability and prevents potential exploitation during move selection. Added error handling to catch and report failures in random number generation.
Copy link
Contributor

coderabbitai bot commented Dec 18, 2024

Walkthrough

This pull request introduces significant architectural changes to the chess library, focusing on move handling, game state management, and the introduction of new components like Polyglot opening book support and Zobrist hashing. The modifications streamline move processing by centralizing validation and application logic, deprecate older methods like MoveStr, and add new functionality for handling chess-related computations such as opening book management and position hashing.

Changes

File Changes
game.go - Removed Move and MakeMove methods
- Added PushMove method with PushMoveOptions
- Updated String method to return FEN representation
game_test.go - Added PGN-related test functions: TestPGNWithValidData, TestPGNWithInvalidData, TestPGNWithEmptyData
hashes.go - Added GetPolyglotHashes() function
move.go - Removed moveSlice type and find method
piece.go - Added ColorFromString function
piece_test.go - Added TestBytesForPieceTypes test function
New: polyglot.go - Introduced comprehensive Polyglot opening book support
- Added multiple types and functions for book management
New: polyglot_test.go - Added comprehensive test suite for Polyglot book functionality
New: zobrist.go - Introduced ChessHasher for generating Zobrist hashes
- Added methods for hash computation and manipulation
New: zobrist_test.go - Added test suite for Zobrist hashing functionality

Sequence Diagram

sequenceDiagram
    participant Game
    participant PushMove
    participant MoveValidator
    participant GameState

    Game->>PushMove: Call with algebraic move
    PushMove->>MoveValidator: Validate move
    MoveValidator-->>PushMove: Validation result
    PushMove->>GameState: Update game state
    GameState-->>PushMove: State updated
    PushMove-->>Game: Return result
Loading

Possibly Related PRs

Poem

🐰 A Rabbit's Chess Ballad 🏁

Moves flow like rivers, smooth and bright,
Polyglot books dance in the light,
Zobrist hashes, a magical key,
Transforming chess with playful glee,
Our code leaps forward, pure delight!

- Sir Hoppington, Chief Coding Rabbit 🐇♟️

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Dec 18, 2024

Codecov Report

Attention: Patch coverage is 93.12500% with 77 lines in your changes missing coverage. Please review.

Project coverage is 73.71%. Comparing base (397e104) to head (2ecef42).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
polyglot.go 74.83% 29 Missing and 10 partials ⚠️
zobrist.go 79.19% 26 Missing and 10 partials ⚠️
game.go 0.00% 1 Missing ⚠️
piece.go 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #10      +/-   ##
==========================================
+ Coverage   66.23%   73.71%   +7.48%     
==========================================
  Files          25       28       +3     
  Lines        3865     4935    +1070     
==========================================
+ Hits         2560     3638    +1078     
+ Misses       1196     1167      -29     
- Partials      109      130      +21     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (12)
zobrist.go (3)

10-10: Rename ChessHasher to Hasher to avoid stutter.
According to static analysis (revive), the name "ChessHasher" stutters when used under the package "chess". Consider renaming it to "Hasher" to follow Go naming conventions.

🧰 Tools
🪛 golangci-lint (1.62.2)

[warning] 10-10: exported: type name will be used as chess.ChessHasher by other packages, and that stutters; consider calling this Hasher

(revive)


125-198: Function hashPieces too long.
Static analysis warns that hashPieces has too many statements (62 > 50). Consider breaking it into smaller helper functions to enhance readability and maintainability.

🧰 Tools
🪛 golangci-lint (1.62.2)

125-125: Function 'hashPieces' has too many statements (62 > 50)

(funlen)


132-132: for loop can be changed to use an integer range (Go 1.22+)

(intrange)


135-135: for loop can be changed to use an integer range (Go 1.22+)

(intrange)


210-210: Use errors.New instead of fmt.Errorf for fixed messages.
Where the error messages have no string formatting, using errors.New can be more direct and slightly more efficient, e.g.:

- return "", fmt.Errorf("invalid FEN format")
+ return "", errors.New("invalid FEN format")

Also applies to: 215-215, 233-233

🧰 Tools
🪛 golangci-lint (1.62.2)

210-210: fmt.Errorf can be replaced with errors.New

(perfsprint)

polyglot.go (1)

198-198: Use errors.Is for checking io.EOF.
Directly comparing err with io.EOF may fail if err is wrapped. Consider using errors.Is for robust error checks:

-if err == io.EOF {
+if errors.Is(err, io.EOF) {
🧰 Tools
🪛 golangci-lint (1.62.2)

198-198: comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)

polyglot_test.go (3)

144-193: Consider adding weight-based test cases

The TestFindMoves function could benefit from additional test cases:

  • Equal weight moves to verify stable sorting
  • Zero weight moves to verify handling

261-278: Add more invalid data test cases

TestInvalidBookData could be enhanced with additional cases:

  • Corrupted entry data
  • Invalid move encodings
  • Maximum file size handling

1-278: Consider splitting test file for better organization

The test file covers multiple distinct functionalities. Consider splitting it into:

  • polyglot_source_test.go: Book source tests
  • polyglot_move_test.go: Move decoding and finding tests
  • polyglot_book_test.go: Book loading and validation tests
🧰 Tools
🪛 golangci-lint (1.62.2)

63-63: comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)


62-62: ineffectual assignment to n

(ineffassign)

zobrist_test.go (3)

40-79: Consider adding test cases for more complex positions.

The known position tests cover standard openings well. Consider adding test cases for:

  • Complex middlegame positions
  • Endgame positions with various piece configurations
  • Positions with multiple pawns and pieces

149-180: Consider adding more en passant test cases.

While the current tests cover basic en passant scenarios, consider adding:

  • Multiple possible en passant squares
  • Invalid en passant squares on wrong ranks

208-239: Enhance piece placement tests with more edge cases.

The piece placement tests could be expanded to include:

  • Multiple pieces of the same type
  • Pieces on edge squares
  • Maximum number of pieces of each type
🧰 Tools
🪛 golangci-lint (1.62.2)

230-230: for loop can be changed to use an integer range (Go 1.22+)

(intrange)

hashes.go (2)

3-787: Consider using a more maintainable approach for hash values.

While the current implementation works, consider:

  1. Storing hash values in a separate file (e.g., JSON, YAML) and loading at runtime
  2. Using a const array or generating values programmatically
  3. Adding documentation about the source and purpose of these hash values

This would improve maintainability and make the code more readable.

🧰 Tools
🪛 golangci-lint (1.62.2)

3-3: Function 'GetPolyglotHashes' is too long (783 > 100)

(funlen)


1-787: Add documentation for the hash values.

Please add:

  • Function documentation explaining the purpose and usage of these hashes
  • Source reference for the hash values
  • Any specific requirements or constraints for using these values
🧰 Tools
🪛 golangci-lint (1.62.2)

3-3: Function 'GetPolyglotHashes' is too long (783 > 100)

(funlen)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 397e104 and 2ecef42.

📒 Files selected for processing (11)
  • game.go (1 hunks)
  • game_test.go (2 hunks)
  • hashes.go (1 hunks)
  • move.go (0 hunks)
  • opening/opening_test.go (4 hunks)
  • piece.go (1 hunks)
  • piece_test.go (2 hunks)
  • polyglot.go (1 hunks)
  • polyglot_test.go (1 hunks)
  • zobrist.go (1 hunks)
  • zobrist_test.go (1 hunks)
💤 Files with no reviewable changes (1)
  • move.go
🧰 Additional context used
🪛 golangci-lint (1.62.2)
zobrist_test.go

8-8: cyclomatic complexity 33 of func TestChessHasher is high (> 30)

(gocyclo)


230-230: for loop can be changed to use an integer range (Go 1.22+)

(intrange)

polyglot_test.go

63-63: comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)


62-62: ineffectual assignment to n

(ineffassign)

zobrist.go

38-38: Error return value of fmt.Sscanf is not checked

(errcheck)


125-125: Function 'hashPieces' has too many statements (62 > 50)

(funlen)


[warning] 10-10: exported: type name will be used as chess.ChessHasher by other packages, and that stutters; consider calling this Hasher

(revive)


60-60: for loop can be changed to use an integer range (Go 1.22+)

(intrange)


132-132: for loop can be changed to use an integer range (Go 1.22+)

(intrange)


135-135: for loop can be changed to use an integer range (Go 1.22+)

(intrange)


35-35: Magic number: 2, in detected

(mnd)


97-97: Magic number: 780, in detected

(mnd)


210-210: fmt.Errorf can be replaced with errors.New

(perfsprint)


215-215: fmt.Errorf can be replaced with errors.New

(perfsprint)


233-233: fmt.Errorf can be replaced with errors.New

(perfsprint)

polyglot.go

27-27: Comment should end in a period

(godot)


69-69: Comment should end in a period

(godot)


77-77: Comment should end in a period

(godot)


92-92: Comment should end in a period

(godot)


107-107: Comment should end in a period

(godot)


112-112: Comment should end in a period

(godot)


117-117: Comment should end in a period

(godot)


123-123: Comment should end in a period

(godot)


128-128: Comment should end in a period

(godot)


198-198: comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)


197-197: shadow: declaration of "err" shadows declaration at line 183

(govet)


195-195: Magic number: 16, in detected

(mnd)


332-332: Magic number: 0x7, in detected

(mnd)


333-333: Magic number: 0x7, in detected

(mnd)


334-334: Magic number: 0x7, in detected

(mnd)


335-335: Magic number: 0x7, in detected

(mnd)


336-336: Magic number: 0x7, in detected

(mnd)


337-337: Magic number: 0x7, in detected

(mnd)


386-386: Magic number: 4, in detected

(mnd)


93-93: named return "n" with type "int" found

(nonamedreturns)


129-129: named return "n" with type "int" found

(nonamedreturns)


162-162: named return "n" with type "int" found

(nonamedreturns)

hashes.go

3-3: Function 'GetPolyglotHashes' is too long (783 > 100)

(funlen)

🔇 Additional comments (11)
zobrist.go (2)

67-69: Magic numbers in xorHash.
The index passed to GetPolyglotHashes()[num] is a magic number. Ensure that 780 or other values used in the code correspond to correct offsets into the pre-defined polyglot array.


35-41: Check the error return from fmt.Sscanf.
The call to "fmt.Sscanf(s[i:i+2], "%02x", &value)" should check the error return value to detect malformed hex strings and handle them accordingly.

🧰 Tools
🪛 golangci-lint (1.62.2)

38-38: Error return value of fmt.Sscanf is not checked

(errcheck)


35-35: Magic number: 2, in detected

(mnd)

game.go (1)

291-291: Clarify String method's usage.
The String() method now returns FEN notation. If the previously expected behavior was to return a PGN or something else, ensure the change is intentional.

piece_test.go (2)

3-3: Import "bytes" for clarity.
Importing "bytes" is correct for comparing slices, and everything looks good here.


28-47: Bytes() test coverage for NoPieceType.
It's great that the test includes NoPieceType. This ensures that edge cases are covered, which improves overall robustness.

opening/opening_test.go (1)

13-14: LGTM! Consistent migration from MoveStr to PushMove

The changes correctly replace the deprecated MoveStr method with PushMove across all test functions, maintaining the same behavior while allowing for future extensibility through the options parameter.

Also applies to: 26-27, 44-47, 60-61

piece.go (1)

17-25: LGTM! Well-implemented color string conversion

The ColorFromString function is a clean implementation that:

  • Handles case-insensitivity correctly
  • Returns NoColor for invalid inputs
  • Maintains consistency with other string conversion functions in the file
polyglot_test.go (1)

13-30: LGTM! Well-structured test helpers

The helper functions createEntry and createTestBook are well-implemented and provide a clean way to create test data.

zobrist_test.go (2)

8-38: LGTM! Well-structured basic position validation tests.

The test cases effectively validate the core functionality:

  • Starting position hash verification
  • Empty board position hash verification
  • Hash length validation
🧰 Tools
🪛 golangci-lint (1.62.2)

8-8: cyclomatic complexity 33 of func TestChessHasher is high (> 30)

(gocyclo)


81-107: LGTM! Comprehensive error handling tests.

Good coverage of error cases:

  • Invalid FEN format
  • Invalid piece placement
  • Invalid ranks count
game_test.go (1)

1007-1013: LGTM! Clear empty data test.

Good verification of the ErrNoGameFound error case.

Comment on lines +197 to +203
_, err := source.Read(buf)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Variable shadowing for err on line 197.
The declaration of err here shadows the outer scope variable, potentially causing confusion. Rename the inner variable to a different identifier to avoid accidental misuse.

-for {
-   _, err := source.Read(buf)
+for {
+   _, readErr := source.Read(buf)

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 golangci-lint (1.62.2)

198-198: comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)


197-197: shadow: declaration of "err" shadows declaration at line 183

(govet)


// Test EOF
source.index = 32
n, err = source.Read(buf)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove ineffectual assignment

The n variable assignment is unused in the EOF test case.

Apply this fix:

-	n, err = source.Read(buf)
+	_, err = source.Read(buf)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
n, err = source.Read(buf)
_, err = source.Read(buf)
🧰 Tools
🪛 golangci-lint (1.62.2)

62-62: ineffectual assignment to n

(ineffassign)

Comment on lines +61 to +65
source.index = 32
n, err = source.Read(buf)
if err != io.EOF {
t.Errorf("Read() error = %v, want EOF", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix error handling in EOF test case

The error comparison should use errors.Is for proper error wrapping support.

Apply this fix:

-	if err != io.EOF {
+	if !errors.Is(err, io.EOF) {
 		t.Errorf("Read() error = %v, want EOF", err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
source.index = 32
n, err = source.Read(buf)
if err != io.EOF {
t.Errorf("Read() error = %v, want EOF", err)
}
source.index = 32
n, err = source.Read(buf)
if !errors.Is(err, io.EOF) {
t.Errorf("Read() error = %v, want EOF", err)
}
🧰 Tools
🪛 golangci-lint (1.62.2)

63-63: comparing with != will fail on wrapped errors. Use errors.Is to check for a specific error

(errorlint)


62-62: ineffectual assignment to n

(ineffassign)

Comment on lines +981 to +996
func TestPGNWithValidData(t *testing.T) {
pgnData := mustParsePGN("fixtures/pgns/single_game.pgn")
r := strings.NewReader(pgnData)
updateFunc, err := PGN(r)
if err != nil {
t.Fatal(err)
}
g := NewGame()
updateFunc(g)
if g.Outcome() != WhiteWon {
t.Fatalf("expected outcome %s but got %s", WhiteWon, g.Outcome())
}
if g.Method() != NoMethod {
t.Fatalf("expected method %s but got %s", NoMethod, g.Method())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add more assertions for game state.

While the test verifies the outcome and method, consider adding assertions for:

  • Move history
  • Position after specific moves
  • Tag pairs from the PGN

Comment on lines +998 to +1005
func TestPGNWithInvalidData(t *testing.T) {
pgnData := "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7 11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 h6 15. Bh4 Re8 16. a3 Bf8 17. Rc1 Qb6 18. dxe5 dxe5 19. Qe2 Nh5 20. Qd2 Nc5 21. Bc2 Nf4 22. Bg3 Rad8 23. Qe3 Qc7 24. Rcd1 Rxd1 25. Rxd1 Nce6 26. Bb3 Bc5 27. Qe1 Nd4 28. Nxd4 Bxd4 29. Bxf4 exf4 30. Rxd4 c5 31. Rd1 c4 32. Bc2 Qe5 33. f3 Qc5+ 34. Qf2 Qe5 35. Qd4 Qg5 36. Qd7 Re7 37. Qd8+ Kh7 38. e5+ g6 39. Qd6 Bxf3 40. Rd2 Rxe5 41. Qd4 Re1+ 42. Kf2 Qg3# 0-1"
r := strings.NewReader(pgnData)
_, err := PGN(r)
if err == nil {
t.Fatal("expected error for invalid PGN data")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Test specific PGN parsing error cases.

The test could be more specific about what makes the PGN data invalid. Consider testing:

  • Malformed move notation
  • Invalid move sequences
  • Missing required fields

@CorentinGS CorentinGS merged commit fdb9968 into main Dec 18, 2024
11 checks passed
@CorentinGS CorentinGS deleted the add_tests branch December 19, 2024 09:12
@coderabbitai coderabbitai bot mentioned this pull request Feb 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant