// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour { /// /// Utility class to perform various encodings. /// public static class TaikoColourDifficultyPreprocessor { /// /// Processes and encodes a list of s into a list of s, /// assigning the appropriate s to each , /// and pre-evaluating colour difficulty of each . /// public static void ProcessAndAssign(List hitObjects) { List hitPatterns = encode(hitObjects); // Assign indexing and encoding data to all relevant objects. Only the first note of each encoding type is // assigned with the relevant encodings. foreach (var repeatingHitPattern in hitPatterns) { repeatingHitPattern.AlternatingMonoPatterns[0].MonoStreaks[0].HitObjects[0].Colour.RepeatingHitPatterns = repeatingHitPattern; // The outermost loop is kept a ForEach loop since it doesn't need index information, and we want to // keep i and j for AlternatingMonoPattern's and MonoStreak's index respectively, to keep it in line with // documentation. for (int i = 0; i < repeatingHitPattern.AlternatingMonoPatterns.Count; ++i) { AlternatingMonoPattern monoPattern = repeatingHitPattern.AlternatingMonoPatterns[i]; monoPattern.Parent = repeatingHitPattern; monoPattern.Index = i; monoPattern.MonoStreaks[0].HitObjects[0].Colour.AlternatingMonoPattern = monoPattern; for (int j = 0; j < monoPattern.MonoStreaks.Count; ++j) { MonoStreak monoStreak = monoPattern.MonoStreaks[j]; monoStreak.Parent = monoPattern; monoStreak.Index = j; monoStreak.HitObjects[0].Colour.MonoStreak = monoStreak; } } } } /// /// Encodes a list of s into a list of s. /// private static List encode(List data) { List monoStreaks = encodeMonoStreak(data); List alternatingMonoPatterns = encodeAlternatingMonoPattern(monoStreaks); List repeatingHitPatterns = encodeRepeatingHitPattern(alternatingMonoPatterns); return repeatingHitPatterns; } /// /// Encodes a list of s into a list of s. /// private static List encodeMonoStreak(List data) { List monoStreaks = new List(); MonoStreak? currentMonoStreak = null; for (int i = 0; i < data.Count; i++) { TaikoDifficultyHitObject taikoObject = (TaikoDifficultyHitObject)data[i]; // This ignores all non-note objects, which may or may not be the desired behaviour TaikoDifficultyHitObject? previousObject = taikoObject.PreviousNote(0); // If this is the first object in the list or the colour changed, create a new mono streak if (currentMonoStreak == null || previousObject == null || (taikoObject.BaseObject as Hit)?.Type != (previousObject.BaseObject as Hit)?.Type) { currentMonoStreak = new MonoStreak(); monoStreaks.Add(currentMonoStreak); } // Add the current object to the encoded payload. currentMonoStreak.HitObjects.Add(taikoObject); } return monoStreaks; } /// /// Encodes a list of s into a list of s. /// private static List encodeAlternatingMonoPattern(List data) { List monoPatterns = new List(); AlternatingMonoPattern? currentMonoPattern = null; for (int i = 0; i < data.Count; i++) { // Start a new AlternatingMonoPattern if the previous MonoStreak has a different mono length, or if this is the first MonoStreak in the list. if (currentMonoPattern == null || data[i].RunLength != data[i - 1].RunLength) { currentMonoPattern = new AlternatingMonoPattern(); monoPatterns.Add(currentMonoPattern); } // Add the current MonoStreak to the encoded payload. currentMonoPattern.MonoStreaks.Add(data[i]); } return monoPatterns; } /// /// Encodes a list of s into a list of s. /// private static List encodeRepeatingHitPattern(List data) { List hitPatterns = new List(); RepeatingHitPatterns? currentHitPattern = null; for (int i = 0; i < data.Count; i++) { // Start a new RepeatingHitPattern. AlternatingMonoPatterns that should be grouped together will be handled later within this loop. currentHitPattern = new RepeatingHitPatterns(currentHitPattern); // Determine if future AlternatingMonoPatterns should be grouped. bool isCoupled = i < data.Count - 2 && data[i].IsRepetitionOf(data[i + 2]); if (!isCoupled) { // If not, add the current AlternatingMonoPattern to the encoded payload and continue. currentHitPattern.AlternatingMonoPatterns.Add(data[i]); } else { // If so, add the current AlternatingMonoPattern to the encoded payload and start repeatedly checking if the // subsequent AlternatingMonoPatterns should be grouped by increasing i and doing the appropriate isCoupled check. while (isCoupled) { currentHitPattern.AlternatingMonoPatterns.Add(data[i]); i++; isCoupled = i < data.Count - 2 && data[i].IsRepetitionOf(data[i + 2]); } // Skip over viewed data and add the rest to the payload currentHitPattern.AlternatingMonoPatterns.Add(data[i]); currentHitPattern.AlternatingMonoPatterns.Add(data[i + 1]); i++; } hitPatterns.Add(currentHitPattern); } // Final pass to find repetition intervals for (int i = 0; i < hitPatterns.Count; i++) { hitPatterns[i].FindRepetitionInterval(); } return hitPatterns; } } }