summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2019-02-16 12:59:19 +0100
committertomsmeding <tom.smeding@gmail.com>2019-02-16 18:20:20 +0100
commit1154c8605a5c42a651bac5e2f74839062d352b9e (patch)
tree42527395c2eec9566d0e23b8bd17e5c59239b1dd
parente2279628e551e5c82f70c739f6c02671e6ccd9fd (diff)
Add referee features
-rw-r--r--main.cpp25
-rw-r--r--process.cpp77
-rw-r--r--process.h1
-rw-r--r--referee.cpp127
-rw-r--r--referee.h43
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 &params) {
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 &params) {
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 <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/time.h>
#include "process.h"
#include "error.h"
@@ -189,6 +191,81 @@ optional<string> Process::readLine() {
}
}
+optional<string> 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<string> readLine();
+ optional<string> 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<string> &players)
- : proc(execname) {
+ : proc(execname), refereeExecname(execname) {
proc.run();
@@ -14,11 +20,15 @@ Referee::Referee(const string_view execname, const vector<string> &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<string> response = readLine();
+ optional<string> 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<string> 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<string> Referee::readLine() {
+void Referee::queryFeatures() {
+ bool first = true;
+
+ while (true) {
+ optional<string> 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<string> 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<string> line = readLine();
+ optional<string> line = proc.readLine();
if (!line) {
cerr << "Referee stopped before providing scores" << endl;
exit(1);
@@ -138,3 +183,7 @@ optional<vector<int>> 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<int> scores;
- bool featureWriteLines = false;
+ Features features;
vector<pair<int, string>> writeLinesList;
- optional<string> readLine();
+ void queryFeatures();
void readScores();
+ void readWriteLines();
public:
Referee(const string_view execname, const vector<string> &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 <name>', indicating that the referee supports the feature given by
+<name>. 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 '<player> <line>',
where <player> 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 <name>' 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 '<player> <line>' to stdout, where <player> is the index of the
player on whose stdin to write <line>. 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'.
*/