tensor_predictors/dataAnalysis/chess/pgn2fen.cpp

145 lines
4.6 KiB
C++
Raw Normal View History

2023-12-11 13:29:51 +00:00
#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;
}