相互依赖的信号

Ina*_*thi 7 elm

不好问题:

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

前言:

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

这是一个实现:

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 ]
Run Code Online (Sandbox Code Playgroud)

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

s2 is undefined
    Open the developer console for more details.
Run Code Online (Sandbox Code Playgroud)

问题似乎是书面的,cost取决于canAfford,取决于balance,取决于spent,这取决于cost再次.

如果我修改成本线那么

...
cost = foldp (+) 0 <| sampleOn build costIncrement
...
Run Code Online (Sandbox Code Playgroud)

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

有任何想法吗?

Apa*_*hka 20

回答你的问题

,在Elm中没有通用的方法来定义相互递归的信号.
问题在于Signal榆树中的a 必须始终具有值的约束.如果定义cost要求canAfford,但canAfford在以下方面被定义cost,问题是从哪里开始解决信号的初始值.当您考虑相互递归信号时,这是一个难以解决的问题.

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

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

OP给出的代码的信号图

花点时间找到代码和图表之间的联系(注意我省略了main简化图表).A foldp是一个带回路的节点,sampleOn有一个闪电等等(我重写sampleOn了一个恒定的信号always).问题部分是红线上升,canAfford在定义中使用cost.
如您所见,basic foldp具有一个带有基值的简单循环.实现这个比像你这样的任意循环更容易.

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

解决您的问题

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

  1. 输入:Mouse,Time和端口在你的情况.
  2. 型号:游戏的状态,你的情况cost,balance,canAfford,spent,gathered等.
  3. 更新:游戏的更新功能,您可以通过较小的更新功能组合这些功能.这些应尽可能是函数.
  4. 视图:查看模型的代码.

通过使用类似的东西将它们组合在一起main = view <~ foldp update modelStartValues inputs.

特别是,我会写它像:

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
Run Code Online (Sandbox Code Playgroud)