pgn_filter 1.0.0

For searching/filtering pgn files of chess games.
Documentation

pgn_filter

For searching/filtering pgn files of chess games.

Features

  • Read and save collections of PGN files
  • Filter games looking for games containing positions with certain combinations of pieces
  • Save collections of PGN games or individual positions after filtering.
  • PGN lexer follows specification in https://www.chessclub.com/help/PGN-spec
    • note that the lexer recognises but ignores comments and NAGs, so they will not appear in any resaved games

Position Definitions

Games are stored in a collection of type pgn_filter::Games, and can be filtered using the search method. The search method takes a BoardFilter definition, which specifies the permitted number of pieces of each type, and returns a new collection containing just those games which match the definition.

For example, the following filter matches 5-4 rook-and-pawn endings, i.e. those with 5 white pawns, 4 black pawns and a rook on either side:

let filter = pgn_filter::Board::must_have()
    .exactly(1, "R")
    .exactly(1, "r")
    .exactly(5, "P")
    .exactly(4, "p");

Notice that the filter is constructed from a base filter obtained by calling "must_have". This base filter recognises positions with exactly one king of each colour, and no pieces or pawns. You build up the filter by adding a relation for each of the pieces you require. If you add a second filter for a given piece, the second filter will replace the first one.

The available filters are:

  • "exactly(n, piece)"
  • "at_least(n, piece)"
  • "at_most(n, piece)"

Example: Extracting all 5-4 R+P endings

The following example shows how to construct a filter to identify 5-4 rook endings, and save the selected games to a file.

// Create a database and add some games
let mut db = pgn_filter::Games::new();
db.add_games("examples/twic1356.pgn").unwrap();

// Display some information
println!("Database has {} game(s)", db.iter().count());

// Create a position definition for 5-4 rook endings
let filter = pgn_filter::Board::must_have()
    .exactly(1, "R")
    .exactly(1, "r")
    .exactly(5, "P")
    .exactly(4, "p");

// Extract those games which at some point reach 
let selected = db.search(&filter);
selected.to_file("rook-endings.pgn").unwrap();

// Report and save result
println!("Selected {} out of {} games", selected.iter().count(), db.iter().count());

Example: Study a game move-by-move

The Game#play_game method takes a closure/function, which is passed the current board and next move after each half move. The following example prints out the board position and information to create a trace of the game:

let fischer = pgn_filter::Games::from_file("examples/fischer.pgn").unwrap();

println!("File has {} game(s)", fischer.games.len());

if fischer.games.len() > 0 {
    let game = &fischer.games[0];

    game.play_game(|board, next_move| {
        println!("{}", board.to_string());
        match next_move {
            Some(mv) => {
                // next move in the game
                println!(
                    "Move {}: {}{}",
                    board.fullmove_number,
                    if board.white_to_move { "" } else { "..." },
                    mv
                );
                println!("");
            }
            None => {
                // game over, so display the result
                println!("Result: {}", game.header.get("Result").unwrap());
            }
        };
    });
}

Sample output:

File has 1 game(s)
rnbqkbnr
pppppppp
........
........
........
........
PPPPPPPP
RNBQKBNR

Move 1: e4

rnbqkbnr
pppppppp
........
........
....P...
........
PPPP.PPP
RNBQKBNR

Move 1: ...e5

rnbqkbnr
pppp.ppp
........
....p...
....P...
........
PPPP.PPP
RNBQKBNR

Move 2: Nf3

rnbqkbnr
pppp.ppp
........
....p...
....P...
.....N..
PPPP.PPP
RNBQKB.R

Move 2: ...Nc6

r.bqkbnr
pppp.ppp
..n.....
....p...
....P...
.....N..
PPPP.PPP
RNBQKB.R

Move 3: Bb5