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

Feat/polyglot utils #17

Merged
merged 8 commits into from
Feb 2, 2025
Merged

Feat/polyglot utils #17

merged 8 commits into from
Feb 2, 2025

Conversation

CorentinGS
Copy link
Owner

@CorentinGS CorentinGS commented Feb 2, 2025

Summary by CodeRabbit

  • New Features
    • Enabled promotion value lookup for chess pieces.
    • Enhanced opening book functionality with weighted move management and flexible loading.
    • Improved chess position evaluation through updated hashing and conversion utilities.
  • Refactor
    • Renamed and streamlined the chess hasher component for clarity and improved error handling.
  • Tests
    • Expanded test coverage to validate move conversion, book updates, and hash conversion.
    • Added tests for Zobrist hash conversion functionality.
  • Chores
    • Revised import handling to restore essential mapping capabilities.

Copy link
Contributor

coderabbitai bot commented Feb 2, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This pull request reinstates a removed import in pgn.go and adds a new method to convert piece types to promotion values in piece.go. It extends the functionality of the PolyglotBook by adding a helper struct and several methods for move encoding, addition, update, deletion, retrieval, and conversion. The file-based book loader is replaced with a reader-based implementation, and corresponding tests are updated. Additionally, the hashing mechanism is modified by renaming ChessHasher to ZobristHasher with updated method signatures, improved error handling, and an added conversion function.

Changes

File(s) Change Summary
pgn.go Reinstated import for golang.org/x/exp/maps to enable maps.Copy in move parsing.
piece.go Added method ToPolyglotPromotionValue on PieceType to return promotion value ints.
polyglot.go & polyglot_test.go Added new struct MoveWithWeight, move encoding functions, and methods (AddMove, UpdateMove, DeleteMoves, GetChessMoves, ToMoveMap); replaced file-based loader with LoadBookFromReader; and introduced extensive tests for these functionalities.
zobrist.go & zobrist_test.go Renamed ChessHasher to ZobristHasher with corresponding updates in constructor and methods; added NewZobristHasher and ZobristHashToUint64; tests updated to reflect these modifications.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PolyglotBook
    Client->>PolyglotBook: AddMove(positionHash, move, weight)
    PolyglotBook-->>PolyglotBook: Insert move & re-sort entries
    PolyglotBook->>Client: Confirm addition
Loading
sequenceDiagram
    participant Client
    participant ZobristHasher
    Client->>ZobristHasher: HashPosition(fen)
    ZobristHasher-->>ZobristHasher: Process FEN (xorHash, parseEnPassant, etc.)
    ZobristHasher->>Client: Return hash string or error
Loading

Possibly related PRs

Poem

I'm just a rabbit hopping through the code,
Tweaking moves and hashes along the road.
New methods and tests sprout like springtime blooms,
With every push, brighter code resumes.
I nibble bugs and hop with cheer,
Celebrating changes, year after year!
🐇🌸 Happy coding under sunny skies!


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2f012d3 and b0939e4.

