GoChess is a chess library and toolset written in Go that provides:
- Chess move generation and validation
- PGN (Portable Game Notation) parsing and management
- FEN (Forsyth-Edwards Notation) support
- SQLite-based game database with import/export capabilities
- Chess.com API integration for downloading game archives
- Lichess API integration for downloading game archives
- CLI tools for game management and analysis
Target Users: Chess enthusiasts, developers building chess applications, analysts working with chess databases.
- Separation of Concerns: Chess logic, database, API client, and UI are in separate packages
- Idiomatic Go: Follows Go best practices, uses standard library where possible
- Testing First: Comprehensive test coverage with table-driven tests
- Context-Aware: All I/O operations accept
context.Contextfor cancellation/timeout - Structured Logging: Uses
log/slogfor observable, parseable logs
gochess/
├── cmd/
│ └── gochess/ # Main CLI application
├── internal/
│ ├── board.go # Chess board representation and logic
│ ├── move.go # Move parsing and notation (algebraic, UCI)
│ ├── move_gen.go # Legal move generation
│ ├── fen.go # FEN parsing and validation
│ ├── perft.go # Performance testing for move generation
│ ├── db/ # SQLite database layer
│ ├── pgn/ # PGN parsing and database
│ ├── chesscom/ # Chess.com API client
│ ├── lichess/ # Lichess API client
│ └── logging/ # Structured logging configuration
├── testdata/ # Test fixtures (PGN files, FEN positions)
└── README.md
- Uses bitboards for piece locations (64-bit integers)
- Efficient for move generation and position evaluation
- Pieces stored as:
WP,WN,WB,WR,WQ,WK(white),BP,BN, etc. (black)
- SQLite for portability and zero-config
- Two tables:
games(main data) andtags(metadata) - Game hash (
game_hash) for duplicate detection - Stores complete PGN text for perfect round-tripping
Schema Highlights:
games: id, event, site, date, white, black, result, white_elo, black_elo,
time_control, pgn_text, game_hash, created_at
tags: id, game_id, tag_name, tag_value- All HTTP requests accept
context.Context - All database operations accept
context.Context - Enables graceful cancellation and timeout handling
- Errors wrapped with
fmt.Errorf("...: %w", err)for stack traces - Custom error types:
PGNImportErrorfor import failures with context - Never panic in library code; return errors
- Library code (internal/*): Uses
log/slogwith structured key-value pairs - CLI output (cmd/*): Uses
fmt.Printffor user-facing messages - Tests: Use
logging.Discard()to suppress log noise - Levels: Debug (verbose), Info (operations), Warn (retries), Error (failures)
- Table-Driven Tests: All tests use
tests := []struct{...}pattern - Descriptive Names: Test names explain the scenario being tested
- Temporary Databases: Use
os.MkdirTemp()for isolated test databases - Discard Logger:
logging.Discard()prevents test output pollution
Example:
func TestValidateGameTags(t *testing.T) {
tests := []struct {
name string
game *pgn.Game
wantError bool
errorMsg string
}{
{name: "Valid game", game: validGame, wantError: false},
// ...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateGameTags(tt.game)
// assertions
})
}
}- Single Responsibility: Functions should do one thing
- Example:
ImportPGNwas 192 lines → split into 4 helper functions
- Example:
- Dependency Injection: Constructors accept logger/config
NewWithLogger()for custom loggersNewClientWithLogger()for HTTP client
- Exported vs Unexported:
- Exported: Public API (PascalCase)
- Unexported: Internal helpers (camelCase)
- Files:
snake_case.go(e.g.,move_gen.go,sqlite_test.go) - Packages: Short, lowercase, no underscores (e.g.,
chesscom,logging) - Constructors:
New()orNewWithX()for customization - Tests:
TestFunctionName_ScenarioorTestFunctionNamewith subtests
- Lichess Integration: Complete API client for downloading games from Lichess
- CLI Commands: Added
gochess lichess downloadwith comprehensive filtering options - Date Range Support: Lichess uses date ranges (since/until) instead of monthly archives
- API Token Support: Optional authentication for private games and higher rate limits
- Comprehensive Tests: Full test coverage for Lichess client with 8 test cases
- All high-priority code quality issues resolved
- Test coverage: Comprehensive unit tests for core functionality
- Production-ready: Context support, logging, error handling, retry logic
- Known test failures:
TestImportPGN_WithFEN(pre-existing, unrelated to recent changes)
- Perft Tests: Don't catch insufficient material scenarios (edge case)
- FEN Validation: Some invalid FENs in testdata cause test failures
- Export All Games:
db exportonly exports single game by ID - Rate Limiting: Currently no protection against multiple parallel processes hitting Chess.com API
- The client automatically retries with exponential backoff
- Default: 3 retries, starting at 1s, max 30s backoff
- No internal mutex - multiple client instances may conflict
- Solution: Make requests sequentially (current behavior in
--all-history)
- The client automatically retries with exponential backoff (same as Chess.com)
- Default: 3 retries, starting at 1s, max 30s backoff
- Uses date range filtering instead of monthly archives
- Optional API token for private games and higher rate limits
- Duplicate Detection: Uses hash of moves + metadata
- FEN Handling: Custom FENs are ignored; standard starting position assumed
- Error Handling: Imports continue even if some games fail
- Transaction Safety: All-or-nothing at file level (commit only on success)
game_hashcolumn added viaaddColumnIfNotExists()for backward compatibility- Existing databases are automatically migrated on first connection
- No explicit migration system; schema changes handled in
createTables()
- Bitboard Squares: 0-63, rank 1 = 0-7, rank 8 = 56-63
- Piece Constants: Don't confuse
WB(white bishop) withBB(black bishop) - Move Generation: Does not check for insufficient material (use
HasInsufficientMaterial())
- Database tests create real SQLite files (not mocked)
- HTTP client tests use
httptest.Serverfor realistic simulation - Perft tests are slow at depth 6+ (use
-shortflag to skip)
// WRONG: Using logger for user-facing messages
logger.Info("Imported 42 games") // Goes to stderr, not formatted
// RIGHT: Use fmt for CLI output
fmt.Printf("Imported %d games\n", count)
// RIGHT: Use logger for operations/debugging
logger.Info("import completed", "count", count, "errors", len(errs))// CLI commands: Use c.Context from urfave/cli
func ImportCommand(c *cli.Context) error {
db.ImportPGN(c.Context, path) // ✓
}
// HTTP clients: Use context from caller
client.GetPlayerGames(ctx, username, year, month) // ✓
// Tests: Usually context.Background()
db.ImportPGN(context.Background(), testFile) // ✓go build ./... # Build all packages
go build ./cmd/gochess # Build main CLIgo test ./... # Run all tests
go test ./internal/db -v # Verbose output for specific package
go test -short ./... # Skip slow tests (perft)
go test -run TestImportPGN ./internal/db # Run specific test# CLI examples
./gochess db import --pgn games.pgn --database ~/.gochess/games.db
./gochess chesscom download --username player --year 2024 --month 12 --import-db
./gochess db list --database ~/.gochess/games.db- Descriptive commit messages with context
- Include "Generated with Claude Code" footer for AI-assisted changes
- Co-Authored-By: Claude for AI contributions
- Commit related changes together (not per-file)
- Add method to
DBstruct ininternal/db/sqlite.go - Accept
context.Contextas first parameter - Use
db.loggerfor structured logging - Return errors, don't panic
- Write table-driven tests in
internal/db/sqlite_test.go - Use
logging.Discard()in tests
- Define response struct in
internal/chesscom/models.go - Add method to
Clientininternal/chesscom/client.go - Use
doRequestWithRetry()for automatic 429 handling - Log with
c.logger.Info()before/after requests - Add tests in
internal/chesscom/client_test.gowithhttptest
- Define request params in
internal/lichess/models.go(if needed) - Add method to
Clientininternal/lichess/client.go - Use
doRequestWithRetry()for automatic 429 handling - Log with
c.logger.Info()before/after requests - Add tests in
internal/lichess/client_test.gowithhttptest
- Define command in
cmd/gochess/main.go - Use urfave/cli/v2 framework
- User output:
fmt.Printf()for messages - Pass
c.Contextto database/HTTP operations - Handle errors gracefully with helpful messages
- CLI Framework:
github.com/urfave/cli/v2 - SQLite Driver:
github.com/mattn/go-sqlite3(requires CGO) - Testing:
github.com/stretchr/testify(assertions)
Standard Library:
log/slog- Structured loggingcontext- Cancellation/timeoutsnet/http- HTTP clientdatabase/sql- Database abstraction
- Move Generation: ~1-2 million positions/second (depth 5 perft)
- PGN Import: ~1000-5000 games/second (depends on game length)
- Database Queries: Fast with proper indexing (indexed on white, black, date, event)
- Chess.com API: Rate limited; sequential requests are unlimited
| Need to... | Look in... |
|---|---|
| Modify board logic | internal/board.go |
| Add move generation | internal/move_gen.go |
| Parse PGN files | internal/pgn/parse.go |
| Database operations | internal/db/sqlite.go |
| Chess.com API | internal/chesscom/client.go |
| Lichess API | internal/lichess/client.go |
| Logging config | internal/logging/logger.go |
| CLI commands | cmd/gochess/main.go |
| Test fixtures | testdata/ |
- Go Version: 1.23+ required (uses new features)
- Toolchain: go1.24.1 or later
- Platform: Cross-platform (tested on macOS, should work on Linux/Windows)
- CGO: Required for SQLite (mattn/go-sqlite3)
- Code Comments: Most complex functions have detailed comments
- Tests as Documentation: See
*_test.gofiles for usage examples - This File: Update when making architectural changes
- Git History: Well-documented commits explain "why" behind changes
Last Updated: 2025-12-09 (after adding Lichess integration)