【问题标题】:Functional Reactive F# - Storing States in Games功能反应式 F# - 在游戏中存储状态
【发布时间】:2011-03-22 00:11:32
【问题描述】:

我是一名学生,目前正在使用 F# 学习功能响应式范式。这对我来说是全新的观点。昨天我学习了使用这种范例创建一个简单的乒乓球游戏。到目前为止,我掌握的想法是:我们认为价值是时间的函数。就其纯粹的形式而言,它是无国籍的。但是,我需要记住球(或状态)的位置。所以我总是把球的当前位置作为全局函数的参数传进去。

如果我们谈论稍微复杂一点的游戏,比如 Space Invaders,我们有很多状态(外星人的位置、外星人当前的 HP、剩余的炸弹数量等)

有没有一种优雅/最好的方法来解决这个问题?我们总是将状态存储在顶层吗?是否应将所有当前状态作为全局函数的附加输入参数给出?

任何人都可以使用 F# 上的简单示例来解释这一点吗? 非常感谢。

【问题讨论】:

    标签: f# functional-programming state frp


    【解决方案1】:

    我对 F# 下的响应式编程没有任何经验,但是纯函数系统中的全局状态问题很常见,并且有一个相当优雅的解决方案:Monads

    虽然 monad 本身主要在 Haskell 中使用,但基本概念在 F# 中作为 computation expressions 使用。

    这个想法是你实际上并没有改变状态,而只是描述状态的转换,即如何产生新的状态。状态本身可以完全隐藏在程序中。通过使用特殊的 monadic 语法,您几乎可以强制编写纯粹但有状态的程序。

    this source 获取(修改)实现,State monad 可能如下所示

    let (>>=) x f =
       (fun s0 ->
          let a,s = x s0    
          f a s)       
    let returnS a = (fun s -> a, s)
    
    type StateBuilder() =
      member m.Delay(f) = f()
      member m.Bind(x, f) = x >>= f
      member m.Return a = returnS a
      member m.ReturnFrom(f) = f
    
    let state = new StateBuilder()     
    
    let getState = (fun s -> s, s)
    let setState s = (fun _ -> (),s) 
    
    let runState m s = m s |> fst
    

    让我们举个例子:我们想编写一个函数,在继续进行时可以将值写入日志(只是一个列表)。因此我们定义

    let writeLog x = state {
      let! oldLog = getState // Notice the ! for monadic computations (i.e. where the state is involved)
      do! setState (oldLog @ [x]) // Set new state
      return () // Just return (), we only update the state
    }
    

    state 中,我们现在可以在命令式语法中使用它,而无需手动处理日志列表。

    let test = state {
       let k = 42
       do! writeLog k // It's just that - no log list we had to handle explicitly
       let b = 2 * k
       do! writeLog b
       return "Blub"
    }
    
    let (result, finalState) = test [] // Run the stateful computation (starting with an empty list)
    printfn "Result: %A\nState: %A" result finalState
    

    不过,这里的一切都是纯粹的功能;)

    【讨论】:

    • 这主要是关于一个状态单子。函数式反应式编程通常涉及 monad,但通常不涉及这种简单的 state monad。
    • 如我所说,我没有 FRP 的经验。尽管如此,状态单子(或根本单子)似乎是所要求的概念 - 方便地存储和修改上下文数据而不会失去参考透明度。如果 FTP 已经使用一元基础设施,那就更好了。一个 State monad 转换器应该做这件事(你的意思是用 simple kind 吗?)。但如果没有解释基本原理,这些信息将毫无用处!
    • FRP 的要点是允许将行为定义为时间的连续函数 - 例如您可以将重力下球的 z 位置定义为 z(t)=9.8*t*t 。单子状态仅与进行离散更改的状态相关 - FRP 中也允许离散更改,但它们的中心性较低,并且通常不符合单子的确切形式。
    • @RD1:很有趣,谢谢。但是,大多数与用户输入相关的操作难道不是固有的离散吗?即使不是 - 为 一个球 定义一个连续的世界函数也很简单,但如果系统稍微复杂一些(更多的球),这一切不都归结为求解(即积分)微分吗方程?
    • 鼠标的实际位置是一个连续变量,在 FRP 中也是这样处理的。当然,可用的位置数据只是对实际位置进行采样和近似,但这无论如何都会在 FRP 中发生,如果鼠标位置用于控制屏幕上某物的位置,将鼠标位置视为时间的连续函数是很自然的或在游戏中。微分方程在某些方法中是相关的,但不是必需的。我不是专家 - 请参阅:stackoverflow.com/questions/1028250/…
    【解决方案2】:

    Tomas 提供了nice talk 关于 F# 中的响应式编程的信息。许多概念应该适用于您的案例。

    【讨论】:

    • 函数式反应式编程不仅仅是函数式语言中的反应式编程。主要技术是将行为表示为时间的函数。这些行为可以相互依赖,也可以依赖于事件。所以这个谈话并不是那么直接相关 - 它有一些相关性,但只是因为事件是 FRP 的一部分。 (不过我同意这是一个很好的谈话。)
    【解决方案3】:

    制作 FRP 的方法不止一种,而且它是一个活跃的研究领域。什么是最好的很大程度上取决于事物如何相互作用的细节,未来可能会出现新的更好的技术。

    从广义上讲,这个想法是让行为成为时间的函数,而不是普通的价值观(如你所说)。行为可以根据其他行为来定义,并且可以定义为在特定事件发生时在其他行为之间交换。

    在您的示例中,您通常不需要通过参数记住球的位置(但对于某些类型的 FRP,您可能会这样做)。相反,你可以有一个行为:
    ballPos : time -> (float * float)
    这可能具有全局范围,或者对于更大的程序,最好有一个本地范围,在该范围内使用它。

    随着事情变得越来越复杂,您将以越来越复杂的方式定义行为,依赖于其他行为和事件 - 包括在不同 FRP 框架中以不同方式处理的递归依赖项。在 F# 中,对于递归依赖项,我希望您需要一个 let rec 包括所有涉及的行为。这些仍然可以组织成结构 - 在顶层你可能有:

    type alienInfo =  { pos : float*float; hp : float }
    type playerInfo = { pos : float*float; bombs : int } 
    let rec aliens : time -> alienInfo array =             // You might want laziness here.
        let behaviours = [| for n in 1..numAliens -> 
                            (alienPos player n, alienHP player n) |]
        fun t -> [| for (posBeh, hpBeh) in behaviours -> 
                    {pos=posBeh t; hp=hpBeh t} |]          // You might want laziness here.
    and player : time -> playerInfo  = fun t ->
        { pos=playerPos aliens t; bombs=playerBombs aliens t}
    

    然后可以定义alienPos,alienHP的行为,依赖玩家,playerPos,playerBombs可以定义依赖外星人。

    无论如何,如果您能提供更多关于您正在使用哪种 FRP 的详细信息,那么提供更具体的建议会更容易。 (如果您想了解哪种类型的建议 - 我个人建议阅读:http://conal.net/papers/push-pull-frp/push-pull-frp.pdf

    【讨论】:

      【解决方案4】:

      也许你会想看看FsReactive

      【讨论】:

      • 解释 FsReactive 如何帮助回答问题将提高您回答的实用性。
      【解决方案5】:

      Elm 是现代 FRP 实现。对于在 Space Invaders 等游戏中普遍存在的动态集合建模,它包含一个基于箭头 FRP 概念的Automaton library。你一定要看看。

      【讨论】:

        猜你喜欢
        • 2021-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-21
        • 2021-07-15
        • 2021-05-17
        相关资源
        最近更新 更多