PGN format is based on minimal algebraic notation for moves. Our goal here is to take an input PGN game
and output it as a long algebraic notation (non-PGN) game.
In other words we start with a something like "1.e4 e5 2.Nf3 Nc6 3..." and we want to end up with
"1.e2-e4 e7-e5 2.Ng1-f3 Nb8-c6 3...".
This is the game we have to deal with:
int main(int argc, char *argv[]) { try { // this time we just hard-coded a single game, instead of reading it from file. std::string pgn_game = "[Event \"Olympiad w\"]\n" "[Site \"Bled SLO\"]\n" "[Date \"2002.10.26\"]\n" "[Round \"1\"]\n" "[White \"Dimovski,A\"]\n" "[Black \"Zhao Xue\"]\n" "[Result \"0-1\"]\n" "[WhiteElo \"2100\"]\n" "[BlackElo \"2367\"]\n" "[ECO \"B90\"]\n" "\n" "1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Nxd4 Nf6 5. Nc3 a6 6. Nb3 e6 7. Bd3 Be7 8.\n" "O-O Nbd7 9. f4 b5 10. Qe2 Qc7 11. Bd2 Bb7 12. Nd1 Nc5 13. Nf2 d5 14. e5\n" "Nfe4 15. Ba5 Qc8 16. Nd4 O-O 17. Ng4 Kh8 18. Rf3 f5 19. exf6 gxf6 20. Rh3\n" "Nxd3 21. Qxd3 Bc5 22. Ne3 e5 23. fxe5 fxe5 24. Nf3 Qxh3 25. gxh3 Rxf3 0-1\n\n"; // put it into the game object in the usual way. pgn::Game the_game; std::stringstream ss; ss << pgn_game; ss >> the_game; ... } |
As we would like to print out the game in a non PGN fashion we could start with a game header, keeping
things as simple as possible.
int main(int argc, char *argv[]) { ... // write down the just the players' name game_header_printout(the_game); ... } // print a minimal and ugly game header void game_header_printout(const pgn::Game &game) { // get infos from pgn tag list ... pgn::TagList tags = game.tags(); std::string white_player = tags["White"].value(); std::string black_player = tags["Black"].value(); // ... and print them out std::cout << std::endl << "------------- " << white_player << " - " << black_player << " -------------" << std::endl; } |
Now we have to convert and print the moves; that's not as straightforward as it may seem because we have
very little information about the starting square of a piece on the move in the pgn text.
We need to know where each piece stands on the board before the move is actually made: this is where
the Position class comes in handy.
int main(int argc, char *argv[]) { ... // As usual we get the moves from the game first pgn::MoveList movelist = the_game.moves(); // and then we define a Position object, by default initialized with the initial piece arrangement pgn::Position p; ... } |
We just feed the moves to the Position object, calling the method Position::update().
This function actually updates the position on the board and, as a side effect, fills up the Ply object
received from the caller.
int main(int argc, char *argv[]) { ... for (pgn::MoveList::iterator itr = movelist.begin(); itr != movelist.end(); itr++) { // print the move number on stdout. std::cout << p.moveNumber() << "."; // get the white ply, feed it to the position, get it back completed and print it. pgn::Ply ply; ply = itr->white(); // update the position and get the ply filled up in exchange. p.update(ply); // move_printout converts ply from pgn to algebraic and print it out move_printout(ply); // same stuff for black move ply = itr->black(); if (!ply.valid()) break; // the game could possibly end with a white move (not this game). p.update(ply); move_printout(ply); // six moves for every line, please if ((p.moveNumber()-1) % 6 == 0) std::cout << std::endl; } ... } |
The move_printout routine is in charge of doing the format conversion, that's not difficult.
void move_printout(const pgn::Ply &ply) { // castling output is the same in pgn or algebraic. if (ply.isLongCastle() || ply.isShortCastle()) std::cout << ply << " "; else // normal move conversion std::cout << ply.piece() << ply.fromSquare() << (ply.isCapture() ? "x" : "-") << ply.toSquare() << (ply.isCheckMate() ? "#" : " "); } |
Now we print the game outcome and we are done.
int main(int argc, char *argv[]) { ... // so, who won? std::cout << the_game.result() << std::endl << std::endl; } |
And this is the output of the program:
------------- Dimovski,A - Zhao Xue ------------- 1.e2-e4 c7-c5 2.Ng1-f3 d7-d6 3.d2-d4 c5xd4 4.Nf3xd4 Ng8-f6 5.Nb1-c3 a7-a6 6.Nd4-b3 e7-e6 7.Bf1-d3 Bf8-e7 8.O-O Nb8-d7 9.f2-f4 b7-b5 10.Qd1-e2 Qd8-c7 11.Bc1-d2 Bc8-b7 12.Nc3-d1 Nd7-c5 13.Nd1-f2 d6-d5 14.e4-e5 Nf6-e4 15.Bd2-a5 Qc7-c8 16.Nb3-d4 O-O 17.Nf2-g4 Kg8-h8 18.Rf1-f3 f7-f5 19.e5xf6 g7xf6 20.Rf3-h3 Nc5xd3 21.Qe2xd3 Be7-c5 22.Ng4-e3 e6-e5 23.f4xe5 f6xe5 24.Nd4-f3 Qc8xh3 25.g2xh3 Rf8xf3 0-1
You can find the complete code for all these examples from this tutorial (and a couple more)
into the distribution tarball.
[6. Moves and pieces] | [home page] | [8. A simple xboard extension] |