/// <summary> /// Randomly selects from discrete options with unequal weights. The program state /// shall be unchanged in the event of an exception. /// </summary> /// /// <typeparam name="T">The type of object to be selected.</typeparam> /// <param name="weightedChoices">A list of (<typeparamref name="T"/>, double) tuples. /// The first value of each tuple represents one of the choices, the second the weight /// for that choice. The odds of selecting two choices equals the ratio of the weights /// between them. All weights must be nonnegative, and at least one must be /// positive.</param> /// <returns>The selected object.</returns> /// /// <exception cref="ArgumentException">Thrown if <c>weightedChoices</c> is /// empty.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if any weight is /// negative, or if no weight is positive.</exception> internal static T weightedSample <T> ( System.Collections.Generic.IList <Tuple <T, double> > weightedChoices) { if (weightedChoices.Count == 0) { throw new ArgumentException( Localizer.Format("#autoLOC_CustomAsteroids_ErrorNoSample"), nameof(weightedChoices)); } double norm = 0.0; foreach (Tuple <T, double> choice in weightedChoices) { if (choice.Item2 < 0) { throw new ArgumentOutOfRangeException( nameof(weightedChoices), Localizer.Format("#autoLOC_CustomAsteroids_ErrorNegativeSample", choice.Item2, choice.Item1)); } norm += choice.Item2; } if (norm <= 0.0) { throw new ArgumentOutOfRangeException( nameof(weightedChoices), Localizer.Format("#autoLOC_CustomAsteroids_ErrorZeroSample")); } // important: no exceptions beyond this point // assert: r is in [0, norm] double threshold = norm * rng.NextDouble(); // If you stack up all the weights, at what level do you hit threshold? double level = 0.0; foreach (Tuple <T, double> choice in weightedChoices) { level += choice.Item2; if (level >= threshold) { return(choice.Item1); } } // Should only get here because of rounding error when threshold = norm return(weightedChoices [weightedChoices.Count - 1].Item1); }
/// <summary> /// Uses criteria similar to ScenarioDiscoverableObjects to decide whether to create an /// asteroid. /// </summary> protected override void checkSpawn() { if (areAsteroidsTrackable() && AsteroidManager.spawnRate() > 0.0 && countUntrackedAsteroids() < rng.Next(SPAWN_GROUP_MIN_LIMIT, SPAWN_GROUP_MAX_LIMIT)) { if (rng.NextDouble() < 1.0 / (1.0 + SPAWN_ODDS_AGAINST)) { spawnAsteroid(); } else { Debug.Log("[StockalikeSpawner]: " + Localizer.Format("#autoLOC_CustomAsteroids_LogSpawnStock", SPAWN_ODDS_AGAINST)); } } }