轨道
/
Factor
Factor
/
练习
/
木板路游戏
木板路游戏

木板路游戏

学习练习

简介

靠运气的游戏需要一个随机性来源。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。

说明

你在海滨木板路游乐场经营着各种赌运气的游戏。玩家们掷骰子,抓娃娃机抓取奖品,牌桌则需要洗牌、发牌,这一切都靠随机性。到了收摊的时候,你还想要一个“重放”功能,让有争议的一局能用同一个种子再跑一遍。

1. 掷骰子

定义roll-die,接收骰子的面数,返回一个介于1到sides(含)之间的点数。

6 roll-die .
! => 4   (some value from 1..6)

2. 抓取奖品

定义pick-prize,接收一个奖品序列,随机返回其中的一个。

{ "teddy" "goldfish" "keyring" } pick-prize .
! => "goldfish"

3. 洗牌

定义shuffle-deck,接收一副牌(一个序列),返回牌序被打乱的这副牌。

{ 1 2 3 4 5 } shuffle-deck .
! => { 3 1 5 2 4 }

4. 发牌

定义deal-hand,接收一副牌和一个数量n,返回这副牌中n张不同的牌。

{ 10 20 30 40 50 } 3 deal-hand .
! => { 40 10 30 }

5. 用种子重放

定义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
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Factor Exercism

准备好开始 木板路游戏 了吗?

注册 Exercism,借助 47 个概念163 个练习 和真人导师指导,学习并掌握 Factor,全部免费。