經過好幾週的期待,你和朋友們終於要一起玩你們的第一場 Dungeons & Dragons(D&D)。 既然這是遊戲的第一場,每位玩家都必須建立一個角色來玩。 角色的能力取決於擲六面骰的結果,但骰子到底在哪裡? 你震驚地發現,朋友們正等著你把骰子生出來,畢竟提議要玩 D&D 的人就是你! 你慌了起來,這才想起自己忘了帶骰子,這麼一來 D&D 就玩不成了。 好在你有些基本的程式設計能力,很快就想出解決辦法:寫一支程式來模擬擲骰子。
在 Dungeons & Dragons 這款遊戲中,每位玩家一開始都要產生一個可以扮演的角色。 這個角色除了其他設定之外,還有 6 項能力:力量、敏捷、體質、智力、感知和魅力。 這 6 項能力的數值都是隨機決定的。 做法是擲 4 顆 6 面骰,然後記下其中最大的 3 顆骰子的點數總和。 你要這樣做 6 次,每項能力各一次。
角色的初始生命值是 10 加上角色的體質調整值。 把角色的體質減去 10,再除以 2 並無條件捨去,就能得到角色的體質調整值。
請寫一個隨機角色產生器,並遵循上述規則。
舉例來說,6 次擲 4 顆骰子的結果可能像這樣:
因為體質是 3,所以體質調整值是 -4,生命值則是 6。
大多數程式語言都有(偽)隨機產生器,但很少有程式語言是為了擲骰子而設計的。 其中一種這樣的語言是 Troll。
在函式語言中,大多數的函式都是_純_的。 這表示對同一組引數,它們總是回傳相同的輸出,不會做其他任何事情。 換句話說,純函式是確定性的。
不過,就定義上來說,隨機值是無法預測、也不具確定性的。 即使是偽隨機,也就是回傳一串_看起來_隨機的數字,在純函式的情境下也沒那麼容易達成。 這是因為偽隨機數產生器(PRNG)需要追蹤內部狀態,才能回傳序列中的下一個數字。
Lean 提供兩種可能的做法來解決這個問題:
使用一種 monad,讓你能改變 PRNG 的內部狀態。
有一個現成的函式 IO.rand,正好可以做到這件事。
使用純函式,把產生器當作引數傳入,回傳的不只是產生的偽隨機值,還有一個已更新、為序列中下一個數字「做好準備」的產生器。
這個練習採用第二種做法。 每個應該產生偽隨機值的函式都會收到一個產生器,而這個函式必須同時回傳該值和一個更新後的產生器。
請注意,給定的產生器是確定性的,也就是說,它_總是_產生相同的值。 若要產生序列中的下一個偽隨機值,就必須使用更新後的產生器。
在這個練習中,隨機性是用卡方檢定來檢查的,顯著水準為p < 0.0001。
這表示如果依照指令產生能力值,正確的實作因為隨機機率而無法通過測試的機率不到 0.01%。
請注意,根據指令,能力值是_四次公平的 d6(六面骰)擲骰中,最大三個結果的總和_。