diff options
-rw-r--r-- | main.cpp | 260 |
1 files changed, 198 insertions, 62 deletions
@@ -166,10 +166,15 @@ MatchResult MatchResult::inverted() const { return r; } +enum class Mode { + competition, + single, +}; + struct Params { + Mode mode; bool referee_verbose = false; string refereePath; - int num_matches = 5; size_t timeout_msec = 10000; }; @@ -244,33 +249,17 @@ static void recordResult(Player &p1, Player &p2, const MatchResult &result) { } } -static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, const Params ¶ms) { - int logId = multiLog.add(p1.fname + " - " + p2.fname + ": "); - - if (optional<MatchResult> optres = readMatchCache(p1, p2, index)) { - multiLog.append(logId, optres->describe(p1, p2) + " (cached)"); - multiLog.complete(logId); - recordResult(p1, p2, *optres); - return; - } - +static MatchResult playMatch(const Player &p1, const Player &p2, const Params ¶ms, const string &gamecode, ostream &gamelog, const string &p1logpath, const string &p2logpath) { MatchResult mres; - string gamelog_path = gameLogPath(p1, p2, index); - ofstream gamelog(gamelog_path); - if (!gamelog) { - cout << "ERROR opening game log file " << gamelog_path << endl; - throw StopCompetitionError(); - } - Referee referee(params.referee_verbose, params.refereePath, {p1.fname, p2.fname}); gamelog << "Player 1: " << p1.fname << "\n" << "Player 2: " << p2.fname << "\n\n" << flush; Process procs[2] = {Process(p1.fname), Process(p2.fname)}; - procs[0].redirectStderr(playerLogPath(p1, p2, index, 1)); - procs[1].redirectStderr(playerLogPath(p1, p2, index, 2)); + procs[0].redirectStderr(p1logpath); + procs[1].redirectStderr(p2logpath); procs[0].run(); procs[1].run(); @@ -286,7 +275,7 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con optional<string> oline = proc.readLine(); if (!oline) { cout << "ERROR reading move from player " << readEvent->player + 1 - << " (game " << gameCodeName(p1, p2, index) << ")" << endl; + << " (game " << gamecode << ")" << endl; cout << "(process exit code: " << proc.waitPoll() << ")" << endl; throw StopCompetitionError(); } @@ -306,7 +295,7 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con } else if (auto writeEvent = get_if<Referee::WriteEvent>(&event)) { if (!procs[writeEvent->player].writeLine(writeEvent->line, writeEvent->allowBrokenPipe)) { cout << "ERROR writing move to player " << writeEvent->player + 1 - << " (game " << gameCodeName(p1, p2, index) << ")" << endl; + << " (game " << gamecode << ")" << endl; throw StopCompetitionError(); } @@ -324,7 +313,7 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con } else if (auto errorEvent = get_if<Referee::ErrorEvent>(&event)) { cout << "ERROR in player " << errorEvent->player + 1 << ": " << errorEvent->message - << " (game " << gameCodeName(p1, p2, index) << ")" << endl; + << " (game " << gamecode << ")" << endl; gamelog << endl << "ERROR in P" << errorEvent->player + 1 << ": " << errorEvent->message << endl; throw StopCompetitionError(); @@ -343,64 +332,154 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con } } - multiLog.append(logId, mres.describe(p1, p2)); - multiLog.complete(logId); - gamelog << "\nResult: " << mres.describe(p1, p2) << "\n" << "P1 took " << mres.ms1 / 1000000.0 << " seconds\n" << "P2 took " << mres.ms2 / 1000000.0 << " seconds\n" << flush; + return mres; +} + +static void playMatchInCompetition(MultiLog &multiLog, Player &p1, Player &p2, int index, const Params ¶ms) { + const int logId = multiLog.add(p1.fname + " - " + p2.fname + ": "); + + if (optional<MatchResult> optres = readMatchCache(p1, p2, index)) { + multiLog.append(logId, optres->describe(p1, p2) + " (cached)"); + multiLog.complete(logId); + recordResult(p1, p2, *optres); + return; + } + + const string gamelog_path = gameLogPath(p1, p2, index); + ofstream gamelog(gamelog_path); + if (!gamelog) { + cout << "ERROR opening game log file " << gamelog_path << endl; + throw StopCompetitionError(); + } + + const MatchResult mres = playMatch( + p1, p2, params, + gameCodeName(p1, p2, index), + gamelog, + playerLogPath(p1, p2, index, 1), + playerLogPath(p1, p2, index, 2) + ); + + multiLog.append(logId, mres.describe(p1, p2)); + multiLog.complete(logId); + writeMatchCache(p1, p2, index, mres); recordResult(p1, p2, mres); } -static void playerPit(Scheduler &scheduler, MultiLog &multiLog, Player &p1, Player &p2, const Params ¶ms) { - for (int i = 0; i < params.num_matches; i++) { - scheduler.submit([i, &p1, &p2, ¶ms, &multiLog]() { - playMatch(multiLog, p1, p2, i + 1, params); +struct CompParams { + Scheduler *scheduler; + MultiLog *multiLog; + int num_matches = 5; +}; + +static void playerPit(CompParams &compParams, Player &p1, Player &p2, const Params ¶ms) { + for (int i = 0; i < compParams.num_matches; i++) { + compParams.scheduler->submit([i, &p1, &p2, ¶ms, &compParams]() { + playMatchInCompetition(*compParams.multiLog, p1, p2, i + 1, params); }); } } -static void fullCompetition(Scheduler &scheduler, MultiLog &multiLog, vector<Player> &players, const Params ¶ms) { +static void fullCompetition(CompParams compParams, vector<Player> &players, const Params ¶ms) { for (size_t p1i = 0; p1i < players.size(); p1i++) { for (size_t p2i = p1i + 1; p2i < players.size(); p2i++) { - playerPit(scheduler, multiLog, players[p1i], players[p2i], params); - playerPit(scheduler, multiLog, players[p2i], players[p1i], params); + playerPit(compParams, players[p1i], players[p2i], params); + playerPit(compParams, players[p2i], players[p1i], params); } } } static void usage(const char *argv0) { - cerr << "Usage: " << argv0 << " [OPTIONS] <referee> <players...>\n" + cerr << + "Usage: " << argv0 << " comp [OPTIONS] <referee> <players...>\n" + "Run a full competition. Gamelogs in " << gamelogdir << ", playerlogs in\n" + << playerlogdir << ". Caches in " << matchcachedir << "; already-played games\n" + "as determined by caches will not be run again.\n" " --noansi Don't use fancy back-updating log output\n" " --vs Verbose scheduler output (use with --noansi)\n" - " --vr Verbose referee-handling output (use with --noansi)\n" " -j <NUM> Set number of parallel games (default ncores/2)\n" " -n <NUM> Set number of matches per player pairing (default " - << Params{}.num_matches << ")\n" + << CompParams{}.num_matches << ")\n" + "\n" + "Usage: " << argv0 << " single [OPTIONS] <referee> <player1> <player2>\n" + "Run only a single game. Gamelog from referee is sent to stdout.\n" + " --log1 <FILE> Playerlog file for player 1\n" + " --log2 <FILE> Playerlog file for player 2\n" + "\n" + "Options that always apply:\n" + " --vr Verbose referee-handling output (use with --noansi if comp mode)\n" " -T <MSEC> Set player timeout in milliseconds (default " - << Params{}.timeout_msec << ")\n"; + << Params{}.timeout_msec << ")\n" + ; } -int main(int argc, char **argv) { +static Params parseCommonOptions(vector<const char*> &argv) { Params params; + + if (argv.size() <= 1) { + usage(argv[0]); + exit(1); + } + + if (strcmp(argv[1], "comp") == 0) params.mode = Mode::competition; + else if (strcmp(argv[1], "single") == 0) params.mode = Mode::single; + else { + usage(argv[0]); + exit(1); + } + + argv.erase(argv.begin() + 1); + + for (size_t i = 1; i < argv.size(); i++) { + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + usage(argv[0]); + exit(0); + } else if (strcmp(argv[i], "--vr") == 0) { + params.referee_verbose = true; + argv.erase(argv.begin() + i); + i--; + } else if (strcmp(argv[i], "-T") == 0 && i + 1 < argv.size()) { + char *endp; + params.timeout_msec = strtoull(argv[i+1], &endp, 10); + if (!argv[i][0] || *endp || params.timeout_msec <= 0) { + cerr << "Invalid number to -T" << endl; + exit(1); + } + argv.erase(argv.begin() + i, argv.begin() + i + 2); + i--; + } else if (strcmp(argv[i], "-j") == 0 || + strcmp(argv[i], "-n") == 0 || + (strlen(argv[i]) >= strlen("--log1") && + memcmp(argv[i], "--log", strlen("--log")) == 0)) { + i++; // skip argument of option + } else if (params.refereePath.empty()) { + params.refereePath = argv[i]; + argv.erase(argv.begin() + i); + i--; + } + } + + return params; +} + +static int mainCompetition(const vector<const char*> &argv, const Params ¶ms) { vector<Player> players; bool multilog_fancy = true; bool scheduler_verbose = false; int num_threads = std::thread::hardware_concurrency() / 2; + CompParams compParams; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { - usage(argv[0]); - return 0; - } else if (strcmp(argv[i], "--noansi") == 0) { + for (size_t i = 1; i < argv.size(); i++) { + if (strcmp(argv[i], "--noansi") == 0) { multilog_fancy = false; } else if (strcmp(argv[i], "--vs") == 0) { scheduler_verbose = true; - } else if (strcmp(argv[i], "--vr") == 0) { - params.referee_verbose = true; - } else if (strcmp(argv[i], "-j") == 0 && i + 1 < argc) { + } else if (strcmp(argv[i], "-j") == 0 && i + 1 < argv.size()) { char *endp; num_threads = strtol(argv[i+1], &endp, 10); if (!argv[i][0] || *endp || num_threads <= 0) { @@ -408,27 +487,17 @@ int main(int argc, char **argv) { return 1; } i++; - } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { + } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argv.size()) { char *endp; - params.num_matches = strtol(argv[i+1], &endp, 10); - if (!argv[i][0] || *endp || params.num_matches <= 0) { + compParams.num_matches = strtol(argv[i+1], &endp, 10); + if (!argv[i][0] || *endp || compParams.num_matches <= 0) { cerr << "Invalid number to -n" << endl; return 1; } i++; - } else if (strcmp(argv[i], "-T") == 0 && i + 1 < argc) { - char *endp; - params.timeout_msec = strtoull(argv[i+1], &endp, 10); - if (!argv[i][0] || *endp || params.timeout_msec <= 0) { - cerr << "Invalid number to -T" << endl; - return 1; - } - i++; } else if (argv[i][0] == '-') { cerr << "Unknown option/flag '" << argv[i] << "'" << endl; return 1; - } else if (params.refereePath.empty()) { - params.refereePath = argv[i]; } else { players.emplace_back(argv[i]); } @@ -439,16 +508,19 @@ int main(int argc, char **argv) { return 1; } - MultiLog multiLog{multilog_fancy}; - mkdirp(matchcachedir); mkdirp(playerlogdir); mkdirp(gamelogdir); signal(SIGPIPE, [](int){}); + MultiLog multiLog{multilog_fancy}; + compParams.multiLog = &multiLog; + Scheduler scheduler(scheduler_verbose, num_threads); - fullCompetition(scheduler, multiLog, players, params); + compParams.scheduler = &scheduler; + + fullCompetition(compParams, players, params); scheduler.finish(); vector<pair<string, int>> scores; @@ -474,4 +546,68 @@ int main(int argc, char **argv) { for (const auto &p : scores) { cout << setw(maxlen) << p.first << " | " << p.second << endl; } + + return 0; +} + +static int mainSingle(const vector<const char*> &argv, const Params ¶ms) { + vector<Player> players; + unordered_map<int, string> logfnames; + + for (size_t i = 1; i < argv.size(); i++) { + if (strlen(argv[i]) >= strlen("--log1") && + memcmp(argv[i], "--log", strlen("--log")) == 0 && + i + 1 < argv.size()) { + const char *idxstring = argv[i] + strlen("--log"); + char *endp; + int playeridx = strtol(idxstring, &endp, 10); + if (!idxstring[0] || *endp || playeridx <= 0) { + cerr << "--logN index invalid" << endl; + return 1; + } + logfnames[playeridx] = argv[i+1]; + i++; + } else if (argv[i][0] == '-') { + cerr << "Unknown option/flag '" << argv[i] << "'" << endl; + return 1; + } else { + players.emplace_back(argv[i]); + } + } + + if (players.size() != 2) { + usage(argv[0]); + return 1; + } + + vector<string> playerlogs(players.size()); + for (size_t i = 0; i < players.size(); i++) { + auto it = logfnames.find(i + 1); + if (it == logfnames.end()) playerlogs[i] = "/dev/null"; + else playerlogs[i] = it->second; + } + + signal(SIGPIPE, [](int){}); + + playMatch(players[0], players[1], params, "single", cout, playerlogs[0], playerlogs[1]); + + return 0; +} + +int main(int argc, char **argv) { + if (argc == 0) { + usage("competition"); + return 1; + } + + vector<const char*> arguments{argv, argv + argc}; + const Params params = parseCommonOptions(arguments); + + switch (params.mode) { + case Mode::competition: + return mainCompetition(arguments, params); + + case Mode::single: + return mainSingle(arguments, params); + } } |