From 1154c8605a5c42a651bac5e2f74839062d352b9e Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Sat, 16 Feb 2019 12:59:19 +0100 Subject: Add referee features --- main.cpp | 25 ++++++++---- process.cpp | 77 ++++++++++++++++++++++++++++++++++++ process.h | 1 + referee.cpp | 127 +++++++++++++++++++++++++++++++++++++++++------------------- referee.h | 43 +++++++++++++++----- 5 files changed, 216 insertions(+), 57 deletions(-) diff --git a/main.cpp b/main.cpp index a4b47ed..ecd12f7 100644 --- a/main.cpp +++ b/main.cpp @@ -257,14 +257,26 @@ static void playMatch(Player &p1, Player &p2, int index, const Params ¶ms) { procs[0].run(); procs[1].run(); + const auto handleWriteLines = [&referee, &procs]() { + for (const auto &p : referee.playerWriteLines()) { + assert(0 <= p.first && p.first < 2); + procs[p.first].writeLine(p.second); + } + }; + + // Received upon initialisation of referee + handleWriteLines(); + 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 - << " (game " << gameCodeName(p1, p2, index) << ")" << endl; - throw StopCompetitionError(); + if (!referee.getFeatures().noLastMove) { + if (!procs[i].writeLine(lastMove)) { + cout << "ERROR writing move to player " << i+1 + << " (game " << gameCodeName(p1, p2, index) << ")" << endl; + throw StopCompetitionError(); + } } procs[i].unStop(); @@ -296,10 +308,7 @@ static void playMatch(Player &p1, Player &p2, int index, const Params ¶ms) { throw StopCompetitionError(); } - for (const auto &p : referee.playerWriteLines()) { - assert(0 <= p.first && p.first < 2); - procs[p.first].writeLine(p.second); - } + handleWriteLines(); if (referee.gameEnded()) { mres.status = MatchResult::Status::ok; diff --git a/process.cpp b/process.cpp index 41325c3..061737e 100644 --- a/process.cpp +++ b/process.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "process.h" #include "error.h" @@ -189,6 +191,81 @@ optional Process::readLine() { } } +optional Process::readLineTimeout(size_t millis) { + if (pid == -1) { + cout << "-- readLineTimeout on pid==-1 --" << endl; + return nullopt; + } + + size_t idx = readBuf.find('\n'); + if (idx != string::npos) { + string res = readBuf.substr(0, idx); + readBuf = readBuf.substr(idx + 1); + return res; + } + + struct timeval start; + gettimeofday(&start, nullptr); + + struct timeval totaltime; + totaltime.tv_sec = millis / 1000; + totaltime.tv_usec = millis % 1000 * 1000; + + struct timeval left = totaltime; + + while (true) { + string s(1024, '\0'); + + ssize_t nr; + + fd_set inset; + FD_ZERO(&inset); + FD_SET(outfd, &inset); + int ret = select(outfd + 1, &inset, nullptr, nullptr, &left); + if (ret < 0) { + if (errno == EINTR) goto continue_adjust_time_left; + perror("select"); + return nullopt; + } + + if (!FD_ISSET(outfd, &inset)) goto continue_adjust_time_left; + + nr = read(outfd, &s[0], s.size()); + if (nr < 0) { + if (errno == EINTR) goto continue_adjust_time_left; + perror("read"); + return nullopt; + } + if (nr == 0) { // EOF + cout << "-- eof in readLine --" << endl; + 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; + + continue_adjust_time_left: + struct timeval now, delta; + gettimeofday(&now, nullptr); + timersub(&now, &start, &delta); + timersub(&totaltime, &delta, &left); + + struct timeval zero; + timerclear(&zero); + // Not greater, so less-equal; c.f. manpage + if (!timercmp(&left, &zero, >)) { + return nullopt; // Exceeded timeout + } + } +} + int Process::terminate() { // TODO: send only SIGTERM, then wait a little while, then send // SIGKILL if necessary diff --git a/process.h b/process.h index 24ff4b0..3505e8e 100644 --- a/process.h +++ b/process.h @@ -40,4 +40,5 @@ public: bool writeLine(const string_view line); optional readLine(); + optional readLineTimeout(size_t millis); }; diff --git a/referee.cpp b/referee.cpp index 15f49c3..ffcbd14 100644 --- a/referee.cpp +++ b/referee.cpp @@ -3,9 +3,15 @@ #include "referee.h" #include "params.h" +// Timeout in milliseconds for the feature negotiation at the start of the +// protocol. If the referee hasn't sent anything after this timeout, we assume +// the referee isn't capable of sending feature lines and will be treated as +// such for the duration of the competition. +#define FEATURE_TIMEOUT 200 + Referee::Referee(const string_view execname, const vector &players) - : proc(execname) { + : proc(execname), refereeExecname(execname) { proc.run(); @@ -14,11 +20,15 @@ Referee::Referee(const string_view execname, const vector &players) for (const string &p : players) cout << " " << p; cout << endl; } - + + queryFeatures(); + proc.writeLine(to_string(players.size())); for (const string &p : players) { proc.writeLine(p); } + + readWriteLines(); } Referee::~Referee() { @@ -39,49 +49,32 @@ bool Referee::moveValid(int player, const string_view line) { proc.writeLine(s); - optional response = readLine(); + optional response = proc.readLine(); if (response) { + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") read <" << *response << ">" << endl; + } + bool result; if (*response == "valid end") { isEnd = true; - readScores(); result = true; } else if (*response == "valid") { result = true; - } else { + } else if (*response == "invalid") { result = false; + } else { + cout << "REF(" << proc.getPid() << ") Invalid validity judgement '" << *response << "'!" << endl; + exit(1); } - if (featureWriteLines) { - writeLinesList.clear(); - - while (true) { - optional line = readLine(); - if (!line) { - if (referee_verbose) { - cout << "REF(" << proc.getPid() << ") EOF!" << endl; - } - return false; - } - - if (line == "write_end") break; + readWriteLines(); - size_t idx = line->find(' '); - if (idx == string::npos) { - cerr << "Referee feature_write_lines protocol violation!" << endl; - exit(1); - } - - int player = stoi(line->substr(0, idx)); - writeLinesList.emplace_back(player, line->substr(idx + 1)); - } - } + if (isEnd) readScores(); return result; } else { - if (referee_verbose) { - cout << "REF(" << proc.getPid() << ") EOF!" << endl; - } + cout << "REF(" << proc.getPid() << ") EOF!" << endl; return false; } } @@ -94,28 +87,80 @@ bool Referee::gameEnded() { return isEnd; } -optional Referee::readLine() { +void Referee::queryFeatures() { + bool first = true; + + while (true) { + optional line; + if (first) { + line = proc.readLineTimeout(FEATURE_TIMEOUT); + if (!line) { + cout << "Referee '" << refereeExecname << "' seems to follow the old protocol!" << endl; + return; + } + + first = false; + } else { + line = proc.readLine(); + } + + if (!line) { + cout << "REF(" << proc.getPid() << ") EOF!" << endl; + exit(1); + } + + if (referee_verbose) { + cout << "REF(" << proc.getPid() << ") read <" << *line << ">" << endl; + } + + if (*line == "feature_end") { + return; + } else if (line->substr(0, 8) == "feature ") { + string featname = line->substr(8); + if (featname == "write_lines") features.writeLines = true; + else if (featname == "no_last_move") features.noLastMove = true; + else { + cerr << "REF(" << proc.getPid() << ") Referee requested unsupported feature '" << featname << "'" << endl; + exit(1); + } + } else { + cerr << "REF(" << proc.getPid() << ") Referee protocol violation in feature negotiation" << endl; + exit(1); + } + } +} + +void Referee::readWriteLines() { + writeLinesList.clear(); + + if (!features.writeLines) return; + while (true) { optional line = proc.readLine(); - if (!line) return line; + if (!line) { + cout << "REF(" << proc.getPid() << ") EOF!" << endl; + break; + } if (referee_verbose) { cout << "REF(" << proc.getPid() << ") read <" << *line << ">" << endl; } - if (line->substr(0, 8) != "feature ") return line; + if (line == "write_end") break; - string featname = line->substr(8); - if (featname == "write_lines") featureWriteLines = true; - else { - cerr << "Referee requested unsupported feature '" << featname << "'" << endl; + size_t idx = line->find(' '); + if (idx == string::npos) { + cerr << "REF(" << proc.getPid() << ") Referee feature_write_lines protocol violation!" << endl; exit(1); } + + int player = stoi(line->substr(0, idx)); + writeLinesList.emplace_back(player, line->substr(idx + 1)); } } void Referee::readScores() { - optional line = readLine(); + optional line = proc.readLine(); if (!line) { cerr << "Referee stopped before providing scores" << endl; exit(1); @@ -138,3 +183,7 @@ optional> Referee::getScores() { void Referee::terminate() { proc.terminate(); } + +const Features& Referee::getFeatures() const { + return features; +} diff --git a/referee.h b/referee.h index 891021d..82b8b02 100644 --- a/referee.h +++ b/referee.h @@ -10,16 +10,28 @@ using namespace std; +struct Features { + // If true, players should not receive the last move on stdin. + // Used by the match runner. + bool noLastMove = false; + + // If true, the referee program can write additional lines to the players. + // Used by the Referee class to produce playerWriteLines() output. + bool writeLines = false; +}; + class Referee { Process proc; + string refereeExecname; bool isEnd = false; vector scores; - bool featureWriteLines = false; + Features features; vector> writeLinesList; - optional readLine(); + void queryFeatures(); void readScores(); + void readWriteLines(); public: Referee(const string_view execname, const vector &players); @@ -34,13 +46,20 @@ public: // Kills the referee process void terminate(); + + const Features& getFeatures() const; }; /* 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. +At startup, the referee should write a number of lines to stdout of the form +'feature ', indicating that the referee supports the feature given by +. The list of features should be ended with a line containing +'feature_end'. For supported features and their effect, see below. + +The referee then 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, @@ -53,12 +72,16 @@ then write a line to stdout that is equal to either 'valid', 'invalid', or the game. FEATURES -At any time the referee may write 'feature ' to stdout, which enables the -protocol feature with the given name. The following features are supported: -- 'write_lines': After a move validity judgement, the referee should write lines +The following features are supported: +- 'write_lines': After having received the list of players at the start of the + protocol, and after a move validity judgement, the referee should write lines of the form ' ' to stdout, where is the index of the player on whose stdin to write . The referee should signal the end of - this list by writing a line 'write_end' to stdout. - This allows the referee to write additional data to players' inputs after they - have written their moves. + this list by writing a line 'write_end' to stdout. This allows the referee to + write additional data to players' inputs after they have written their moves. + Note that 'valid end' is also a move validity judgement, and the player lines + should be written *before* writing the scores. +- 'no_last_move': Prevent the competition manager from writing the last player's + last move to stdin of the next player. Should probably be used in conjunction + with 'write_lines'. */ -- cgit v1.2.3