namespace ALttPRandomizer.Model { using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; public interface IRandomizableWeights { public bool IsEmpty { get; } public object? Roll(); } public class RandomizableWeights : IRandomizableWeights where T : struct, Enum { private static readonly Random random = new(); public ImmutableList<(T Item, int Weight)> Options { get; } public int TotalWeights { get; } public RandomizableWeights(IList<(T Item, int Weight)>? items = null) { if (items == null) { this.TotalWeights = 0; this.Options = ImmutableList<(T, int)>.Empty; return; } int totalWeight = 0; var builder = new List<(T, int)>(); foreach (var pair in items) { if (pair.Weight < 0) { throw new ArgumentException("Weights must be non-negative", $"items[{pair.Item}]"); } if (pair.Weight > 0) { builder.Add(pair); totalWeight += pair.Weight; } } this.TotalWeights = totalWeight; this.Options = builder.ToImmutableList(); } public static RandomizableWeights EmptyWeights => new(); public static RandomizableWeights EqualAll { get => RandomizableWeights.FromDictionary(Enum.GetValues().ToDictionary(v => v, v => 1)); } public static RandomizableWeights ConstantWeights(T value) { return new(new List<(T, int)> { (value, 100) }); } public static RandomizableWeights FromDictionary(IDictionary dict) { return new(dict.Select(kvp => (kvp.Key, kvp.Value)).ToList()); } public bool IsEmpty { get => this.Options.Count == 0; } public T? Roll() { if (this.TotalWeights <= 0) { return default; } int value = random.Next(this.TotalWeights); foreach (var (item, weight) in this.Options) { value -= weight; if (value < 0) { return item; } } // should never reach return default; } object? IRandomizableWeights.Roll() => Roll(); } }