using Games.Common.Randomness; namespace Basketball; /// /// Supports a chain of actions to be performed based on various probabilities. The original game code gets a new /// random number for each probability check. Evaluating a set of probabilities against a single random number is /// much simpler, but yield a very different outcome distribution. The purpose of this class is to simplify the code /// to for the original probabilistic branch decisions. /// internal struct Probably { private readonly float _defenseFactor; private readonly IRandom _random; private readonly bool? _result; internal Probably(float defenseFactor, IRandom random, bool? result = null) { _defenseFactor = defenseFactor; _random = random; _result = result; } public Probably Do(float probability, Action action) => ShouldResolveAction(probability) ? new Probably(_defenseFactor, _random, Resolve(action) ?? false) : this; public Probably Do(float probability, Func action) => ShouldResolveAction(probability) ? new Probably(_defenseFactor, _random, Resolve(action) ?? false) : this; public Probably Or(float probability, Action action) => Do(probability, action); public Probably Or(float probability, Func action) => Do(probability, action); public bool Or(Action action) => _result ?? Resolve(action) ?? false; private bool? Resolve(Action action) { action.Invoke(); return _result; } private bool? Resolve(Func action) => action.Invoke(); private readonly bool ShouldResolveAction(float probability) => _result is null && _random.NextFloat() <= probability * _defenseFactor; }