diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 3fdf0f3..25c0e9d 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -62,19 +62,68 @@ namespace PerformanceCalculator.Simulate } else { - // Let Great=6, Good=2, Meh=1, Miss=0. The total should be this. - var targetTotal = (int)Math.Round(accuracy * totalResultCount * 6); + // Total result count excluding countMiss + int relevantResultCount = totalResultCount - countMiss; - // Start by assuming every non miss is a meh - // This is how much increase is needed by greats and goods - var delta = targetTotal - (totalResultCount - countMiss); + // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss + // So it's better to pretened that there were 0 misses in the 1st place + double relevantAccuracy = accuracy * totalResultCount / relevantResultCount; - // Each great increases total by 5 (great-meh=5) - countGreat = delta / 5; - // Each good increases total by 1 (good-meh=1). Covers remaining difference. - countGood = delta % 5; - // Mehs are left over. Could be negative if impossible value of amountMiss chosen - countMeh = totalResultCount - countGreat - countGood - countMiss; + // Clamp accuracy to account for user trying to break the algorithm by inputting impossible values + relevantAccuracy = Math.Clamp(relevantAccuracy, 0, 1); + + // Main curve for accuracy > 25%, the closer accuracy is to 25% - the more 50s it adds + if (relevantAccuracy >= 0.25) + { + // Main curve. Zero 50s if accuracy is 100%, one 50 per 9 100s if accuracy is 75% (excluding misses), 4 50s per 9 100s if accuracy is 50% + double ratio50to100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); + + // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 + double count100estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50to100 + 4); + + // Get count50 according to c50 = c100 * ratio50to100 + double count50estimate = count100estimate * ratio50to100; + + // Round it to get int number of 100s + countGood = (int?)Math.Round(count100estimate); + + // Get number of 50s as difference between total mistimed hits and count100 + countMeh = (int?)(Math.Round(count100estimate + count50estimate) - countGood); + } + // If accuracy is between 16.67% and 25% - we assume that we have no 300s + else if (relevantAccuracy >= 1.0 / 6) + { + // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = 0 + double count100estimate = 6 * relevantResultCount * relevantAccuracy - relevantResultCount; + + // We only had 100s and 50s in that scenario so rest of the hits are 50s + double count50estimate = relevantResultCount - count100estimate; + + // Round it to get int number of 100s + countGood = (int?)Math.Round(count100estimate); + + // Get number of 50s as difference between total mistimed hits and count100 + countMeh = (int?)(Math.Round(count100estimate + count50estimate) - countGood); + } + // If accuracy is less than 16.67% - it means that we have only 50s or misses + // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy + else + { + // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = c100 = 0 + double count50estimate = 6 * relevantResultCount * relevantAccuracy; + + // We have 0 100s, because we can't start adding 100s again after reaching "only 50s" point + countGood = 0; + + // Round it to get int number of 50s + countMeh = (int?)Math.Round(count50estimate); + + // Fill the rest results with misses overwriting initial countMiss + countMiss = (int)(totalResultCount - countMeh); + } + + // Rest of the hits are 300s + countGreat = (int)(totalResultCount - countGood - countMeh - countMiss); } return new Dictionary diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 0369f3c..3fd4002 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -137,39 +137,8 @@ namespace PerformanceCalculatorGUI // Clamp accuracy to account for user trying to break the algorithm by inputting impossible values relevantAccuracy = Math.Clamp(relevantAccuracy, 0, 1); - // If accuracy is less than 16.67% - it means that we have only 50s or misses - // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy - if (relevantAccuracy < 1.0 / 6) - { - // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = c100 = 0 - double count50estimate = 6 * relevantResultCount * relevantAccuracy; - - // We have 0 100s, because we can't start adding 100s again after reaching "only 50s" point - countGood = 0; - - // Round it to get int number of 50s - countMeh = (int?)Math.Round(count50estimate); - - // Fill the rest results with misses overwriting initial countMiss - countMiss = (int)(totalResultCount - countMeh); - } - // If accuracy is between 16.67% and 25% - we assume that we have no 300s - else if (relevantAccuracy < 0.25) - { - // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = 0 - double count100estimate = 6 * relevantResultCount * relevantAccuracy - relevantResultCount; - - // We only had 100s and 50s in that scenario so rest of the hits are 50s - double count50estimate = relevantResultCount - count100estimate; - - // Round it to get int number of 100s - countGood = (int?)Math.Round(count100estimate); - - // Get number of 50s as difference between total mistimed hits and count100 - countMeh = (int?)(Math.Round(count100estimate + count50estimate) - countGood); - } // Main curve for accuracy > 25%, the closer accuracy is to 25% - the more 50s it adds - else + if (relevantAccuracy >= 0.25) { // Main curve. Zero 50s if accuracy is 100%, one 50 per 9 100s if accuracy is 75% (excluding misses), 4 50s per 9 100s if accuracy is 50% double ratio50to100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); @@ -186,6 +155,37 @@ namespace PerformanceCalculatorGUI // Get number of 50s as difference between total mistimed hits and count100 countMeh = (int?)(Math.Round(count100estimate + count50estimate) - countGood); } + // If accuracy is between 16.67% and 25% - we assume that we have no 300s + else if (relevantAccuracy >= 1.0 / 6) + { + // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = 0 + double count100estimate = 6 * relevantResultCount * relevantAccuracy - relevantResultCount; + + // We only had 100s and 50s in that scenario so rest of the hits are 50s + double count50estimate = relevantResultCount - count100estimate; + + // Round it to get int number of 100s + countGood = (int?)Math.Round(count100estimate); + + // Get number of 50s as difference between total mistimed hits and count100 + countMeh = (int?)(Math.Round(count100estimate + count50estimate) - countGood); + } + // If accuracy is less than 16.67% - it means that we have only 50s or misses + // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy + else + { + // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = c100 = 0 + double count50estimate = 6 * relevantResultCount * relevantAccuracy; + + // We have 0 100s, because we can't start adding 100s again after reaching "only 50s" point + countGood = 0; + + // Round it to get int number of 50s + countMeh = (int?)Math.Round(count50estimate); + + // Fill the rest results with misses overwriting initial countMiss + countMiss = (int)(totalResultCount - countMeh); + } // Rest of the hits are 300s countGreat = (int)(totalResultCount - countGood - countMeh - countMiss);