private double calculateDifficulty(List <CatchDifficultyHitObject> objects, double timeRate)
        {
            // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
            double actualStrainStep = strain_step * timeRate;

            // Find the highest strain value within each strain step
            var    highestStrains  = new List <double>();
            double intervalEndTime = actualStrainStep;
            double maximumStrain   = 0; // We need to keep track of the maximum strain in the current interval

            CatchDifficultyHitObject previousHitObject = null;

            foreach (CatchDifficultyHitObject hitObject in objects)
            {
                // 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(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
                        maximumStrain = previousHitObject.Strain * decay;
                    }

                    // Go to the next time interval
                    intervalEndTime += actualStrainStep;
                }

                // Obtain maximum strain
                maximumStrain = Math.Max(hitObject.Strain, maximumStrain);

                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);
        }
        private bool calculateStrainValues(List <CatchDifficultyHitObject> objects, double timeRate)
        {
            CatchDifficultyHitObject lastObject = null;

            if (!objects.Any())
            {
                return(false);
            }

            // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
            foreach (var currentObject in objects)
            {
                if (lastObject != null)
                {
                    currentObject.CalculateStrains(lastObject, timeRate);
                }

                lastObject = currentObject;
            }

            return(true);
        }
Esempio n. 3
0
        internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
        {
            // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
            // See Taiko feedback thread.
            double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
            double decay       = Math.Pow(DECAY_BASE, timeElapsed / 1000);

            // Update new position with lazy movement.
            PlayerPositionOffset =
                MathHelper.Clamp(
                    previousHitObject.ActualNormalizedPosition,
                    NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
                    NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
                - NormalizedPosition;                                                            // Subtract HitObject position to obtain offset

            LastMovement = DistanceTo(previousHitObject);
            double addition = spacingWeight(LastMovement);

            if (NormalizedPosition < previousHitObject.NormalizedPosition)
            {
                LastMovement = -LastMovement;
            }

            CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;

            double additionBonus = 0;
            double sqrtTime      = Math.Sqrt(Math.Max(timeElapsed, 25));

            // Direction changes give an extra point!
            if (Math.Abs(LastMovement) > 0.1)
            {
                if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
                {
                    double bonus = direction_change_bonus / sqrtTime;

                    // Weight bonus by how
                    double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;

                    // We want time to play a role twice here!
                    addition += bonus * bonusFactor;

                    // Bonus for tougher direction switches and "almost" hyperdashes at this point
                    if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
                    {
                        additionBonus += 0.3 * bonusFactor;
                    }
                }

                // Base bonus for every movement, giving some weight to streams.
                addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
            }

            // Bonus for "almost" hyperdashes at corner points
            if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
            {
                if (!previousHitCircle.HyperDash)
                {
                    additionBonus += 1.0;
                }
                else
                {
                    // After a hyperdash we ARE in the correct position. Always!
                    PlayerPositionOffset = 0;
                }

                addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
            }

            addition *= 850.0 / Math.Max(timeElapsed, 25);

            Strain = previousHitObject.Strain * decay + addition;
        }
Esempio n. 4
0
 internal float DistanceTo(CatchDifficultyHitObject other)
 {
     return(Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition));
 }