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 <regions> 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
<region> tag in the team definition.
This commit is contained in:
Tom Stellard 2021-03-15 20:13:29 -07:00
parent 34db509a84
commit 2345d797f0
10 changed files with 102 additions and 1 deletions

View File

@ -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. */

View File

@ -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(&regions);
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);

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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;i<league->properties->len;i++)
xml_write_string(fil, (gchar*)g_ptr_array_index(league->properties, i),

View File

@ -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);

View File

@ -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)