add a tournament option to disable COM entries

This commit is contained in:
Martin Michelsen
2023-09-15 19:49:12 -07:00
parent 27081bd3da
commit 9cef4a14f8
3 changed files with 96 additions and 68 deletions
+58 -56
View File
@@ -200,24 +200,32 @@ string Tournament::Match::str() const {
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) {
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
// arbitrarily
if (this->preceding_a->winner_team &&
this->preceding_b->winner_team &&
!this->preceding_a->winner_team->has_any_human_players() &&
!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 {
auto winner_a = this->preceding_a->winner_team;
auto winner_b = this->preceding_b->winner_team;
// If at least one match before this is not resolved, don't resolve this one
if (!winner_a || !winner_b) {
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() {
@@ -231,7 +239,7 @@ void Tournament::Match::on_winner_team_set() {
// Resolve the following match if possible (this skips CPU-only matches). If
// the following match can't be resolved, mark it pending.
auto following = this->following.lock();
if (following && !following->resolve_if_no_human_players()) {
if (following && !following->resolve_if_skippable()) {
tournament->pending_matches.emplace(following);
}
@@ -287,7 +295,8 @@ Tournament::Tournament(
shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2)
bool is_2v2,
bool has_com_teams)
: log(string_printf("[Tournament/%02hhX] ", number)),
map_index(map_index),
com_deck_index(com_deck_index),
@@ -297,6 +306,7 @@ Tournament::Tournament(
rules(rules),
num_teams(num_teams),
is_2v2(is_2v2),
has_com_teams(has_com_teams),
current_state(State::REGISTRATION) {
if (this->num_teams < 4) {
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->rules = Rules(this->source_json.at("rules"));
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");
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()},
{"rules", this->rules.json()},
{"is_2v2", this->is_2v2},
{"has_com_teams", this->has_com_teams},
{"is_registration_complete", (this->current_state != State::REGISTRATION)},
{"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 {
if (this->current_state != State::COMPLETE) {
return nullptr;
@@ -574,14 +554,30 @@ void Tournament::start() {
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;
// 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++) {
auto m = this->zero_round_matches[z];
auto t = m->winner_team;
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) {
if (player.is_com()) {
@@ -591,14 +587,18 @@ void Tournament::start() {
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
throw runtime_error("not enough COM decks to complete team");
}
// 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());
// If we allow all-COM teams, or this is a 2v2 tournament and the team has
// only one human on it, add a COM
if (this->has_com_teams || !t->players.empty()) {
// 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) {
m->on_winner_team_set();
}
@@ -666,6 +666,7 @@ TournamentIndex::TournamentIndex(
} catch (const cannot_open_file&) {
json = JSON::list();
}
if (json.size() > 0x20) {
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,
const Rules& rules,
size_t num_teams,
bool is_2v2) {
bool is_2v2,
bool has_com_teams) {
// Find an unused tournament number
uint8_t number;
for (number = 0; number < 0x20; number++) {
@@ -721,7 +723,7 @@ shared_ptr<Tournament> TournamentIndex::create_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();
this->tournaments[number] = t;
return t;
+33 -11
View File
@@ -84,7 +84,7 @@ public:
std::shared_ptr<Team> winner_team);
std::string str() const;
bool resolve_if_no_human_players();
bool resolve_if_skippable();
void on_winner_team_set();
void set_winner_team(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,
const Rules& rules,
size_t num_teams,
bool is_2v2);
bool is_2v2,
bool has_com_teams);
Tournament(
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
@@ -110,15 +111,34 @@ public:
JSON json() const;
uint8_t get_number() const;
const std::string& get_name() const;
std::shared_ptr<const MapIndex::MapEntry> get_map() const;
const Rules& get_rules() const;
bool get_is_2v2() const;
State get_state() const;
inline uint8_t get_number() const {
return this->number;
}
inline const std::string& get_name() const {
return this->name;
}
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<Match> next_match_for_team(std::shared_ptr<Team> team) const;
std::shared_ptr<Match> get_final_match() const;
@@ -141,6 +161,7 @@ private:
Rules rules;
size_t num_teams;
bool is_2v2;
bool has_com_teams;
State current_state;
std::set<uint32_t> all_player_serial_numbers;
@@ -177,7 +198,8 @@ public:
std::shared_ptr<const MapIndex::MapEntry> map,
const Rules& rules,
size_t num_teams,
bool is_2v2);
bool is_2v2,
bool has_com_teams);
void delete_tournament(uint8_t number);
std::shared_ptr<Tournament> get_tournament(uint8_t number) const;
std::shared_ptr<Tournament> get_tournament(const std::string& name) const;
+5 -1
View File
@@ -161,6 +161,7 @@ Server commands:\n\
and map names, unless the names contain no spaces.\n\
OPTIONS may include:\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\
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\
@@ -460,12 +461,15 @@ Proxy session commands:\n\
Episode3::Rules rules;
rules.set_defaults();
bool is_2v2 = false;
bool has_com_teams = true;
if (!command_args.empty()) {
auto tokens = split(command_args, ' ');
for (auto& token : tokens) {
token = tolower(token);
if (token == "2v2") {
is_2v2 = true;
} else if (token == "no-coms") {
has_com_teams = false;
} else if (starts_with(token, "dice=")) {
auto subtokens = split(token.substr(5), '-');
if (subtokens.size() != 2) {
@@ -535,7 +539,7 @@ Proxy session commands:\n\
fprintf(stderr, "warning: some rules were invalid and reset to defaults\n");
}
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();
fprintf(stderr, "created tournament %02hhX\n", tourn->get_number());