diff options
author | Tom Smeding <tom.smeding@gmail.com> | 2018-08-20 21:53:38 +0200 |
---|---|---|
committer | Tom Smeding <tom.smeding@gmail.com> | 2018-08-20 21:53:38 +0200 |
commit | 1c42efb1b116287949e59370198cad2df465835d (patch) | |
tree | 6c191f0a629b23566763827e73071c523f0d3e2b /main.cpp |
Initial
Diffstat (limited to 'main.cpp')
-rw-r--r-- | main.cpp | 386 |
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 ¶ms) { + 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 ¶ms) { + 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 ¶ms) { + 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; + } +} |