From 2345d797f063d25421951a15dc0eb1f288d5fc5e Mon Sep 17 00:00:00 2001 From: Tom Stellard Date: Mon, 15 Mar 2021 20:13:29 -0700 Subject: [PATCH] Add support for regional leagues This allows promotion / relegation of teams into region based leagues. In some countries lower level leagues are region based, and teams that are relegated to this level must play in a specific regional league. To create a regional league, use the tag in the league definiton to specify a space separated list of region names that this league covers. To specify which region a team belongs to, use the tag in the team definition. --- src/free.c | 3 +++ src/league.c | 55 ++++++++++++++++++++++++++++++++++++++- src/league.h | 6 +++++ src/league_struct.h | 3 +++ src/team.c | 1 + src/team_struct.h | 4 +++ src/xml_league.c | 14 ++++++++++ src/xml_loadsave_league.c | 5 ++++ src/xml_loadsave_teams.c | 5 ++++ src/xml_team.c | 7 +++++ 10 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/free.c b/src/free.c index 11c15e53..2ae4a829 100644 --- a/src/free.c +++ b/src/free.c @@ -404,6 +404,8 @@ free_league(League *league) free_g_array(&league->two_match_weeks[1]); free_league_stats(&league->stats); + + free_gchar_ptr(league->regions); } /** Free the promotion/relegation struct of a league. */ @@ -562,6 +564,7 @@ free_team(Team *tm) free_gchar_ptr(tm->def_file); free_player_array(&tm->players); + g_free(tm->region); } /** Free an array containing players. */ diff --git a/src/league.c b/src/league.c index 4c57692e..ea2421f9 100644 --- a/src/league.c +++ b/src/league.c @@ -82,6 +82,7 @@ league_new(gboolean new_id) new.yellow_red = 1000; new.stats = stat_league_new("", ""); + new.regions = NULL; return new; } @@ -1318,6 +1319,40 @@ league_team_movements_compare_dest_idcs(gconstpointer a, gconstpointer b, return 0; } +gboolean +league_has_region(const League *league) +{ + return league->regions != NULL; +} + +/** Return TRUE if \p team is eligible for \p league baed on the regions + * of both. Note: This function will return TRUE for if either \p league or + * \p team have no regions defined. + */ +gboolean +league_team_is_in_region(const League *league, const Team *team) +{ + GPtrArray *regions; + gboolean result = FALSE; + gint i; + + if (!league_has_region(league) || !team->region) + return TRUE; + + regions = misc_separate_strings(league->regions); + for (i = 0; i < regions->len; i++) { + const gchar *region = g_ptr_array_index(regions, i); + if (!g_strcmp0(team->region, region)) { + result = TRUE; + goto done; + } + } +done: + free_gchar_array(®ions); + return result; +} + + /** Assign a random destination for the team move with given index and remove the destination from all other unassigned moves if the dest league is full. */ @@ -1335,6 +1370,19 @@ league_team_movements_assign_dest(GArray *team_movements, gint idx, if(debug > 60) g_print("league_team_movements_assign_dest %s\n", tmove->tm.name); + /* Filter out destination leagues that are in a different region */ + if (tmove->tm.region) { + GArray* new_dest_idcs = g_array_new(FALSE, FALSE, sizeof(gint)); + for (i = 0; i < tmove->dest_idcs->len; i++) { + gint dest_idx = g_array_index(tmove->dest_idcs, gint, i); + const League *league = &lig(dest_idx); + if (league_team_is_in_region(league, &tmove->tm)) + g_array_append_val(new_dest_idcs, dest_idx); + } + g_array_unref(tmove->dest_idcs); + tmove->dest_idcs = new_dest_idcs; + } + if(tmove->dest_idcs->len == 1) dest_idx = g_array_index(tmove->dest_idcs, gint, 0); else @@ -1347,7 +1395,12 @@ league_team_movements_assign_dest(GArray *team_movements, gint idx, league_cur_size[dest_idx]++; - if(league_cur_size[dest_idx] > league_size[dest_idx]) + /* The size of region based leagues can fluxute depending on when teams + * are relegated in a given year, so we skip the size check for those + * types of leagues. + */ + if(league_cur_size[dest_idx] > league_size[dest_idx] && + !league_has_region(&lig(dest_idx))) main_exit_program(EXIT_PROM_REL, "league_team_movements_assign_dest: no room in league %s for team %s.", lig(dest_idx).name, tmove->tm.name); diff --git a/src/league.h b/src/league.h index 0f15d096..f0b9d84c 100644 --- a/src/league.h +++ b/src/league.h @@ -148,6 +148,12 @@ void league_team_movements_prune(GArray *team_movements, const gint *league_size, gint *league_cur_size); +gboolean +league_has_region(const League *league); + +gboolean +league_team_is_in_region(const League *league, const Team *team); + void league_team_movements_assign_dest(GArray *team_movements, gint idx, const gint *league_size, gint *league_cur_size); diff --git a/src/league_struct.h b/src/league_struct.h index 0c9e102d..6c27550b 100644 --- a/src/league_struct.h +++ b/src/league_struct.h @@ -193,6 +193,9 @@ typedef struct the fixtures of which should be avoided when scheduling the league fixtures. */ GPtrArray *skip_weeks_with; + /** Space delminted list of region names for this league. Only Teams that + * belong to one of these regions may play in this league. */ + gchar *regions; } League; #endif diff --git a/src/team.c b/src/team.c index c4ff21c2..73042a4a 100644 --- a/src/team.c +++ b/src/team.c @@ -72,6 +72,7 @@ team_new(gboolean new_id) new.first_team_sid = NULL; new.first_team_id = 0; new.reserve_level = 0; + new.region = NULL; return new; } diff --git a/src/team_struct.h b/src/team_struct.h index 9dd2f693..c18d5e60 100644 --- a/src/team_struct.h +++ b/src/team_struct.h @@ -117,6 +117,10 @@ typedef struct * to have Green II be level 2 and not 1 is because it makes the xml * definitions less confusing.*/ gint reserve_level; + + /** The region this team belongs to. This can be used to determine + * which leagues a team can play in. */ + gchar *region; } Team; #endif diff --git a/src/xml_league.c b/src/xml_league.c index 05833ae5..10b21aef 100644 --- a/src/xml_league.c +++ b/src/xml_league.c @@ -46,6 +46,7 @@ #define TAG_AVERAGE_TALENT "average_talent" #define TAG_NAMES_FILE "names_file" #define TAG_BREAK "break" +#define TAG_REGIONS "regions" #define TAG_JOINED_LEAGUE "joined_league" #define TAG_NEW_TABLE "new_table" #define TAG_PROM_REL "prom_rel" @@ -70,6 +71,7 @@ #define TAG_TEAM_FIRST_TEAM "first_team" #define TAG_TEAM_RESERVE_LEVEL "reserve_level" #define TAG_TEAM_DEF_FILE "def_file" +#define TAG_TEAM_REGION "region" #define TAG_TWO_MATCH_WEEK_START "two_match_week_start" #define TAG_TWO_MATCH_WEEK_END "two_match_week_end" @@ -119,12 +121,14 @@ enum XmlLeagueStates STATE_TEAM_FIRST_TEAM, STATE_TEAM_RESERVE_LEVEL, STATE_TEAM_DEF_FILE, + STATE_TEAM_REGION, STATE_BREAK, STATE_JOINED_LEAGUE, STATE_PROPERTY, STATE_NEW_TABLE, STATE_TWO_MATCH_WEEK_START, STATE_TWO_MATCH_WEEK_END, + STATE_REGIONS, STATE_END }; @@ -202,6 +206,8 @@ xml_league_read_start_element (GMarkupParseContext *context, state = STATE_NAMES_FILE; else if(strcmp(element_name, TAG_BREAK) == 0) state = STATE_BREAK; + else if(strcmp(element_name, TAG_REGIONS) == 0) + state = STATE_REGIONS; else if(strcmp(element_name, TAG_DEF_PROPERTY) == 0) state = STATE_PROPERTY; else if(strcmp(element_name, TAG_JOINED_LEAGUE) == 0) @@ -294,6 +300,8 @@ xml_league_read_start_element (GMarkupParseContext *context, state = STATE_TEAM_FIRST_TEAM; else if(strcmp(element_name, TAG_TEAM_RESERVE_LEVEL) == 0) state = STATE_TEAM_RESERVE_LEVEL; + else if(strcmp(element_name, TAG_TEAM_REGION) == 0) + state = STATE_TEAM_REGION; else debug_print_message("xml_league_read_start_element: unknown tag: %s; I'm in state %d\n", element_name, state); @@ -335,6 +343,7 @@ xml_league_read_end_element (GMarkupParseContext *context, strcmp(element_name, TAG_TWO_MATCH_WEEK_START) == 0 || strcmp(element_name, TAG_TWO_MATCH_WEEK_END) == 0 || strcmp(element_name, TAG_PROM_REL) == 0 || + strcmp(element_name, TAG_REGIONS) == 0 || strcmp(element_name, TAG_TEAMS) == 0) state = STATE_LEAGUE; else if(strcmp(element_name, TAG_PROM_GAMES) == 0 || @@ -362,6 +371,7 @@ xml_league_read_end_element (GMarkupParseContext *context, strcmp(element_name, TAG_TEAM_DEF_FILE) == 0 || strcmp(element_name, TAG_TEAM_FIRST_TEAM) == 0 || strcmp(element_name, TAG_TEAM_RESERVE_LEVEL) == 0 || + strcmp(element_name, TAG_TEAM_REGION) == 0 || strcmp(element_name, TAG_TEAM_AVERAGE_TALENT) == 0 || strcmp(element_name, TAG_TEAM_SYMBOL) == 0 || strcmp(element_name, TAG_TEAM_NAMES_FILE) == 0) @@ -428,6 +438,8 @@ xml_league_read_text (GMarkupParseContext *context, misc_string_assign(&new_league.names_file, buf); else if(state == STATE_BREAK) league_cup_fill_rr_breaks(new_league.rr_breaks, buf); + else if (state == STATE_REGIONS) + misc_string_assign(&new_league.regions, buf); else if(state == STATE_PROPERTY) g_ptr_array_add(new_league.properties, g_strdup(buf)); else if(state == STATE_JOINED_LEAGUE) @@ -509,6 +521,8 @@ xml_league_read_text (GMarkupParseContext *context, misc_string_assign(&g_array_index(new_league.teams, Team, new_league.teams->len - 1).first_team_sid, buf); else if(state == STATE_TEAM_RESERVE_LEVEL) g_array_index(new_league.teams, Team, new_league.teams->len - 1).reserve_level = int_value; + else if(state == STATE_TEAM_REGION) + misc_string_assign(&g_array_index(new_league.teams, Team, new_league.teams->len - 1).region, buf); } /** diff --git a/src/xml_loadsave_league.c b/src/xml_loadsave_league.c index 2802016d..0beb025c 100644 --- a/src/xml_loadsave_league.c +++ b/src/xml_loadsave_league.c @@ -66,6 +66,7 @@ enum TAG_LEAGUE_TWO_MATCH_WEEK_START, TAG_LEAGUE_TWO_MATCH_WEEK_END, TAG_LEAGUE_TABLE_FILE, + TAG_LEAGUE_REGIONS, TAG_END }; @@ -155,6 +156,7 @@ xml_loadsave_league_end_element (GMarkupParseContext *context, tag == TAG_LEAGUE_TWO_MATCH_WEEK_START || tag == TAG_LEAGUE_TWO_MATCH_WEEK_END || tag == TAG_LEAGUE_TABLE_FILE || + tag == TAG_LEAGUE_REGIONS || tag == TAG_LEAGUE_AVERAGE_TALENT || tag == TAG_LEAGUE_AVERAGE_TALENT || tag == TAG_LEAGUE_ROUND_ROBINS || @@ -292,6 +294,8 @@ xml_loadsave_league_text (GMarkupParseContext *context, xml_loadsave_table_read(buf2, &new_table); g_array_append_val(new_league->tables, new_table); } + else if(state == TAG_LEAGUE_REGIONS) + misc_string_assign(&new_league->regions, buf); else if(state == TAG_LEAGUE_AVERAGE_TALENT) new_league->average_talent = float_value; else if(state == TAG_LEAGUE_PROM_REL_PROM_GAMES_DEST_SID) @@ -391,6 +395,7 @@ xml_loadsave_league_write(const gchar *prefix, const League *league) xml_write_string(fil, league->names_file, TAG_NAMES_FILE, I0); xml_write_string(fil, league->sid, TAG_SID, I0); xml_write_string(fil, league->symbol, TAG_SYMBOL, I0); + xml_write_string(fil, league->regions, TAG_LEAGUE_REGIONS, I0); for(i=0;iproperties->len;i++) xml_write_string(fil, (gchar*)g_ptr_array_index(league->properties, i), diff --git a/src/xml_loadsave_teams.c b/src/xml_loadsave_teams.c index 3ac6378e..58aeee30 100644 --- a/src/xml_loadsave_teams.c +++ b/src/xml_loadsave_teams.c @@ -53,6 +53,7 @@ enum TAG_TEAM_FIRST_TEAM_SID, TAG_TEAM_FIRST_TEAM_ID, TAG_TEAM_RESERVE_LEVEL, + TAG_TEAM_REGION, TAG_END }; @@ -137,6 +138,7 @@ xml_loadsave_teams_end_element (GMarkupParseContext *context, tag == TAG_TEAM_FIRST_TEAM_SID || tag == TAG_TEAM_FIRST_TEAM_ID || tag == TAG_TEAM_RESERVE_LEVEL || + tag == TAG_TEAM_REGION || tag == TAG_TEAM_LUCK) state = TAG_TEAM; else if(tag == TAG_TEAM_STADIUM_NAME || @@ -219,6 +221,8 @@ xml_loadsave_teams_text (GMarkupParseContext *context, new_team.first_team_id = int_value; else if(state == TAG_TEAM_RESERVE_LEVEL) new_team.reserve_level = int_value; + else if(state == TAG_TEAM_REGION) + misc_string_assign(&new_team.region, buf); else if(state >= TAG_START_PLAYERS && state <= TAG_END_PLAYERS) xml_loadsave_players_text(buf); } @@ -303,6 +307,7 @@ xml_loadsave_teams_write_team(FILE *fil, const Team* team) xml_write_string(fil, team->names_file, TAG_TEAM_NAMES_FILE, I1); xml_write_string(fil, team->strategy_sid, TAG_TEAM_STRATEGY_SID, I1); xml_write_string(fil, team->first_team_sid, TAG_TEAM_FIRST_TEAM_SID, I1); + xml_write_string(fil, team->region, TAG_TEAM_REGION, I1); xml_write_int(fil, team->clid, TAG_TEAM_CLID, I1); xml_write_int(fil, team->id, TAG_TEAM_ID, I1); diff --git a/src/xml_team.c b/src/xml_team.c index 8c2d2ebf..6e3c060b 100644 --- a/src/xml_team.c +++ b/src/xml_team.c @@ -45,6 +45,7 @@ #define TAG_NAMES_FILE "names_file" #define TAG_FIRST_TEAM "first_team" #define TAG_RESERVE_LEVEL "reserve_level" +#define TAG_REGION "region" #define TAG_PLAYER "player" #define TAG_PLAYER_NAME "player_name" #define TAG_PLAYER_BIRTH_YEAR "birth_year" @@ -64,6 +65,7 @@ enum XmlTeamStates STATE_NAMES_FILE, STATE_FIRST_TEAM, STATE_RESERVE_LEVEL, + STATE_REGION, STATE_PLAYER, STATE_PLAYER_NAME, STATE_PLAYER_BIRTH_YEAR, @@ -109,6 +111,8 @@ xml_team_read_start_element (GMarkupParseContext *context, state = STATE_FIRST_TEAM; else if(strcmp(element_name, TAG_RESERVE_LEVEL) == 0) state = STATE_RESERVE_LEVEL; + else if(strcmp(element_name, TAG_REGION) == 0) + state = STATE_REGION; else if(strcmp(element_name, TAG_PLAYER) == 0) { state = STATE_PLAYER; @@ -155,6 +159,7 @@ xml_team_read_end_element (GMarkupParseContext *context, strcmp(element_name, TAG_NAMES_FILE) == 0 || strcmp(element_name, TAG_FIRST_TEAM) == 0 || strcmp(element_name, TAG_RESERVE_LEVEL) == 0 || + strcmp(element_name, TAG_REGION) == 0 || strcmp(element_name, TAG_PLAYER) == 0) { state = STATE_TEAM; @@ -230,6 +235,8 @@ xml_team_read_text (GMarkupParseContext *context, misc_string_assign(&team->first_team_sid, buf); else if(state == STATE_RESERVE_LEVEL) team->reserve_level = int_value; + else if(state == STATE_REGION) + misc_string_assign(&team->region, buf); else if(state == STATE_PLAYER_NAME) misc_string_assign(&new_player.name, buf); else if(state == STATE_PLAYER_BIRTH_YEAR && opt_int("int_opt_load_defs") == 1)