#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] [--rating ] [--ply ] []"}; // Convert PGN (Portable Game Notation) input stream to single FENs // streamed to stdout void pgn2fen( std::istream& input, const bool scored, const unsigned long rating, const unsigned long ply ) { // Instantiate Boards, the start of every game as well as the current state // of the Board while processing a PGN game Board startpos, pos; // Parse white and black ELO ratings unsigned long whiteElo = 0; unsigned long blackElo = 0; // Read input line by line std::string line; while (std::getline(input, line)) { // read rating metadata lines if (rating != static_cast(-1)) { // [WhiteElo "1111"] // [BlackElo "999"] try { if (line.rfind("[WhiteElo \"", 0) != std::string::npos) { whiteElo = std::stoul(line.substr(11)); } else if (line.rfind("[BlackElo \"", 0) != std::string::npos) { blackElo = std::stoul(line.substr(11)); } } catch (...) { std::cerr << "ERROR: Parsing player rating metadata '" << line << "' failed." << std::endl; break; } } // Skip empty and further metadata lines (every PGN game starts with ".") if (line.empty() || line.front() == '[') { continue; } // In case of rating requested, only parse game when rating is detected if (rating != static_cast(-1) && (whiteElo < rating || blackElo < rating)) { 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 (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; break; } 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; } // Skip positions with too small ply count if (pos.plyCount() < ply) { continue; } // Write positions if (scored && rating != static_cast(-1)) { // 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 << "; " << whiteElo << "; " << blackElo << '\n'; } else if (rating != static_cast(-1)) { // Otherwise, classic eval score to be parsed in centipawns std::cout << pos.fen() << "; " << whiteElo << "; " << blackElo << '\n'; } else if (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'; } } // Reset ELO after every game to ensure that games without an elo // metadata tag don't get the wrong rating from a previous game whiteElo = 0; blackElo = 0; } } int main(int argn, char* argv[]) { // Setup control variables bool scored = false; unsigned long rating = -1; unsigned long ply = 0; // unsigned min_rating = 0; std::string file = ""; // Parse command arguments for (int i = 1; i < argn; ++i) { if (std::string("--scored") == argv[i]) { scored = true; } else if (std::string("--rating") == argv[i]) { if (i + 1 < argn) { try { rating = std::stoul(argv[++i]); } catch (...) { std::cerr << "ERROR: illegal --rating argument " << argv[i] << std::endl; std::cout << usage << std::endl; return 1; } } else { std::cout << usage << std::endl; return 1; } } else if (std::string("--ply") == argv[i]) { if (i + 1 < argn) { try { ply = std::stoul(argv[++i]); } catch (...) { std::cerr << "ERROR: illegal --ply argument " << argv[i] << std::endl; std::cout << usage << std::endl; return 1; } } else { std::cout << usage << std::endl; return 1; } } else if (file != "") { file = argv[i]; } else { std::cout << usage << std::endl; return 1; } } // Invoke converter ether with file input or stdin if (file == "") { pgn2fen(std::cin, scored, rating, ply); } else { // Open input file std::ifstream input(file); if (!input) { std::cerr << "ERROR: opening '" << file << "' failed" << std::endl; return 1; } pgn2fen(input, scored, rating, ply); } return 0; }