From 57a1091dfb2c6403cbe63c7ddca8acc7e3d0f940 Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Mon, 20 Aug 2018 23:51:16 +0200 Subject: Switch to https://git.tomsmeding.com/competition --- .gitignore | 1 + competition/.gitignore | 4 - competition/Makefile | 21 --- competition/error.cpp | 9 -- competition/error.h | 12 -- competition/job.cpp | 51 ------- competition/job.h | 39 ----- competition/main.cpp | 368 ----------------------------------------------- competition/multilog.cpp | 57 -------- competition/multilog.h | 32 ----- competition/process.cpp | 136 ------------------ competition/process.h | 32 ----- referee/.gitignore | 1 + referee/Makefile | 13 ++ referee/referee.cpp | 74 ++++++++++ 15 files changed, 89 insertions(+), 761 deletions(-) delete mode 100644 competition/.gitignore delete mode 100644 competition/Makefile delete mode 100644 competition/error.cpp delete mode 100644 competition/error.h delete mode 100644 competition/job.cpp delete mode 100644 competition/job.h delete mode 100644 competition/main.cpp delete mode 100644 competition/multilog.cpp delete mode 100644 competition/multilog.h delete mode 100644 competition/process.cpp delete mode 100644 competition/process.h create mode 100644 referee/.gitignore create mode 100644 referee/Makefile create mode 100644 referee/referee.cpp diff --git a/.gitignore b/.gitignore index 87e54c2..7efa90e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ main *.o +competition/ diff --git a/competition/.gitignore b/competition/.gitignore deleted file mode 100644 index 95f3130..0000000 --- a/competition/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -comp_cache/ -comp_gamelogs/ -comp_playerlogs/ -competition diff --git a/competition/Makefile b/competition/Makefile deleted file mode 100644 index 8b2641c..0000000 --- a/competition/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -CXX = g++ -CXXFLAGS = -Wall -Wextra -O3 -std=c++17 -fwrapv -flto -pthread - -TARGET = competition - -SOURCES := $(wildcard *.cpp) ../board.cpp -HEADERS := $(wildcard *.h) ../board.h - -.PHONY: all clean - -all: $(TARGET) - -clean: - rm -f $(TARGET) *.o - - -competition: $(SOURCES:.cpp=.o) - $(CXX) $(CXXFLAGS) $^ -o $@ - -%.o: %.cpp $(HEADERS) - $(CXX) $(CXXFLAGS) -c -o $@ $< diff --git a/competition/error.cpp b/competition/error.cpp deleted file mode 100644 index 4c737c2..0000000 --- a/competition/error.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "error.h" - - -StopCompetitionError::StopCompetitionError() - : runtime_error("StopCompetitionError") {} -StopCompetitionError::StopCompetitionError(const string &what_arg) - : runtime_error(what_arg) {} -StopCompetitionError::StopCompetitionError(const char *what_arg) - : runtime_error(what_arg) {} diff --git a/competition/error.h b/competition/error.h deleted file mode 100644 index 403f81b..0000000 --- a/competition/error.h +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -using namespace std; - - -class StopCompetitionError : public runtime_error { -public: - StopCompetitionError(); - explicit StopCompetitionError(const string &what_arg); - explicit StopCompetitionError(const char *what_arg); -}; diff --git a/competition/job.cpp b/competition/job.cpp deleted file mode 100644 index 043074e..0000000 --- a/competition/job.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include "job.h" - -void Scheduler::workerEntry() { - while (true) { - Job *job = nullptr; - - { - lock_guard commMutGuard(commMut); - if (terminateFlag) break; - if (jobs.size() > 0) { - job = jobs.front(); - jobs.pop(); - } - } - - if (job) { - job->callback(); - } else { - this_thread::sleep_for(chrono::milliseconds(100)); - } - } -} - -Scheduler::Scheduler(int nthreads) - : nthreads(nthreads) { - - assert(nthreads > 0); - - workers.reserve(nthreads); - for (int i = 0; i < nthreads; i++) { - workers.emplace_back([this]() { workerEntry(); }); - } -} - -Scheduler::~Scheduler() { - finish(); -} - -void Scheduler::submit(const function &func) { - Job *job = new Job(func); - lock_guard commMutGuard(commMut); - jobs.push(job); -} - -void Scheduler::finish() { - for (int i = 0; i < nthreads; i++) { - workers[i].join(); - } -} diff --git a/competition/job.h b/competition/job.h deleted file mode 100644 index 9dba159..0000000 --- a/competition/job.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -using namespace std; - - -class Scheduler { - struct Job { - function callback; - - Job(const function callback) - : callback(callback) {} - }; - - queue jobs; - bool terminateFlag = false; - mutex commMut; - - vector workers; - - void workerEntry(); - -public: - const int nthreads; - - Scheduler(int nthreads); - ~Scheduler(); - - // func is run in child thread - // doneCallback is run in host thread - void submit(const function &func); - - void finish(); -}; 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 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 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 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 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 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 &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] << " " << endl; - return 1; - } - - vector 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> 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 &a, const pair &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; - } -} diff --git a/competition/multilog.cpp b/competition/multilog.cpp deleted file mode 100644 index bf10ec6..0000000 --- a/competition/multilog.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include -#include "multilog.h" - - -static int uniqid() { - static int i = 0; - return i++; -} - - -MultiLog::Item::Item() - : id(uniqid()) {} - -int MultiLog::add(const string_view prefix) { - lock_guard guard(mut); - - items.emplace_back(); - items.back().line = prefix; - cout << prefix << endl; - return items.back().id; -} - -void MultiLog::append(int id, const string_view text) { - lock_guard guard(mut); - - assert(text.find('\n') == string::npos); - - size_t idx = findId(id); - items[idx].line += text; - redrawLine(idx); -} - -void MultiLog::complete(int id) { - lock_guard guard(mut); - - size_t idx = findId(id); - items[idx].complete = true; - size_t nextIdx; - for (nextIdx = 0; nextIdx < items.size(); nextIdx++) { - if (!items[nextIdx].complete) break; - } - if (nextIdx > 0) items.erase(items.begin(), items.begin() + (nextIdx - 1)); -} - -size_t MultiLog::findId(int id) { - for (int i = 0; i < (int)items.size(); i++) { - if (items[i].id == id) return i; - } - assert(false); -} - -void MultiLog::redrawLine(size_t idx) { - size_t offset = items.size() - idx; - cout << "\r\x1B[" << offset << "A\x1B[K" << items[idx].line << "\r\x1B[" << offset << "B" << flush; -} diff --git a/competition/multilog.h b/competition/multilog.h deleted file mode 100644 index ba0ff4d..0000000 --- a/competition/multilog.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include - -using namespace std; - - -// Access is thread-safe -class MultiLog { - struct Item { - int id; - string line; - bool complete = false; - - Item(); - }; - - mutex mut; - vector items; - - // Methods assume mutex is taken. - size_t findId(int id); - void redrawLine(size_t idx); - -public: - // Returns id of new item - int add(const string_view prefix); - void append(int id, const string_view text); - void complete(int id); -}; diff --git a/competition/process.cpp b/competition/process.cpp deleted file mode 100644 index a728401..0000000 --- a/competition/process.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include -#include "process.h" -#include "error.h" - - -Process::Process(const string_view execname) - : execname(execname) {} - -void Process::redirectStderr(const string_view fname) { - stderrRedirect = fname; -} - -void Process::run() { - int stderrfd = -1; - if (stderrRedirect) { - stderrfd = open(stderrRedirect->data(), O_WRONLY|O_CREAT|O_TRUNC, 0644); - if (stderrfd < 0) { - perror("open"); - cout << endl << "ERROR: Cannot open player log file '" << *stderrRedirect << "'" << endl; - throw StopCompetitionError(); - } - } - - int pipefds[2]; - if (pipe(pipefds) < 0) { - perror("pipe"); - exit(1); - } - infd = pipefds[1]; - int child_in = pipefds[0]; - - if (pipe(pipefds) < 0) { - perror("pipe"); - exit(1); - } - outfd = pipefds[0]; - int child_out = pipefds[1]; - - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } - - if (pid == 0) { - if (stderrRedirect) dup2(stderrfd, STDERR_FILENO); - dup2(child_in, STDIN_FILENO); - dup2(child_out, STDOUT_FILENO); - close(infd); - close(outfd); - - execlp(execname.data(), execname.data(), NULL); - cerr << endl << "ERROR: Error executing player file '" << execname << "'" << endl; - exit(255); - } - - if (stderrfd >= 0) close(stderrfd); - close(child_in); - close(child_out); -} - -void Process::wait() { - while (true) { - int status; - if (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) continue; - perror("waitpid"); - break; - } - if (WIFEXITED(status)) break; - } -} - -void Process::stop() { - if (pid != -1) kill(pid, SIGSTOP); -} - -void Process::unStop() { - if (pid != -1) kill(pid, SIGCONT); -} - -bool Process::writeLine(const string_view line) { - string str; - str.reserve(line.size() + 1); - str += line; - str += '\n'; - - size_t cursor = 0; - while (cursor < str.size()) { - ssize_t nw = write(infd, str.data() + cursor, str.size() - cursor); - if (nw < 0) { - if (errno == EINTR) continue; - perror("write"); - return false; - } - cursor += nw; - } - - return true; -} - -optional Process::readLine() { - size_t idx = readBuf.find('\n'); - if (idx != string::npos) { - string res = readBuf.substr(0, idx); - readBuf = readBuf.substr(idx + 1); - return res; - } - - while (true) { - string s(1024, '\0'); - ssize_t nr = read(outfd, &s[0], s.size()); - if (nr < 0) { - if (errno == EINTR) continue; - perror("read"); - return nullopt; - } - s.resize(nr); - - idx = s.find('\n'); - if (idx != string::npos) { - string res = readBuf + s.substr(0, idx); - readBuf = s.substr(idx + 1); - return res; - } - - readBuf += s; - } -} - -void Process::terminate() { - if (pid != -1) kill(pid, SIGTERM); -} diff --git a/competition/process.h b/competition/process.h deleted file mode 100644 index ddd80b9..0000000 --- a/competition/process.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -using namespace std; - - -class Process { - string execname; - optional stderrRedirect; - pid_t pid = -1; - int infd = -1, outfd = -1; - - string readBuf; - -public: - Process(const string_view execname); - - void redirectStderr(const string_view fname); - - void run(); - void wait(); - void stop(); - void unStop(); - void terminate(); - - bool writeLine(const string_view line); - optional readLine(); -}; diff --git a/referee/.gitignore b/referee/.gitignore new file mode 100644 index 0000000..1c8d92c --- /dev/null +++ b/referee/.gitignore @@ -0,0 +1 @@ +referee diff --git a/referee/Makefile b/referee/Makefile new file mode 100644 index 0000000..67317b2 --- /dev/null +++ b/referee/Makefile @@ -0,0 +1,13 @@ +CXX = g++ +CXXFLAGS = -Wall -Wextra -std=c++17 -g -O2 + +.PHONY: all clean + +all: referee + +clean: + rm -f referee + + +referee: referee.cpp ../board.h ../board.o + $(CXX) $(CXXFLAGS) -o $@ $< ../board.o diff --git a/referee/referee.cpp b/referee/referee.cpp new file mode 100644 index 0000000..78c4193 --- /dev/null +++ b/referee/referee.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include "../board.h" + + +struct MoveLine { + int pindex; + string line; +}; + +MoveLine readMoveLine() { + while (true) { + if (!isspace(cin.peek())) break; + cin.get(); + } + + MoveLine ml; + cin >> ml.pindex; + if (!cin) exit(0); + + assert(cin.get() == ' '); + getline(cin, ml.line); + + return ml; +} + +int8_t playerToClr(int player) { + return 2 * player - 1; +} + +int main() { + Board bd = Board::makeInitial(); + + int nplayers; + cin >> nplayers; + assert(nplayers == 2); + cin.ignore(1000, '\n'); + + string p1, p2; + getline(cin, p1); + getline(cin, p2); + + int onturn = 0; + + while (true) { + MoveLine ml = readMoveLine(); + if (ml.pindex != onturn) { + cout << "invalid" << endl; + continue; + } + + optional mv = Move::parse(ml.line); + + if (mv && !bd.isValid(*mv, playerToClr(onturn))) { + cout << "invalid" << endl; + continue; + } + + int win = bd.applyCW(*mv); + + if (win != 0) { + cout << "valid end" << endl; + if (win == -1) cout << "3 1" << endl; + else if (win == 1) cout << "1 3" << endl; + break; + } else { + cout << "valid" << endl; + } + + onturn = !onturn; + } +} -- cgit v1.2.3