public static CalendarResult Calculate(CalendarGenInput input, IList <Field> fields, string locale, TeamLocationPreferenceProviderDelegate preferenceProvider, IDbConnection c) { Assert.IsNotNull(input); Assert.IsNotNull(input.TeamIds); Assert.IsNotNull(input.WeekdaySlots); Assert.IsNotNull(input.FieldIds); var result = new CalendarResult(); var teams = input.TeamIds; bool noTeamsDefined = teams.Length == 0; int numberOfTeams = 0; if (noTeamsDefined) // No teams defined yet. { long idGroup = input.Group.IdGroup; numberOfTeams = c.QueryFirst <int>($"SELECT numteams FROM stageGroups WHERE id = {idGroup};"); } else { numberOfTeams = teams.Length; } if (!IsPowerOfTwo(numberOfTeams)) { throw new PlannerException("Error.NotPowerOfTwo"); } // Should also check that teams are actually valid (!= -1) var matchRounds = CreateRounds(teams, input.Group, numberOfTeams); string roundNameCallback(int index) => GetRoundName(index, matchRounds.Count, locale); var numSlotsNeededPerRound = numberOfTeams / 2; //var numFields = input.FieldIds.Length; //var availableTimeSlots = PlannerScheduler.GetTimeSlots(input.WeekdaySlots, input.GameDuration); //var availableSlots = availableTimeSlots * numFields; //if (availableSlots < numSlotsNeededPerRound) throw new PlannerException("Error.Calendar.NotEnoughHours"); var teamPrefs = noTeamsDefined ? null : preferenceProvider?.Invoke(input.TeamIds); PlannerScheduler.SpreadMatchesInCalendar( result, matchRounds, input.WeekdaySlots, input.StartDate, input.FieldIds, numSlotsNeededPerRound, input.GameDuration, false, input.Group, input.ForbiddenDays, teamPrefs, roundNameCallback); return(result); }
/* * LEAGUE * * input * - list of teams (ids) * - start date * - weekdays available * - with range of hours * - days forbidden * - list of play fields * - with their availability * - game duration (minutes) * - is preview? * * output: * - a collection of days, each containing: * - date * - list of matches, each containing: * - teams * - hour * - play field * - status: scheduled * * Notes: * - First create a list of matches per day, then spread it in the available slots. * - From the number of teams, the algorithm can predict how many slots are needed in each day * (it's the same as the number of teams). This should be used in the UI to set up the * calendar parameters. * - To create the list of matches per day, start with the list of teams. * - In the UI, allow the organizer to order them any way the want (display a message for this). * - Then do every possible combination. If odd number of teams, one will be left out each round, in order. * - To spread the matches in slots, randomize the list and assign to slots. * - Have to consider the "home" setting of each team. Only one team should play at home, but then, * what if two have then same home? no matter what, they will have to play, and their home won't * change. * * Should this be implemented in the server instead? Having this not validated on the client implies an * organizer can mess the calendar. Is it possible that it can ultimately generate payments to users? * In the server this could be implemented as a simple API call that calculates the preview. An argument * can decide whether the generated result is just a preview or has to be commited to the DB. * Since the game field availability is a lot of data that has to be queried to the database, it's * benefitial to do it on the server side. * * Test environment for this. */ /// <summary> /// /// </summary> /// <param name="input"></param> /// <param name="fields">List of fields, including matches already scheduled to consider in the plan</param> /// <returns></returns> public static CalendarResult Calculate(CalendarGenInput input, IList <Field> fields, string locale, TeamLocationPreferenceProviderDelegate preferenceProvider, TeamNameProvider nameProvider) { //-First create a list of matches per round, then spread it in the available slots. // - From the number of teams, the algorithm can predict how many slots are needed in each round // (it's the same as the number of teams). This should be used in the UI to set up the // calendar parameters. // -To create the list of matches per round, start with the list of teams. // - In the UI, allow the organizer to order them any way the want(display a message for this). // - Then apply the round robin algorithm, preserving the first entry in the list. // - To spread the matches in slots, randomize each round list and assign to slots in sequence. // - Have to consider the "home" setting of each team. Only one team should play at home, but then, // what if two have the same home? no matter what, they will have to play, and their home won't // change. Assert.IsNotNull(input); Assert.IsNotNull(input.TeamIds); Assert.IsNotNull(input.WeekdaySlots); Assert.IsNotNull(input.FieldIds); var result = new CalendarResult(); if (input.TeamIds.Length <= 2) { throw new PlannerException("Error.Calendar.NotEnoughTeams"); } var teams = input.TeamIds; var oddNumTeams = teams.Length % 2 == 1; if (oddNumTeams) { // Add a "free day" team (id = -1) Array.Resize(ref teams, teams.Length + 1); teams[teams.Length - 1] = -1; } var numSlotsNeededPerRound = teams.Length / 2; //var numFields = input.FieldIds.Length; //var availableTimeSlots = PlannerScheduler.GetTimeSlots(input.WeekdaySlots, input.GameDuration); //var availableSlots = availableTimeSlots * numFields; //if (availableSlots < numSlotsNeededPerRound) throw new PlannerException("Error.Calendar.NotEnoughHours"); var matchRounds = CreateRoundRobinMatches(teams, input.NumRounds); string roundNameCallback(int index) => Localization.Get("Jornada {0}", locale, index); var locationPreferences = preferenceProvider?.Invoke(input.TeamIds); // Now assign matches to days. Consider field availability. PlannerScheduler.SpreadMatchesInCalendar( result, matchRounds, input.WeekdaySlots, input.StartDate, input.FieldIds, numSlotsNeededPerRound, input.GameDuration, input.RandomizeFields, input.Group, input.ForbiddenDays, locationPreferences, roundNameCallback); return(result); }