抽獎算法-指定機率的隨機

 

抽獎模型

普通機率模型

普通機率模型是最經常使用的一種模型,可是在遊戲運營過程當中的確發現不少小白玩家不能正確理解——他們認爲中獎率 10% 的設定等同於抽 10 次確定會中一次。這顯然是錯誤的,普通機率模型的中獎抽獎次數是基於正態分佈的,並且每次抽獎的事件是獨立的,並不會由於你前面抽了 9 次沒中獎,第十次就必定能中獎。html

雖然在大量的統計中,兩次中獎的平均間隔是 10 次,可是還有一個有趣的數據是連續 10 次都沒中獎的機率約爲 (1-10%)^10 ~= 34.8% 可不小呢。c++

此外「標準差」是一個頗有意思的數據,通過模擬統計,10% 中獎率獲得的標準差爲 9.62 ——也就是說絕大分部人通過 10 ± 9.62 次抽獎即能中獎,運氣再背抽 20 次也差很少能獲得獎勵了。算法

這種機率模型能很是準確地實現策劃的需求,可是會惹來一些小白玩家的差評——爲何你說中獎率是 10% 可是我抽了 20 次尚未中獎!而後給你打個一星。因此不少遊戲運營商爲了顧及玩家的體驗,會對普通機率模型進行修訂,增設一些保底抽獎次數,例如每第 10 次固定中獎(10,20,30...)segmentfault

對於這種作法,我暫不於評價。可是讓咱們看看若是硬生生地加入固定中獎的設定,會給數值帶來什麼變化吧。數組

固定中獎模型

每次抽獎中獎率依舊爲 10% ,但每第十次抽獎必中。dom

這時候玩家獲得的抽獎體驗是:10 次抽獎確定能中獎,並且不止中一次,爽暴了是否是。實際指望高達 19% 這遠遠超出策劃 10% 的預期。因此策劃琢磨着不能便宜了玩家,只能把中獎率調低。可是這會致使中獎集中在每 10 次附近,抽獎的樂趣幾近喪失。優化

這樣看來,固定中獎模型是否真的無藥可救?其實仍是有能夠優化的地方。ui

計數器模型

每次抽獎中獎率依舊爲 10% ,若連續 9 次未中獎,下一次抽獎必中獎。code

這個需求看起來和上面好像沒什麼不一樣,可是保底的條件再也不是每第 10 次,而是發生在每連續 9 次未中獎後。也就是說計數器會在每次中獎後清 0 重計。htm

 

隨機步長累加模型

也是一種保底中獎模型,只不過去掉了獨立隨機事件,並把計數增加改成隨機量,最終在累計超過閾值時得獎。這種模型若是有個較大的閾值和較小的步長下限,還能夠起到讓玩家在頭幾回抽獎必然不中(大)獎的效果。另外在這種模型下,計數器甚至能夠對玩家可見,讓看玩家看到進度和目標,感覺到獎勵是可達的、近在眼前的。

 

 

 

o9vrr

 

抽獎算法

/// <summary>
/// 抽獎
/// </summary>
public class Prize
{
    /// <summary>
    /// 獎品關鍵字
    /// </summary>
    public string Key { get; set; }

    /// <summary>
    /// 權重/數量
    /// </summary>
    public int Poll { get; set; }


    /// <summary>
    /// 中獎區間
    /// </summary>
    class Area
    {
        /// <summary>
        /// 獎品關鍵字
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// 開始索引位置
        /// </summary>
        public int Start { get; set; }

        /// <summary>
        /// 截止索引位置
        /// </summary>
        public int Over { get; set; }
    }

    /// <summary>
    /// 隨機種子
    /// </summary>
    static Random Rand = new Random((int)DateTime.Now.Ticks);


    /// <summary>
    /// 輪盤抽獎,權重值(在輪盤中佔的面積大小)爲中獎概率
    /// </summary>
    /// <param name="prizeList">禮品列表(若是不是百分百中獎則輪空須要加入到列表裏面)</param>
    /// <returns></returns>
    public static string Roulette(List<Prize> prizeList)
    {
        if (prizeList == null || prizeList.Count == 0) return string.Empty;
        if (prizeList.Any(x => x.Poll < 1)) throw new ArgumentOutOfRangeException("poll權重值不能小於1");
        if (prizeList.Count == 1) return prizeList[0].Key; //只有一種禮品

        Int32 total = prizeList.Sum(x => x.Poll); //權重和               
        if (total > 1000) throw new ArgumentOutOfRangeException("poll權重和不能大於1000"); //數組存儲空間的限制。最多一千種獎品(及每種獎品的權重值都是1)

        List<int> speed = new List<int>(); //隨機種子
        for (int i = 0; i < total; i++) speed.Add(i);

        int pos = 0;
        Dictionary<int, string> box = new Dictionary<int, string>();
        foreach (Prize p in prizeList)
        {
            for (int c = 0; c < p.Poll; c++) //權重越大所佔的面積份數就越多
            {
                pos = Prize.Rand.Next(speed.Count); //取隨機種子座標
                box[speed[pos]] = p.Key; //亂序 禮品放入索引是speed[pos]的箱子裏面
                speed.RemoveAt(pos); //移除已抽取的箱子索引號
            }
        }
        return box[Prize.Rand.Next(total)];
    }

    /// <summary>
    /// 獎盒抽獎,每一個參與者對應一個獎盒,多少人蔘與就有多少獎盒
    /// </summary>
    /// <param name="prizeList">禮品列表</param>
    /// <param name="peopleCount">參與人數</param>
    /// <returns></returns>
    public static string LunkyBox(List<Prize> prizeList, int peopleCount)
    {
        if (prizeList == null || prizeList.Count == 0) return string.Empty;
        if (prizeList.Any(x => x.Poll < 1)) throw new ArgumentOutOfRangeException("poll禮品數量不能小於1個");
        if (peopleCount < 1) throw new ArgumentOutOfRangeException("參數人數不能小於1人");
        if (prizeList.Count == 1 && peopleCount <= prizeList[0].Poll) return prizeList[0].Key; //只有一種禮品且禮品數量大於等於參與人數

        int pos = 0;
        List<Area> box = new List<Area>();
        foreach (Prize p in prizeList)
        {
            box.Add(new Area() { Key = p.Key, Start = pos, Over = pos + p.Poll }); //把禮品放入獎盒區間
            pos = pos + p.Poll;
        }

        int total = prizeList.Sum(x => x.Poll); //禮品總數
        int speed = Math.Max(total, peopleCount); //取禮品總數和參數總人數中的最大值

        pos = Prize.Rand.Next(speed);
        Area a = box.FirstOrDefault(x => pos >= x.Start && pos < x.Over); //查找索引在獎盒中對應禮品的位置

        return a == null ? string.Empty : a.Key;
    }

}


/*
List<Prize> prizes = new List<Prize>();
prizes.Add(new Prize() { Key = "電腦", Poll = 1 });
prizes.Add(new Prize() { Key = "機櫃", Poll = 2 });
prizes.Add(new Prize() { Key = "鼠標", Poll = 3 });
                        

string lp1 = Prize.LunkyBox(prizes, 6);
Console.WriteLine(lp1);


prizes.Add(new Prize() { Key = "謝謝惠顧", Poll = 5 });
string lp2 = Prize.Roulette(prizes);      
Console.WriteLine(lp2);
*/

 

 

參考

Return random `list` item by its `weight`

不一樣機率模型的抽獎體驗

抽獎算法

一個簡單抽獎算法的實現以及如何預防超中

相關文章
相關標籤/搜索