summaryrefslogtreecommitdiff
path: root/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'main.cpp')
-rw-r--r--main.cpp386
1 files changed, 386 insertions, 0 deletions
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..6e363e7
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,386 @@
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <iomanip>
+#include <vector>
+#include <string>
+#include <utility>
+#include <algorithm>
+#include <optional>
+#include <string_view>
+#include <cstdint>
+#include <cstring>
+#include <cstdlib>
+#include <cctype>
+#include <cassert>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "job.h"
+#include "process.h"
+#include "error.h"
+#include "multilog.h"
+#include "referee.h"
+#include "params.h"
+
+using namespace std;
+
+
+// TODO no globals
+static MultiLog gMultiLog;
+
+
+static char hexchar(int n) {
+ return "0123456789ABCDEF"[n];
+}
+
+static string makeSafe(const string_view str) {
+ string res;
+ res.reserve(str.size() + 3);
+
+ for (char c : str) {
+ if (isalnum(c)) {
+ res += c;
+ } else {
+ res += '_';
+ res += hexchar((c >> 4) & 0xf);
+ res += hexchar(c & 0xf);
+ }
+ }
+
+ return res;
+}
+
+// Only creates a single component
+static void mkdirp(const string_view name) {
+ if (mkdir(name.data(), 0755) < 0) {
+ if (errno != EEXIST) {
+ perror("mkdir");
+ exit(1);
+ }
+ }
+}
+
+static int64_t fileLastModified(const string_view fname) {
+ struct stat st;
+ if (stat(fname.data(), &st) < 0) {
+ if (errno == EEXIST) return 0;
+ perror("stat");
+ exit(1);
+ }
+ return st.st_mtim.tv_sec * 1000000LL + st.st_mtim.tv_nsec / 1000;
+}
+
+static int64_t gettimestamp() {
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ return tv.tv_sec * 1000000LL + tv.tv_usec;
+}
+
+struct Player;
+
+// Methods represent results for player 1, unless inverted
+struct MatchResult {
+ enum class Win {
+ win, loss, tie, timeout, crash
+ };
+
+ enum class Status { // crash is not used yet
+ ok, timeout, crash
+ };
+
+ Status status;
+ int sc1, sc2;
+ size_t ms1 = 0, ms2 = 0;
+
+ int score() const;
+ Win getWin() const;
+ string describe(const Player &p1, const Player &p2) const;
+ MatchResult inverted() const;
+};
+
+struct Player {
+ string fname, safename;
+ vector<MatchResult> results;
+ int64_t lastModified;
+
+ Player(const string &fname)
+ : fname(fname), safename(makeSafe(fname)),
+ lastModified(fileLastModified(fname)) {}
+};
+
+int MatchResult::score() const {
+ return sc1;
+}
+
+MatchResult::Win MatchResult::getWin() const {
+ switch (status) {
+ case Status::ok:
+ if (sc1 > sc2) return Win::win;
+ else if (sc1 == sc2) return Win::tie;
+ else return Win::loss;
+
+ case Status::timeout:
+ return Win::timeout;
+
+ case Status::crash:
+ return Win::crash;
+
+ default:
+ assert(false);
+ }
+}
+
+string MatchResult::describe(const Player &p1, const Player &p2) const {
+ string prefix;
+ switch (getWin()) {
+ case Win::win: prefix = p1.fname + " won: "; break;
+ case Win::loss: prefix = p2.fname + " won: "; break;
+ case Win::tie: prefix = "tie: "; break;
+ case Win::timeout: prefix = "timeout: "; break;
+ case Win::crash: prefix = "crash: "; break;
+ }
+ return prefix + to_string(sc1) + "-" + to_string(sc2);
+}
+
+MatchResult MatchResult::inverted() const {
+ MatchResult r;
+ r.status = status;
+ r.sc2 = sc1; r.sc1 = sc2;
+ r.ms2 = ms1; r.ms1 = ms2;
+ return r;
+}
+
+struct Params {
+ string refereePath;
+};
+
+static string gameCodeName(const Player &p1, const Player &p2, int index) {
+ return p1.safename + "-" + p2.safename + "-" + to_string(index);
+}
+
+static string playerLogPath(const Player &p1, const Player &p2, int index, int who) {
+ return string(playerlogdir) + "/" + gameCodeName(p1, p2, index) + "-" + to_string(who) + ".txt";
+}
+
+static string matchCachePath(const Player &p1, const Player &p2, int index) {
+ return string(matchcachedir) + "/" + gameCodeName(p1, p2, index) + ".txt";
+}
+
+static string gameLogPath(const Player &p1, const Player &p2, int index) {
+ return string(gamelogdir) + "/" + gameCodeName(p1, p2, index) + ".txt";
+}
+
+static optional<MatchResult> readMatchCache(const Player &p1, const Player &p2, int index) {
+ string path = matchCachePath(p1, p2, index);
+ int64_t cacheStamp = fileLastModified(path);
+
+ ifstream f(path);
+ if (!f) return nullopt;
+
+ if (p1.lastModified > cacheStamp || p2.lastModified > cacheStamp) {
+ cerr << "(cache ignored because players newer) " << cacheStamp << " " << p1.lastModified << " " << p2.lastModified << endl;
+ return nullopt;
+ }
+
+ MatchResult mres;
+ string word;
+ f >> word >> mres.sc1 >> mres.sc2 >> mres.ms1 >> mres.ms2;
+ if (!f) {
+ cerr << "(cache ignored because error reading file)" << endl;
+ return nullopt;
+ }
+
+ if (word == "ok") mres.status = MatchResult::Status::ok;
+ else if (word == "timeout") mres.status = MatchResult::Status::timeout;
+ else if (word == "crash") mres.status = MatchResult::Status::crash;
+ else return nullopt;
+
+ return mres;
+}
+
+static void writeMatchCache(const Player &p1, const Player &p2, int index, const MatchResult &mres) {
+ string path = matchCachePath(p1, p2, index);
+ ofstream f(path);
+ if (!f) {
+ cout << endl << "ERROR: Cannot open match cache file '" << path << "'" << endl;
+ throw StopCompetitionError();
+ }
+
+ switch (mres.status) {
+ case MatchResult::Status::ok: f << "ok"; break;
+ case MatchResult::Status::timeout: f << "timeout"; break;
+ case MatchResult::Status::crash: f << "crash"; break;
+ }
+
+ f << ' ' << mres.sc1 << ' ' << mres.sc2;
+ f << ' ' << mres.ms1 << ' ' << mres.ms2 << endl;
+}
+
+static void recordResult(Player &p1, Player &p2, const MatchResult &result) {
+ p1.results.push_back(result);
+ p2.results.push_back(result.inverted());
+}
+
+static void playMatch(Player &p1, Player &p2, int index, const Params &params) {
+ int logId = gMultiLog.add(p1.fname + " - " + p2.fname + ": ");
+
+ if (optional<MatchResult> optres = readMatchCache(p1, p2, index)) {
+ gMultiLog.append(logId, optres->describe(p1, p2) + " (cached)");
+ gMultiLog.complete(logId);
+ recordResult(p1, p2, *optres);
+ return;
+ }
+
+ 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.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].run();
+ procs[1].run();
+
+ string lastMove = "Start";
+
+ while (true) {
+ for (int i = 0; i < 2; i++) {
+ if (!procs[i].writeLine(lastMove)) {
+ cout << "ERROR writing move to player " << i+1 << endl;
+ throw StopCompetitionError();
+ }
+
+ procs[i].unStop();
+ int64_t start = gettimestamp();
+ optional<string> oline = procs[i].readLine();
+ if (!oline) {
+ cout << "ERROR reading move from player " << i+1 << endl;
+ throw StopCompetitionError();
+ }
+
+ lastMove = *oline;
+ (i == 0 ? mres.ms1 : mres.ms2) += gettimestamp() - start;
+ procs[i].stop();
+
+ gamelog << "P" << i+1 << ": " << lastMove << endl;
+
+ if (mres.ms1 / 1000 > timeout_msec || mres.ms2 / 1000 > timeout_msec) {
+ mres.status = MatchResult::Status::timeout;
+ mres.sc1 = mres.sc2 = 0;
+ goto match_done;
+ }
+
+ if (!referee.moveValid(i, lastMove)) {
+ cout << "ERROR in player " << i+1 << ": invalid move '" << lastMove << "'" << endl;
+ throw StopCompetitionError();
+ }
+
+ if (referee.gameEnded()) {
+ goto match_done;
+ }
+ }
+ }
+
+match_done:
+ optional<vector<int>> oscores = referee.getScores();
+ assert(oscores);
+ mres.status = MatchResult::Status::ok;
+ mres.sc1 = oscores->at(0);
+ mres.sc2 = oscores->at(1);
+
+ for (int i = 0; i < 2; i++) {
+ bool success = procs[i].writeLine("Stop");
+ procs[i].unStop();
+ if (success) usleep(10000);
+ procs[i].terminate();
+ }
+ for (int i = 0; i < 2; i++) {
+ procs[i].wait();
+ }
+
+ gMultiLog.append(logId, mres.describe(p1, p2));
+ gMultiLog.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;
+
+ writeMatchCache(p1, p2, index, mres);
+ recordResult(p1, p2, mres);
+}
+
+static void playerPit(Scheduler &scheduler, Player &p1, Player &p2, const Params &params) {
+ for (int i = 0; i < num_matches; i++) {
+ scheduler.submit([&p1, &p2, i, params]() { playMatch(p1, p2, i + 1, params); });
+ }
+}
+
+static void fullCompetition(Scheduler &scheduler, vector<Player> &players, const Params &params) {
+ for (size_t p1i = 0; p1i < players.size(); p1i++) {
+ for (size_t p2i = p1i + 1; p2i < players.size(); p2i++) {
+ playerPit(scheduler, players[p1i], players[p2i], params);
+ playerPit(scheduler, players[p2i], players[p1i], params);
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ cerr << "Usage: " << argv[0] << " <referee> <players...>" << endl;
+ return 1;
+ }
+
+ Params params;
+ params.refereePath = argv[1];
+
+ vector<Player> players;
+ for (int i = 2; i < argc; i++) {
+ players.emplace_back(argv[i]);
+ }
+
+
+ mkdirp(matchcachedir);
+ mkdirp(playerlogdir);
+ mkdirp(gamelogdir);
+
+ Scheduler scheduler(num_threads);
+ fullCompetition(scheduler, players, params);
+ scheduler.finish();
+
+ vector<pair<string, int>> scores;
+ for (const Player &player : players) {
+ int score = 0;
+ for (const MatchResult &result : player.results) {
+ score += result.score();
+ }
+ scores.emplace_back(player.fname, score);
+ }
+
+ sort(scores.begin(), scores.end(),
+ [](const pair<string, int> &a, const pair<string, int> &b) { return a.second > b.second; });
+
+ size_t maxlen = strlen("Player");
+ for (const Player &player : players) {
+ maxlen = max(maxlen, player.fname.size());
+ }
+
+ cout << endl << setw(maxlen) << "Player" << " | " << "Score" << endl;
+ cout << string(maxlen, '-') << "-+------" << endl;
+
+ for (const auto &p : scores) {
+ cout << setw(maxlen) << p.first << " | " << p.second << endl;
+ }
+}