diff options
| -rw-r--r-- | main.cpp | 260 | 
1 files changed, 198 insertions, 62 deletions
@@ -166,10 +166,15 @@ MatchResult MatchResult::inverted() const {  	return r;  } +enum class Mode { +	competition, +	single, +}; +  struct Params { +	Mode mode;  	bool referee_verbose = false;  	string refereePath; -	int num_matches = 5;  	size_t timeout_msec = 10000;  }; @@ -244,33 +249,17 @@ static void recordResult(Player &p1, Player &p2, const MatchResult &result) {  	}  } -static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, const Params ¶ms) { -	int logId = multiLog.add(p1.fname + " - " + p2.fname + ": "); - -	if (optional<MatchResult> optres = readMatchCache(p1, p2, index)) { -		multiLog.append(logId, optres->describe(p1, p2) + " (cached)"); -		multiLog.complete(logId); -		recordResult(p1, p2, *optres); -		return; -	} - +static MatchResult playMatch(const Player &p1, const Player &p2, const Params ¶ms, const string &gamecode, ostream &gamelog, const string &p1logpath, const string &p2logpath) {  	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.referee_verbose, 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].redirectStderr(p1logpath); +	procs[1].redirectStderr(p2logpath);  	procs[0].run();  	procs[1].run(); @@ -286,7 +275,7 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con  			optional<string> oline = proc.readLine();  			if (!oline) {  				cout << "ERROR reading move from player " << readEvent->player + 1 -				     << " (game " << gameCodeName(p1, p2, index) << ")" << endl; +				     << " (game " << gamecode << ")" << endl;  				cout << "(process exit code: " << proc.waitPoll() << ")" << endl;  				throw StopCompetitionError();  			} @@ -306,7 +295,7 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con  		} else if (auto writeEvent = get_if<Referee::WriteEvent>(&event)) {  			if (!procs[writeEvent->player].writeLine(writeEvent->line, writeEvent->allowBrokenPipe)) {  				cout << "ERROR writing move to player " << writeEvent->player + 1 -					 << " (game " << gameCodeName(p1, p2, index) << ")" << endl; +					 << " (game " << gamecode << ")" << endl;  				throw StopCompetitionError();  			} @@ -324,7 +313,7 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con  		} else if (auto errorEvent = get_if<Referee::ErrorEvent>(&event)) {  			cout << "ERROR in player " << errorEvent->player + 1 << ": " << errorEvent->message -				 << " (game " << gameCodeName(p1, p2, index) << ")" << endl; +				 << " (game " << gamecode << ")" << endl;  			gamelog << endl  			        << "ERROR in P" << errorEvent->player + 1 << ": " << errorEvent->message << endl;  			throw StopCompetitionError(); @@ -343,64 +332,154 @@ static void playMatch(MultiLog &multiLog, Player &p1, Player &p2, int index, con  		}  	} -	multiLog.append(logId, mres.describe(p1, p2)); -	multiLog.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; +	return mres; +} + +static void playMatchInCompetition(MultiLog &multiLog, Player &p1, Player &p2, int index, const Params ¶ms) { +	const int logId = multiLog.add(p1.fname + " - " + p2.fname + ": "); + +	if (optional<MatchResult> optres = readMatchCache(p1, p2, index)) { +		multiLog.append(logId, optres->describe(p1, p2) + " (cached)"); +		multiLog.complete(logId); +		recordResult(p1, p2, *optres); +		return; +	} + +	const string gamelog_path = gameLogPath(p1, p2, index); +	ofstream gamelog(gamelog_path); +	if (!gamelog) { +		cout << "ERROR opening game log file " << gamelog_path << endl; +		throw StopCompetitionError(); +	} + +	const MatchResult mres = playMatch( +		p1, p2, params, +		gameCodeName(p1, p2, index), +		gamelog, +		playerLogPath(p1, p2, index, 1), +		playerLogPath(p1, p2, index, 2) +	); + +	multiLog.append(logId, mres.describe(p1, p2)); +	multiLog.complete(logId); +  	writeMatchCache(p1, p2, index, mres);  	recordResult(p1, p2, mres);  } -static void playerPit(Scheduler &scheduler, MultiLog &multiLog, Player &p1, Player &p2, const Params ¶ms) { -	for (int i = 0; i < params.num_matches; i++) { -		scheduler.submit([i, &p1, &p2, ¶ms, &multiLog]() { -			playMatch(multiLog, p1, p2, i + 1, params); +struct CompParams { +	Scheduler *scheduler; +	MultiLog *multiLog; +	int num_matches = 5; +}; + +static void playerPit(CompParams &compParams, Player &p1, Player &p2, const Params ¶ms) { +	for (int i = 0; i < compParams.num_matches; i++) { +		compParams.scheduler->submit([i, &p1, &p2, ¶ms, &compParams]() { +			playMatchInCompetition(*compParams.multiLog, p1, p2, i + 1, params);  		});  	}  } -static void fullCompetition(Scheduler &scheduler, MultiLog &multiLog, vector<Player> &players, const Params ¶ms) { +static void fullCompetition(CompParams compParams, 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, multiLog, players[p1i], players[p2i], params); -			playerPit(scheduler, multiLog, players[p2i], players[p1i], params); +			playerPit(compParams, players[p1i], players[p2i], params); +			playerPit(compParams, players[p2i], players[p1i], params);  		}  	}  }  static void usage(const char *argv0) { -	cerr << "Usage: " << argv0 << " [OPTIONS] <referee> <players...>\n" +	cerr << +		"Usage: " << argv0 << " comp [OPTIONS] <referee> <players...>\n" +		"Run a full competition. Gamelogs in " << gamelogdir << ", playerlogs in\n" +		<< playerlogdir << ". Caches in " << matchcachedir << "; already-played games\n" +		"as determined by caches will not be run again.\n"  		"  --noansi   Don't use fancy back-updating log output\n"  		"  --vs       Verbose scheduler output (use with --noansi)\n" -		"  --vr       Verbose referee-handling output (use with --noansi)\n"  		"  -j <NUM>   Set number of parallel games (default ncores/2)\n"  		"  -n <NUM>   Set number of matches per player pairing (default " -			<< Params{}.num_matches << ")\n" +			<< CompParams{}.num_matches << ")\n" +		"\n" +		"Usage: " << argv0 << " single [OPTIONS] <referee> <player1> <player2>\n" +		"Run only a single game. Gamelog from referee is sent to stdout.\n" +		"  --log1 <FILE>  Playerlog file for player 1\n" +		"  --log2 <FILE>  Playerlog file for player 2\n" +		"\n" +		"Options that always apply:\n" +		"  --vr       Verbose referee-handling output (use with --noansi if comp mode)\n"  		"  -T <MSEC>  Set player timeout in milliseconds (default " -			<< Params{}.timeout_msec << ")\n"; +			<< Params{}.timeout_msec << ")\n" +		;  } -int main(int argc, char **argv) { +static Params parseCommonOptions(vector<const char*> &argv) {  	Params params; + +	if (argv.size() <= 1) { +		usage(argv[0]); +		exit(1); +	} + +	if (strcmp(argv[1], "comp") == 0) params.mode = Mode::competition; +	else if (strcmp(argv[1], "single") == 0) params.mode = Mode::single; +	else { +		usage(argv[0]); +		exit(1); +	} + +	argv.erase(argv.begin() + 1); + +	for (size_t i = 1; i < argv.size(); i++) { +		if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { +			usage(argv[0]); +			exit(0); +		} else if (strcmp(argv[i], "--vr") == 0) { +			params.referee_verbose = true; +			argv.erase(argv.begin() + i); +			i--; +		} else if (strcmp(argv[i], "-T") == 0 && i + 1 < argv.size()) { +			char *endp; +			params.timeout_msec = strtoull(argv[i+1], &endp, 10); +			if (!argv[i][0] || *endp || params.timeout_msec <= 0) { +				cerr << "Invalid number to -T" << endl; +				exit(1); +			} +			argv.erase(argv.begin() + i, argv.begin() + i + 2); +			i--; +		} else if (strcmp(argv[i], "-j") == 0 || +					strcmp(argv[i], "-n") == 0 || +					(strlen(argv[i]) >= strlen("--log1") && +						memcmp(argv[i], "--log", strlen("--log")) == 0)) { +			i++;  // skip argument of option +		} else if (params.refereePath.empty()) { +			params.refereePath = argv[i]; +			argv.erase(argv.begin() + i); +			i--; +		} +	} + +	return params; +} + +static int mainCompetition(const vector<const char*> &argv, const Params ¶ms) {  	vector<Player> players;  	bool multilog_fancy = true;  	bool scheduler_verbose = false;  	int num_threads = std::thread::hardware_concurrency() / 2; +	CompParams compParams; -	for (int i = 1; i < argc; i++) { -		if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { -			usage(argv[0]); -			return 0; -		} else if (strcmp(argv[i], "--noansi") == 0) { +	for (size_t i = 1; i < argv.size(); i++) { +		if (strcmp(argv[i], "--noansi") == 0) {  			multilog_fancy = false;  		} else if (strcmp(argv[i], "--vs") == 0) {  			scheduler_verbose = true; -		} else if (strcmp(argv[i], "--vr") == 0) { -			params.referee_verbose = true; -		} else if (strcmp(argv[i], "-j") == 0 && i + 1 < argc) { +		} else if (strcmp(argv[i], "-j") == 0 && i + 1 < argv.size()) {  			char *endp;  			num_threads = strtol(argv[i+1], &endp, 10);  			if (!argv[i][0] || *endp || num_threads <= 0) { @@ -408,27 +487,17 @@ int main(int argc, char **argv) {  				return 1;  			}  			i++; -		} else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { +		} else if (strcmp(argv[i], "-n") == 0 && i + 1 < argv.size()) {  			char *endp; -			params.num_matches = strtol(argv[i+1], &endp, 10); -			if (!argv[i][0] || *endp || params.num_matches <= 0) { +			compParams.num_matches = strtol(argv[i+1], &endp, 10); +			if (!argv[i][0] || *endp || compParams.num_matches <= 0) {  				cerr << "Invalid number to -n" << endl;  				return 1;  			}  			i++; -		} else if (strcmp(argv[i], "-T") == 0 && i + 1 < argc) { -			char *endp; -			params.timeout_msec = strtoull(argv[i+1], &endp, 10); -			if (!argv[i][0] || *endp || params.timeout_msec <= 0) { -				cerr << "Invalid number to -T" << endl; -				return 1; -			} -			i++;  		} else if (argv[i][0] == '-') {  			cerr << "Unknown option/flag '" << argv[i] << "'" << endl;  			return 1; -		} else if (params.refereePath.empty()) { -			params.refereePath = argv[i];  		} else {  			players.emplace_back(argv[i]);  		} @@ -439,16 +508,19 @@ int main(int argc, char **argv) {  		return 1;  	} -	MultiLog multiLog{multilog_fancy}; -  	mkdirp(matchcachedir);  	mkdirp(playerlogdir);  	mkdirp(gamelogdir);  	signal(SIGPIPE, [](int){}); +	MultiLog multiLog{multilog_fancy}; +	compParams.multiLog = &multiLog; +  	Scheduler scheduler(scheduler_verbose, num_threads); -	fullCompetition(scheduler, multiLog, players, params); +	compParams.scheduler = &scheduler; + +	fullCompetition(compParams, players, params);  	scheduler.finish();  	vector<pair<string, int>> scores; @@ -474,4 +546,68 @@ int main(int argc, char **argv) {  	for (const auto &p : scores) {  		cout << setw(maxlen) << p.first << " | " << p.second << endl;  	} + +	return 0; +} + +static int mainSingle(const vector<const char*> &argv, const Params ¶ms) { +	vector<Player> players; +	unordered_map<int, string> logfnames; + +	for (size_t i = 1; i < argv.size(); i++) { +		if (strlen(argv[i]) >= strlen("--log1") && +				memcmp(argv[i], "--log", strlen("--log")) == 0 && +				i + 1 < argv.size()) { +			const char *idxstring = argv[i] + strlen("--log"); +			char *endp; +			int playeridx = strtol(idxstring, &endp, 10); +			if (!idxstring[0] || *endp || playeridx <= 0) { +				cerr << "--logN index invalid" << endl; +				return 1; +			} +			logfnames[playeridx] = argv[i+1]; +			i++; +		} else if (argv[i][0] == '-') { +			cerr << "Unknown option/flag '" << argv[i] << "'" << endl; +			return 1; +		} else { +			players.emplace_back(argv[i]); +		} +	} + +	if (players.size() != 2) { +		usage(argv[0]); +		return 1; +	} + +	vector<string> playerlogs(players.size()); +	for (size_t i = 0; i < players.size(); i++) { +		auto it = logfnames.find(i + 1); +		if (it == logfnames.end()) playerlogs[i] = "/dev/null"; +		else playerlogs[i] = it->second; +	} + +	signal(SIGPIPE, [](int){}); + +	playMatch(players[0], players[1], params, "single", cout, playerlogs[0], playerlogs[1]); + +	return 0; +} + +int main(int argc, char **argv) { +	if (argc == 0) { +		usage("competition"); +		return 1; +	} + +	vector<const char*> arguments{argv, argv + argc}; +	const Params params = parseCommonOptions(arguments); + +	switch (params.mode) { +		case Mode::competition: +			return mainCompetition(arguments, params); + +		case Mode::single: +			return mainSingle(arguments, params); +	}  }  | 
