add shuffle and resize options in tournaments

This commit is contained in:
Martin Michelsen
2023-09-15 23:23:03 -07:00
parent 1d45c18ce8
commit 4ddc4fce1d
4 changed files with 163 additions and 74 deletions
+133 -55
View File
@@ -297,8 +297,7 @@ Tournament::Tournament(
shared_ptr<const MapIndex::MapEntry> map, shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2, uint8_t flags)
bool has_com_teams)
: log(string_printf("[Tournament/%s] ", name.c_str())), : log(string_printf("[Tournament/%s] ", name.c_str())),
map_index(map_index), map_index(map_index),
com_deck_index(com_deck_index), com_deck_index(com_deck_index),
@@ -306,8 +305,7 @@ Tournament::Tournament(
map(map), map(map),
rules(rules), rules(rules),
num_teams(num_teams), num_teams(num_teams),
is_2v2(is_2v2), flags(flags),
has_com_teams(has_com_teams),
current_state(State::REGISTRATION), current_state(State::REGISTRATION),
menu_item_id(0xFFFFFFFF) { menu_item_id(0xFFFFFFFF) {
if (this->num_teams < 4) { if (this->num_teams < 4) {
@@ -339,8 +337,10 @@ void Tournament::init() {
this->name = this->source_json.get_string("name"); this->name = this->source_json.get_string("name");
this->map = this->map_index->definition_for_number(this->source_json.get_int("map_number")); this->map = this->map_index->definition_for_number(this->source_json.get_int("map_number"));
this->rules = Rules(this->source_json.at("rules")); this->rules = Rules(this->source_json.at("rules"));
this->is_2v2 = this->source_json.get_bool("is_2v2"); this->flags = this->source_json.get_int("flags", 0x02);
this->has_com_teams = this->source_json.get_bool("has_com_teams", true); if (this->source_json.get_bool("is_2v2", false)) {
this->flags |= Flag::IS_2V2;
}
is_registration_complete = this->source_json.get_bool("is_registration_complete"); is_registration_complete = this->source_json.get_bool("is_registration_complete");
for (const auto& team_json : this->source_json.get_list("teams")) { for (const auto& team_json : this->source_json.get_list("teams")) {
@@ -368,40 +368,18 @@ void Tournament::init() {
// Create empty teams // Create empty teams
while (this->teams.size() < this->num_teams) { while (this->teams.size() < this->num_teams) {
auto t = make_shared<Team>( auto t = make_shared<Team>(
this->shared_from_this(), this->teams.size(), this->is_2v2 ? 2 : 1); this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
this->teams.emplace_back(t); this->teams.emplace_back(t);
} }
is_registration_complete = false; is_registration_complete = false;
} }
// Create the match structure
while (this->zero_round_matches.size() < this->num_teams) {
this->zero_round_matches.emplace_back(make_shared<Match>(
this->shared_from_this(), this->teams[this->zero_round_matches.size()]));
}
// Compute the match state from the teams' states // Compute the match state from the teams' states
if (is_registration_complete) { if (is_registration_complete) {
this->current_state = State::IN_PROGRESS; this->current_state = State::IN_PROGRESS;
this->create_bracket_matches();
// Create the bracket matches // Start with all zero-round matches in the match queue
vector<shared_ptr<Match>> current_round_matches = this->zero_round_matches;
while (current_round_matches.size() > 1) {
vector<shared_ptr<Match>> next_round_matches;
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
auto m = make_shared<Match>(
this->shared_from_this(),
current_round_matches[z],
current_round_matches[z + 1]);
current_round_matches[z]->following = m;
current_round_matches[z + 1]->following = m;
next_round_matches.emplace_back(std::move(m));
}
current_round_matches = std::move(next_round_matches);
}
this->final_match = current_round_matches.at(0);
// Start with all first-round matches in the match queue
unordered_set<shared_ptr<Match>> match_queue; unordered_set<shared_ptr<Match>> match_queue;
for (auto match : this->zero_round_matches) { for (auto match : this->zero_round_matches) {
match_queue.emplace(match->following.lock()); match_queue.emplace(match->following.lock());
@@ -457,16 +435,47 @@ void Tournament::init() {
} }
} else { } else {
// Make all the zero round matches pending (this is needed so that start()
// will auto-resolve all-CPU matches in the first round)
for (auto m : this->zero_round_matches) {
this->pending_matches.emplace(m);
}
this->current_state = State::REGISTRATION; this->current_state = State::REGISTRATION;
} }
} }
void Tournament::create_bracket_matches() {
if (this->teams.size() < 4) {
throw logic_error("tournaments must have at least 4 teams");
}
if (this->teams.size() > 32) {
throw logic_error("tournaments must have at most 32 teams");
}
if (this->teams.size() & (this->teams.size() - 1)) {
throw logic_error("tournaments team count is not a power of 2");
}
// Create the zero-round matches and make them all pending
this->zero_round_matches.clear();
for (const auto& team : this->teams) {
auto m = make_shared<Match>(this->shared_from_this(), team);
this->zero_round_matches.emplace_back(m);
this->pending_matches.emplace(m);
}
// Create the bracket matches
vector<shared_ptr<Match>> current_round_matches = this->zero_round_matches;
while (current_round_matches.size() > 1) {
vector<shared_ptr<Match>> next_round_matches;
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
auto m = make_shared<Match>(
this->shared_from_this(),
current_round_matches[z],
current_round_matches[z + 1]);
current_round_matches[z]->following = m;
current_round_matches[z + 1]->following = m;
next_round_matches.emplace_back(std::move(m));
}
current_round_matches = std::move(next_round_matches);
}
this->final_match = current_round_matches.at(0);
}
JSON Tournament::json() const { JSON Tournament::json() const {
auto teams_list = JSON::list(); auto teams_list = JSON::list();
for (auto team : this->teams) { for (auto team : this->teams) {
@@ -490,8 +499,7 @@ JSON Tournament::json() const {
{"name", this->name}, {"name", this->name},
{"map_number", this->map->map.map_number.load()}, {"map_number", this->map->map.map_number.load()},
{"rules", this->rules.json()}, {"rules", this->rules.json()},
{"is_2v2", this->is_2v2}, {"flags", this->flags},
{"has_com_teams", this->has_com_teams},
{"is_registration_complete", (this->current_state != State::REGISTRATION)}, {"is_registration_complete", (this->current_state != State::REGISTRATION)},
{"teams", std::move(teams_list)}, {"teams", std::move(teams_list)},
}); });
@@ -557,22 +565,67 @@ void Tournament::start() {
throw runtime_error("tournament has already started"); throw runtime_error("tournament has already started");
} }
bool has_com_teams = (this->flags & Flag::HAS_COM_TEAMS);
// If there aren't enough entrants (1 if has_com_teams is false, else 2), // If there aren't enough entrants (1 if has_com_teams is false, else 2),
// don't allow the tournament to start (because it would enter the COMPLETE // don't allow the tournament to start (because it would enter the COMPLETE
// state immediately) // state immediately)
size_t num_human_teams = 0; size_t num_human_teams = 0;
for (size_t z = 0; z < this->zero_round_matches.size(); z++) { for (size_t z = 0; z < this->teams.size(); z++) {
if (this->zero_round_matches[z]->winner_team->has_any_human_players()) { if (this->teams[z]->has_any_human_players()) {
num_human_teams++; num_human_teams++;
} }
} }
fprintf(stderr, "num_human_teams: %zu\n", num_human_teams); if (num_human_teams < (has_com_teams ? 1 : 2)) {
fprintf(stderr, "has_com_teams: %s\n", this->has_com_teams ? "true" : "false");
if (num_human_teams < (this->has_com_teams ? 1 : 2)) {
throw runtime_error("not enough registrants to start tournament"); throw runtime_error("not enough registrants to start tournament");
} }
if ((this->flags & Flag::SHUFFLE_ENTRIES) && (this->flags & Flag::RESIZE_ON_START)) {
// If both of these flags are set, pack the human teams into the lowest part
// of the teams list so we can resize the tournament to the smallest
// possible size. This is OK since we're going to shuffle them later anyway
size_t r_offset = 0, w_offset = 0;
for (; r_offset < this->teams.size(); r_offset++) {
if (this->teams[r_offset]->has_any_human_players()) {
if (r_offset != w_offset) {
this->teams[r_offset].swap(this->teams[w_offset]);
}
w_offset++;
}
}
}
if (this->flags & Flag::RESIZE_ON_START) {
// Resize the tournament by repeatedly deleting the second half of it, until
// the second half contains human players or the tournament size is 4
while (this->teams.size() > 4) {
size_t z;
for (z = this->teams.size() >> 1; z < this->teams.size(); z++) {
if (this->teams[z]->has_any_human_players()) {
break;
}
}
if (z == this->teams.size()) {
this->teams.resize(this->teams.size() >> 1);
} else {
break;
}
}
this->num_teams = this->teams.size();
}
if (this->flags & Flag::SHUFFLE_ENTRIES) {
// Shuffle all the tournament entries
for (size_t z = this->teams.size(); z > 0; z--) {
size_t index = random_object<uint32_t>() % z;
if (index != z - 1) {
this->teams[z - 1].swap(this->teams[index]);
}
}
}
this->current_state = State::IN_PROGRESS; this->current_state = State::IN_PROGRESS;
this->create_bracket_matches();
// Assign names to COM teams, and assign COM decks to all empty slots unless // Assign names to COM teams, and assign COM decks to all empty slots unless
// has_com_teams is false // has_com_teams is false
@@ -580,7 +633,7 @@ void Tournament::start() {
auto m = this->zero_round_matches[z]; auto m = this->zero_round_matches[z];
auto t = m->winner_team; auto t = m->winner_team;
if (t->name.empty()) { if (t->name.empty()) {
t->name = this->has_com_teams ? string_printf("COM:%zu", z) : "(no entrant)"; t->name = has_com_teams ? string_printf("COM:%zu", z) : "(no entrant)";
} }
for (const auto& player : t->players) { for (const auto& player : t->players) {
if (player.is_com()) { if (player.is_com()) {
@@ -592,7 +645,7 @@ void Tournament::start() {
} }
// If we allow all-COM teams, or this is a 2v2 tournament and the team has // If we allow all-COM teams, or this is a 2v2 tournament and the team has
// only one human on it, add a COM // only one human on it, add a COM
if (this->has_com_teams || !t->players.empty()) { if (has_com_teams || !t->players.empty()) {
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the // TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the
// same team // same team
while (t->players.size() < t->max_players) { while (t->players.size() < t->max_players) {
@@ -624,6 +677,20 @@ void Tournament::send_all_state_updates(shared_ptr<ServerState> s) const {
} }
} }
void Tournament::send_all_state_updates_on_deletion() const {
for (const auto& team : this->teams) {
for (const auto& player : team->players) {
auto c = player.client.lock();
if (c &&
(c->flags & Client::Flag::IS_EPISODE_3) &&
!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) &&
(c->ep3_tournament_team.lock() == team)) {
send_ep3_confirm_tournament_entry(nullptr, c, nullptr);
}
}
}
}
void Tournament::print_bracket(FILE* stream) const { void Tournament::print_bracket(FILE* stream) const {
function<void(shared_ptr<Match>, size_t)> print_match = [&](shared_ptr<Match> m, size_t indent_level) -> void { function<void(shared_ptr<Match>, size_t)> print_match = [&](shared_ptr<Match> m, size_t indent_level) -> void {
for (size_t z = 0; z < indent_level; z++) { for (size_t z = 0; z < indent_level; z++) {
@@ -644,7 +711,10 @@ void Tournament::print_bracket(FILE* stream) const {
fprintf(stream, " Map: %08" PRIX32 " (%s)\n", this->map->map.map_number.load(), map_name.c_str()); fprintf(stream, " Map: %08" PRIX32 " (%s)\n", this->map->map.map_number.load(), map_name.c_str());
string rules_str = this->rules.str(); string rules_str = this->rules.str();
fprintf(stream, " Rules: %s\n", rules_str.c_str()); fprintf(stream, " Rules: %s\n", rules_str.c_str());
fprintf(stream, " Structure: %s, %zu entries\n", this->is_2v2 ? "2v2" : "1v1", this->num_teams); fprintf(stream, " Structure: %s, %zu entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
fprintf(stream, " COM teams: %s\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
fprintf(stream, " Shuffle entries: %s\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
fprintf(stream, " Resize on start: %s\n", (this->flags & Flag::RESIZE_ON_START) ? "yes" : "no");
switch (this->current_state) { switch (this->current_state) {
case State::REGISTRATION: case State::REGISTRATION:
fprintf(stream, " State: REGISTRATION\n"); fprintf(stream, " State: REGISTRATION\n");
@@ -663,10 +733,18 @@ void Tournament::print_bracket(FILE* stream) const {
fprintf(stream, " Standings:\n"); fprintf(stream, " Standings:\n");
print_match(this->final_match, 2); print_match(this->final_match, 2);
} }
fprintf(stream, " Pending matches:\n"); if (this->current_state == State::REGISTRATION) {
for (const auto& match : this->pending_matches) { fprintf(stream, " Teams:\n");
string match_str = match->str(); for (const auto& team : this->teams) {
fprintf(stream, " %s\n", match_str.c_str()); string team_str = team->str();
fprintf(stream, " %s\n", team_str.c_str());
}
} else {
fprintf(stream, " Pending matches:\n");
for (const auto& match : this->pending_matches) {
string match_str = match->str();
fprintf(stream, " %s\n", match_str.c_str());
}
} }
} }
@@ -733,7 +811,7 @@ void TournamentIndex::save() const {
for (const auto& it : this->name_to_tournament) { for (const auto& it : this->name_to_tournament) {
json.emplace(it.second->get_name(), it.second->json()); json.emplace(it.second->get_name(), it.second->json());
} }
save_file(this->state_filename, json.serialize(JSON::SerializeOption::FORMAT)); save_file(this->state_filename, json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS));
} }
shared_ptr<Tournament> TournamentIndex::create_tournament( shared_ptr<Tournament> TournamentIndex::create_tournament(
@@ -741,14 +819,13 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
shared_ptr<const MapIndex::MapEntry> map, shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2, uint8_t flags) {
bool has_com_teams) {
if (this->name_to_tournament.size() >= 0x20) { if (this->name_to_tournament.size() >= 0x20) {
throw runtime_error("there can be at most 32 tournaments at a time"); throw runtime_error("there can be at most 32 tournaments at a time");
} }
auto t = make_shared<Tournament>( auto t = make_shared<Tournament>(
this->map_index, this->com_deck_index, name, map, rules, num_teams, is_2v2, has_com_teams); this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
t->init(); t->init();
this->name_to_tournament.emplace(t->get_name(), t); this->name_to_tournament.emplace(t->get_name(), t);
@@ -780,6 +857,7 @@ bool TournamentIndex::delete_tournament(const string& name) {
it->second->set_menu_item_id(0xFFFFFFFF); it->second->set_menu_item_id(0xFFFFFFFF);
} }
} }
it->second->send_all_state_updates_on_deletion();
this->name_to_tournament.erase(it); this->name_to_tournament.erase(it);
this->save(); this->save();
return true; return true;
+14 -11
View File
@@ -22,6 +22,12 @@ namespace Episode3 {
class Tournament : public std::enable_shared_from_this<Tournament> { class Tournament : public std::enable_shared_from_this<Tournament> {
public: public:
enum Flag : uint8_t {
IS_2V2 = 0x01,
HAS_COM_TEAMS = 0x02,
SHUFFLE_ENTRIES = 0x04,
RESIZE_ON_START = 0x08,
};
enum class State { enum class State {
REGISTRATION = 0, REGISTRATION = 0,
IN_PROGRESS, IN_PROGRESS,
@@ -104,8 +110,7 @@ public:
std::shared_ptr<const MapIndex::MapEntry> map, std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2, uint8_t flags);
bool has_com_teams);
Tournament( Tournament(
std::shared_ptr<const MapIndex> map_index, std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index, std::shared_ptr<const COMDeckIndex> com_deck_index,
@@ -124,11 +129,8 @@ public:
inline const Rules& get_rules() const { inline const Rules& get_rules() const {
return this->rules; return this->rules;
} }
inline bool get_is_2v2() const { inline uint8_t get_flags() const {
return this->is_2v2; return this->flags;
}
inline bool get_has_com_teams() const {
return this->has_com_teams;
} }
inline State get_state() const { inline State get_state() const {
return this->current_state; return this->current_state;
@@ -155,10 +157,13 @@ public:
void start(); void start();
void send_all_state_updates(std::shared_ptr<ServerState> s) const; void send_all_state_updates(std::shared_ptr<ServerState> s) const;
void send_all_state_updates_on_deletion() const;
void print_bracket(FILE* stream) const; void print_bracket(FILE* stream) const;
private: private:
void create_bracket_matches();
PrefixedLogger log; PrefixedLogger log;
std::shared_ptr<const MapIndex> map_index; std::shared_ptr<const MapIndex> map_index;
@@ -168,8 +173,7 @@ private:
std::shared_ptr<const MapIndex::MapEntry> map; std::shared_ptr<const MapIndex::MapEntry> map;
Rules rules; Rules rules;
size_t num_teams; size_t num_teams;
bool is_2v2; uint8_t flags;
bool has_com_teams;
State current_state; State current_state;
uint32_t menu_item_id; uint32_t menu_item_id;
@@ -223,8 +227,7 @@ public:
std::shared_ptr<const MapIndex::MapEntry> map, std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules, const Rules& rules,
size_t num_teams, size_t num_teams,
bool is_2v2, uint8_t flags);
bool has_com_teams);
bool delete_tournament(const std::string& name); bool delete_tournament(const std::string& name);
std::shared_ptr<Tournament::Team> team_for_serial_number(uint32_t serial_number) const; std::shared_ptr<Tournament::Team> team_for_serial_number(uint32_t serial_number) const;
+5 -3
View File
@@ -2258,6 +2258,8 @@ void send_ep3_confirm_tournament_entry(
shared_ptr<ServerState> s, shared_ptr<ServerState> s,
shared_ptr<Client> c, shared_ptr<Client> c,
shared_ptr<const Episode3::Tournament> tourn) { shared_ptr<const Episode3::Tournament> tourn) {
// WARNING: s is permitted to be null if tourn is null
if (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) { if (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) {
throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client"); throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client");
} }
@@ -2326,7 +2328,7 @@ void send_ep3_tournament_entry_list(
shared_ptr<const Episode3::Tournament> tourn, shared_ptr<const Episode3::Tournament> tourn,
bool is_for_spectator_team_create) { bool is_for_spectator_team_create) {
S_TournamentEntryList_GC_Ep3_E2 cmd; S_TournamentEntryList_GC_Ep3_E2 cmd;
cmd.players_per_team = tourn->get_is_2v2() ? 2 : 1; cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1;
size_t z = 0; size_t z = 0;
for (const auto& team : tourn->all_teams()) { for (const auto& team : tourn->all_teams()) {
if (z >= 0x20) { if (z >= 0x20) {
@@ -2366,7 +2368,7 @@ void send_ep3_tournament_details(
cmd.bracket_entries[z].team_name = teams[z]->name; cmd.bracket_entries[z].team_name = teams[z]->name;
} }
cmd.num_bracket_entries = teams.size(); cmd.num_bracket_entries = teams.size();
cmd.players_per_team = tourn->get_is_2v2() ? 2 : 1; cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1;
send_command_t(c, 0xE3, 0x02, cmd); send_command_t(c, 0xE3, 0x02, cmd);
} }
@@ -2409,7 +2411,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
entry.team_name = teams[z]->name; entry.team_name = teams[z]->name;
} }
cmd.num_bracket_entries = teams.size(); cmd.num_bracket_entries = teams.size();
cmd.players_per_team = tourn->get_is_2v2() ? 2 : 1; cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1;
if (primary_lobby) { if (primary_lobby) {
auto serial_number_to_client = primary_lobby->clients_by_serial_number(); auto serial_number_to_client = primary_lobby->clients_by_serial_number();
+11 -5
View File
@@ -162,6 +162,9 @@ Server commands:\n\
OPTIONS may include:\n\ OPTIONS may include:\n\
2v2: Set team size to 2 players (default is 1 without this option)\n\ 2v2: Set team size to 2 players (default is 1 without this option)\n\
no-coms: Don\'t add any COM teams to the tournament bracket\n\ no-coms: Don\'t add any COM teams to the tournament bracket\n\
shuffle: Shuffle entries when starting the tournament\n\
resize: If the tournament is less than half full when it starts, reduce\n\
the number of rounds to fit the existing entries\n\
dice=MIN-MAX: Set minimum and maximum dice rolls\n\ dice=MIN-MAX: Set minimum and maximum dice rolls\n\
overall-time-limit=N: Set battle time limit (in multiples of 5 minutes)\n\ overall-time-limit=N: Set battle time limit (in multiples of 5 minutes)\n\
phase-time-limit=N: Set phase time limit (in seconds)\n\ phase-time-limit=N: Set phase time limit (in seconds)\n\
@@ -460,16 +463,19 @@ Proxy session commands:\n\
uint32_t num_teams = stoul(get_quoted_string(command_args), nullptr, 0); uint32_t num_teams = stoul(get_quoted_string(command_args), nullptr, 0);
Episode3::Rules rules; Episode3::Rules rules;
rules.set_defaults(); rules.set_defaults();
bool is_2v2 = false; uint8_t flags = Episode3::Tournament::Flag::HAS_COM_TEAMS;
bool has_com_teams = true;
if (!command_args.empty()) { if (!command_args.empty()) {
auto tokens = split(command_args, ' '); auto tokens = split(command_args, ' ');
for (auto& token : tokens) { for (auto& token : tokens) {
token = tolower(token); token = tolower(token);
if (token == "2v2") { if (token == "2v2") {
is_2v2 = true; flags |= Episode3::Tournament::Flag::IS_2V2;
} else if (token == "no-coms") { } else if (token == "no-coms") {
has_com_teams = false; flags &= (~Episode3::Tournament::Flag::HAS_COM_TEAMS);
} else if (token == "shuffle") {
flags |= Episode3::Tournament::Flag::SHUFFLE_ENTRIES;
} else if (token == "resize") {
flags |= Episode3::Tournament::Flag::RESIZE_ON_START;
} else if (starts_with(token, "dice=")) { } else if (starts_with(token, "dice=")) {
auto subtokens = split(token.substr(5), '-'); auto subtokens = split(token.substr(5), '-');
if (subtokens.size() != 2) { if (subtokens.size() != 2) {
@@ -539,7 +545,7 @@ Proxy session commands:\n\
fprintf(stderr, "warning: some rules were invalid and reset to defaults\n"); fprintf(stderr, "warning: some rules were invalid and reset to defaults\n");
} }
auto tourn = this->state->ep3_tournament_index->create_tournament( auto tourn = this->state->ep3_tournament_index->create_tournament(
name, map, rules, num_teams, is_2v2, has_com_teams); name, map, rules, num_teams, flags);
fprintf(stderr, "created tournament \"%s\"\n", tourn->get_name().c_str()); fprintf(stderr, "created tournament \"%s\"\n", tourn->get_name().c_str());
} else if (command_name == "delete-tournament") { } else if (command_name == "delete-tournament") {