功能反应式 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 存储状态?