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

Functional Reactive F# - Storing States in Games

我是一名学生,目前正在学习使用f的功能反应范式。这对我来说是一个全新的观点。昨天我学习了如何用这种模式创建一个简单的乒乓球游戏。到目前为止,我所理解的观点是:我们认为价值是时间的函数。在它的纯形式上,它是无状态的。但是,我需要记住球的位置(或状态)。所以我总是通过球的当前位置作为全局函数的参数。

如果我们谈论一些稍微复杂一些的游戏,比如太空入侵者,我们有很多状态(外星人的位置、外星人当前的生命值、剩余炸弹的数量等)

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

有人能用F上的简单样本来解释这个吗?谢谢。


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

大体上来说,我们的想法是让行为具有时间的功能,而不是普通的价值观(如你所说)。行为可以根据其他行为来定义,并且可以定义为在发生特定事件时在其他行为之间进行交换。

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

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

1
2
3
4
5
6
7
8
9
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可以依赖于外星人。

不管怎样,如果你能提供你所使用的玻璃钢的更多细节,就更容易给出更具体的建议。(如果你想要什么样的建议-我个人建议阅读:http://conal.net/papers/push-pull-frp/push-pull-frp.pdf)


我没有在f_下进行反应式编程的经验,但是纯功能系统中的全局状态问题是很常见的,并且有一个非常优雅的解决方案:monads。

虽然monad本身主要用于haskell,但底层概念将其作为计算表达式变成了f。

这个想法是,你不需要改变状态,只需要描述状态的转换,即如何产生新的状态。状态本身可以完全隐藏在程序中。通过使用特殊的单体语法,您几乎可以命令性地编写纯的但有状态的程序。

从这个源代码获取(修改过的)实现,Statemonad可能看起来像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

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

1
2
3
4
5
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中,我们现在可以在命令式语法中使用它,而无需手动处理日志列表。

1
2
3
4
5
6
7
8
9
10
11
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
State: %A" result finalState

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


Tomas在f_中对反应式编程做了一个很好的讨论。许多概念应该适用于您的案例。


榆树是一种现代化的玻璃钢结构。对于在诸如"太空入侵者"之类的游戏中普遍存在的动态集合建模,它包含一个基于箭头化FRP概念的自动化库。你一定要去看看。


也许你想看看fsreactive。