summaryrefslogtreecommitdiff
path: root/competition/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'competition/main.cpp')
-rw-r--r--competition/main.cpp368
1 files changed, 0 insertions, 368 deletions
diff --git a/competition/main.cpp b/competition/main.cpp
deleted file mode 100644
index 604263c..0000000
--- a/competition/main.cpp
+++ /dev/null
@@ -1,368 +0,0 @@
-#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 "../board.h"
-#include "job.h"
-#include "process.h"
-#include "error.h"
-#include "multilog.h"
-
-using namespace std;
-
-
-static const char *matchcachedir = "comp_cache";
-static const char *playerlogdir = "comp_playerlogs";
-static const char *gamelogdir = "comp_gamelogs";
-static const int num_matches = 5;
-static const int timeout_msec = 60000;
-
-
-// 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;
-}
-
-static int64_t gettimestamp() {
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- return tv.tv_sec * 1000000LL + tv.tv_usec;
-}
-
-struct Player;
-
-// 'win' is for player 1
-struct MatchResult {
- enum class Win {
- win, loss, tie, timeout
- };
-
- Win win;
- size_t ms1 = 0, ms2 = 0;
-
- int score() 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 {
- switch (win) {
- case Win::win: return 3;
- case Win::loss: return 1;
- case Win::tie: return 2;
- case Win::timeout: return 0;
- default: assert(false);
- }
-}
-
-string MatchResult::describe(const Player &p1, const Player &p2) const {
- string prefix;
- switch (win) {
- 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;
- }
- return prefix + to_string(score()) + "-" + to_string(inverted().score());
-}
-
-MatchResult MatchResult::inverted() const {
- MatchResult r;
- switch (win) {
- case Win::win: r.win = Win::loss; break;
- case Win::loss: r.win = Win::win; break;
- case Win::tie: r.win = Win::tie; break;
- case Win::timeout: r.win = Win::timeout; break;
- }
- r.ms2 = ms1; r.ms1 = ms2;
- return r;
-}
-
-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);
- ifstream f(path);
- if (!f) return nullopt;
-
- int64_t cacheStamp = fileLastModified(path);
- if (p1.lastModified > cacheStamp || p2.lastModified > cacheStamp) return nullopt;
-
- MatchResult mres;
- string word;
- f >> word >> mres.ms1 >> mres.ms2;
- if (!f) return nullopt;
-
- if (word == "win") mres.win = MatchResult::Win::win;
- else if (word == "loss") mres.win = MatchResult::Win::loss;
- else if (word == "tie") mres.win = MatchResult::Win::tie;
- else if (word == "timeout") mres.win = MatchResult::Win::timeout;
- 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.win) {
- case MatchResult::Win::win: f << "win"; break;
- case MatchResult::Win::loss: f << "loss"; break;
- case MatchResult::Win::tie: f << "tie"; break;
- case MatchResult::Win::timeout: f << "timeout"; break;
- }
-
- 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) {
- 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();
- }
-
- 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();
-
- Board board = Board::makeInitial();
- 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.win = MatchResult::Win::timeout;
- goto match_done;
- }
-
- optional<Move> omv = Move::parse(lastMove);
- if (!omv) {
- cout << "ERROR in player " << i+1 << ": unreadable move '" << lastMove << "'" << endl;
- throw StopCompetitionError();
- }
- if (!board.isValid(*omv, i == 0 ? -1 : 1)) {
- cout << "ERROR in player " << i+1 << ": invalid move " << *omv << endl;
- throw StopCompetitionError();
- }
-
- int outcome = board.applyCW(*omv);
- if (outcome != 0) {
- if (outcome == 1) mres.win = MatchResult::Win::loss;
- else if (outcome == -1) mres.win = MatchResult::Win::win;
- else assert(false);
- goto match_done;
- }
-
- stringstream ss;
- ss << *omv;
- lastMove = ss.str();
- }
- }
-
-match_done:
- 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) {
- for (int i = 0; i < num_matches; i++) {
- scheduler.submit([&p1, &p2, i]() { playMatch(p1, p2, i + 1); });
- }
-}
-
-static void fullCompetition(Scheduler &scheduler, vector<Player> &players) {
- 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]);
- playerPit(scheduler, players[p2i], players[p1i]);
- }
- }
-}
-
-int main(int argc, char **argv) {
- if (argc <= 1) {
- cerr << "Usage: " << argv[0] << " <players...>" << endl;
- return 1;
- }
-
- vector<Player> players;
- for (int i = 0; i < argc - 1; i++) {
- players.emplace_back(argv[i+1]);
- }
-
- mkdirp(matchcachedir);
- mkdirp(playerlogdir);
- mkdirp(gamelogdir);
-
- Scheduler scheduler(2);
- fullCompetition(scheduler, players);
- 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;
- }
-}