Page
Library
Module
Module type
Parameter
Class
Class type
Source
A PGN (Portable Game Notation) parser for chess games written in OCaml. This parser can parse chess games from platforms like Lichess and other chess websites.
Move Parsing: Parse chess moves including:
API Integration:
Lichess API: Fetch and parse real games from Lichess
Chess.com API: Fetch games, player stats, tournaments, and daily puzzles
Advanced Features:
git clone https://github.com/Ckaf/pgn_parser.git
cd pgn_parser
opam install qcheck
dune build
Run all tests:
dune runtest
This will run:
Run various demo programs:
# Basic PGN parsing demo
dune exec examples/simple_demo
# Board visualization demo
dune exec examples/board_demo
# Zobrist hash demo
dune exec examples/zobrist_demo
# Lichess API demo
dune exec examples/lichess_demo
# Chess.com API demo
dune exec examples/chess_com_demo
# PGN parsing demo
dune exec examples/pgn_demo
Run all tests:
dune runtest
Run specific test suites:
# Property-based tests
dune exec test/test_pgn_parser
# Zobrist hash tests
dune exec test/test_zobrist
# Advanced move parsing tests
dune exec test/test_advanced_moves
# Unfinished games tests
dune exec test/test_unfinished_games
# Lichess API tests (online)
dune exec test/test_lichess_api
# Lichess API tests (offline)
dune exec test/test_lichess_api_offline
# Chess.com API tests (online)
dune exec test/test_chess_com_api
# Chess.com API tests (offline)
dune exec test/test_chess_com_api_offline
# Integration tests
dune exec test/test_integration
open Pgn_parser
let pgn = "[Event \"Test Game\"]\n[White \"Player1\"]\n[Black \"Player2\"]\n\n1. 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. Nc3 Bb7 12. Bc2 Re8 13. Nf1 Bf8 14. Ng3 g6 15. a4 c5 16. d5 c4 17. Bg5 h6 18. Be3 Nc5 19. Qd2 Kh7 20. Rae1 Qd7 21. Bg5 Bg7 22. f3 Rae8 23. Kh2 Qf7 24. Nf1 f5 25. exf5 gxf5 26. f4 exf4 27. Bxf4 Qe7 28. Qe2 Qe5 29. Qxe5 dxe5 30. Be3 f4 31. Bf2 e4 32. Ng1 Bc8 33. N1e2 Bd7 34. b4 cxb3 35. Bxb3 Bc5 36. Nc3 Bb6 37. Ncd5 Bxd5 38. Nxd5 Re5 39. c4 bxc4 40. Bxc4 Rg5 41. Bf1 Rxg2+ 42. Kxg2 e3+ 43. Kg1 e2 44. Bxe2 f3 45. Bxf3 Nxf3+ 46. Kf2 Nxd2 47. Nc7 Nxf1 48. Kf1 Rf8+ 49. Ke1 Rf2 50. Kd1 Rd2+ 51. Kc1 Rd1#"
match parse_game pgn with
| Ok game ->
Printf.printf "White: %s\n" (match game.info.white with Some w -> w.name | None -> "Unknown");
Printf.printf "Black: %s\n" (match game.info.black with Some b -> b.name | None -> "Unknown");
Printf.printf "Moves: %d\n" (List.length game.moves);
Printf.printf "Result: %s\n" (match game.info.result with
| Some r -> (match r with WhiteWin -> "1-0" | BlackWin -> "0-1" | Draw -> "1/2-1/2" | Ongoing -> "*")
| None -> "None")
| Error e ->
Printf.printf "Parse error: %s\n" (match e with
| InvalidMove s -> "Invalid move: " ^ s
| InvalidTag s -> "Invalid tag: " ^ s
| InvalidFormat s -> "Invalid format: " ^ s
| UnexpectedEnd s -> "Unexpected end: " ^ s)
The parser supports complex chess moves including:
open Pgn_parser
(* Test various move types *)
let test_moves = [
"e4"; (* Basic pawn move *)
"Nf3"; (* Basic piece move *)
"Bxe4"; (* Capture *)
"O-O"; (* Kingside castling *)
"O-O+"; (* Castling with check *)
"O-O-O#"; (* Queenside castling with checkmate *)
"e8=Q"; (* Promotion *)
"e8=Q+"; (* Promotion with check *)
"exd8=Q#"; (* Capture promotion with checkmate *)
"exd6e.p."; (* En passant with explicit notation *)
"exd6ep"; (* En passant with short notation *)
"Rae1"; (* File disambiguation *)
"R1e1"; (* Rank disambiguation *)
"Ra1e1"; (* Full disambiguation *)
"Nbd7"; (* Knight file disambiguation *)
"N1d7"; (* Knight rank disambiguation *)
"Nb1d7"; (* Knight full disambiguation *)
]
(* Parse each move *)
List.iter (fun move_str ->
match parse_simple_move move_str with
| Ok move -> Printf.printf "✅ %s\n" move_str
| Error e -> Printf.printf "❌ %s: %s\n" move_str (match e with InvalidMove s -> s | _ -> "Error")
) test_moves
The parser provides robust error handling for invalid moves:
open Pgn_parser
(* Test invalid moves *)
let invalid_moves = [
""; (* Empty move *)
"X"; (* Invalid piece *)
"i1"; (* Invalid file *)
"a9"; (* Invalid rank *)
"K"; (* Incomplete move *)
"O-O-O-O"; (* Invalid castling *)
"e8=P"; (* Invalid promotion piece *)
"e8=K"; (* Invalid promotion piece *)
]
(* All should fail validation *)
List.iter (fun move_str ->
match parse_simple_move move_str with
| Ok _ -> Printf.printf "⚠️ %s (unexpectedly succeeded)\n" move_str
| Error _ -> Printf.printf "✅ %s (correctly failed)\n" move_str
) invalid_moves
The parser now includes Zobrist hashing for efficient position comparison:
open Pgn_parser
(* Create starting position *)
let board = create_starting_position ()
let hash = calculate_zobrist_hash board
(* Apply a move *)
let e4_move = Normal (Pawn, ('e', 2), ('e', 4))
let new_board = apply_move_to_board board e4_move true
let new_hash = calculate_zobrist_hash new_board
(* Compare positions *)
let positions_are_equal = positions_equal board new_board
let hashes_are_equal = zobrist_equal hash new_hash
(* Each move in parsed games includes board state and Zobrist hash *)
match parse_game pgn with
| Ok game ->
List.iter (fun move ->
match move.zobrist_after_white with
| Some hash -> Printf.printf "Position hash: %Ld\n" hash
| None -> ()
) game.moves
| Error _ -> ()
The parser includes functions for visualizing chess positions:
open Pgn_parser
(* Print a board position *)
let board = create_starting_position ()
print_board board
(* Get board as string *)
let board_str = board_to_string board
(* Get board position after specific move *)
let board_after_e4 = get_board_after_move game.moves 1 true
(* Get final board position *)
let final_board = get_final_board game.moves
(* Visualize entire game progression *)
let visualization = visualize_game_progression game
Run the examples:
# Basic PGN parsing demo
dune exec examples/pgn_demo
# Board visualization demo
dune exec examples/board_demo
# Zobrist hash demo
dune exec examples/zobrist_demo
# Lichess API demo
dune exec examples/lichess_demo
## API Integration
### Lichess API Integration
The parser includes integration with the Lichess API to fetch and parse real chess games:
open Lwt.Syntax
open Pgn_parser
open Lichess_api
(* Fetch a random game from Lichess *)
let* game_opt = fetch_random_game () in
match game_opt with
| Some game ->
Printf.printf "Game: %s vs %s\n" game.white game.black;
Printf.printf "PGN length: %d characters\n" (String.length game.pgn);
(* Parse the PGN *)
match parse_game game.pgn with
| Ok parsed_game ->
Printf.printf "Parsed %d moves\n" (List.length parsed_game.moves)
| Error e -> Printf.printf "Parse error\n"
| None -> Printf.printf "No game found\n"
The parser also includes integration with the Chess.com API:
open Lwt.Syntax
open Pgn_parser
open Chess_com_api
(* Fetch player games from Chess.com *)
let* games = fetch_player_games "hikaru" ~max_games:5 () in
List.iter (fun game ->
Printf.printf "Game: %s vs %s (%s)\n" game.white game.black game.speed
) games
(* Get player statistics *)
let* stats_opt = get_player_stats "hikaru" in
match stats_opt with
| Some stats ->
Printf.printf "Win rate: %.1f%%\n" (stats.win_rate *. 100.0)
| None -> Printf.printf "Stats not available\n"
Lichess API:
Chess.com API:
The current implementation has some limitations:
The project uses comprehensive testing approaches:
Uses QCheck2 to generate random chess data and test parser properties:
Comprehensive testing of complex chess moves:
Tests with real chess games from multiple platforms:
No hardcoded test data - all tests use either:
Run all tests:
dune runtest # All tests
dune exec test/test_pgn_parser # Property-based tests
dune exec test/test_zobrist # Zobrist hash tests
dune exec test/test_advanced_moves # Advanced move parsing tests
dune exec test/test_unfinished_games # Unfinished games tests
The project uses GitHub Actions for continuous integration and deployment:
ci.yml
): Main workflow for testing, linting, security checks, and releasesquick-check.yml
): Fast tests for feature branchescompatibility.yml
): Tests with different OCaml versionsperformance.yml
): Performance and memory usage checkscache.yml
): OPAM dependency cachingocamlformat
This project is licensed under the MIT License.