Functional Reactive F# - Storing States in Games

后端 未结 5 1457
借酒劲吻你
借酒劲吻你 2021-01-31 04:34

I am a student currently learning about Functional Reactive paradigm using F#. It\'s radically new viewpoint for me. Yesterday I learned about creating a simple ping-pong game u

相关标签:
5条回答
  • 2021-01-31 05:09

    Elm is a modern FRP implementation. For modelling dynamic collections which are ubiquitous in games such as Space Invaders, it contains an Automaton library based on the concepts of arrowized FRP. You should definitely check it out.

    0 讨论(0)
  • 2021-01-31 05:12

    Tomas gave a nice talk about reactive programming in F#. Many concepts should apply in your case.

    0 讨论(0)
  • 2021-01-31 05:14

    Maybe you will want to have a look at FsReactive.

    0 讨论(0)
  • 2021-01-31 05:22

    I haven't got any experience with reactive programming under F#, but the problem of global state in purely functional systems is quite common and has a quite elegant solution: Monads.

    While monads themselves are primarily used in Haskell, the underlying concept made it into F# as computation expressions.

    The idea is that you don't actually change states but just describe the transitions of the state, i.e. how to produce new states. The state itself can be completely hidden in the programm. By using special monadic syntax, you can write pure but stateful programms almost imperatively.

    Taking a (modified) implementation from this source, the State monad could look like this

    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
    

    So let's have an example: We want to write a function that can write values into a log (just a list) while proceeding. We therefore define

    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
    }
    

    Within state, we can now use this in an imperative syntax without having to handle the log list manually.

    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
    

    Still, everything is purely functional here ;)

    0 讨论(0)
  • 2021-01-31 05:23

    There's more than one way to do FRP, and it's an active area of research. What's best can depend a lot on the details of how things interact with each other, and new and better techniques may appear in the future.

    Broadly the idea is to have behaviours that are functions of time in place of ordinary values (as you said). Behaviours can be defined in terms of other behaviours, and can be defined to swap between other behaviours when particular events occur.

    In your example, you generally wouldn't need to remember the position of the ball via arguments (but for some kinds of FRP you might do). Instead you can just have a behaviour:
    ballPos : time -> (float * float)
    This might have global scope, or for a larger program it may be better to have a local scope with all uses of it in that scope.

    As things get more complicated, you'll have behaviours defined in increasingly complex ways, depend on other behaviours and events - including recursive dependencies which are handled differently in different FRP frameworks. In F# for recursive dependencies I'd expect you'd need a let rec including all involved behaviours. These can still be organised into structures though - at the top-level you might have:

    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}
    

    And then the behaviours for alienPos, alienHP can be defined, with dependencies on the player, and playerPos, playerBombs can be defined with dependencies on aliens.

    Anyway, if you can give more details of what kind of FRP you're using, it will be easier to give more specific advice. (And if you want advice on what kind - personally I'd recommend reading: http://conal.net/papers/push-pull-frp/push-pull-frp.pdf)

    0 讨论(0)
提交回复
热议问题