FsChessPgn

F# chess tools including PGN

View on GitHub

Home Types Core Functions PGN Functions WinForms

PGN reading, writing and statistics

The PGN related facilities are included in the namespace FSChess.Pgn in the module Games.

Reading

You can either load a set of Games in its entirety into an F# List or can load them lazily using an F# Sequence. These are the relevant functions:

Function Type Description
ReadListFromFile string -> Game list Given a PGN file name, read this into a list of Games
ReadSeqFromFile string -> Seq Given a PGN file name, read this into a lazy sequence of Games

Here is some sample code illustrating how to use these options:


#load "setup.fsx"
open FsChess.Pgn

let pgn = __SOURCE_DIRECTORY__ + "/data/pgn/kasparov-deep-blue-1997.pgn"

//loads all games
let gml = Games.ReadListFromFile(pgn)
let count = gml.Length

//does not load any game yet
let gms = Games.ReadSeqFromFile(pgn)

//Just loads first game
let first_game = gms|>Seq.head
let event = first_game.Event

This produces these results:

val gml : FsChess.Types.Game list =
  [moves: ...Rd5 44. f6 Rd1 45. g7 1-0; moves: ...Qb6+ 44. Kf1 Rb8 45. Ra6 1-0;
   moves: ...Kh7 47. Ra3 Kh8 48. Ra6 1/2-1/2;
   moves: ...Re4 55. a4 Kb3 56. Kc1 1/2-1/2;
   moves: ...48. g6 Kxb5 49. g7 Kb4 1/2-1/2;
   moves: ...exf5 18. Rxe7 Bxe7 19. c4 1-0]
val count : int = 6
val gms : seq<FsChess.Types.Game>
val first_game : FsChess.Types.Game = moves: ...Rd5 44. f6 Rd1 45. g7 1-0
val event : string = "IBM Man-Machine, New York USA"

Writing

When you have a list of games you can write it to a file using this function:

Function Type Description
WriteFile string -> Game list-> unit Given a PGN file name, write this list of Games to that file

Here is some sample code illustrating how to use this function:


#load "setup.fsx"
open FsChess.Pgn

let pgn = __SOURCE_DIRECTORY__ + "/data/pgn/kasparov-deep-blue-1997.pgn"

//loads all games
let gml = Games.ReadListFromFile(pgn)

let gml2 = gml.[0..1]
let pgn2 = "c:/temp/test.pgn"

gml2|>Games.WriteFile pgn2

This produces these results:

val gml : FsChess.Types.Game list =
  [moves: ...Rd5 44. f6 Rd1 45. g7 1-0; moves: ...Qb6+ 44. Kf1 Rb8 45. Ra6 1-0;
   moves: ...Kh7 47. Ra3 Kh8 48. Ra6 1/2-1/2;
   moves: ...Re4 55. a4 Kb3 56. Kc1 1/2-1/2;
   moves: ...48. g6 Kxb5 49. g7 Kb4 1/2-1/2;
   moves: ...exf5 18. Rxe7 Bxe7 19. c4 1-0]
val gml2 : FsChess.Types.Game list =
  [moves: ...Rd5 44. f6 Rd1 45. g7 1-0; moves: ...Qb6+ 44. Kf1 Rb8 45. Ra6 1-0]
val pgn2 : string = "c:/temp/test.pgn"
val it : unit = ()

A file containing the first 2 games is created as test.pgn.

Statistics

Working with individual games from a PGN file is done using the finctions in FsChess in module Game. In addition functions relating to all the games in a PGN file are documented here.

For small PGN files we provide a simple way to search for a board using the function FindBoard in the module Games. The results can then be summarised using Get from the module Stats.

Function Type Description
FindBoard Brd -> string -> (int * Game * string) list Finds the Games that containing the specified Board
Get (int * Game * string) list -> BrdStats Get Statistics for the Board

This is illustrated by this code:


#load "setup.fsx"
open FsChess.Pgn
open FsChess

let pgn = __SOURCE_DIRECTORY__ + "/data/pgn/french_Qd7.pgn"

//set seach board
let fen = "r1b1kbnr/1pq2ppp/p3p3/8/2BN4/8/PPP2PPP/R1BQ1RK1 w kq - 1 11"
let bd = fen|>Board.FromStr

let gmmvl = Games.FindBoard bd pgn

let len = gmmvl.Length

let stats = gmmvl|>Stats.Get

This produces these results:

val pgn : string = "D:\GitHub\FsChessPgn\scripts/data/pgn/french_Qd7.pgn"
val fen : string =
  "r1b1kbnr/1pq2ppp/p3p3/8/2BN4/8/PPP2PPP/R1BQ1RK1 w kq - 1 11"
val bd : FsChess.Types.Brd =
  r1b1kbnr/1pq2ppp/p3p3/8/2BN4/8/PPP2PPP/R1BQ1RK1 w kq - 1 11
