Repository: Disservin/fastchess (language: cpp)

Issue:
-epdout addition?
cutechess has -epdout option to output the final fen of each game in an epd file, i think this one is pretty useful for constructing opening books when combined with draw adjudication/resign


Discussion / hints:
Sounds good to me, can they use that alongside the pgnout? Or only one of those can be used
hmm i didnt really check, but ideally theres no reason why both cant be used at the same time

i dont really know how to code this one so ill leave it to you 😅 
> hmm i didnt really check, but ideally theres no reason why both cant be used at the same time
> 
> i dont really know how to code this one so ill leave it to you 😅

Could start by looking at the pgnbuilder and removing everything which isn’t required
does fast-chess generate a fen after each move? if so we can just print the last move directly into the file
https://github.com/Disservin/fast-chess/blob/master/src/pgn/pgn_builder.cpp#L91

Well are have replayed all moves. After that there’s the last fen, but this should be duplicated in a epdbuilder and everything else should be removed. There isn’t necessarily a place where a new fen is generated after a move. 

Fix patch:
diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp
index 123403b1b..e82d96109 100644
--- a/src/cli/cli.cpp
+++ b/src/cli/cli.cpp
@@ -201,6 +201,16 @@ void parsePgnOut(int &i, int argc, char const *argv[], ArgumentData &argument_da
     }
 }
 
+void parseEpdOut(int &i, int argc, char const *argv[], ArgumentData &argument_data) {
+    parseDashOptions(i, argc, argv, [&](const std::string &key, const std::string &value) {
+        if (key == "file") {
+            argument_data.tournament_options.epd.file = value;
+        } else {
+            OptionsParser::throwMissing("epdout", key, value);
+        }
+    });
+}
+
 void parseOpening(int &i, int argc, char const *argv[], ArgumentData &argument_data) {
     parseDashOptions(i, argc, argv, [&](const std::string &key, const std::string &value) {
         if (key == "file") {
@@ -504,6 +514,7 @@ OptionsParser::OptionsParser(int argc, char const *argv[]) {
     addOption("engine", engine::parseEngine);
     addOption("each", parseEach);
     addOption("pgnout", parsePgnOut);
+    addOption("epdout", parseEpdOut);
     addOption("openings", parseOpening);
     addOption("sprt", parseSprt);
     addOption("draw", parseDraw);
diff --git a/src/epd/epd_builder.hpp b/src/epd/epd_builder.hpp
new file mode 100644
index 000000000..0ca79b444
--- /dev/null
+++ b/src/epd/epd_builder.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include <chess.hpp>
+
+#include <matchmaking/match/match.hpp>
+
+#include <types/tournament_options.hpp>
+
+namespace fast_chess {
+
+class EpdBuilder {
+   public:
+    EpdBuilder(const MatchData &match, const options::Tournament &tournament_options) {
+        chess::Board board = chess::Board();
+        board.set960(tournament_options.variant == VariantType::FRC);
+        board.setFen(match.fen);
+
+        for (const auto &move : match.moves) {
+            const auto illegal = !move.legal;
+
+            if (illegal) {
+                break;
+            }
+
+            board.makeMove(chess::uci::uciToMove(board, move.move));
+        }
+
+        epd << board.getEpd() << "\n";
+    }
+
+    /// @brief Get the newly created epd
+    /// @return
+    [[nodiscard]] std::string get() const noexcept { return epd.str(); }
+
+   private:
+    std::stringstream epd;
+};
+
+}  // namespace fast_chess
\ No newline at end of file
diff --git a/src/matchmaking/tournament/base/tournament.cpp b/src/matchmaking/tournament/base/tournament.cpp
index f2a5f6a4e..3a986d60f 100644
--- a/src/matchmaking/tournament/base/tournament.cpp
+++ b/src/matchmaking/tournament/base/tournament.cpp
@@ -2,6 +2,7 @@
 
 #include <affinity/affinity_manager.hpp>
 #include <engines/uci_engine.hpp>
+#include <epd/epd_builder.hpp>
 #include <matchmaking/book/opening_book.hpp>
 #include <matchmaking/match/match.hpp>
 #include <matchmaking/output/output.hpp>
@@ -25,7 +26,8 @@ BaseTournament::BaseTournament(const options::Tournament &config,
     cores_              = std::make_unique<affinity::AffinityManager>(config.affinity,
                                                          getMaxAffinity(engine_configs));
 
-    if (!config.pgn.file.empty()) file_writer_ = std::make_unique<FileWriter>(config.pgn.file);
+    if (!config.pgn.file.empty()) file_writer_pgn = std::make_unique<FileWriter>(config.pgn.file);
+    if (!config.epd.file.empty()) file_writer_epd = std::make_unique<FileWriter>(config.epd.file);
 
     pool_.resize(config.concurrency);
 }
@@ -75,10 +77,12 @@ void BaseTournament::playGame(const std::pair<EngineConfiguration, EngineConfigu
 
     const auto match_data = match.get();
 
-    // If the game was stopped, don't write the PGN
-    if (match_data.termination != MatchTermination::INTERRUPT &&
-        !tournament_options_.pgn.file.empty()) {
-        file_writer_->write(PgnBuilder(match_data, tournament_options_, game_id + 1).get());
+    // If the game was interrupted(didn't completely finish)
+    if (match_data.termination != MatchTermination::INTERRUPT) {
+        if (!tournament_options_.pgn.file.empty())
+            file_writer_pgn->write(PgnBuilder(match_data, tournament_options_, game_id + 1).get());
+        if (!tournament_options_.epd.file.empty())
+            file_writer_epd->write(EpdBuilder(match_data, tournament_options_).get());
     }
 
     finish({match_data}, match_data.reason);
diff --git a/src/matchmaking/tournament/base/tournament.hpp b/src/matchmaking/tournament/base/tournament.hpp
index 897a85103..ee3254d01 100644
--- a/src/matchmaking/tournament/base/tournament.hpp
+++ b/src/matchmaking/tournament/base/tournament.hpp
@@ -77,7 +77,8 @@ class BaseTournament {
 
     std::unique_ptr<IOutput> output_;
     std::unique_ptr<affinity::AffinityManager> cores_;
-    std::unique_ptr<FileWriter> file_writer_;
+    std::unique_ptr<FileWriter> file_writer_pgn;
+    std::unique_ptr<FileWriter> file_writer_epd;
 
     OpeningBook book_;
     options::Tournament tournament_options_;
diff --git a/src/types/tournament_options.hpp b/src/types/tournament_options.hpp
index 130b6b502..37ff83815 100644
--- a/src/types/tournament_options.hpp
+++ b/src/types/tournament_options.hpp
@@ -26,6 +26,11 @@ struct Pgn {
 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ORDERED_JSON(Pgn, file, notation, track_nodes, track_seldepth,
                                                 track_nps)
 
+struct Epd {
+    std::string file;
+};
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ORDERED_JSON(Epd, file)
+
 struct Sprt {
     double alpha         = 0.0;
     double beta          = 0.0;
@@ -56,6 +61,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ORDERED_JSON(ResignAdjudication, move_count,
 struct Tournament {
     Opening opening = {};
     Pgn pgn         = {};
+    Epd epd         = {};
 
     Sprt sprt = {};
 


Test patch: