Home > Software engineering >  Weight based probability for Fortune Wheel in Unity
Weight based probability for Fortune Wheel in Unity

Time:06-13

Good morning to you all, It is my first post on Stack overflow so I apologies if it is not clear enough. Following some research and instruction I found online, I am trying to implement a probability based Fortune Wheel. I encountered a bug, every time I would lunch the code it would run the code twice therefore messing up the result. I then realised that it is giving me Index Out Of Range error. I am stump. Here is the code.

    [Serializable]
    public class WeightedValue
    {
        public int Value;
        public float Weight;

        public WeightedValue(int value, float weight)
        {
            Value = value;
            Weight = weight;
        }
    }
    public Text _text;
    public static int itemNumber;// sneding info to another script
    // just a struct to get both the index and value at the same time
    private struct RandomInfo
    {
        public readonly int Index;
        public int Value;
        public List<int> WeightedOptions;
        public readonly int AmountOfFullRotations;

        public RandomInfo(List<int> weightedOptions, int minRotations, int maxRotations)
        {
            WeightedOptions = weightedOptions;

            // get a random index
            Index = UnityEngine.Random.Range(0, WeightedOptions.Count);
            // get the actual according value
// this where the INDEX OUT OF BOUND ERROR is returned.

            Value = WeightedOptions[Index];

            AmountOfFullRotations = UnityEngine.Random.Range(minRotations, maxRotations);
        }
    }


    public List<WeightedValue> PricesWithWeights = new List<WeightedValue> { //               Value | Weight TODO: Make sure these sum up to 100
    new WeightedValue(1,        9),
    new WeightedValue(2,        9),
    new WeightedValue(3,        9),
    new WeightedValue(4,        9),
    new WeightedValue(5,        9),
    new WeightedValue(6,        1),
    new WeightedValue(7,        9),
    new WeightedValue(8,        9),
    new WeightedValue(9,        9),
    new WeightedValue(10,       9),
    new WeightedValue(11,       9),
    };


    // minimum full rotations
    // adjust in the Inspector
    public int MinRotations = 2;

    // maximum full rotations
    // adjust in the Inspector
    public int MaxRotations = 6;

    // seconds one complete rotation shall take
    // adjust in the Inspector
    public float SpinDuration = 5;

    // you can't assign this directly since you want it weighted
    private readonly List<int> _weightedList = new List<int>();

    public bool _spinning;
    private float _anglePerItem;

    private void Start()
    {

        _anglePerItem = 360f / PricesWithWeights.Count;

        _weightedList.Clear();

        // first fill the randomResults accordingly to the given wheights
        foreach (var kvp in PricesWithWeights)
        {
            // add kvp.Key to the list kvp.value times
            for (var i = 0; i < kvp.Weight; i  )
            {
                _weightedList.Add(kvp.Value);
            }
        }
    }

    private void OnEnable()

//it used to be on Update but to prevent it from being called twice i changed it to Enable. I got the out of range error afterward.

    {
        // spinning is less expensive to check so do it first
        if (!_spinning)
        {
            _spinning = true;
            StartCoroutine(SpinTheWheel());
        }
    }


    private IEnumerator SpinTheWheel(Action<int> onResult = null)
    {

        // this now has all information we need
        var randomInfo = new RandomInfo(_weightedList, MinRotations, MaxRotations);

        var itemNumberAngle = randomInfo.Value * _anglePerItem;
        var currentAngle = 0;
        // reset/clamp currentAngle to a value 0-360 since itemNumberAngle will be in this range
        while (currentAngle >= 360)
        {
            currentAngle -= 360;
        }
        while (currentAngle < 0)
        {
            currentAngle  = 360;
        }

        // Now we can compose the actual total target rotation
        // depends on your setup of course .. For my example below I will use it negative (rotation clockwise) like
        var targetAngle = itemNumberAngle   360f * randomInfo.AmountOfFullRotations;

        Debug.Log($"Will spin {randomInfo.AmountOfFullRotations} times before ending at {randomInfo.Value} with an angle of {itemNumberAngle}", this);
        // Debug.Log($"The odds for this were {PricesWithWeights[randomInfo.Index - 1].Weight / (float)PricesWithWeights.Sum(p => p.Weight):P} !");

        yield return SpinTheWheelS(currentAngle, targetAngle, randomInfo.AmountOfFullRotations * SpinDuration, randomInfo.Value, onResult);
    }


    private IEnumerator SpinTheWheelS(float fromAngle, float toAngle, float withinSeconds, int result, Action<int> onResult = null)
    {


        var passedTime = 0f;
        while (passedTime < withinSeconds)
        {
            // here you can use any mathematical curve for easing the animation
            // in this case Smoothstep uses a simple ease-in and ease-out
            // so the rotation starts slow, reaches a maximum in the middle and ends slow
            // you could also e.g. use SmoothDamp to start fast and only end slow
            // and you can stack them to amplify their effect
            var lerpFactor = Mathf.SmoothStep(0, 1, (Mathf.SmoothStep(0, 1, passedTime / withinSeconds)));

            transform.localEulerAngles = new Vector3(0.0f, 0.0f, Mathf.Lerp(fromAngle, toAngle, lerpFactor));
            passedTime  = Time.deltaTime;

            yield return null;
        }

        transform.eulerAngles = new Vector3(0.0f, 0.0f, toAngle);

        Debug.Log("Prize: "   result);

        // if provided invoke the given callback
        onResult?.Invoke(result);
        if (passedTime >= SpinDuration)
        {
            if (result == 0)
            {
                _text.text = "Congratulation on your new NFT!Thank you for playing!";
                itemNumber = 1;
                Debug.Log(itemNumber);
                Debug.Log(_text.text);
            }
            if (result == 2 || result == 7)
            {
                _text.text = "You deserve some of our best merchendizes! Thank you for playing!";
                itemNumber = 4;
                Debug.Log(itemNumber);
                Debug.Log(_text.text);
            }
            if (result == 1 || result == 3 || result == 4 || result == 6 || result == 8 || result == 9)
            {
                _text.text = "You earned a spot on our White List! Congratulations!Thank you for playing!";
                itemNumber = 3;
                Debug.Log(itemNumber);
                Debug.Log(_text.text);
            }
            if (result == 5 || result == 10)
            {
                _text.text = "You lost! Sorry. Try again later!";
                itemNumber = 2;
                Debug.Log(itemNumber);
                Debug.Log(_text.text);
            }
        }
    }
}

CodePudding user response:

After a lot more experimenting I got a solution to bypass but not solve the problems. just need to call the coroutine in Enable in Start instead and it will work.

CodePudding user response:

If you need a generalized weighted random generator, try this. It'll be easier than many "if" statements.

https://github.com/cdanek/KaimiraWeightedList

  • Related