從Dota2的偽隨機(jī)談開(kāi)

起因是在知乎上看到木七七工作室轉(zhuǎn)發(fā)的談隨機(jī)處理的一個(gè)內(nèi)部視頻谭溉,視頻里面聊到Dota2的技能概率處理方式社搅,比如大魚(yú)人(Sorry好久沒(méi)玩Dota了已經(jīng)想不起來(lái)這位的真名)的被動(dòng)技能升滿(mǎn)后:

  • 每次攻擊有25%的幾率讓敵人眩暈号坡。

一般做法用純隨機(jī) True random distribution的方式买羞,每次攻擊時(shí)計(jì)算概率爷光,獨(dú)立判斷是否觸發(fā)眩暈。不過(guò)可能會(huì)出現(xiàn)一些體驗(yàn)問(wèn)題:

  1. 因?yàn)槊看为?dú)立計(jì)算概率席怪,極限情況會(huì)導(dǎo)致一直觸發(fā)眩暈和一直不觸發(fā)应闯,間接造成歐皇和非洲酋長(zhǎng)之間的戰(zhàn)爭(zhēng)。
  2. 從玩家體驗(yàn)上來(lái)說(shuō)挂捻,感官上25%的幾率碉纺,超過(guò)5,6次不觸發(fā)刻撒,他就會(huì)開(kāi)始懷疑幾率是否被策劃運(yùn)營(yíng)篡改骨田,幕后是否有骯臟的PY交易,而不是回想下初中的數(shù)學(xué)課疫赎。

純隨機(jī)在數(shù)學(xué)上是無(wú)罪的,機(jī)器底層的隨機(jī)函數(shù)是清白的(其實(shí)也不是那么清白碎节,畢竟純隨機(jī)是不存在的捧搞,不過(guò)這個(gè)就扯深了,先默認(rèn)一般的random接口函數(shù)就是純隨機(jī))狮荔,但是有些時(shí)候并不是最佳解決方案胎撇。

用偽隨機(jī)分布Pseudo-random distribution處理概率

Dota2的偽隨機(jī)分布采用概率補(bǔ)償?shù)姆绞剑看斡|發(fā)概率從一個(gè)值開(kāi)始遞增殖氏,第N次的觸發(fā)概率P(N) = C * N晚树,比如25%的幾率,C值大概為8.5%雅采,運(yùn)算流程如下:

  1. 第一次觸發(fā)眩暈概率為8.5%
  2. 第二次為17%爵憎,以此類(lèi)推遞增
  3. 如果觸發(fā)眩暈成功,則概率重新從8.5%開(kāi)始遞增計(jì)算婚瓜。

這種方式使得連續(xù)觸發(fā)或連續(xù)不觸發(fā)的幾率降低宝鼓,避免了運(yùn)氣成分過(guò)于影響戰(zhàn)斗結(jié)果(特別是競(jìng)技游戲)。

一般幾率對(duì)應(yīng)的C值可以參考下面這張圖巴刻。P(T)代表預(yù)期值愚铡,就是游戲中顯示的幾率值。P(A)是用了偽隨機(jī)后的實(shí)際概率胡陪。MaxN表示最壞情況下觸發(fā)概率的次數(shù)沥寥。

rpic1.png

計(jì)算C值的方式和程序?qū)崿F(xiàn)可以參考這個(gè)鏈接下的回答,有C#的實(shí)現(xiàn)代碼:

//CfromP是主函數(shù)柠座,傳入理論概率P就可以求得遞增的C值
public decimal CfromP( decimal p )
{
    decimal Cupper = p;
    decimal Clower = 0m;
    decimal Cmid;
    decimal p1;
    decimal p2 = 1m;
    while(true)
    {
        Cmid = ( Cupper + Clower ) / 2m;
        p1 = PfromC( Cmid );
        if ( Math.Abs( p1 - p2 ) <= 0m ) break;

        if ( p1 > p )
        {
            Cupper = Cmid;
        }
        else
        {
            Clower = Cmid;
        }

        p2 = p1;
    }

    return Cmid;
}

private decimal PfromC( decimal C )
{
    decimal pProcOnN = 0m;
    decimal pProcByN = 0m;
    decimal sumNpProcOnN = 0m;

    int maxFails = (int)Math.Ceiling( 1m / C );
    for (int N = 1; N <= maxFails; ++N)
    {
        pProcOnN = Math.Min( 1m, N * C ) * (1m - pProcByN);
        pProcByN += pProcOnN;
        sumNpProcOnN += N * pProcOnN;
    }

    return ( 1m / sumNpProcOnN );
}

