機率遊戲需要隨機性的來源。Factor 的random詞彙表提供一個泛用詞random,它會配合你交給它的任何東西,另外還有用於洗牌與取樣的輔助詞,以及讓隨機性能在測試中可重現的方法。
inline
這個練習有些任務會定義一個詞,它接受一個 quotation(一個 [ … ] 區塊)作為引數並執行它。Factor 對此有一條鐵律:一個會call它作為引數收到的 quotation 的詞,必須宣告為inline,寫法是把inline直接放在結尾的;之後。
inline會告訴編譯器,把該詞的本體拼接進每一個呼叫位置,而不是將它獨立編譯一次。
這裡有一個小詞,它會推入10,然後執行交給它的 quotation:
USING: kernel math prettyprint ;
: with-ten ( quot -- ) 10 swap call ; inline
它的堆疊效應( quot -- )並未指名任何輸出,然而用兩個不同效應的 quotation 呼叫它,會產生不同的結果,而且全都流回呼叫端:
[ 1 + ] with-ten . ! => 11 (quotation ( n -- n ): one value back)
[ dup 2 * ] with-ten . . ! => 20
! => 10 (quotation ( n -- n n ): two values back)
random:單一元素random(在random中)對它的引數是泛型的:
random ( obj -- elt )
n,它會回傳一個落在[0, n)範圍內的隨機整數:USING: random ;
6 random . ! 4 (a value from 0 to 5)
{ "rock" "paper" "scissors" } random . ! "paper"
要擲出一般的骰子,可以把範圍往上挪一位:對6取random會得到 0 到 5,所以6 random 1 +會得到 1 到 6。
要收集固定數量的隨機抽取結果,可以用replicate(在sequences中)重複執行一個 quotation:
replicate ( n quot -- seq )
USING: random sequences ;
3 [ 6 random 1 + ] replicate . ! { 4 1 6 } (three dice)
randomize:就地洗牌randomize(在random中)會回傳把元素以隨機順序排列的序列:
randomize ( seq -- randomized )
{ 1 2 3 4 5 } randomize . ! { 3 1 5 2 4 }
它是就地洗牌,並回傳同一個序列,所以如果你需要保留原本的順序,請先clone。
sample:多個相異元素sample(在random中)會從序列中抽取n個相異元素,就像發一手牌,絕不會拿到同一張牌兩次:
sample ( seq n -- seq' )
{ 10 20 30 40 50 } 3 sample . ! { 40 10 30 }
要求的元素數量超過序列所擁有的數量時,會產生錯誤。
預設情況下,random會從以系統時鐘作為種子的全域產生器抽取,所以每次執行都不同。若要用於測試,或任何你想要能重播的遊戲的時候,就用固定的種子綁定一個產生器。
<mersenne-twister>(在random.mersenne-twister中)會用整數種子建立一個產生器,而with-random(在random中)會安裝該產生器並執行一個 quotation:
<mersenne-twister> ( seed -- rnd )
with-random ( rnd quot -- )
USING: random random.mersenne-twister ;
42 <mersenne-twister> [ 6 random 1 + ] with-random .
! always the same value for seed 42
quotation 內的所有東西(random、randomize、sample)都會從設定好種子的產生器抽取,所以同樣的種子永遠會重現同樣的結果。
一個接受 quotation 作為引數、並透過with-random執行它的詞,屬於將詞標記為inline這條規則的範圍:with-random最後會call那個 quotation,所以這樣的包裝詞必須標記為inline。
你在海濱木板路的遊樂場裡經營各種機率遊戲。客人擲骰子、夾娃娃機夾取獎品,牌桌則需要洗牌、發牌,全都由隨機性驅動。打烊時,你還想要一個「重播」功能,好讓有爭議的遊戲能從同一個種子重新跑一次。
定義roll-die,讓它接受骰子的面數,並回傳1到sides(含)範圍內的點數。
6 roll-die .
! => 4 (some value from 1..6)
定義pick-prize,讓它接受一串獎品,並隨機回傳其中一個。
{ "teddy" "goldfish" "keyring" } pick-prize .
! => "goldfish"
定義shuffle-deck,讓它接受一副牌(一個序列),並回傳牌被隨機排列後的結果。
{ 1 2 3 4 5 } shuffle-deck .
! => { 3 1 5 2 4 }
定義deal-hand,讓它接受一副牌和一個數量n,並從牌組中回傳n張互不相同的牌。
{ 10 20 30 40 50 } 3 deal-hand .
! => { 40 10 30 }
定義play-seeded,讓它接受一個種子和一個 quotation,並用一個以該整數為種子的產生器來執行這個 quotation,如此一來同一個種子永遠會重現相同的結果。quotation 的結果應該回傳給呼叫方。
42 [ 6 roll-die ] play-seeded .
! => the same value every time for seed 42
! Same seed, same game:
99 [ 5 deal-hand ] play-seeded 99 [ 5 deal-hand ] play-seeded = .
! => t