榆树 – 相互依赖的信号

不好问题:

有没有办法在Elm中定义一对相互依赖的信号?

前言:

我正在尝试编写一个小型的Cookie-clicker风格的浏览器游戏,其中玩家正在收集资源,然后花费他们购买自动资源收集结构,这些结构在购买时会变得更加昂贵.这意味着三个相关信号:聚集(玩家收集了多少资源),花费(玩家已经花费了多少资源)和成本(升级成本多少).

这是一个实现:

module Test where

import Mouse
import Time

port gather : Signal Bool
port build : Signal String

costIncrement = constant 50
cost = foldp (+) 0 <| keepWhen canAfford 0 <| sampleOn build costIncrement
nextCost = lift2 (+) cost costIncrement

spent = foldp (+) 0 <| merges [ sampleOn build cost ]

gathered = foldp (+) 0 <| merges [ sampleOn gather <| constant 1,sampleOn tick tickIncrement ]

balance = lift round <| lift2 (-) gathered spent

canAfford = lift2 (>) balance <| lift round nextCost

tickIncrement = foldp (+) 0 <| sampleOn cost <| constant 0.01
tick = sampleOn (every Time.millisecond) <| constant True

main = lift (flow down) <| combine [ lift asText balance,lift asText canAfford,lift asText spent,lift asText gathered,lift asText nextCost ]

这编译得很好,但是当我将它嵌入一个HTML文件中时,连接了相应的按钮以将消息发送到上面的相应端口,我得到了错误

s2 is undefined
    Open the developer console for more details.

问题似乎是书面的,成本取决于canAfford,这取决于平衡,这取决于花费,这取决于成本.

如果我修改成本线那么

...
cost = foldp (+) 0 <| sampleOn build costIncrement
...

它开始按预期工作(除了玩家被允许花费在负资源上,这是我想要避免的).

有任何想法吗?

回答你的问题

不,Elm中没有通用的方法来定义相互递归的信号.
问题在于Elm中的信号必须始终具有值的约束.如果成本的定义需要canAfford但canAfford是根据成本来定义的,那么问题就在于从哪里开始解决信号的初始值.当您考虑相互递归信号时,这是一个难以解决的问题.

相互递归信号与过去的信号值有关. foldp构造允许您指定相对于一个点的相互递归信号的等价物.通过将foldp的显式参数作为初始值来解决初始值问题的解决方案.但约束是foldp只接受纯函数.

这个问题很难以不需要任何先验知识的方式清楚地解释.所以这是另一种解释,基于我对你的代码做的图表.

花点时间找到代码和图表之间的联系(请注意,我遗漏了主要内容以简化图表). foldp是一个带有环回的节点,sampleOn有一个闪电等等(我在常量信号上重写了sampleOn).有问题的部分是红线上升,使用canAfford定义成本.
如您所见,基本foldp具有带基值的简单循环.实现这个比像你这样的任意循环更容易.

我希望你现在明白这个问题.限制在榆树,这不是你的错.
我正在解决Elm中的这个限制,尽管这需要一些时间.

解决您的问题

虽然命名信号并与之合作可能会很好,但在Elm中实现游戏时,通常有助于使用different programming style.链接文章中的想法归结为将代码拆分为:

>输入:您的案例中的鼠标,时间和端口.
>模型:游戏的状态,在你的情况下成本,平衡,可以支付,花费,收集等.
>更新:游戏的更新功能,你可以用较小的更新功能组成这些.这些应该是尽可能纯粹的功能.
>查看:查看模型的代码.

通过使用main = view< ~follp update modelStartValues输入之类的东西将它们组合在一起. 特别是,我会写它像:

import Mouse
import Time

-- Constants
costInc      = 50
tickIncStep  = 0.01
gatherAmount = 1

-- Inputs
port gather : Signal Bool
port build : Signal String

tick = (always True) <~ (every Time.millisecond)

data Input = Build String | Gather Bool | Tick Bool

inputs = merges [ Build  <~ build,Gather <~ gather,Tick   <~ tick
                ]

-- Model

type GameState = { cost          : Float,spent         : Float,gathered      : Float,tickIncrement : Float
                 }

gameState = GameState 0 0 0 0

-- Update

balance {gathered,spent} = round (gathered - spent)
nextCost {cost} = cost + costInc
canAfford gameSt = balance gameSt > round (nextCost gameSt)

newCost input gameSt =
  case input of
    Build _ -> 
      if canAfford gameSt
        then gameSt.cost + costInc
        else gameSt.cost
    _ -> gameSt.cost

newSpent input {spent,cost} = 
  case input of
    Build _ -> spent + cost
    _ -> spent

newGathered input {gathered,tickIncrement} = 
  case input of
    Gather _ -> gathered + gatherAmount
    Tick   _ -> gathered + tickIncrement
    _ -> gathered

newTickIncrement input {tickIncrement} =
  case input of
    Tick _ -> tickIncrement + tickIncStep
    _ -> tickIncrement

update input gameSt = GameState (newCost          input gameSt)
                                (newSpent         input gameSt)
                                (newGathered      input gameSt)
                                (newTickIncrement input gameSt)

-- View
view gameSt = 
  flow down <| 
    map ((|>) gameSt)
      [ asText . balance,asText . canAfford,asText . .spent,asText . .gathered,asText . nextCost ]

-- Main

main = view <~ foldp update gameState inputs

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...