上面的偽隨機(jī)分布算是用概率補(bǔ)償?shù)姆绞娇刂聘怕蕘?lái)改善玩家的體驗(yàn)邑雅,詳細(xì)的可以參考Dota2的Wiki(打Dota2,向冰蛙學(xué)數(shù)學(xué))妈经。

當(dāng)然也有其他方式控制隨機(jī)數(shù)和概率蒂阱,正好前一陣子看了一個(gè)從D&D擲骰角度談控制隨機(jī)分布的文章锻全,下面也算一個(gè)翻譯和整理。

我這把可是1d2有毒的飛刀

D&D里面NdS表示投擲S面的骰子N次录煤,累加結(jié)果鳄厌。比如1d12表示投擲一個(gè)12面骰子一次,3d4表示投擲一個(gè)4面骰子3次妈踊。

假設(shè)我們要獲取[0,24]之間的隨機(jī)值了嚎,可以先設(shè)置一個(gè)函數(shù)rollDice(N, S)來(lái)模擬骰子投擲:

public static int rollDice(int N, int S) {
    int value = 0;
    for (int i = 0; i < N; i++) {
        //每次隨機(jī)結(jié)果為[0, S]
        value += Random.Range(0, S + 1);
    }

    return value;
}

我們可以rollDice(1,24),也可以拆分成2次廊营,變成rollDice(2,12)歪泳,變成兩次[0,12]的和,以此類(lèi)推rollDice(3,8)露筒、rollDice(4,6)呐伞,下面這張圖可以看到最終結(jié)果的分布變化:

rpic5.jpg

可以看到投擲的次數(shù)越多,最終結(jié)果分布就越集中在[0,24]的平均值附近慎式,所以4d6的武器比3d8的武器輸出更平穩(wěn)伶氢,但3d8的武器造成高傷害的幾率也更高。

除了控制隨機(jī)取值的集中區(qū)域瘪吏,我們還可以用簡(jiǎn)單的方式控制隨機(jī)取值是大部分分散在平均值以下還是大部分分散在平均值以上癣防。

兩次隨機(jī)取較大/較小值

還是以取[0,24]之間隨機(jī)值為例,每次rollDice(2,12)兩次掌眠,取較大值:

int roll1 = rollDice(2, 12);
int roll2 = rollDice(2, 12);
int result = Math.Max(roll1,  roll2);

分布圖如下:

rpic2.png

反過(guò)來(lái)蕾盯,取較小值,可以獲得集中在平均值以下的分布:

int roll1 = rollDice(2, 12)
int roll2 = rollDice(2, 12)
int result = Math.Min(roll1, roll2);
rpic4.png

取較小值在計(jì)算傷害值比較常見(jiàn)蓝丙,比如一個(gè)角色的攻擊力在20到40之間级遭,利用這種方法可以使得最后結(jié)果集中在較低的范圍,高傷害出現(xiàn)的幾率較低渺尘。

三次隨機(jī)取較大的兩個(gè)值

rollDice(1,12)三次装畅,取較大的兩個(gè)值:

int roll1 = rollDice(1, 12);
int roll2 = rollDice(1, 12);
int roll3 = rollDice(1, 12);

int result = roll1 + roll2 + roll3;
result = result - Math.Min(roll1, roll2, roll3);

分布圖如下:

rpic3.png

可以看出比兩次取較大/較小值分布更為平滑。

總結(jié)一下沧烈,可以看到在控制某個(gè)范圍內(nèi)隨機(jī)數(shù)時(shí)掠兄,可以從下面幾個(gè)角度進(jìn)行自定義以滿(mǎn)足需求:

  1. 范圍。確定隨機(jī)范圍的最大值和最小值锌雀,如果需要可以做一些偏移蚂夕,比如[20, 30]可以分解為20 + rollDice(1, 10)。
  2. 方差腋逆。將一次隨機(jī)分解為多次隨機(jī)婿牍,可以使結(jié)果更靠近中間值。相反惩歉,次數(shù)越少等脂,結(jié)果分布范圍越廣俏蛮。
  3. 不對(duì)稱(chēng)性∩弦#可以通過(guò)上面介紹的兩種方法搏屑,使隨機(jī)結(jié)果更多分布在平均值之前或者之后。

自定義概率分布

