相互依赖的信号 [英] Mutually dependant signals

查看:70
本文介绍了相互依赖的信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一个简单的问题:

在榆树中是否有定义一对相互依赖的信号的方法?

Is there a way of defining a pair of signals that depend on each other in Elm?

序言:

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

I'm trying to write a tiny Cookie-clicker-style browser game in which the player is gathering resources, then spending them to purchase autonomous resource-gathering constructs which get more expensive as they're purchased. That implies three relevant signals: gathered (how much resources the player has gathered), spent (how much resource the player has already spent) and cost (how much an upgrade costs).

这是一个实现:

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文件中时,就会收到错误消息

This compiles fine, but when I embed it in an HTML file with the appropriate buttons hooked up to send messages to the appropriate ports above, I get the error

s2 is undefined
    Open the developer console for more details.

问题似乎是书面形式,成本取决于 canAfford ,取决于余额,取决于已花费,这又取决于 cost

The problem seems to be that as written, cost depends on canAfford, which depends on balance, which depends on spent, which depends on cost again.

如果我修改成本行,这样

If I modify the cost line such that

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

它按预期开始工作(除了播放器允许花费在负面资源上,这是我要避免的事情。)

it starts working as expected (except that the player is allowed to spend into negative resources, which is what I'd like to avoid).

有什么想法吗?

推荐答案

回答您的基本问题



,榆木中没有通用的方法来相互定义递归信号。

问题在于Elm中的 Signal 必须始终具有值的约束。如果 cost 的定义需要 canAfford ,但定义了 canAfford cost 而言,问题是从解析信号的初始值开始。当您考虑相互递归信号时,这是一个很难解决的问题。

Answer to your bare question

No, there is no general way in Elm to define mutually recursive signals.
The problem lies in the constraint that a Signal in Elm must always have a value. If the definition of cost requires canAfford but canAfford is defined in terms of cost, the problem is where to start with resolving the initial value of the signal. This is a tough problem to solve when you think in terms of mutually recursive signals.

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

Mutually recursive signals have everything to do with past values of signals. The foldp construct allows you to specify the equivalent of mutually recursive signals up to a point. The solution to the initial value problem is solved by having an explicit argument to foldp that is the initial value. But the constraint is that foldp only takes pure functions.

这个问题很难用不需要任何先验知识的方式清楚地解释。因此,这是我根据您的代码制作的图表的另一种解释。

This problem is hard to clearly explain in a way that doesn't require any prior knowledge. So here's another explanation, based on a diagram I made of your code.

花点时间查找代码与图表之间的连接(请注意,为简化起见,我省略了 main 图)。 foldp 是一个具有环回的节点, sampleOn 具有闪电等。(我重写了 sampleOn 始终向总是发送信号)。问题部分是红线上升,在 cost 的定义中使用 canAfford

如您所见,基本的 foldp 有一个带有基值的简单循环。实施此方法比像您这样的任意环回更容易。

Take your time to find the connections between the code and the diagram (note that I left out main to simplify the graph). A foldp is a node with a loop back, sampleOn has a lightning bolt etc. (I rewrote sampleOn on a constant signal to always). The problematic part is the red line going up, using canAfford in the definition of cost.
As you can see, a basic foldp has a simple loop with a base value. Implementing this is easier than arbitrary loop-back like yours.

我希望您现在就明白了这个问题。限制在Elm中,这不是您的错。

我正在Elm中解决此限制,尽管这样做需要一些时间。

I hope you understand the problem now. The limitation is in Elm, it's not your fault.
I'm resolving this limitation in Elm although it will take some time to do so.

尽管在实施游戏时为信号命名并与之配合会很不错在Elm中,通常可以使用不同的编程风格。链接文章中的想法归结为将您的代码拆分为以下内容:

Although it can be nice to name signals and work with those, when implementing games in Elm it usually helps to use a different programming style. The idea in the linked article comes down to splitting your code up in:


  1. 输入:鼠标时间和端口。

  2. 型号:游戏的状态,在您的情况下费用余额 canAfford 已花费聚集

  3. 更新:游戏的更新功能,您可以从较小的更新中组成功能。这些应该尽可能是 pure 函数。

  4. 查看:用于查看模型的代码。

  1. Inputs: Mouse, Time and ports in your case.
  2. Model: The state of the game, in your case cost, balance, canAfford, spent, gathered etc.
  3. Update: The update function of the game, you can compose these out of smaller update functions. These should be pure functions as much as possible.
  4. View: Code to view the model.

通过使用 main = view<〜foldp 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

这篇关于相互依赖的信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