diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | error.cpp | 9 | ||||
-rw-r--r-- | error.h | 12 | ||||
-rw-r--r-- | job.cpp | 62 | ||||
-rw-r--r-- | job.h | 40 | ||||
-rw-r--r-- | main.cpp | 386 | ||||
-rw-r--r-- | multilog.cpp | 68 | ||||
-rw-r--r-- | multilog.h | 32 | ||||
-rw-r--r-- | params.h | 15 | ||||
-rw-r--r-- | process.cpp | 152 | ||||
-rw-r--r-- | process.h | 36 | ||||
-rw-r--r-- | referee.cpp | 92 | ||||
-rw-r--r-- | referee.h | 45 |
14 files changed, 975 insertions, 0 deletions
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) {} @@ -0,0 +1,12 @@ +#include <stdexcept> +#include <string> + +using namespace std; + + +class StopCompetitionError : public runtime_error { +public: + StopCompetitionError(); + explicit StopCompetitionError(const string &what_arg); + explicit StopCompetitionError(const char *what_arg); +}; @@ -0,0 +1,62 @@ +#include <iostream> +#include <chrono> +#include <cassert> +#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<void()> &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; +} @@ -0,0 +1,40 @@ +#pragma once + +#include <functional> +#include <queue> +#include <vector> +#include <thread> +#include <mutex> + +using namespace std; + + +class Scheduler { + struct Job { + function<void()> callback; + + Job(const function<void()> callback) + : callback(callback) {} + }; + + queue<Job*> jobs; + bool finishFlag = false; + mutex commMut; + + bool hasJoined = false; + + vector<thread> workers; + + void workerEntry(); + +public: + const int nthreads; + + Scheduler(int nthreads); + ~Scheduler(); + + // func is run in child thread + void submit(const function<void()> &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 <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; + } +} 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 <iostream> +#include <string> +#include <cassert> +#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 <vector> +#include <string_view> +#include <mutex> + +using namespace std; + + +// Access is thread-safe +class MultiLog { + struct Item { + int id; + string line; + bool complete = false; + + Item(); + }; + + mutex mut; + vector<Item> 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 <iostream> +#include <cstdlib> +#include <fcntl.h> +#include <sys/wait.h> +#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<string> 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 <string> +#include <optional> +#include <string_view> +#include <unistd.h> + +using namespace std; + + +class Process { + string execname; + optional<string> 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<string> 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 <iostream> +#include <cstdlib> +#include "referee.h" +#include "params.h" + + +Referee::Referee(const string_view execname, const vector<string> &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<string> 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<string> 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<vector<int>> 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 <vector> +#include <string> +#include <string_view> +#include <optional> +#include "process.h" + +using namespace std; + + +class Referee { + Process proc; + bool isEnd = false; + vector<int> scores; + + void readScores(); + +public: + Referee(const string_view execname, const vector<string> &players); + ~Referee(); + + // player: 0 = first player, 1 = second player + bool moveValid(int player, const string_view line); + + bool gameEnded(); + optional<vector<int>> 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 '<player> <line>', +where <player> is the index of the player in the game (0 is first, 1 is second, +etc.) and <line> 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. +*/ |