很多情況下粉楚,策劃過(guò)來(lái)找你的時(shí)候辣恋,情景有可能是:我這里有10種掉落物品,每種的掉率我都想單獨(dú)配置模软,比如A掉率10%伟骨,B掉率20%等等和一個(gè)Excel文件。

最終的配置文件可能是像這樣一個(gè)數(shù)組燃异,前面是掉率(以100算100%)携狭,后面跟著物品ID。

local dropRate = {
    {10, 100001},
    {20, 100002},
    {30, 100003},
    {40, 100004},
}

掉率的總和不一定正好是100回俐,畢竟要考慮些對(duì)配置文件的容錯(cuò)性逛腿,所以先算出概率和sumRate,取random(sumRate)的值value鲫剿,依次遍歷dropDate表鳄逾,累加概率和weight稻轨,如果value小于等于weight灵莲,則算是落在當(dāng)前區(qū)間,返回對(duì)應(yīng)的物品ID殴俱。我用lua寫(xiě)了一段測(cè)試代碼政冻,畢竟lua的table實(shí)在是太方便了。

local dropRate = {
    {10, 100001},
    {20, 100002},
    {30, 100003},
    {40, 100004},
}

local distribute = {
    [100001] = 0,
    [100002] = 0,
    [100003] = 0,
    [100004] = 0,
}

local checkRate = function(t, value)
    local weight = 0
    for i=1,#t do
        weight = weight + t[i][1]
        if value <= weight then
            return t[i][2]
        end
    end

    return nil
end

local getDropItem = function(t)
    local weightTotal = 0
    for k,v in pairs(t) do
        weightTotal = weightTotal + v[1]
    end

    local value = math.random(weightTotal)

    return checkRate(t, value)
end

local main = function()
    --用倒序時(shí)間設(shè)置random的seed线欲,確保seed隨時(shí)間顯著變化
    math.randomseed(tostring(os.time()):reverse():sub(1, 6))

    for i=1,10000 do
        local id = getDropItem(dropRate)
        if id and distribute[id] then
            distribute[id] = distribute[id] + 1
        end
    end

    for index,dis in pairs(distribute) do
        print("index:",index)
        print("dis:",dis)
        print("percent:",dis / 10000)
        print("=================")
    end
end

main()

測(cè)試結(jié)果和配置概率很接近明场,這樣就可以讓策劃盡情發(fā)揮他的奇怪掉率了。

總結(jié)

上面的部分只是最近看到的一些有意思的隨機(jī)數(shù)討論整理李丰,真正在實(shí)際項(xiàng)目中苦锨,隨機(jī)數(shù)的處理是跟隨不同的需求做變化的,隨機(jī)可以增加游戲過(guò)程的樂(lè)趣趴泌,可以給游戲增加賣(mài)點(diǎn)舟舒,也可以變成各種“坑”,對(duì)于開(kāi)發(fā)來(lái)說(shuō)嗜憔,只要這個(gè)坑是可控制的秃励,不要坑到自己就行了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吉捶,隨后出現(xiàn)的幾起案子夺鲜,更是在濱河造成了極大的恐慌皆尔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件币励,死亡現(xiàn)場(chǎng)離奇詭異慷蠕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)榄审,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)砌们,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人搁进,你說(shuō)我怎么就攤上這事抵怎》暇常” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)曾撤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)果录,這世上最難降的妖魔是什么咽袜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮盅视,結(jié)果婚禮上捐名,老公的妹妹穿的比我還像新娘。我一直安慰自己闹击,他們只是感情好镶蹋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赏半,像睡著了一般贺归。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上断箫,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天拂酣,我揣著相機(jī)與錄音,去河邊找鬼仲义。 笑死婶熬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埃撵。 我是一名探鬼主播赵颅,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盯另!你這毒婦竟也來(lái)了性含?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸳惯,失蹤者是張志新(化名)和其女友劉穎商蕴,沒(méi)想到半個(gè)月后叠萍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绪商,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年苛谷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片格郁。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腹殿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出例书,到底是詐尸還是另有隱情锣尉,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布决采,位于F島的核電站自沧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏树瞭。R本人自食惡果不足惜拇厢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晒喷。 院中可真熱鬧孝偎,春花似錦、人聲如沸凉敲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荡陷。三九已至雨效,卻和暖如春迅涮,著一層夾襖步出監(jiān)牢的瞬間废赞,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工叮姑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唉地,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓传透,卻偏偏與公主長(zhǎng)得像耘沼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子朱盐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容