summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2019-08-08 22:06:03 +0200
committertomsmeding <tom.smeding@gmail.com>2019-08-08 22:06:03 +0200
commitc01700906812f949c9088709b6c34307fbf63338 (patch)
tree73f97ed62a11aebb5e566ae6bf694a6c2442d4e8
parent56dfacebe34bac5ab53108e34b7872fee78ddf65 (diff)
BREAKING New referee protocol
-rw-r--r--Makefile2
-rw-r--r--main.cpp81
-rw-r--r--referee.cpp217
-rw-r--r--referee.h95
4 files changed, 203 insertions, 192 deletions
diff --git a/Makefile b/Makefile
index 3d4f9b3..52d8e7e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
ifeq ($(shell uname),Darwin)
- CXX = g++-8
+ CXX = g++-9
else
CXX = g++
endif
diff --git a/main.cpp b/main.cpp
index fa07cf4..24edd5b 100644
--- a/main.cpp
+++ b/main.cpp
@@ -257,78 +257,69 @@ 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 (!referee.getFeatures().noLastMove) {
- if (!procs[i].writeLine(lastMove)) {
- cout << "ERROR writing move to player " << i+1
- << " (game " << gameCodeName(p1, p2, index) << ")" << endl;
- throw StopCompetitionError();
- }
- }
+ auto event = referee.nextEvent();
+
+ if (auto readEvent = get_if<Referee::ReadEvent>(&event)) {
+ Process &proc = procs[readEvent->player];
- procs[i].unStop();
+ proc.unStop();
int64_t start = gettimestamp();
- optional<string> oline = procs[i].readLine();
+
+ optional<string> oline = proc.readLine();
if (!oline) {
- cout << "ERROR reading move from player " << i+1
+ cout << "ERROR reading move from player " << readEvent->player + 1
<< " (game " << gameCodeName(p1, p2, index) << ")" << endl;
- cout << "(process exit code: " << procs[i].waitPoll() << ")" << endl;
+ cout << "(process exit code: " << proc.waitPoll() << ")" << endl;
throw StopCompetitionError();
}
- lastMove = *oline;
- (i == 0 ? mres.ms1 : mres.ms2) += gettimestamp() - start;
- procs[i].stop();
+ (readEvent->player == 0 ? mres.ms1 : mres.ms2) += gettimestamp() - start;
+ proc.stop();
- gamelog << "P" << i+1 << ": " << lastMove << endl;
+ readEvent->callback(*oline);
if (mres.ms1 / 1000 > timeout_msec || mres.ms2 / 1000 > timeout_msec) {
mres.status = MatchResult::Status::timeout;
mres.sc1 = mres.sc2 = 0;
referee.terminate();
- goto match_done;
+ break;
}
- if (!referee.moveValid(i, lastMove)) {
- cout << "ERROR in player " << i+1 << ": invalid move '" << lastMove << "'"
- << " (game " << gameCodeName(p1, p2, index) << ")" << endl;
+ } else if (auto writeEvent = get_if<Referee::WriteEvent>(&event)) {
+ if (!procs[writeEvent->player].writeLine(writeEvent->line)) {
+ cout << "ERROR writing move to player " << writeEvent->player + 1
+ << " (game " << gameCodeName(p1, p2, index) << ")" << endl;
throw StopCompetitionError();
}
- handleWriteLines();
+ } else if (auto gamelogEvent = get_if<Referee::GamelogEvent>(&event)) {
+ gamelog << "P" << gamelogEvent->player + 1 << ": "
+ << gamelogEvent->line << endl;
- if (referee.gameEnded()) {
- mres.status = MatchResult::Status::ok;
+ } else if (auto endEvent = get_if<Referee::EndEvent>(&event)) {
+ mres.status = MatchResult::Status::ok;
- optional<vector<int>> oscores = referee.getScores();
- assert(oscores);
- assert(oscores->size() == 2);
- mres.sc1 = oscores->at(0);
- mres.sc2 = oscores->at(1);
+ assert(endEvent->scores.size() == 2);
+ mres.sc1 = endEvent->scores[0];
+ mres.sc2 = endEvent->scores[1];
+ break;
- goto match_done;
- }
+ } else if (auto errorEvent = get_if<Referee::ErrorEvent>(&event)) {
+ cout << "ERROR in player " << errorEvent->player + 1 << ": " << errorEvent->message
+ << " (game " << gameCodeName(p1, p2, index) << ")" << endl;
+ gamelog << endl
+ << "ERROR in P" << errorEvent->player + 1 << ": " << errorEvent->message << endl;
+ throw StopCompetitionError();
+
+ } else {
+ assert(false);
}
}
-match_done:
for (int i = 0; i < 2; i++) {
- bool success = procs[i].writeLine("Quit");
procs[i].unStop();
- if (success) usleep(10000);
+ if (procs[i].waitPoll() == -1) usleep(10000);
int ret = procs[i].terminate();
if (ret != 0 && ret != 1009) {
cout << "Player " << i+1 << " exited with code " << ret << endl;
diff --git a/referee.cpp b/referee.cpp
index ffcbd14..2701e78 100644
--- a/referee.cpp
+++ b/referee.cpp
@@ -1,90 +1,160 @@
#include <iostream>
+#include <tuple>
#include <cstdlib>
#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.
+// the referee isn't capable of sending feature lines.
#define FEATURE_TIMEOUT 200
Referee::Referee(const string_view execname, const vector<string> &players)
- : proc(execname), refereeExecname(execname) {
+ : proc(execname), refereeExecname(execname), numPlayers(players.size()) {
proc.run();
+ reftag = "REF(" + to_string(proc.getPid()) + ") ";
+
if (referee_verbose) {
- cout << "REF(" << proc.getPid() << ") starting for:";
+ cout << reftag << "starting for:";
for (const string &p : players) cout << " " << p;
cout << endl;
}
queryFeatures();
+ if (!features.version2) {
+ cout << reftag << "does not support protocol version 2!" << endl;
+ exit(1);
+ }
+
proc.writeLine(to_string(players.size()));
for (const string &p : players) {
proc.writeLine(p);
}
-
- readWriteLines();
}
Referee::~Referee() {
if (referee_verbose) {
- cout << "REF(" << proc.getPid() << ") stopping" << endl;
+ cout << reftag << "stopping" << endl;
}
proc.wait();
proc.terminate();
}
-bool Referee::moveValid(int player, const string_view line) {
- string s = to_string(player) + " ";
- s.append(line);
+Referee::Event Referee::nextEvent() {
+ optional<string> ocommand = proc.readLine();
+ if (!ocommand) {
+ cout << reftag << "Unexpected EOF before 'end' command!" << endl;
+ exit(1);
+ }
+
+ string &command = *ocommand;
if (referee_verbose) {
- cout << "REF(" << proc.getPid() << ") write <" << s << ">" << endl;
+ cout << reftag << "read <" << command << ">" << endl;
}
- proc.writeLine(s);
+ auto getPlayer = [this](const string &command, const char *commandName, int prefixLen) {
+ int player;
+ try {
+ player = stoi(command.substr(prefixLen));
+ } catch (...) {
+ cout << reftag << "Protocol violation in '" << commandName << "' command!" << endl;
+ exit(1);
+ }
- optional<string> response = proc.readLine();
- if (response) {
- if (referee_verbose) {
- cout << "REF(" << proc.getPid() << ") read <" << *response << ">" << endl;
+ if (player < 0 || player >= numPlayers) {
+ cout << reftag << "Invalid player index " << player << " mentioned in '" << commandName << "' command!" << endl;
+ exit(1);
}
- bool result;
- if (*response == "valid end") {
- isEnd = true;
- result = true;
- } else if (*response == "valid") {
- result = true;
- } else if (*response == "invalid") {
- result = false;
- } else {
- cout << "REF(" << proc.getPid() << ") Invalid validity judgement '" << *response << "'!" << endl;
+ size_t afterIndex = command.find(' ', prefixLen);
+ if (afterIndex != string::npos) afterIndex++;
+
+ return make_pair(player, afterIndex);
+ };
+
+ if (command.substr(0, 5) == "read ") {
+ int player = getPlayer(command, "read", 5).first;
+ return ReadEvent{
+ player,
+ [this](const string &line) {
+ if (referee_verbose) {
+ cout << reftag << "write <" << line << ">" << endl;
+ }
+
+ if (!proc.writeLine(line)) {
+ cout << reftag << "Referee exited unexpectedly after 'read' command!" << endl;
+ exit(1);
+ }
+ }
+ };
+ } else if (command.substr(0, 6) == "write ") {
+ int player;
+ size_t afterIndex;
+ tie(player, afterIndex) = getPlayer(command, "write", 6);
+
+ if (afterIndex == string::npos) {
+ cout << reftag << "Protocol violation in 'write' command: no line!" << endl;
exit(1);
}
- readWriteLines();
+ return WriteEvent{player, command.substr(afterIndex)};
+ } else if (command.substr(0, 8) == "gamelog ") {
+ int player;
+ size_t afterIndex;
+ tie(player, afterIndex) = getPlayer(command, "gamelog", 8);
- if (isEnd) readScores();
+ if (afterIndex == string::npos) {
+ cout << reftag << "Protocol violation in 'gamelog' command: no line!" << endl;
+ exit(1);
+ }
- return result;
- } else {
- cout << "REF(" << proc.getPid() << ") EOF!" << endl;
- return false;
- }
-}
+ return GamelogEvent{player, command.substr(afterIndex)};
+ } else if (command.substr(0, 4) == "end ") {
+ size_t cursor = 4;
-vector<pair<int, string>> Referee::playerWriteLines() {
- return writeLinesList;
-}
+ vector<int> scores;
+ while (cursor < command.size()) {
+ size_t idx = command.find(' ', cursor);
+ if (idx == string::npos) idx = command.size();
+
+ string substr = command.substr(cursor, idx - cursor);
+
+ int score;
+ try {
+ score = stoi(substr);
+ } catch (...) {
+ cout << reftag << "Protocol violation in 'end' command: invalid scores!" << endl;
+ exit(1);
+ }
+
+ scores.push_back(score);
+
+ cursor = idx + 1;
+ }
+
+ proc.terminate();
+ return EndEvent{move(scores)};
+ } else if (command.substr(0, 6) == "error ") {
+ int player;
+ size_t afterIndex;
+ tie(player, afterIndex) = getPlayer(command, "error", 6);
+
+ if (afterIndex == string::npos) {
+ cout << reftag << "Protocol violation in 'error' command: no message!" << endl;
+ exit(1);
+ }
-bool Referee::gameEnded() {
- return isEnd;
+ proc.terminate();
+ return ErrorEvent{player, command.substr(6)};
+ } else {
+ cout << reftag << "Protocol violation: unknown command '" << command << "'!" << endl;
+ exit(1);
+ }
}
void Referee::queryFeatures() {
@@ -95,8 +165,8 @@ void Referee::queryFeatures() {
if (first) {
line = proc.readLineTimeout(FEATURE_TIMEOUT);
if (!line) {
- cout << "Referee '" << refereeExecname << "' seems to follow the old protocol!" << endl;
- return;
+ cout << "Referee '" << refereeExecname << "' does not support feature detection!" << endl;
+ exit(1);
}
first = false;
@@ -105,85 +175,30 @@ void Referee::queryFeatures() {
}
if (!line) {
- cout << "REF(" << proc.getPid() << ") EOF!" << endl;
+ cout << reftag << "EOF!" << endl;
exit(1);
}
if (referee_verbose) {
- cout << "REF(" << proc.getPid() << ") read <" << *line << ">" << endl;
+ cout << reftag << "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;
+ if (featname == "version_2") features.version2 = true;
else {
- cerr << "REF(" << proc.getPid() << ") Referee requested unsupported feature '" << featname << "'" << endl;
+ cerr << reftag << "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) {
- cout << "REF(" << proc.getPid() << ") EOF!" << endl;
- break;
- }
-
- if (referee_verbose) {
- cout << "REF(" << proc.getPid() << ") read <" << *line << ">" << endl;
- }
-
- if (line == "write_end") break;
-
- size_t idx = line->find(' ');
- if (idx == string::npos) {
- cerr << "REF(" << proc.getPid() << ") Referee feature_write_lines protocol violation!" << endl;
+ cerr << reftag << "Referee protocol violation in feature negotiation" << endl;
exit(1);
}
-
- int player = stoi(line->substr(0, idx));
- writeLinesList.emplace_back(player, line->substr(idx + 1));
}
}
-void Referee::readScores() {
- optional<string> line = proc.readLine();
- if (!line) {
- cerr << "Referee stopped before providing scores" << endl;
- exit(1);
- }
-
- 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;
-}
-
void Referee::terminate() {
proc.terminate();
}
-
-const Features& Referee::getFeatures() const {
- return features;
-}
diff --git a/referee.h b/referee.h
index 82b8b02..1b9d507 100644
--- a/referee.h
+++ b/referee.h
@@ -5,49 +5,58 @@
#include <string_view>
#include <optional>
#include <utility>
+#include <functional>
+#include <variant>
#include "process.h"
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;
+ int numPlayers;
+
+ string reftag;
+ struct Features {
+ // Referee supports current version of the protocol.
+ bool version2;
+ };
Features features;
- vector<pair<int, string>> writeLinesList;
void queryFeatures();
- void readScores();
- void readWriteLines();
public:
+ struct ReadEvent {
+ int player;
+ // call with the line read (no newline)
+ function<void(const string&)> callback;
+ };
+ struct WriteEvent {
+ int player;
+ string line;
+ };
+ struct GamelogEvent {
+ int player;
+ string line;
+ };
+ struct EndEvent {
+ vector<int> scores;
+ };
+ struct ErrorEvent {
+ int player;
+ string message;
+ };
+ using Event = variant<ReadEvent, WriteEvent, GamelogEvent, EndEvent, ErrorEvent>;
+
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);
- vector<pair<int, string>> playerWriteLines();
-
- bool gameEnded();
- optional<vector<int>> getScores();
+ Event nextEvent();
// Kills the referee process
void terminate();
-
- const Features& getFeatures() const;
};
/*
@@ -56,32 +65,28 @@ Referee protocol:
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.
+'feature_end'. To use the version of the protocol described here, it is
+mandatory to request 'feature version_2'. 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,
-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.
+Then, the referee has the following commands available:
+- 'read <player>': read a line from the player with index <player>; this line
+ is written directly to the referee afterwards. The first player is 0, the
+ second player is 1, etc.
+- 'write <player> <line>': write a line to the player with index <player>.
+- 'gamelog <player> <line>': write a line in the game log indicating that the
+ player with index <player> made the move given by <line>.
+- 'end <score> <score> ...': signify that the game has ended. The final scores
+ of the players are given after 'end'. These scores should be given as a
+ space-separated list of integers, as many as there are players.
+- 'error <player> <message>': indicate that the player with index <player> has
+ produced erroneous output. This ends the game. The <message> will be
+ presented to the user and written to the gamelog.
FEATURES
-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.
- 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'.
+Currently, the following features are supported:
+- 'version_2': must be used by all referees.
*/