有没有办法在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