val gmmvl : (int * FsChess.Types.Game * string) list =
  [(2, moves: ...Kxh8 45. Qxg6 Rc4 46. Qe8+ 1/2-1/2, "Qe2");
   (8, moves: ...Rxb7 38. Nxb7 Kxb7 39. g4 1-0, "Bb3");
   (11, moves: ...61. Bf3 Kd7 62. Bd5 Kc7 1/2-1/2, "Bb3");
   (22, moves: ...18. Qg2 Ne5 19. Be2 Bc5 1/2-1/2, "Qe2");
   (28, moves: ...d4 51. Nxf3 d3 52. Rb4+ 1-0, "Bd3");
   (30, moves: ...Rfd8 21. Rg4 g6 22. Rxg6+ 1-0, "Bb3");
   (32, moves: ...Qxc4 19. Bxc4 Bf4 20. Bxf4 1/2-1/2, "Qe2");
   (33, moves: ...Qe7 39. f4 exf3 40. Qh4+ 1-0, "Bb3");
   (40, moves: ...34. Kg2 Rd2+ 35. Kh1 Rd1+ 1/2-1/2, "Bd3");
   (44, moves: ...25. Rxe6 Qxb3 26. Qxg4 Rxd4 0-1, "Bb3");
   (46, moves: ...26. Ba7 Qg3 27. Bf2 Qb8 1/2-1/2, "Qe2");
   (48, moves: ...Kd7 36. Rc1 Ra8 37. Rxa8 1/2-1/2, "Qe2");
   (54, moves: ...40. Rg2 Rf4 41. Re2 Rf1+ 0-1, "Bb3");
   (61, moves: ...60. c6 bxc6 61. Ra5 Kxf2 0-1, "Bb3");
   (64, moves: ...40. Kd4 Ra6 41. Kd3 Kxc5 0-1, "Bb3");
   (70, moves: ...Ke6 45. Nc7+ Kd6 46. Na6 1/2-1/2, "Qe2");
   (72, moves: ...b3 62. Bxb3 axb3 63. axb3 1-0, "Qe2");
   (74, moves: ...55. Rxc5 Kxc5 56. h4 e5 0-1, "Bb3")]
val len : int = 18
val stats : FsChess.Types.BrdStats =
  |  Move  | Count | Percent | WhiteWins |  Draws  | BlackWins |  Score  | DrawPc  |
  |--------|-------|---------|-----------|---------|-----------|---------|---------|
  | Bb3    |  9    |  50.0%  |  3        |  1      |  5        |  38.9%  |  11.1%  |
  | Qe2    |  7    |  38.9%  |  1        |  6      |  0        |  57.1%  |  85.7%  |
  | Bd3    |  2    |  11.1%  |  1        |  1      |  0        |  75.0%  |  50.0%  |
  |--------|-------|---------|-----------|---------|-----------|---------|---------|
  | TOTAL  |  18   |  100.0% |  5        |  8      |  5        |  50.0%  |  44.4%  |

For larger PGN files the above approach is slow. Instead a much quicker process uses these functions:

Function Type Description
CreateIndex string -> unit Creates index on PGN for fast searches
GetIndex string -> Dictionary<Set,int list> Get Statistics for the Board
ReadIndexListFromFile string -> (int * Game) list Get a list of index * Game from a file
FastFindBoard Brd -> Dictionary<Set,int list> -> (int * Game) list -> (int * Game * string) list Does a fast search using the Index and the index list of Games

You first create an index for the PGN file using CreateIndex. This creates a file with the same name as the PGN file, but with a “.bin” extension. This only needs to be done once until the PGN file is changed.

You then load this index using GetIndex and then load the games but with their index for easier processing using ReadIndexListFromFile.

You can then carry out multiple quick searches against different Boards using FastFindBoard.

The results can be summarised in the same way as with the slower approach.

This is illustrated by this code:


#load "setup.fsx"
open FsChess.Pgn
open FsChess

let pgn = __SOURCE_DIRECTORY__ + "/data/pgn/french_Qd7.pgn"
//only needs to be done once
pgn|>Games.CreateIndex

//set seach board
let fen = "r1b1kbnr/1pq2ppp/p3p3/8/2BN4/8/PPP2PPP/R1BQ1RK1 w kq - 1 11"
let bd = fen|>Board.FromStr

let indx = pgn|>Games.GetIndex
let igms = pgn|>Games.ReadIndexListFromFile

let gmmvl = Games.FastFindBoard bd indx igms

let len = gmmvl.Length

let stats = gmmvl|>Stats.Get

This produces these results:

val pgn : string = "D:\GitHub\FsChessPgn\scripts/data/pgn/french_Qd7.pgn"
val fen : string =
  "r1b1kbnr/1pq2ppp/p3p3/8/2BN4/8/PPP2PPP/R1BQ1RK1 w kq - 1 11"
val bd : FsChess.Types.Brd =
  r1b1kbnr/1pq2ppp/p3p3/8/2BN4/8/PPP2PPP/R1BQ1RK1 w kq - 1 11
