/// <summary> /// Calculates the winning <see cref="Runner"/>. /// </summary> /// <returns></returns> public Runner RunRace() { if (!_canRunRace || _raceMargin == 0) { return(null); } // Calculate the chances of each of the runners winning, based on their margin. var runnersAndChances = Runners.Select(r => new { Runner = r, Chances = r.GetMargin() / _raceMargin }); // Now, randomize the runners' chances using a randomization margin. var runnersAndRanomizedChances = runnersAndChances.Select(r => new { Runner = r.Runner, Chances = r.Chances + Convert.ToDecimal(_random.NextDouble() * 2 - 1) * _randomMargin }); // Re-normalize the chances so that they stack up to 100%. Decimal totalRandomizedChances = runnersAndRanomizedChances.Sum(r => r.Chances); var runnersAndNormalizedRandomizedChances = runnersAndRanomizedChances .Select(r => new { Runner = r.Runner, Chances = r.Chances / totalRandomizedChances }) .ToList(); // Calculate a random outcome for the race. Decimal raceOutcome = Convert.ToDecimal(_random.NextDouble()); // Now, iterate over the runners to see if their chances of winning covers the random race outcome. Decimal cumulativeChances = 0M; foreach (var runnerChances in runnersAndNormalizedRandomizedChances.OrderBy(r => r.Chances)) { Decimal runnerMinChance = cumulativeChances; Decimal runnerMaxChance = cumulativeChances + runnerChances.Chances; if (raceOutcome > runnerMinChance && raceOutcome <= runnerMaxChance) { // If the runners chances covers the outcome, we have a winner. // Note that this case covers 1. return(runnerChances.Runner); } else if (cumulativeChances == 0 && raceOutcome == 0) { // If the race outcome was zero and this is the first runner, then this one wins. return(runnerChances.Runner); } // If we didn't find a winner, increase the cumulative chances and continue. cumulativeChances += runnerChances.Chances; } // If we reach this point, its likely that the runnerMaxChance was still less than // the race outcome due to accuracy issues. If this is the case, then last runner // would have been the winner. return(runnersAndNormalizedRandomizedChances.OrderBy(r => r.Chances).Last().Runner); }