예제 #1
0
        public static void SaveRounds(IDbConnection conn, GroupCoords coords, CalendarResult result)
        {
            var transaction = conn.BeginTransaction();

            try
            {
                var dbGroup = conn.Get <StageGroup>(coords.IdGroup);
                if (dbGroup == null)
                {
                    throw new Exception("Error.NotFound");
                }
                if ((dbGroup.Flags & (int)StageGroupFlags.HasGeneratedCalendar) > 0)
                {
                    throw new Exception("Tournament.AlreadyHasCalendar");
                }

                foreach (var day in result.Days)
                {
                    InsertDay(conn, transaction, day);
                }

                dbGroup.Flags = dbGroup.Flags | (int)StageGroupFlags.HasGeneratedCalendar;
                conn.Update(dbGroup, transaction);

                transaction.Commit();
            }
            catch (System.Exception)
            {
                transaction.Rollback();
                throw;
            }
        }
예제 #2
0
        public static void AssignSpecificPreferencesToSlots(CalendarResult result, List <Match> matchesInRound, IList <CalendarSlot> slots, IEnumerable <TeamLocationPreference> prefs, string roundName)
        {
            // Locate slots for those prefs, remove slots and assign them to matches
            foreach (var pref in prefs)
            {
                // Locate slot for that preference
                var slot = GetSlotForPreference(pref, slots);
                if (slot == null)
                {
                    result.AddMessage(OutputLineType.Warning, "{1}: El horario preferido del equipo {0} no está en los rangos de horas especificados, o el campo preferido no está en la lista de seleccionados. Ignorando la preferencia del equipo.", pref.IdTeam, roundName);
                    continue;
                }

                // Assign the slot to the match (find the match for this team) and remove the slot in slots.
                var match = GetMatchForHomeTeamId(matchesInRound, pref.IdTeam);
                if (match == null)
                {
                    result.AddMessage(OutputLineType.Error, "{1}: Hay una preferencia para el equipo {0} pero no está en la lista de equipos. Inconsistencia interna.", pref.IdTeam, roundName);
                }
                else
                {
                    // Assign slot to match and remove from the list of available slots
                    slot.AssignToMatch(match);
                    slots.Remove(slot);
                }
            }
        }
예제 #3
0
        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);
        }
예제 #4
0
        public static PlayDay CreateAndFillRound(CalendarResult result, List <Match> matchesInRound, IList <CalendarSlot> slots, GroupCoords coords, IEnumerable <TeamLocationPreference> fieldPreferences, string roundName)
        {
            // Round Id -1: when saving to the database, will have to set proper ID here and to the matches.
            var day = new PlayDay {
                Id = -1, Matches = new List <Match>()
            };

            // Spread fieldPreferences in the slots (consuming them). Then fill the rest of the matches.
            AssignPreferencesToSlots(result, matchesInRound, slots, fieldPreferences, roundName);

            foreach (var match in matchesInRound)
            {
                match.IdTournament = coords.IdTournament;
                match.IdStage      = coords.IdStage;
                match.IdGroup      = coords.IdGroup;
                match.Status       = (int)MatchStatus.Created;
                match.IdDay        = day.Id;

                match.Status = (int)MatchStatus.Created;

                if (IsFillerMatch(match))
                {
                    // Add match, but do not consume slot
                    match.Status = (int)MatchStatus.Skip;
                    day.Matches.Add(match);
                    continue;
                }

                // If the match doesn't already have time
                if (match.StartTime == default(DateTime))
                {
                    if (slots.Count == 0)
                    {
                        throw new Exception("Error.Calendar.NotEnoughHours");
                    }

                    // Assign first available slot and remove from the list.
                    var slot = slots[0];
                    slot.AssignToMatch(match);
                    slots.RemoveAt(0);
                }

                day.Matches.Add(match);
            }

            day.SetDatesFromDatesList();

            return(day);
        }
