靠运气的游戏需要一个随机性来源。Factor 的 random 词汇表提供了一个泛型单词 random,它会根据你交给它的东西自动调整,另外还有用于洗牌和抽样的辅助单词,以及一种让随机性在测试中变得可复现的办法。
inline
这个练习中的有些任务会定义一个单词,它接收一个引用(一个[ … ]块)作为实参并运行它。Factor 对此有一条硬性规定:如果一个单词用calls 调用它作为实参接收到的引用,就必须声明为inline,写法就是把inline紧跟在结尾的;后面。
inline会告诉编译器,把这个单词的函数体插入到每个调用点,而不是把它单独编译一次。
下面这个小小的单词会压入10,然后运行传给它的引用:
USING: kernel math prettyprint ;
: with-ten ( quot -- ) 10 swap call ; inline
它的栈效应( quot -- )没有声明任何输出,但用两个效果不同的引用调用它,却会产生不同的结果,而且都会回到调用方:
[ 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 中)重复执行一个引用:
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 中)则在装好该生成器的情况下运行一个引用:
<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
引用内部的一切,random、randomize、sample,都从这个已播种的生成器中取值,所以同一个种子总能重现同样的结果。
如果一个单词接收引用作为实参,并通过with-random运行它,就落在把单词标记为inline那条规则的范围内:with-random最终会calling 那个引用,所以这样的包装单词必须标记为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,接收一个种子和一个引用,用这个整数作为种子初始化生成器,然后运行该引用,这样同一个种子总能重现相同的结果。引用的结果应返回给调用者。
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