145 lines
4.6 KiB
C++
145 lines
4.6 KiB
C++
#include <iostream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
#include "utils.h"
|
|
#include "Board.h"
|
|
#include "Move.h"
|
|
#include "search.h"
|
|
#include "uci.h"
|
|
|
|
static const std::string usage{"usage: pgn2fen [--scored] [<input>]"};
|
|
|
|
// 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 "<nr>.")
|
|
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;
|
|
}
|