Skip to main content

「偽隨機」和「真隨機」的應用

在許多應用,尤其是遊戲相關的應用,我們都會需要使用到隨機來實做一些功能,例如:掉寶,開箱、抽卡、洗牌等等。

在程式中實現隨機,又可以分為「真隨機」及「偽隨機」。

在討論「真隨機」及「偽隨機」之前,我們先了解我們程式中常用到的虛擬亂數產生器PRNGs)。

虛擬亂數產生器 是一個可以產生虛擬亂數序列的算法,但 PRNG 生成的亂數序列並不是「真隨機」,因此它完全由一個初始值決定,這個初始值被稱為 PRNG 的隨機種子(seed,但這個種子可能包含真隨機數)。

Demo

// example of 1 seed always generate same result
console.log(randomNumber(5)); // 0.8975141500470741
console.log(randomNumber(5)); // 0.8975141500470741
console.log(randomNumber(5)); // 0.8975141500470741

PRNG 雖然並不是「真隨機」,並且我們有其他的方式可以產生「真隨機」序列,但 PRNG 的生成速度迅速,並且可重現,因此再實踐中我們更常使用到 PRNG。

程式原理上的真偽隨機

真隨機

「真隨機」又名「純隨機」就是我們平常一直說的那種、一般意義上的「隨機」。在「真隨機」中,每一個事件都是相互獨立、服從「真隨機」分布的,不受其他事件的發生而改變。 但在程式裡,不存在「不確定」的數字,只有確定的「1」和「0」,因此程式不能自己生成「隨機」的東西。 在程式原理上,「真隨機」的定義是指透過外部的硬體設備,來進行監測或觀測某個真正隨機的事物的狀態,來取得一個隨機數。

例如,random.org 就提供線上的隨機數產生服務,它是透過設備監測大氣中的噪音來得到隨機數。

所以,採用「真隨機」對於程式來說,成本極高效率極低,因此在實踐中,沒有人會買設備去做「真隨機」。

偽隨機

「偽隨機」是在不確定性的隨機事件當中,通過一系列算法來讓隨機事件分布的更均勻,儘可能減少或消除極端情況的發生。 我們可以理解「偽隨機」就是透過人為創造出來的機制,來讓隨機事件分佈均勻的同時,也還是保留一定的隨機性。

為什麼會需要「偽隨機」呢?

比方說某款遊戲為了吸引用戶,擁有這麼一個隨機抽卡系統:每次抽卡時,都有 1%的機率抽出 SSR 卡片,並且這個概率服從「真隨機」分布。

一般用戶會認為,連抽 100 次一定能抽到一張,但實際上,連抽 100 次卻抽不出 1%的 SSR 卡的機率是為(1-0.01)^100=36.6%,甚至還超過了 1/3。將連抽數字上升至 300,也仍有 4.9%的機率。

假設有 10000 個玩家連抽 100 次,就有約 3660 個玩家抽不出這張 SSR;10000 個玩家連抽 300 次,也仍有約 490 個玩家抽不出這張 SSR。

這對玩家的遊戲體驗來說可以說是毀滅性的打擊,儘管「真隨機」就是如此操作的,但玩家在抽卡的時候可不會想那麼多,因此會需要「偽隨機」來讓抽獎這類的遊戲體驗更佳。

常見的偽隨機算法及應用

GitHub 範例

偽隨機分佈 (Pseudo-Random Distribution)

  1. 輪盤抽獎 手游裡面常見的可以累計幸運值的輪盤,得獎率初始為 N%,每抽一次得獎率翻倍直到抽中為止,抽中後得獎率重置。

  2. 暴擊率 像 Dota / LoL 這類競技類遊戲中,連續數次的「走運」會大大的影響遊戲的競技性和觀賞性。 因此這類競技類遊戲中,會有很多「偽隨機」機制來避免極端的狀況發生 例如:遊戲中的暴擊率 20% 並不是每一刀都是 20% 暴擊率,而是以 5.57%作為初始暴率,如果第一刀不暴,則第二刀的暴率增加到初始值的 2 倍:11.14%,如果還是不暴,就繼續增加到初始值的 3 倍:16.71%,以此類推。至到打出暴擊後,暴擊率重置為 5.57%。

保底機制 (Pity Timer)

  1. 抽卡 手游裡面常見的抽 N 次必得 SSR,為抽卡事件加保底次數,每次抽卡皆為「真隨機」,直到事件連續不發生的次數高於保底次數,讓事件強制發生。

洗牌算法

  1. 撲克牌

  2. 音樂播放器 歌單有 20 首歌,就建立一個 1 到 20 的數組,再把這 20 個數字像洗牌一樣洗成亂序。在洗完之後,如果第一個數字是 n,第一次就播放歌單裡的第 n 首歌。以此類推。 早期的音樂播放器的隨機播放就是採取「真隨機」,但就發現經常會連續播放到同一首歌或者在幾首歌之間來回切換,某一些歌就是永遠播放不到。

  3. 限額抽獎 假設一個抽獎活動只有 1000 個名額,並且獎品項目都已確定,可以先將所有獎品項目都產生進一個陣列,用戶依次從該陣列中抽取獎品,也就結果一開始就已經確定了,但對用戶來說是隨機的。

參考

  1. GitHub 範例