# 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
```


