功能反应式 F# - 在游戏中存储状态

Posted

技术标签:

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

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

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

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

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

【问题讨论】:

【参考方案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 中发生,如果鼠标位置用于控制屏幕上某物的位置,将鼠标位置视为时间的连续函数是很自然的或在游戏中。微分方程在某些方法中是相关的,但不是必需的。我不是专家 - 请参阅:***.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。你一定要看看。

【讨论】:

以上是关于功能反应式 F# - 在游戏中存储状态的主要内容,如果未能解决你的问题,请参考以下文章

如何在同一个反应组件中使用本地状态和 redux 存储状态?

如何将反应js状态保存到本地存储中

在功能组件中存储非状态变量

我可以做些啥来存储类的实例并在反应应用程序的上下文中管理状态

反应 SPA。用于处理登录状态的全局状态存储 API?

反应状态下的对象数组不起作用