From 1c42efb1b116287949e59370198cad2df465835d Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Mon, 20 Aug 2018 21:53:38 +0200 Subject: Initial --- .gitignore | 5 + Makefile | 21 ++++ error.cpp | 9 ++ error.h | 12 ++ job.cpp | 62 ++++++++++ job.h | 40 +++++++ main.cpp | 386 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ multilog.cpp | 68 +++++++++++ multilog.h | 32 +++++ params.h | 15 +++ process.cpp | 152 +++++++++++++++++++++++ process.h | 36 ++++++ referee.cpp | 92 ++++++++++++++ referee.h | 45 +++++++ 14 files changed, 975 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 error.cpp create mode 100644 error.h create mode 100644 job.cpp create mode 100644 job.h create mode 100644 main.cpp create mode 100644 multilog.cpp create mode 100644 multilog.h create mode 100644 params.h create mode 100644 process.cpp create mode 100644 process.h create mode 100644 referee.cpp create mode 100644 referee.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4791239 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +comp_cache/ +comp_gamelogs/ +comp_playerlogs/ +competition +*.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3edc3f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CXX = g++ +CXXFLAGS = -Wall -Wextra -O2 -std=c++17 -fwrapv -pthread + +TARGET = competition + +SOURCES := $(wildcard *.cpp) +HEADERS := $(wildcard *.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/error.cpp b/error.cpp new file mode 100644 index 0000000..4c737c2 --- /dev/null +++ b/error.cpp @@ -0,0 +1,9 @@ +#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/error.h b/error.h new file mode 100644 index 0000000..403f81b --- /dev/null +++ b/error.h @@ -0,0 +1,12 @@ +#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/job.cpp b/job.cpp new file mode 100644 index 0000000..90fede2 --- /dev/null +++ b/job.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include "job.h" + +void Scheduler::workerEntry() { + while (true) { + Job *job = nullptr; + + { + lock_guard commMutGuard(commMut); + if (jobs.size() > 0) { + job = jobs.front(); + jobs.pop(); + } + } + + if (job) { + job->callback(); + } else if (finishFlag) { + break; + } 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() { + if (hasJoined) return; + + { + lock_guard commMutGuard(commMut); + finishFlag = true; + } + + for (int i = 0; i < nthreads; i++) { + workers[i].join(); + } + + hasJoined = true; +} diff --git a/job.h b/job.h new file mode 100644 index 0000000..95da7c7 --- /dev/null +++ b/job.h @@ -0,0 +1,40 @@ +#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 finishFlag = false; + mutex commMut; + + bool hasJoined = false; + + vector workers; + + void workerEntry(); + +public: + const int nthreads; + + Scheduler(int nthreads); + ~Scheduler(); + + // func is run in child thread + void submit(const function &func); + + void finish(); +}; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 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 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 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> 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 &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] << " " << endl; + return 1; + } + + Params params; + params.refereePath = argv[1]; + + vector 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> 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/multilog.cpp b/multilog.cpp new file mode 100644 index 0000000..d2dce10 --- /dev/null +++ b/multilog.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "multilog.h" +#include "params.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); + + assert(prefix.find('\n') == string::npos); + + items.emplace_back(); + items.back().line = prefix; + if (multilog_fancy) { + cout << prefix << endl; + } else { + cout << "[" << items.back().id << "] " << 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; + if (multilog_fancy) { + redrawLine(idx); + } else { + cout << "[" << items[idx].id << "]... " << text << endl; + } +} + +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/multilog.h b/multilog.h new file mode 100644 index 0000000..ba0ff4d --- /dev/null +++ b/multilog.h @@ -0,0 +1,32 @@ +#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/params.h b/params.h new file mode 100644 index 0000000..c3e6d5e --- /dev/null +++ b/params.h @@ -0,0 +1,15 @@ +#pragma once + + +// Actual variable needs to be const as well to enable C++ default-internal linkage +const char *const matchcachedir = "comp_cache"; +const char *const playerlogdir = "comp_playerlogs"; +const char *const gamelogdir = "comp_gamelogs"; + +const int num_threads = 2; + +const int num_matches = 5; +const int timeout_msec = 60000; + +const bool multilog_fancy = true; +const bool referee_verbose = false; diff --git a/process.cpp b/process.cpp new file mode 100644 index 0000000..00a2e08 --- /dev/null +++ b/process.cpp @@ -0,0 +1,152 @@ +#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; + if (errno == ECHILD) break; + perror("waitpid"); + break; + } + if (WIFEXITED(status)) { + pid = -1; + 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; + } + if (nr == 0) { // EOF + 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() { + // TODO: send only SIGTERM, then wait a little while, then send + // SIGKILL if necessary + if (pid != -1) { + kill(pid, SIGKILL); // force kill + pid = -1; + } +} + +pid_t Process::getPid() const { + return pid; +} diff --git a/process.h b/process.h new file mode 100644 index 0000000..900a0b6 --- /dev/null +++ b/process.h @@ -0,0 +1,36 @@ +#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); + Process(const Process&) = delete; + Process(Process&&) = default; + + void redirectStderr(const string_view fname); + + void run(); + void wait(); + void stop(); + void unStop(); + void terminate(); + + pid_t getPid() const; + + bool writeLine(const string_view line); + optional readLine(); +}; diff --git a/referee.cpp b/referee.cpp new file mode 100644 index 0000000..3f15b6b --- /dev/null +++ b/referee.cpp @@ -0,0 +1,92 @@ +#include +#include +#include "referee.h" +#include "params.h" + + +Referee::Referee(const string_view execname, const vector &players) + : proc(execname) { + + proc.run(); + + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") starting for:"; + for (const string &p : players) cout << " " << p; + cout << endl; + } + + proc.writeLine(to_string(players.size())); + for (const string &p : players) { + proc.writeLine(p); + } +} + +Referee::~Referee() { + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") stopping" << endl; + } + proc.wait(); + proc.terminate(); +} + +bool Referee::moveValid(int player, const string_view line) { + string s = to_string(player) + " "; + s.append(line); + + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") write <" << s << ">" << endl; + } + + proc.writeLine(s); + + optional response = proc.readLine(); + if (response) { + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") read <" << *response << ">" << endl; + } + + if (*response == "valid end") { + isEnd = true; + readScores(); + return true; + } else if (*response == "valid") { + return true; + } else { + return false; + } + } else { + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") EOF!" << endl; + } + return false; + } +} + +bool Referee::gameEnded() { + return isEnd; +} + +void Referee::readScores() { + optional line = proc.readLine(); + if (!line) { + cerr << "Referee stopped before providing scores" << endl; + exit(1); + } + + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") readScores <" << *line << ">" << endl; + } + + size_t cursor = 0; + while (cursor < line->size()) { + size_t idx = line->find(' ', cursor); + if (idx == string::npos) idx = line->size(); + scores.push_back(stoi(line->substr(cursor, idx - cursor))); + cursor = idx + 1; + } +} + +optional> Referee::getScores() { + if (isEnd) return scores; + else return nullopt; +} diff --git a/referee.h b/referee.h new file mode 100644 index 0000000..5f278c5 --- /dev/null +++ b/referee.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include "process.h" + +using namespace std; + + +class Referee { + Process proc; + bool isEnd = false; + vector scores; + + void readScores(); + +public: + Referee(const string_view execname, const vector &players); + ~Referee(); + + // player: 0 = first player, 1 = second player + bool moveValid(int player, const string_view line); + + bool gameEnded(); + optional> getScores(); +}; + +/* +Referee protocol: + +At startup, the referee receives a line containing the number of players in the +game, followed by that number of lines containing the players' names. + +Then, it repeatedly receives a line on stdin of the form ' ', +where is the index of the player in the game (0 is first, 1 is second, +etc.) and is the line as written out by the player. The referee should +then write a line to stdout that is equal to either 'valid', 'invalid', or +'valid end'. 'valid' and 'invalid' indicate a valid and invalid move, while +'valid end' indicates a valid move that ends the game. After writing the line +'valid end', the referee should write a line containing as many integers +(separated by spaces) as there are players, giving their scores at the end of +the game. +*/ -- cgit v1.2.3-54-g00ecf