add a tournament option to disable COM entries
This commit is contained in:
+58
-56
@@ -200,24 +200,32 @@ string Tournament::Match::str() const {
|
|||||||
return string_printf("[Match round=%zu winner=%s]", this->round_num, winner_str.c_str());
|
return string_printf("[Match round=%zu winner=%s]", this->round_num, winner_str.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Tournament::Match::resolve_if_no_human_players() {
|
bool Tournament::Match::resolve_if_skippable() {
|
||||||
if (this->winner_team) {
|
if (this->winner_team) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// If both matches before this one are resolved and neither winner team has
|
|
||||||
// any humans on it, skip this match entirely and just make one team advance
|
auto winner_a = this->preceding_a->winner_team;
|
||||||
// arbitrarily
|
auto winner_b = this->preceding_b->winner_team;
|
||||||
if (this->preceding_a->winner_team &&
|
|
||||||
this->preceding_b->winner_team &&
|
// If at least one match before this is not resolved, don't resolve this one
|
||||||
!this->preceding_a->winner_team->has_any_human_players() &&
|
if (!winner_a || !winner_b) {
|
||||||
!this->preceding_b->winner_team->has_any_human_players()) {
|
|
||||||
this->set_winner_team((random_object<uint8_t>() & 1)
|
|
||||||
? this->preceding_b->winner_team
|
|
||||||
: this->preceding_a->winner_team);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// If one of the preceding winner teams is empty, make the other the winner
|
||||||
|
if (winner_a->players.empty() != winner_b->players.empty()) {
|
||||||
|
this->set_winner_team(winner_a->players.empty() ? winner_b : winner_a);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If neither preceding winner team has any humans on it, skip this match
|
||||||
|
// entirely and just make one team advance arbitrarily (note that this also
|
||||||
|
// handles the case where both preceding winner teams are empty)
|
||||||
|
if (!winner_a->has_any_human_players() && !winner_b->has_any_human_players()) {
|
||||||
|
this->set_winner_team((random_object<uint8_t>() & 1) ? winner_b : winner_a);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tournament::Match::on_winner_team_set() {
|
void Tournament::Match::on_winner_team_set() {
|
||||||
@@ -231,7 +239,7 @@ void Tournament::Match::on_winner_team_set() {
|
|||||||
// Resolve the following match if possible (this skips CPU-only matches). If
|
// Resolve the following match if possible (this skips CPU-only matches). If
|
||||||
// the following match can't be resolved, mark it pending.
|
// the following match can't be resolved, mark it pending.
|
||||||
auto following = this->following.lock();
|
auto following = this->following.lock();
|
||||||
if (following && !following->resolve_if_no_human_players()) {
|
if (following && !following->resolve_if_skippable()) {
|
||||||
tournament->pending_matches.emplace(following);
|
tournament->pending_matches.emplace(following);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +295,8 @@ 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)
|
bool is_2v2,
|
||||||
|
bool has_com_teams)
|
||||||
: log(string_printf("[Tournament/%02hhX] ", number)),
|
: log(string_printf("[Tournament/%02hhX] ", number)),
|
||||||
map_index(map_index),
|
map_index(map_index),
|
||||||
com_deck_index(com_deck_index),
|
com_deck_index(com_deck_index),
|
||||||
@@ -297,6 +306,7 @@ Tournament::Tournament(
|
|||||||
rules(rules),
|
rules(rules),
|
||||||
num_teams(num_teams),
|
num_teams(num_teams),
|
||||||
is_2v2(is_2v2),
|
is_2v2(is_2v2),
|
||||||
|
has_com_teams(has_com_teams),
|
||||||
current_state(State::REGISTRATION) {
|
current_state(State::REGISTRATION) {
|
||||||
if (this->num_teams < 4) {
|
if (this->num_teams < 4) {
|
||||||
throw invalid_argument("team count must be 4 or more");
|
throw invalid_argument("team count must be 4 or more");
|
||||||
@@ -330,6 +340,7 @@ void Tournament::init() {
|
|||||||
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->is_2v2 = this->source_json.get_bool("is_2v2");
|
||||||
|
this->has_com_teams = this->source_json.get_bool("has_com_teams", true);
|
||||||
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")) {
|
||||||
@@ -480,43 +491,12 @@ JSON Tournament::json() const {
|
|||||||
{"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},
|
{"is_2v2", this->is_2v2},
|
||||||
|
{"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)},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Tournament::get_number() const {
|
|
||||||
return this->number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const string& Tournament::get_name() const {
|
|
||||||
return this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const MapIndex::MapEntry> Tournament::get_map() const {
|
|
||||||
return this->map;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Rules& Tournament::get_rules() const {
|
|
||||||
return this->rules;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Tournament::get_is_2v2() const {
|
|
||||||
return this->is_2v2;
|
|
||||||
}
|
|
||||||
|
|
||||||
Tournament::State Tournament::get_state() const {
|
|
||||||
return this->current_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vector<shared_ptr<Tournament::Team>>& Tournament::all_teams() const {
|
|
||||||
return this->teams;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<Tournament::Team> Tournament::get_team(size_t index) const {
|
|
||||||
return this->teams.at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
|
shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
|
||||||
if (this->current_state != State::COMPLETE) {
|
if (this->current_state != State::COMPLETE) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -574,14 +554,30 @@ void Tournament::start() {
|
|||||||
throw runtime_error("tournament has already started");
|
throw runtime_error("tournament has already started");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// state immediately)
|
||||||
|
size_t num_human_teams = 0;
|
||||||
|
for (size_t z = 0; z < this->zero_round_matches.size(); z++) {
|
||||||
|
if (this->zero_round_matches[z]->winner_team->has_any_human_players()) {
|
||||||
|
num_human_teams++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr, "num_human_teams: %zu\n", num_human_teams);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
this->current_state = State::IN_PROGRESS;
|
this->current_state = State::IN_PROGRESS;
|
||||||
|
|
||||||
// Assign names to COM teams, and assign COM decks to all empty slots
|
// Assign names to COM teams, and assign COM decks to all empty slots unless
|
||||||
|
// has_com_teams is false
|
||||||
for (size_t z = 0; z < this->zero_round_matches.size(); z++) {
|
for (size_t z = 0; z < this->zero_round_matches.size(); z++) {
|
||||||
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 = string_printf("COM:%zu", z);
|
t->name = this->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()) {
|
||||||
@@ -591,14 +587,18 @@ void Tournament::start() {
|
|||||||
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
|
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
|
||||||
throw runtime_error("not enough COM decks to complete team");
|
throw runtime_error("not enough COM decks to complete team");
|
||||||
}
|
}
|
||||||
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same
|
// If we allow all-COM teams, or this is a 2v2 tournament and the team has
|
||||||
// team
|
// only one human on it, add a COM
|
||||||
while (t->players.size() < t->max_players) {
|
if (this->has_com_teams || !t->players.empty()) {
|
||||||
t->players.emplace_back(this->com_deck_index->random_deck());
|
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the
|
||||||
|
// same team
|
||||||
|
while (t->players.size() < t->max_players) {
|
||||||
|
t->players.emplace_back(this->com_deck_index->random_deck());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve all possible CPU-only matches
|
// Resolve all possible skippable matches
|
||||||
for (auto m : this->zero_round_matches) {
|
for (auto m : this->zero_round_matches) {
|
||||||
m->on_winner_team_set();
|
m->on_winner_team_set();
|
||||||
}
|
}
|
||||||
@@ -666,6 +666,7 @@ TournamentIndex::TournamentIndex(
|
|||||||
} catch (const cannot_open_file&) {
|
} catch (const cannot_open_file&) {
|
||||||
json = JSON::list();
|
json = JSON::list();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.size() > 0x20) {
|
if (json.size() > 0x20) {
|
||||||
throw runtime_error("tournament JSON list length is incorrect");
|
throw runtime_error("tournament JSON list length is incorrect");
|
||||||
}
|
}
|
||||||
@@ -708,7 +709,8 @@ 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) {
|
bool is_2v2,
|
||||||
|
bool has_com_teams) {
|
||||||
// Find an unused tournament number
|
// Find an unused tournament number
|
||||||
uint8_t number;
|
uint8_t number;
|
||||||
for (number = 0; number < 0x20; number++) {
|
for (number = 0; number < 0x20; number++) {
|
||||||
@@ -721,7 +723,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto t = make_shared<Tournament>(
|
auto t = make_shared<Tournament>(
|
||||||
this->map_index, this->com_deck_index, number, name, map, rules, num_teams, is_2v2);
|
this->map_index, this->com_deck_index, number, name, map, rules, num_teams, is_2v2, has_com_teams);
|
||||||
t->init();
|
t->init();
|
||||||
this->tournaments[number] = t;
|
this->tournaments[number] = t;
|
||||||
return t;
|
return t;
|
||||||
|
|||||||
+33
-11
@@ -84,7 +84,7 @@ public:
|
|||||||
std::shared_ptr<Team> winner_team);
|
std::shared_ptr<Team> winner_team);
|
||||||
std::string str() const;
|
std::string str() const;
|
||||||
|
|
||||||
bool resolve_if_no_human_players();
|
bool resolve_if_skippable();
|
||||||
void on_winner_team_set();
|
void on_winner_team_set();
|
||||||
void set_winner_team(std::shared_ptr<Team> team);
|
void set_winner_team(std::shared_ptr<Team> team);
|
||||||
void set_winner_team_without_triggers(std::shared_ptr<Team> team);
|
void set_winner_team_without_triggers(std::shared_ptr<Team> team);
|
||||||
@@ -99,7 +99,8 @@ 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);
|
bool is_2v2,
|
||||||
|
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,
|
||||||
@@ -110,15 +111,34 @@ public:
|
|||||||
|
|
||||||
JSON json() const;
|
JSON json() const;
|
||||||
|
|
||||||
uint8_t get_number() const;
|
inline uint8_t get_number() const {
|
||||||
const std::string& get_name() const;
|
return this->number;
|
||||||
std::shared_ptr<const MapIndex::MapEntry> get_map() const;
|
}
|
||||||
const Rules& get_rules() const;
|
inline const std::string& get_name() const {
|
||||||
bool get_is_2v2() const;
|
return this->name;
|
||||||
State get_state() const;
|
}
|
||||||
|
inline std::shared_ptr<const MapIndex::MapEntry> get_map() const {
|
||||||
|
return this->map;
|
||||||
|
}
|
||||||
|
inline const Rules& get_rules() const {
|
||||||
|
return this->rules;
|
||||||
|
}
|
||||||
|
inline bool get_is_2v2() const {
|
||||||
|
return this->is_2v2;
|
||||||
|
}
|
||||||
|
inline bool get_has_com_teams() const {
|
||||||
|
return this->has_com_teams;
|
||||||
|
}
|
||||||
|
inline State get_state() const {
|
||||||
|
return this->current_state;
|
||||||
|
}
|
||||||
|
inline const std::vector<std::shared_ptr<Team>>& all_teams() const {
|
||||||
|
return this->teams;
|
||||||
|
}
|
||||||
|
std::shared_ptr<Team> get_team(size_t index) const {
|
||||||
|
return this->teams.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<std::shared_ptr<Team>>& all_teams() const;
|
|
||||||
std::shared_ptr<Team> get_team(size_t index) const;
|
|
||||||
std::shared_ptr<Team> get_winner_team() const;
|
std::shared_ptr<Team> get_winner_team() const;
|
||||||
std::shared_ptr<Match> next_match_for_team(std::shared_ptr<Team> team) const;
|
std::shared_ptr<Match> next_match_for_team(std::shared_ptr<Team> team) const;
|
||||||
std::shared_ptr<Match> get_final_match() const;
|
std::shared_ptr<Match> get_final_match() const;
|
||||||
@@ -141,6 +161,7 @@ private:
|
|||||||
Rules rules;
|
Rules rules;
|
||||||
size_t num_teams;
|
size_t num_teams;
|
||||||
bool is_2v2;
|
bool is_2v2;
|
||||||
|
bool has_com_teams;
|
||||||
State current_state;
|
State current_state;
|
||||||
|
|
||||||
std::set<uint32_t> all_player_serial_numbers;
|
std::set<uint32_t> all_player_serial_numbers;
|
||||||
@@ -177,7 +198,8 @@ 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);
|
bool is_2v2,
|
||||||
|
bool has_com_teams);
|
||||||
void delete_tournament(uint8_t number);
|
void delete_tournament(uint8_t number);
|
||||||
std::shared_ptr<Tournament> get_tournament(uint8_t number) const;
|
std::shared_ptr<Tournament> get_tournament(uint8_t number) const;
|
||||||
std::shared_ptr<Tournament> get_tournament(const std::string& name) const;
|
std::shared_ptr<Tournament> get_tournament(const std::string& name) const;
|
||||||
|
|||||||
+5
-1
@@ -161,6 +161,7 @@ Server commands:\n\
|
|||||||
and map names, unless the names contain no spaces.\n\
|
and map names, unless the names contain no spaces.\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\
|
||||||
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,12 +461,15 @@ Proxy session commands:\n\
|
|||||||
Episode3::Rules rules;
|
Episode3::Rules rules;
|
||||||
rules.set_defaults();
|
rules.set_defaults();
|
||||||
bool is_2v2 = false;
|
bool is_2v2 = false;
|
||||||
|
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;
|
is_2v2 = true;
|
||||||
|
} else if (token == "no-coms") {
|
||||||
|
has_com_teams = false;
|
||||||
} 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) {
|
||||||
@@ -535,7 +539,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);
|
name, map, rules, num_teams, is_2v2, has_com_teams);
|
||||||
this->state->ep3_tournament_index->save();
|
this->state->ep3_tournament_index->save();
|
||||||
fprintf(stderr, "created tournament %02hhX\n", tourn->get_number());
|
fprintf(stderr, "created tournament %02hhX\n", tourn->get_number());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user