Пример #1
0
        private double CalculateDifficulty(DifficultyType Type)
        {
            // Find the highest strain value within each strain step
            List <double> HighestStrains  = new List <double>();
            double        IntervalEndTime = STRAIN_STEP;
            double        MaximumStrain   = 0; // We need to keep track of the maximum strain in the current interval

            tpHitObject PreviousHitObject = null;

            foreach (tpHitObject hitObject in tpHitObjects)
            {
                // While we are beyond the current interval push the currently available maximum to our strain list
                while (hitObject.BaseHitObject.StartTime > IntervalEndTime)
                {
                    HighestStrains.Add(MaximumStrain);

                    // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
                    // until the beginning of the next interval.
                    if (PreviousHitObject == null)
                    {
                        MaximumStrain = 0;
                    }
                    else
                    {
                        double Decay = Math.Pow(tpHitObject.DECAY_BASE[(int)Type], (double)(IntervalEndTime - PreviousHitObject.BaseHitObject.StartTime) / 1000);
                        MaximumStrain = PreviousHitObject.Strains[(int)Type] * Decay;
                    }

                    // Go to the next time interval
                    IntervalEndTime += STRAIN_STEP;
                }

                // Obtain maximum strain
                if (hitObject.Strains[(int)Type] > MaximumStrain)
                {
                    MaximumStrain = hitObject.Strains[(int)Type];
                }

                PreviousHitObject = hitObject;
            }

            // Build the weighted sum over the highest strains for each interval
            double Difficulty = 0;
            double Weight     = 1;

            HighestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.

            foreach (double Strain in HighestStrains)
            {
                Difficulty += Weight * Strain;
                Weight     *= DECAY_WEIGHT;
            }

            return(Difficulty);
        }
Пример #2
0
        private void CalculateSpecificStrain(tpHitObject PreviousHitObject, AiModtpDifficulty.DifficultyType Type)
        {
            double Addition    = 0;
            double TimeElapsed = BaseHitObject.StartTime - PreviousHitObject.BaseHitObject.StartTime;
            double Decay       = Math.Pow(DECAY_BASE[(int)Type], TimeElapsed / 1000);

            if ((BaseHitObject.Type & HitObjectType.Spinner) > 0)
            {
                // Do nothing for spinners
            }
            else if ((BaseHitObject.Type & HitObjectType.Slider) > 0)
            {
                switch (Type)
                {
                case AiModtpDifficulty.DifficultyType.Speed:

                    // For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast.
                    // The spacing weight exists to differentiate between being able to easily alternate or having to single.
                    Addition =
                        SpacingWeight(PreviousHitObject.LazySliderLengthFirst +
                                      PreviousHitObject.LazySliderLengthSubsequent * (PreviousHitObject.BaseHitObject.SegmentCount - 1) +
                                      DistanceTo(PreviousHitObject), Type) *
                        SPACING_WEIGHT_SCALING[(int)Type];
                    break;


                case AiModtpDifficulty.DifficultyType.Aim:

                    // For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference
                    // to multiple jumps.
                    Addition =
                        (
                            SpacingWeight(PreviousHitObject.LazySliderLengthFirst, Type) +
                            SpacingWeight(PreviousHitObject.LazySliderLengthSubsequent, Type) * (PreviousHitObject.BaseHitObject.SegmentCount - 1) +
                            SpacingWeight(DistanceTo(PreviousHitObject), Type)
                        ) *
                        SPACING_WEIGHT_SCALING[(int)Type];
                    break;
                }
            }
            else if ((BaseHitObject.Type & HitObjectType.Normal) > 0)
            {
                Addition = SpacingWeight(DistanceTo(PreviousHitObject), Type) * SPACING_WEIGHT_SCALING[(int)Type];
            }

            // Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero.
            // You will never find maps that require this amongst ranked maps.
            Addition /= Math.Max(TimeElapsed, 50);

            Strains[(int)Type] = PreviousHitObject.Strains[(int)Type] * Decay + Addition;
        }