📒 Files selected for processing (1)
  • zobrist_test.go (9 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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
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: 2

🧹 Nitpick comments (7)
polyglot.go (3)

84-108: Consider returning an error instead of silently returning an empty Move.
Silently returning an empty Move may mask decoding errors; returning an error or logging might provide better visibility.


422-435: Frequent re-sorting could impact performance on large insertion volumes.
If you plan to add a large number of moves repeatedly, consider a more efficient insertion mechanism or deferring sort operations.


464-476: Potentially handle Move decoding errors more explicitly.
If a decode fails inside the loop, you might lose the opportunity to inform the caller about invalid data.

polyglot_test.go (2)

108-109: Handle potential errors to avoid silent failures.
Currently, the errors from os.Open and NewReaderBookSource are ignored. Please verify or handle them to ensure proper test failure if something goes wrong.


283-347: Comprehensive coverage for Move → Polyglot encoding.
Consider shortening the test function name to something like TestMoveToPolyglot for readability.

zobrist.go (2)

24-33: Enhance deprecation notice with migration guidance.

The deprecation notice should include guidance on how to migrate from the old constructor to the new one.

-// Deprecated: Use NewZobristHasher instead
+// Deprecated: Use NewZobristHasher instead. This constructor will be removed in a future version.
+// Migration: Replace NewChessHasher() with NewZobristHasher() in your code.

227-227: Improve error message clarity.

The error messages could be more descriptive to help users understand and fix the issues.

-		return "", errors.New("invalid FEN format")
+		return "", errors.New("invalid FEN format: insufficient fields")
-		return "", errors.New("invalid piece placement")
+		return "", errors.New("invalid piece placement: incorrect number of pieces or invalid piece positions")

Also applies to: 245-245

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 81d9023 and 0ae33dd.

📒 Files selected for processing (6)
  • pgn.go (1 hunks)
  • piece.go (1 hunks)
  • polyglot.go (3 hunks)
  • polyglot_test.go (4 hunks)
  • zobrist.go (8 hunks)
  • zobrist_test.go (9 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pgn.go
🧰 Additional context used
🪛 golangci-lint (1.62.2)
zobrist.go

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

(funlen)

🔇 Additional comments (21)
polyglot.go (8)

43-43: No issues found in the updated documentation comment.


58-62: Struct definition looks good.


63-72: Ensure input validation for file/rank bounds.
If Move.S1() or Move.S2() could ever yield out-of-range values, consider adding checks or clarifying assumptions.


74-82: Symmetrical encoding logic is consistent and clear.


401-420: Method appears straightforward and correct.


437-451: Verify whether you want to update all matching moves or just the first.
Currently, the loop updates every matching move. If only updating the first match is desired, break after the update.


453-462: Bulk deletion logic is clear and efficient.


478-490: Conversion to map is well-structured and straightforward.

piece.go (1)

149-162: Promotion value mapping is consistent with polyglot standards.

polyglot_test.go (10)

206-206: Regular move test looks correct.


215-215: Castling move test logic is valid and provides good coverage.


349-400: Encoding tests are thorough, verifying normal and promotion moves.


402-457: Conversion tests (PolyglotMove → Move) effectively cover promotions and castling.


459-503: Tests confirm correct initialization from map data.


505-541: AddMove test accurately verifies new entry insertion and sorting.


543-581: UpdateMove test reliably checks weight updates and error handling for missing moves.


583-615: DeleteMoves test properly cleans up all related moves.


617-656: Enforces correct retrieval of multiple moves, including promotions.
Please verify the order checks at lines 644 and 647; the indexing might appear reversed, but if it’s intentional for weighting or ordering, everything is fine.


658-703: Map conversion test demonstrates robust coverage.

zobrist.go (1)

136-210: Complexity is justified by domain requirements.

While the function exceeds the recommended statement count (62 > 50), breaking it down would likely reduce readability given that it handles chess-specific piece placement and en passant logic in a cohesive way.

🧰 Tools
🪛 golangci-lint (1.62.2)

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

(funlen)


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

(intrange)


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

(intrange)

zobrist_test.go (1)

10-10: LGTM!

The constructor updates are consistent throughout all test cases.

Also applies to: 41-41, 82-82, 110-110, 132-132, 150-150, 183-183, 209-209

Comment on lines +251 to +265
func ZobristHashToUint64(hash string) uint64 {
var result uint64

// Ensure the input is exactly 16 hex digits
if len(hash) != 16 {
return 0
}

// Try to parse the hash
if _, err := fmt.Sscanf(hash, "%016x", &result); err != nil {
return 0
}

return result
}
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

Consider returning an error for invalid input.

Silently returning 0 for invalid input could mask errors in the calling code. Consider returning an error to allow callers to handle invalid cases explicitly.

-func ZobristHashToUint64(hash string) uint64 {
+func ZobristHashToUint64(hash string) (uint64, error) {
 	var result uint64

 	// Ensure the input is exactly 16 hex digits
 	if len(hash) != 16 {
-		return 0
+		return 0, errors.New("invalid hash length: must be exactly 16 hex digits")
 	}

 	// Try to parse the hash
 	if _, err := fmt.Sscanf(hash, "%016x", &result); err != nil {
-		return 0
+		return 0, fmt.Errorf("invalid hash format: %v", err)
 	}

-	return result
+	return result, nil
}
📝 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
func ZobristHashToUint64(hash string) uint64 {
var result uint64
// Ensure the input is exactly 16 hex digits
if len(hash) != 16 {
return 0
}
// Try to parse the hash
if _, err := fmt.Sscanf(hash, "%016x", &result); err != nil {
return 0
}
return result
}
func ZobristHashToUint64(hash string) (uint64, error) {
var result uint64
// Ensure the input is exactly 16 hex digits
if len(hash) != 16 {
return 0, errors.New("invalid hash length: must be exactly 16 hex digits")
}
// Try to parse the hash
if _, err := fmt.Sscanf(hash, "%016x", &result); err != nil {
return 0, fmt.Errorf("invalid hash format: %v", err)
}
return result, nil
}

zobrist_test.go Outdated Show resolved Hide resolved
Copy link

codecov bot commented Feb 2, 2025

Codecov Report

Attention: Patch coverage is 81.29032% with 29 lines in your changes missing coverage. Please review.

Project coverage is 73.96%. Comparing base (81d9023) to head (b0939e4).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
polyglot.go 89.56% 9 Missing and 3 partials ⚠️
zobrist.go 60.71% 10 Missing and 1 partial ⚠️
piece.go 50.00% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #17      +/-   ##
==========================================
+ Coverage   73.29%   73.96%   +0.67%     
==========================================
  Files          27       27              
  Lines        4957     5078     +121     
==========================================
+ Hits         3633     3756     +123     
+ Misses       1194     1188       -6     
- Partials      130      134       +4     

☔ 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 (3)
polyglot.go (3)

430-443: Consider optimizing the insertion.

Instead of appending and re-sorting the entire slice, consider using binary search to find the correct insertion point and insert the entry directly at that position.

Apply this diff to optimize the insertion:

 func (book *PolyglotBook) AddMove(positionHash uint64, move Move, weight uint16) {
     entry := PolyglotEntry{
         Key:    positionHash,
         Move:   MoveToPolyglot(move),
         Weight: weight,
         Learn:  0,
     }
-    book.entries = append(book.entries, entry)
-    // Re-sort after adding
-    sort.Slice(book.entries, func(i, j int) bool {
-        return book.entries[i].Key < book.entries[j].Key
-    })
+    // Find insertion point using binary search
+    idx := sort.Search(len(book.entries), func(i int) bool {
+        return book.entries[i].Key >= positionHash
+    })
+    // Insert at the correct position
+    book.entries = append(book.entries, PolyglotEntry{})
+    copy(book.entries[idx+1:], book.entries[idx:])
+    book.entries[idx] = entry
 }

445-459: Consider optimizing the update operation.

Instead of linearly searching through all entries, consider using binary search to find the range of entries with the matching position hash.

Apply this diff to optimize the update operation:

 func (book *PolyglotBook) UpdateMove(positionHash uint64, move Move, newWeight uint16) error {
     target := MoveToPolyglot(move)
-    updated := false
-    for i, entry := range book.entries {
-        if entry.Key == positionHash && entry.Move == target {
-            book.entries[i].Weight = newWeight
-            updated = true
-        }
+    // Find the range of entries with matching position hash
+    idx := sort.Search(len(book.entries), func(i int) bool {
+        return book.entries[i].Key >= positionHash
+    })
+    if idx >= len(book.entries) || book.entries[idx].Key != positionHash {
+        return errors.New("move not found for update")
     }
-    if !updated {
-        return errors.New("move not found for update")
+    // Search through entries with matching position hash
+    for i := idx; i < len(book.entries) && book.entries[i].Key == positionHash; i++ {
+        if book.entries[i].Move == target {
+            book.entries[i].Weight = newWeight
+            return nil
+        }
     }
-    return nil
+    return errors.New("move not found for update")
 }

461-470: Consider optimizing the deletion operation.

Instead of linearly searching through all entries, consider using binary search to find the range of entries to delete.

Apply this diff to optimize the deletion operation:

 func (book *PolyglotBook) DeleteMoves(positionHash uint64) {
-    var newEntries []PolyglotEntry
-    for _, entry := range book.entries {
-        if entry.Key != positionHash {
-            newEntries = append(newEntries, entry)
-        }
+    // Find the range of entries with matching position hash
+    start := sort.Search(len(book.entries), func(i int) bool {
+        return book.entries[i].Key >= positionHash
+    })
+    if start >= len(book.entries) || book.entries[start].Key != positionHash {
+        return
     }
-    book.entries = newEntries
+    // Find the end of the range
+    end := start
+    for end < len(book.entries) && book.entries[end].Key == positionHash {
+        end++
+    }
+    // Remove the range
+    copy(book.entries[start:], book.entries[end:])
+    book.entries = book.entries[:len(book.entries)-(end-start)]
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae33dd and 90dc2ea.

📒 Files selected for processing (2)
  • polyglot.go (3 hunks)
  • polyglot_test.go (4 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
polyglot.go

[warning] 89-94: polyglot.go#L89-L94
Added lines #L89 - L94 were not covered by tests


[warning] 104-105: polyglot.go#L104-L105
Added lines #L104 - L105 were not covered by tests


[warning] 109-109: polyglot.go#L109
Added line #L109 was not covered by tests

🔇 Additional comments (12)
polyglot.go (5)

58-62: LGTM!

The helper struct is well-designed, simple, and focused on its purpose of coupling a chess move with its weight.


64-72: LGTM!

The function correctly encodes chess moves into polyglot format using well-documented bit operations.


74-82: LGTM!

The method correctly encodes polyglot moves using well-documented bit operations, maintaining consistency with MoveToPolyglot.


84-103: LGTM!

The method correctly converts polyglot moves to chess moves, handling file/rank indices, promotion pieces, and castling moves.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 89-94: polyglot.go#L89-L94
Added lines #L89 - L94 were not covered by tests


409-428: LGTM!

The function correctly creates a PolyglotBook from a map, properly converting and sorting the entries.

polyglot_test.go (7)

283-347: LGTM!

The test function is well-structured and covers important scenarios including regular moves, promotion moves, and castling moves.


349-400: LGTM!

The test function thoroughly verifies the bit-level encoding of polyglot moves for various scenarios.


402-458: LGTM!

The test function thoroughly verifies the conversion of polyglot moves to chess moves for various scenarios.


460-504: LGTM!

The test function thoroughly verifies the creation and sorting of polyglot book entries from a map.


506-542: LGTM!

The test function thoroughly verifies adding moves to a polyglot book and ensures proper sorting.


544-582: LGTM!

The test function thoroughly verifies updating moves in a polyglot book, including error cases.


584-616: LGTM!

The test function thoroughly verifies deleting moves from a polyglot book and ensures proper cleanup.

Comment on lines +486 to +498
func (book *PolyglotBook) ToMoveMap() map[uint64][]MoveWithWeight {
result := make(map[uint64][]MoveWithWeight)
for _, entry := range book.entries {
pm := DecodeMove(entry.Move)
move := pm.ToMove()
mw := MoveWithWeight{
Move: move,
Weight: entry.Weight,
}
result[entry.Key] = append(result[entry.Key], mw)
}
return result
}
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

Consider improving error handling.

The method should handle potential errors from ToMove to ensure all moves are valid.

Apply this diff to improve error handling:

-func (book *PolyglotBook) ToMoveMap() map[uint64][]MoveWithWeight {
+func (book *PolyglotBook) ToMoveMap() (map[uint64][]MoveWithWeight, []error) {
     result := make(map[uint64][]MoveWithWeight)
+    var errs []error
     for _, entry := range book.entries {
         pm := DecodeMove(entry.Move)
-        move := pm.ToMove()
+        move, err := pm.ToMove()
+        if err != nil {
+            errs = append(errs, fmt.Errorf("failed to convert move: %w", err))
+            continue
+        }
         mw := MoveWithWeight{
             Move:   move,
             Weight: entry.Weight,
         }
         result[entry.Key] = append(result[entry.Key], mw)
     }
-    return result
+    return result, errs
 }
📝 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
func (book *PolyglotBook) ToMoveMap() map[uint64][]MoveWithWeight {
result := make(map[uint64][]MoveWithWeight)
for _, entry := range book.entries {
pm := DecodeMove(entry.Move)
move := pm.ToMove()
mw := MoveWithWeight{
Move: move,
Weight: entry.Weight,
}
result[entry.Key] = append(result[entry.Key], mw)
}
return result
}
func (book *PolyglotBook) ToMoveMap() (map[uint64][]MoveWithWeight, []error) {
result := make(map[uint64][]MoveWithWeight)
var errs []error
for _, entry := range book.entries {
pm := DecodeMove(entry.Move)
move, err := pm.ToMove()
if err != nil {
errs = append(errs, fmt.Errorf("failed to convert move: %w", err))
continue
}
mw := MoveWithWeight{
Move: move,
Weight: entry.Weight,
}
result[entry.Key] = append(result[entry.Key], mw)
}
return result, errs
}

Comment on lines +472 to +484
func (book *PolyglotBook) GetChessMoves(positionHash uint64) ([]Move, error) {
entries := book.FindMoves(positionHash)
if entries == nil {
return nil, errors.New("no moves found for the given position")
}
var moves []Move
for _, entry := range entries {
pm := DecodeMove(entry.Move)
move := pm.ToMove()
moves = append(moves, move)
}
return moves, nil
}
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

Consider improving error handling.

The method should handle potential errors from ToMove to ensure all moves are valid.

Apply this diff to improve error handling:

-func (book *PolyglotBook) GetChessMoves(positionHash uint64) ([]Move, error) {
+func (book *PolyglotBook) GetChessMoves(positionHash uint64) ([]Move, []error) {
     entries := book.FindMoves(positionHash)
     if entries == nil {
-        return nil, errors.New("no moves found for the given position")
+        return nil, []error{errors.New("no moves found for the given position")}
     }
     var moves []Move
+    var errs []error
     for _, entry := range entries {
         pm := DecodeMove(entry.Move)
-        move := pm.ToMove()
-        moves = append(moves, move)
+        move, err := pm.ToMove()
+        if err != nil {
+            errs = append(errs, fmt.Errorf("failed to convert move: %w", err))
+            continue
+        }
+        moves = append(moves, move)
     }
-    return moves, nil
+    return moves, errs
 }
📝 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
func (book *PolyglotBook) GetChessMoves(positionHash uint64) ([]Move, error) {
entries := book.FindMoves(positionHash)
if entries == nil {
return nil, errors.New("no moves found for the given position")
}
var moves []Move
for _, entry := range entries {
pm := DecodeMove(entry.Move)
move := pm.ToMove()
moves = append(moves, move)
}
return moves, nil
}
func (book *PolyglotBook) GetChessMoves(positionHash uint64) ([]Move, []error) {
entries := book.FindMoves(positionHash)
if entries == nil {
return nil, []error{errors.New("no moves found for the given position")}
}
var moves []Move
var errs []error
for _, entry := range entries {
pm := DecodeMove(entry.Move)
move, err := pm.ToMove()
if err != nil {
errs = append(errs, fmt.Errorf("failed to convert move: %w", err))
continue
}
moves = append(moves, move)
}
return moves, errs
}

Comment on lines +104 to +105
return Move{}
}
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

Consider improving error handling.

Instead of silently returning an empty move on error, consider propagating the error to allow proper error handling by the caller.

Apply this diff to improve error handling:

-func (pm PolyglotMove) ToMove() Move {
+func (pm PolyglotMove) ToMove() (Move, error) {
     fromSquare := fmt.Sprintf("%c%d", 'a'+pm.FromFile, pm.FromRank+1)
     toSquare := fmt.Sprintf("%c%d", 'a'+pm.ToFile, pm.ToRank+1)
     var promo string
     switch pm.Promotion {
     case 1:
         promo = "n"
     case 2:
         promo = "b"
     case 3:
         promo = "r"
     case 4:
         promo = "q"
     default:
         promo = ""
     }
     moveStr := fromSquare + toSquare + promo

     decode, err := UCINotation{}.Decode(nil, moveStr)
     if err != nil {
-        return Move{}
+        return Move{}, fmt.Errorf("failed to decode move: %w", err)
     }

     if pm.CastlingMove {
         if pm.FromFile == 4 && pm.ToFile == 0 {
             decode.addTag(QueenSideCastle)
         } else {
             decode.addTag(KingSideCastle)
         }
     }

-    return *decode
+    return *decode, nil
}
📝 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
return Move{}
}
func (pm PolyglotMove) ToMove() (Move, error) {
fromSquare := fmt.Sprintf("%c%d", 'a'+pm.FromFile, pm.FromRank+1)
toSquare := fmt.Sprintf("%c%d", 'a'+pm.ToFile, pm.ToRank+1)
var promo string
switch pm.Promotion {
case 1:
promo = "n"
case 2:
promo = "b"
case 3:
promo = "r"
case 4:
promo = "q"
default:
promo = ""
}
moveStr := fromSquare + toSquare + promo
decode, err := UCINotation{}.Decode(nil, moveStr)
if err != nil {
return Move{}, fmt.Errorf("failed to decode move: %w", err)
}
if pm.CastlingMove {
if pm.FromFile == 4 && pm.ToFile == 0 {
decode.addTag(QueenSideCastle)
} else {
decode.addTag(KingSideCastle)
}
}
return *decode, nil
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 104-105: polyglot.go#L104-L105
Added lines #L104 - L105 were not covered by tests

Comment on lines +618 to +657
func TestGetChessMoves(t *testing.T) {
pos := uint64(50)
move1 := Move{
s1: D2,
s2: D4,
promo: NoPieceType,
}
move2 := Move{
s1: G7,
s2: H8,
promo: Queen, // promotion move
}
book := &PolyglotBook{
entries: []PolyglotEntry{
{Key: pos, Move: MoveToPolyglot(move1), Weight: 120, Learn: 0},
{Key: pos, Move: MoveToPolyglot(move2), Weight: 220, Learn: 0},
},
}
moves, err := book.GetChessMoves(pos)
if err != nil {
t.Fatalf("GetChessMoves returned error: %v", err)
}
if len(moves) != 2 {
t.Fatalf("Expected 2 moves, got %d", len(moves))
}
// Since the conversion functions (ChessMoveToPolyglot, DecodeMove, PolyglotToChessMove)
// should produce a round-trip, the moves should equal the originals.
if !reflect.DeepEqual(moves[1], move1) {
t.Errorf("First move mismatch: expected %+v, got %+v", move1, moves[0])
}
if !reflect.DeepEqual(moves[0], move2) {
t.Errorf("Second move mismatch: expected %+v, got %+v", move2, moves[1])
}

// Test error case: request moves for a non-existent position.
_, err = book.GetChessMoves(999)
if err == nil {
t.Errorf("Expected error for non-existent position, got nil")
}
}
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

Update test to handle improved error handling.

The test needs to be updated to match the improved error handling in GetChessMoves.

Apply this diff to update the test:

 func TestGetChessMoves(t *testing.T) {
     // ... test setup ...
-    moves, err := book.GetChessMoves(pos)
-    if err != nil {
-        t.Fatalf("GetChessMoves returned error: %v", err)
+    moves, errs := book.GetChessMoves(pos)
+    if len(errs) > 0 {
+        t.Fatalf("GetChessMoves returned errors: %v", errs)
     }
     // ... rest of the test ...

     // Test error case: request moves for a non-existent position.
-    _, err = book.GetChessMoves(999)
-    if err == nil {
-        t.Errorf("Expected error for non-existent position, got nil")
+    _, errs = book.GetChessMoves(999)
+    if len(errs) == 0 {
+        t.Errorf("Expected errors for non-existent position, got none")
     }
 }
📝 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
func TestGetChessMoves(t *testing.T) {
pos := uint64(50)
move1 := Move{
s1: D2,
s2: D4,
promo: NoPieceType,
}
move2 := Move{
s1: G7,
s2: H8,
promo: Queen, // promotion move
}
book := &PolyglotBook{
entries: []PolyglotEntry{
{Key: pos, Move: MoveToPolyglot(move1), Weight: 120, Learn: 0},
{Key: pos, Move: MoveToPolyglot(move2), Weight: 220, Learn: 0},
},
}
moves, err := book.GetChessMoves(pos)
if err != nil {
t.Fatalf("GetChessMoves returned error: %v", err)
}
if len(moves) != 2 {
t.Fatalf("Expected 2 moves, got %d", len(moves))
}
// Since the conversion functions (ChessMoveToPolyglot, DecodeMove, PolyglotToChessMove)
// should produce a round-trip, the moves should equal the originals.
if !reflect.DeepEqual(moves[1], move1) {
t.Errorf("First move mismatch: expected %+v, got %+v", move1, moves[0])
}
if !reflect.DeepEqual(moves[0], move2) {
t.Errorf("Second move mismatch: expected %+v, got %+v", move2, moves[1])
}
// Test error case: request moves for a non-existent position.
_, err = book.GetChessMoves(999)
if err == nil {
t.Errorf("Expected error for non-existent position, got nil")
}
}
func TestGetChessMoves(t *testing.T) {
pos := uint64(50)
move1 := Move{
s1: D2,
s2: D4,
promo: NoPieceType,
}
move2 := Move{
s1: G7,
s2: H8,
promo: Queen, // promotion move
}
book := &PolyglotBook{
entries: []PolyglotEntry{
{Key: pos, Move: MoveToPolyglot(move1), Weight: 120, Learn: 0},
{Key: pos, Move: MoveToPolyglot(move2), Weight: 220, Learn: 0},
},
}
moves, errs := book.GetChessMoves(pos)
if len(errs) > 0 {
t.Fatalf("GetChessMoves returned errors: %v", errs)
}
if len(moves) != 2 {
t.Fatalf("Expected 2 moves, got %d", len(moves))
}
// Since the conversion functions (ChessMoveToPolyglot, DecodeMove, PolyglotToChessMove)
// should produce a round-trip, the moves should equal the originals.
if !reflect.DeepEqual(moves[1], move1) {
t.Errorf("First move mismatch: expected %+v, got %+v", move1, moves[0])
}
if !reflect.DeepEqual(moves[0], move2) {
t.Errorf("Second move mismatch: expected %+v, got %+v", move2, moves[1])
}
// Test error case: request moves for a non-existent position.
_, errs = book.GetChessMoves(999)
if len(errs) == 0 {
t.Errorf("Expected errors for non-existent position, got none")
}
}

Comment on lines +659 to +703
func TestToMoveMap(t *testing.T) {
pos1 := uint64(10)
pos2 := uint64(20)
move1 := Move{
s1: A2,
s2: A3,
promo: NoPieceType,
}
move2 := Move{
s1: A3,
s2: A4,
promo: NoPieceType,
}
move3 := Move{
s1: B2,
s2: B3,
promo: NoPieceType,
}
book := &PolyglotBook{
entries: []PolyglotEntry{
{Key: pos1, Move: MoveToPolyglot(move1), Weight: 50, Learn: 0},
{Key: pos1, Move: MoveToPolyglot(move2), Weight: 75, Learn: 0},
{Key: pos2, Move: MoveToPolyglot(move3), Weight: 100, Learn: 0},
},
}

moveMap := book.ToMoveMap()
if len(moveMap) != 2 {
t.Fatalf("Expected move map with 2 keys, got %d", len(moveMap))
}
movesPos1, ok := moveMap[pos1]
if !ok {
t.Fatalf("Position %d not found in move map", pos1)
}
if len(movesPos1) != 2 {
t.Errorf("Expected 2 moves for position %d, got %d", pos1, len(movesPos1))
}
movesPos2, ok := moveMap[pos2]
if !ok {
t.Fatalf("Position %d not found in move map", pos2)
}
if len(movesPos2) != 1 {
t.Errorf("Expected 1 move for position %d, got %d", pos2, len(movesPos2))
}
}
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

Update test to handle improved error handling.

The test needs to be updated to match the improved error handling in ToMoveMap.

Apply this diff to update the test:

 func TestToMoveMap(t *testing.T) {
     // ... test setup ...
-    moveMap := book.ToMoveMap()
+    moveMap, errs := book.ToMoveMap()
+    if len(errs) > 0 {
+        t.Fatalf("ToMoveMap returned errors: %v", errs)
+    }
     // ... rest of the test ...
 }
📝 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
func TestToMoveMap(t *testing.T) {
pos1 := uint64(10)
pos2 := uint64(20)
move1 := Move{
s1: A2,
s2: A3,
promo: NoPieceType,
}
move2 := Move{
s1: A3,
s2: A4,
promo: NoPieceType,
}
move3 := Move{
s1: B2,
s2: B3,
promo: NoPieceType,
}
book := &PolyglotBook{
entries: []PolyglotEntry{
{Key: pos1, Move: MoveToPolyglot(move1), Weight: 50, Learn: 0},
{Key: pos1, Move: MoveToPolyglot(move2), Weight: 75, Learn: 0},
{Key: pos2, Move: MoveToPolyglot(move3), Weight: 100, Learn: 0},
},
}
moveMap := book.ToMoveMap()
if len(moveMap) != 2 {
t.Fatalf("Expected move map with 2 keys, got %d", len(moveMap))
}
movesPos1, ok := moveMap[pos1]
if !ok {
t.Fatalf("Position %d not found in move map", pos1)
}
if len(movesPos1) != 2 {
t.Errorf("Expected 2 moves for position %d, got %d", pos1, len(movesPos1))
}
movesPos2, ok := moveMap[pos2]
if !ok {
t.Fatalf("Position %d not found in move map", pos2)
}
if len(movesPos2) != 1 {
t.Errorf("Expected 1 move for position %d, got %d", pos2, len(movesPos2))
}
}
func TestToMoveMap(t *testing.T) {
pos1 := uint64(10)
pos2 := uint64(20)
move1 := Move{
s1: A2,
s2: A3,
promo: NoPieceType,
}
move2 := Move{
s1: A3,
s2: A4,
promo: NoPieceType,
}
move3 := Move{
s1: B2,
s2: B3,
promo: NoPieceType,
}
book := &PolyglotBook{
entries: []PolyglotEntry{
{Key: pos1, Move: MoveToPolyglot(move1), Weight: 50, Learn: 0},
{Key: pos1, Move: MoveToPolyglot(move2), Weight: 75, Learn: 0},
{Key: pos2, Move: MoveToPolyglot(move3), Weight: 100, Learn: 0},
},
}
moveMap, errs := book.ToMoveMap()
if len(errs) > 0 {
t.Fatalf("ToMoveMap returned errors: %v", errs)
}
if len(moveMap) != 2 {
t.Fatalf("Expected move map with 2 keys, got %d", len(moveMap))
}
movesPos1, ok := moveMap[pos1]
if !ok {
t.Fatalf("Position %d not found in move map", pos1)
}
if len(movesPos1) != 2 {
t.Errorf("Expected 2 moves for position %d, got %d", pos1, len(movesPos1))
}
movesPos2, ok := moveMap[pos2]
if !ok {
t.Fatalf("Position %d not found in move map", pos2)
}
if len(movesPos2) != 1 {
t.Errorf("Expected 1 move for position %d, got %d", pos2, len(movesPos2))
}
}

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 1

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 90dc2ea and 2f012d3.

📒 Files selected for processing (1)
  • zobrist_test.go (9 hunks)
🧰 Additional context used
🪛 GitHub Check: test
zobrist_test.go

[failure] 246-246:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value


[failure] 257-257:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value


[failure] 265-265:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value

🪛 GitHub Actions: CI
zobrist_test.go

[error] 246-246: Assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value.

🔇 Additional comments (2)
zobrist_test.go (2)

10-10: LGTM! Consistent renaming of hasher initialization.

The changes from NewChessHasher() to NewZobristHasher() are applied consistently across all test cases, maintaining the original test coverage and functionality.

Also applies to: 41-41, 82-82, 110-110, 132-132, 150-150, 183-183, 209-209


243-253: LGTM! Well-structured test cases with good error handling.

The test cases are well-organized and cover essential scenarios:

  • Valid hash conversion
  • Invalid hash format handling
  • Empty hash handling

The error handling aligns with the recommended approach from previous reviews.

Also applies to: 255-261, 263-269

🧰 Tools
🪛 GitHub Check: test

[failure] 246-246:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value

🪛 GitHub Actions: CI

[error] 246-246: Assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value.

zobrist_test.go Outdated
Comment on lines 242 to 270
func TestZobristHashToUint64(t *testing.T) {
t.Run("valid 16-digit hexadecimal hash is converted correctly", func(t *testing.T) {
hash := "463b96181691fc9c"
expected := uint64(0x463b96181691fc9c)
result, err := ZobristHashToUint64(hash)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != expected {
t.Fatalf("expected value %v but got %v", expected, result)
}
})

t.Run("invalid hash format returns error", func(t *testing.T) {
hash := "invalidhash"
_, err := ZobristHashToUint64(hash)
if err == nil {
t.Fatal("expected error for invalid hash format")
}
})

t.Run("empty hash returns error", func(t *testing.T) {
hash := ""
_, err := ZobristHashToUint64(hash)
if err == nil {
t.Fatal("expected error for empty hash")
}
})
}
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 assignment mismatch in test cases.

The test cases expect ZobristHashToUint64 to return (uint64, error), but the function appears to return only one value. This mismatch is causing pipeline failures.

Update the function signature in zobrist.go to match the test expectations:

-func ZobristHashToUint64(hash string) uint64 {
+func ZobristHashToUint64(hash string) (uint64, error) {
📝 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
func TestZobristHashToUint64(t *testing.T) {
t.Run("valid 16-digit hexadecimal hash is converted correctly", func(t *testing.T) {
hash := "463b96181691fc9c"
expected := uint64(0x463b96181691fc9c)
result, err := ZobristHashToUint64(hash)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != expected {
t.Fatalf("expected value %v but got %v", expected, result)
}
})
t.Run("invalid hash format returns error", func(t *testing.T) {
hash := "invalidhash"
_, err := ZobristHashToUint64(hash)
if err == nil {
t.Fatal("expected error for invalid hash format")
}
})
t.Run("empty hash returns error", func(t *testing.T) {
hash := ""
_, err := ZobristHashToUint64(hash)
if err == nil {
t.Fatal("expected error for empty hash")
}
})
}
package zobrist
// ZobristHashToUint64 converts a hexadecimal Zobrist hash string to a uint64 value.
// It returns an error if the hash format is invalid.
func ZobristHashToUint64(hash string) (uint64, error) {
// TODO: Update the implementation to handle error returns.
// For example:
// 1. Validate that the hash string conforms to a 16-digit hexadecimal format.
// 2. Convert the string to uint64 and return any error encountered during conversion.
// Example (dummy implementation):
if len(hash) != 16 {
return 0, fmt.Errorf("invalid hash length")
}
// Assuming proper conversion is done here
var result uint64
// ... conversion logic ...
return result, nil
}
🧰 Tools
🪛 GitHub Check: test

[failure] 246-246:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value


[failure] 257-257:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value


[failure] 265-265:
assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value

🪛 GitHub Actions: CI

[error] 246-246: Assignment mismatch: 2 variables but ZobristHashToUint64 returns 1 value.

@CorentinGS CorentinGS merged commit ca3054d into main Feb 2, 2025
10 of 11 checks passed
@CorentinGS CorentinGS deleted the feat/polyglot_utils branch February 3, 2025 09:04
@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