#include #include #include #include #include #include "utils.h" #include "Board.h" #include "Move.h" #include "search.h" #include "uci.h" static const std::string usage{"usage: pgn2fen [--scored] []"}; // Convert PGN (Portable Game Notation) input stream to single FENs // streamed to stdout void pgn2fen(std::istream& input, const bool only_scored) { // Instantiate Boards, the start of every game as well as the current state // of the Board while processing a PGN game Board startpos, pos; // Read input line by line std::string line; while (std::getline(input, line)) { // Skip empty and metadata lines (every PGN game starts with ".") if (line.empty() || line.front() == '[') { continue; } // Reset position to the start position, every game starts here! pos = startpos; // Read game content (assuming one line is the entire game) std::istringstream game(line); std::string count, san, token, eval; while (game >> count >> san >> token) { // Consume/Parse PGN comments if (only_scored) { // consume the comment and search for an evaluation bool has_score = false; while (game >> token) { // Search for evaluation token (position score _after_ the move) if (token == "[%eval") { game >> eval; eval.pop_back(); // delete trailing ']' has_score = true; // Consume the remainder of the comment (ignore it) std::getline(game, token, '}'); break; } else if (token == "}") { break; } } // In case of not finding an evaluation, skip the game (_not_ an error) if (!has_score) { break; } } else { // Consume the remainder of the comment (ignore it) std::getline(game, token, '}'); } // Perform move bool parseError = false; Move move = UCI::parseSAN(san, pos, parseError); if (parseError) { std::cerr << "[ Error ] Parsing '" << san << "' at position '" << pos.fen() << "' failed." << std::endl; } move = pos.isLegal(move); // validate legality and extend move info if (move) { pos.make(move); } else { std::cerr << "[ Error ] Encountered illegal move '" << san << " (" << move << ") ' at position '" << pos.fen() << "'." << std::endl; break; } // Write positions if (only_scored) { // Ingore "check mate in" scores (not relevant for eval training) // Do this after "make move" in situations where the check mate // was overlooked, leading to new positions if (eval.length() && eval[0] == '#') { continue; } // Otherwise, classic eval score to be parsed in centipawns std::cout << pos.fen() << "; " << eval << '\n'; } else { // Write only the position FEN std::cout << pos.fen() << '\n'; } } } } int main(int argn, char* argv[]) { // Setup control variables bool only_scored = false; std::string file = ""; // Parse command arguments switch (argn) { case 1: break; case 2: if (std::string("--scored") == argv[1]) { only_scored = true; } else { file = argv[1]; } break; case 3: if (std::string("--scored") != argv[1]) { std::cout << usage << std::endl; return 1; } only_scored = true; file = argv[2]; break; default: std::cout << usage << std::endl; return 1; } // Invoke converter ether with file input or stdin if (file == "") { pgn2fen(std::cin, only_scored); } else { // Open input file std::ifstream input(file); if (!input) { std::cerr << "Error opening '" << file << "'" << std::endl; return 1; } pgn2fen(input, only_scored); } return 0; }