Пример #3
0
        // Exceptions would be nicer to handle errors, but for this small project it shall be ignored.
        private bool CalculateStrainValues()
        {
            // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
            List <tpHitObject> .Enumerator HitObjectsEnumerator = tpHitObjects.GetEnumerator();
            if (HitObjectsEnumerator.MoveNext() == false)
            {
                Reports.Add(new AiReport(Severity.Info, "Can not compute difficulty of empty beatmap."));
                return(false);
            }

            tpHitObject CurrentHitObject = HitObjectsEnumerator.Current;
            tpHitObject NextHitObject;

            // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See tpHitObject.

            while (HitObjectsEnumerator.MoveNext())
            {
                NextHitObject = HitObjectsEnumerator.Current;
                NextHitObject.CalculateStrains(CurrentHitObject);
                CurrentHitObject = NextHitObject;
            }

            return(true);
        }
        private void CalculateSpecificStrain(tpHitObject PreviousHitObject, AiModtpDifficulty.DifficultyType Type)
        {
            double Addition = 0;
            double TimeElapsed = BaseHitObject.StartTime - PreviousHitObject.BaseHitObject.StartTime;
            double Decay = Math.Pow(DECAY_BASE[(int)Type], TimeElapsed / 1000);

            if ((BaseHitObject.Type & HitObjectType.Spinner) > 0)
            {
                // Do nothing for spinners
            }
            else if ((BaseHitObject.Type & HitObjectType.Slider) > 0)
            {
                switch(Type)
                {
                    case AiModtpDifficulty.DifficultyType.Speed:

                        // For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast.
                        // The spacing weight exists to differentiate between being able to easily alternate or having to single.
                        Addition =
                            SpacingWeight(PreviousHitObject.LazySliderLengthFirst +
                                          PreviousHitObject.LazySliderLengthSubsequent * (PreviousHitObject.BaseHitObject.SegmentCount - 1) +
                                          DistanceTo(PreviousHitObject), Type) *
                            SPACING_WEIGHT_SCALING[(int)Type];
                        break;

                    case AiModtpDifficulty.DifficultyType.Aim:

                        // For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference
                        // to multiple jumps.
                        Addition =
                            (
                                SpacingWeight(PreviousHitObject.LazySliderLengthFirst, Type) +
                                SpacingWeight(PreviousHitObject.LazySliderLengthSubsequent, Type) * (PreviousHitObject.BaseHitObject.SegmentCount - 1) +
                                SpacingWeight(DistanceTo(PreviousHitObject), Type)
                            ) *
                            SPACING_WEIGHT_SCALING[(int)Type];
                        break;
                }

            }
            else if ((BaseHitObject.Type & HitObjectType.Normal) > 0)
            {
                Addition = SpacingWeight(DistanceTo(PreviousHitObject), Type) * SPACING_WEIGHT_SCALING[(int)Type];
            }

            // Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero.
            // You will never find maps that require this amongst ranked maps.
            Addition /= Math.Max(TimeElapsed, 50);

            Strains[(int)Type] = PreviousHitObject.Strains[(int)Type] * Decay + Addition;
        }
 public double DistanceTo(tpHitObject other)
 {
     // Scale the distance by circle size.
     return (NormalizedStartPosition - other.NormalizedEndPosition).Length();
 }
 public void CalculateStrains(tpHitObject PreviousHitObject)
 {
     CalculateSpecificStrain(PreviousHitObject, AiModtpDifficulty.DifficultyType.Speed);
     CalculateSpecificStrain(PreviousHitObject, AiModtpDifficulty.DifficultyType.Aim);
 }
Пример #7
0
 public double DistanceTo(tpHitObject other)
 {
     // Scale the distance by circle size.
     return((NormalizedStartPosition - other.NormalizedEndPosition).Length());
 }
Пример #8
0
 public void CalculateStrains(tpHitObject PreviousHitObject)
 {
     CalculateSpecificStrain(PreviousHitObject, AiModtpDifficulty.DifficultyType.Speed);
     CalculateSpecificStrain(PreviousHitObject, AiModtpDifficulty.DifficultyType.Aim);
 }