7

素朴な質問:

Elm で互いに依存するシグナルのペアを定義する方法はありますか?

前文:

私は小さな Cookie クリッカー スタイルのブラウザー ゲームを作成しようとしています。このゲームでは、プレーヤーがリソースを収集し、それらを使って自律的なリソース収集構造を購入し、購入するほど高価になります。これは、(gatheredプレーヤーが収集したspentリソースの量)、(プレーヤーが既に費やしたリソースの量)、およびcost(アップグレードのコスト) という 3 つの関連するシグナルを意味します。

実装は次のとおりです。

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.

問題は、書かれているように、にcost依存しcanAfford、 に依存しbalance、 に依存しspent、 に依存しているcostようです。

コストラインを次のように変更すると

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

期待どおりに機能し始めます(プレーヤーが負のリソースに費やすことが許可されていることを除いて、これは避けたいことです)。

何か案は?

4

1 に答える 1

20

あなたの素朴な質問に答えます

いいえ、Elm には相互に再帰的なシグナルを定義する一般的な方法はありません。問題は、Elm の a が常に値を持たなければならない
という制約にあります。Signalの定義が をcost必要としているcanAffordcanAffordで定義されているcost場合、問題は信号の初期値の解決をどこから開始するかです。これは、相互に再帰的なシグナルの観点から考えると、解決するのが難しい問題です。

相互再帰シグナルは、シグナルの過去の値とすべて関係があります。このfoldp構文を使用すると、相互に再帰的な信号に相当するものをポイントまで指定できます。初期値の問題の解決策は、初期値である明示的な引数を持つことによって解決されfoldpます。しかし、制約は、foldp純粋な関数のみを取ることです。

この問題は、事前の知識を必要としない方法で明確に説明するのは困難です。したがって、私があなたのコードで作成した図に基づいて、別の説明があります。

OPによって与えられたコードのシグナルグラフ

main時間をかけてコードと図の間の関係を見つけてください (グラフを簡略化するために省略していることに注意してください)。Afoldpはループバックのあるノードで、sampleOn稲妻などがあります (sampleOn一定の信号を に書き直しましたalways)。canAfford問題の部分は、の定義で使用されている赤い線が上がっていることですcost
ご覧のとおり、basicfoldpには base 値を持つ単純なループがあります。これを実装するのは、あなたのような任意のループバックよりも簡単です。

問題を理解していただければ幸いです。制限は Elm にあります。それはあなたのせいではありません。
Elm でこの制限を解決していますが、これには時間がかかります。

あなたの問題の解決策

シグナルに名前を付けて操作するのは良いことですが、Elm でゲームを実装する場合は通常、別のプログラミング スタイルを使用すると役立ちます。リンクされた記事のアイデアは、コードを次のように分割することに帰着します。

  1. 入力: MouseTimeおよびポート。
  2. モデル: ゲームの状態。あなたのcost場合、、、、、balanceなど。canAffordspentgathered
  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
于 2014-03-09T15:56:19.960 に答える