val indx :
  System.Collections.Generic.Dictionary<Set<FsChess.Types.Square>,int list> =
  dict
    [(set [a2], []); (set [b2], []); (set [c2], []); (set [d2], [50; 15; 9]);
     (set [e2],
      [74; 73; 72; 71; 70; 69; 68; 67; 66; 65; 64; 63; 62; 61; 60; 59; 58; 57;
       56; 55; 54; 53; 52; 51; 49; 48; 47; 46; 45; 44; 43; 42; 41; 40; 39; 38;
       37; 36; 35; 34; 33; 32; 31; 30; 29; 28; 27; 26; 25; 24; 23; 22; 21; 20;
       19; 18; 17; 16; 14; 13; 12; 11; 10; 8; 7; 6; 5; 4; 3; 2; 1; 0]);
     (set [f2], []); (set [g2], []); (set [h2], []); (set [a7], []);
     (set [b7], []); (set [c7], []); (set [d7], []); (set [e7], []);
     (set [f7], []); (set [g7], []); (set [h7], []); (set [a2; b2], []);
     (set [a2; c2], []); (set [a2; d2], []); (set [a2; e2], []);
     (set [a2; f2], []); (set [a2; g2], []); (set [a2; h2], []);
     ...
val igms : (int * FsChess.Types.Game) list =
  [(0, moves: ...Rb1 78. Kc7 Rxb7+ 79. Kxb7 1/2-1/2);
   (1, moves: ...Kh6 99. Rf6+ Kxg7 100. f8=Q+ 1-0);
   (2, moves: ...Kxh8 45. Qxg6 Rc4 46. Qe8+ 1/2-1/2);
   (3, moves: ...36. h3 hxg4 37. hxg4 Rg7 0-1);
   (4, moves: ...35. Rc7 Bxg2+ 36. Ke1 Bf3 0-1);
   (5, moves: ...Qb1+ 47. Kh2 Qg6 48. Qh8# 1-0);
   (6, moves: ...29. Re2 Rd8 30. Re4 Rd2 1/2-1/2);
   ...
val gmmvl : (int * FsChess.Types.Game * string) list =
  [(2, moves: ...Kxh8 45. Qxg6 Rc4 46. Qe8+ 1/2-1/2, "Qe2");
   (8, moves: ...Rxb7 38. Nxb7 Kxb7 39. g4 1-0, "Bb3");
   (11, moves: ...61. Bf3 Kd7 62. Bd5 Kc7 1/2-1/2, "Bb3");
   (22, moves: ...18. Qg2 Ne5 19. Be2 Bc5 1/2-1/2, "Qe2");
   (28, moves: ...d4 51. Nxf3 d3 52. Rb4+ 1-0, "Bd3");
   (30, moves: ...Rfd8 21. Rg4 g6 22. Rxg6+ 1-0, "Bb3");
   (32, moves: ...Qxc4 19. Bxc4 Bf4 20. Bxf4 1/2-1/2, "Qe2");
   (33, moves: ...Qe7 39. f4 exf3 40. Qh4+ 1-0, "Bb3");
   (40, moves: ...34. Kg2 Rd2+ 35. Kh1 Rd1+ 1/2-1/2, "Bd3");
   (44, moves: ...25. Rxe6 Qxb3 26. Qxg4 Rxd4 0-1, "Bb3");
   (46, moves: ...26. Ba7 Qg3 27. Bf2 Qb8 1/2-1/2, "Qe2");
   (48, moves: ...Kd7 36. Rc1 Ra8 37. Rxa8 1/2-1/2, "Qe2");
   (54, moves: ...40. Rg2 Rf4 41. Re2 Rf1+ 0-1, "Bb3");
   (61, moves: ...60. c6 bxc6 61. Ra5 Kxf2 0-1, "Bb3");
   (64, moves: ...40. Kd4 Ra6 41. Kd3 Kxc5 0-1, "Bb3");
   (70, moves: ...Ke6 45. Nc7+ Kd6 46. Na6 1/2-1/2, "Qe2");
   (72, moves: ...b3 62. Bxb3 axb3 63. axb3 1-0, "Qe2");
   (74, moves: ...55. Rxc5 Kxc5 56. h4 e5 0-1, "Bb3")]
val len : int = 18
val stats : FsChess.Types.BrdStats =
  |  Move  | Count | Percent | WhiteWins |  Draws  | BlackWins |  Score  | DrawPc  |
  |--------|-------|---------|-----------|---------|-----------|---------|---------|
  | Bb3    |  9    |  50.0%  |  3        |  1      |  5        |  38.9%  |  11.1%  |
  | Qe2    |  7    |  38.9%  |  1        |  6      |  0        |  57.1%  |  85.7%  |
  | Bd3    |  2    |  11.1%  |  1        |  1      |  0        |  75.0%  |  50.0%  |
  |--------|-------|---------|-----------|---------|-----------|---------|---------|
  | TOTAL  |  18   |  100.0% |  5        |  8      |  5        |  50.0%  |  44.4%  |

TODO