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