예제 #5
0
        private static DateTime?GetSlotForTime(CalendarResult result, IList <DateTime> slots, int gameDuration, DateTime time, long idTeam)
        {
            foreach (var slot in slots)
            {
                var slotStartTime = GetTime(slot);
                var slotEndTime   = slotStartTime.AddMinutes(gameDuration);

                if (slotStartTime >= time && time >= slotEndTime)
                {
                    return(slot);
                }
            }

            result.Messages.Add(new OutputLine(OutputLineType.Warning, Localization.Get("El equipo {0} prefiere la hora {1} que no está en el rango de horas asignado. Ignorando la preferencia.", null, idTeam, time)));
            return(null);
        }
예제 #6
0
        public static void AssignPreferencesToSlots(CalendarResult result, List <Match> matchesInRound, IList <CalendarSlot> slots, IEnumerable <TeamLocationPreference> teamPreferences, string roundName)
        {
            if (teamPreferences == null)
            {
                return;
            }

            var localTeamIds = GetLocalTeams(matchesInRound);

            // * Assign first LOCAL teams with field AND time preference
            var prefs = GetPreferencesForLocalTeams(GetFieldAndTimePreferences(teamPreferences), localTeamIds);

            AssignSpecificPreferencesToSlots(result, matchesInRound, slots, prefs, roundName);


            // * Now assign LOCAL teams with field preference to any available hour (time preference only is not allowed)
            prefs = GetPreferencesForLocalTeams(GetFieldPreferences(teamPreferences), localTeamIds);
            AssignSpecificPreferencesToSlots(result, matchesInRound, slots, prefs, roundName);
        }
예제 #7
0
        public static void SpreadMatchesInCalendar(
            CalendarResult result,
            List <List <Match> > matchRounds,
            DailySlot[][] weekdaySlots,
            DateTime startDate,
            IList <long> fieldIds,
            int numSlots,
            int gameDuration,
            bool wantsRandom,
            GroupCoords coords,
            DateTime[] forbiddenDays,
            IEnumerable <TeamLocationPreference> fieldPreferences,
            Func <int, string> roundNameCallback)
        {
            var days = new List <PlayDay>();
            //var fieldIds = GetFieldIds(fields);

            var roundNumber = 1;

            // First, spread the rounds in days in the calendar
            foreach (var matchRound in matchRounds)
            {
                startDate = GetUnforbiddenStartDate(startDate, weekdaySlots, forbiddenDays);

                var slots     = GetRoundSlots(startDate, weekdaySlots, numSlots, gameDuration, fieldIds);
                var roundName = roundNameCallback(roundNumber);

                var round = CreateAndFillRound(result, matchRound, slots, coords, fieldPreferences, roundName);
                round.IdTournament  = coords.IdTournament;
                round.IdStage       = coords.IdStage;
                round.IdGroup       = coords.IdGroup;
                round.Name          = roundName;
                round.SequenceOrder = roundNumber;
                days.Add(round);

                startDate = startDate.AddDays(7);

                roundNumber++;
            }

            result.Days = days;
        }
예제 #8
0
        public IActionResult GenerateTournamentCalendar([FromBody] CalendarGenInput input)
        {
            return(DbOperation(c => {
                if (input == null)
                {
                    throw new NoDataException();
                }
                if (!IsOrganizationAdmin())
                {
                    throw new UnauthorizedAccessException();
                }

                CalendarResult result = null;

                var teams = GetTeams(c, null, input.TeamIds);

                switch ((CalendarType)input.Type)
                {
                case CalendarType.League:
                    result = LeaguePlanner.Calculate(input, null, GetUserLocale(), teamIds => GetTeamPreferences(c, null, teamIds), idTeam => GetTeamName(teams, idTeam));
                    break;

                case CalendarType.Knockout:
                    result = KnockoutPlanner.Calculate(input, null, GetUserLocale(), teamIds => GetTeamPreferences(c, null, teamIds), c);
                    break;

                default:
                    break;
                }

                if (!input.IsPreview)
                {
                    CalendarStorer.SaveRounds(c, input.Group, result);
                }

                return result;
            }));
        }
예제 #9
0
        /*
         * 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);
        }