I know very little about functional programming other than the idea of pure functions. In John Carmack's 2013 Quakecon talk he mentioned one of the questions often asked about functional programming as related to games: how do you fire a gun and do damage to another player if you don't have access to state? (paraphrased) In mentioned something about an event system, which I didn't quite understand, since it seems to me an event system would still need state?
How would one accomplish this in a purely functional language?
Games are commonly programmed in imperative languages. Functional languages have been known to have benefits but have rarely been used to program games.
In pure functional programming, state is manipulated by functions that take some state and return the next state. Sequencing through states is then achieved by passing the state through a sequence of pure functions. Even global mutable state can be modeled this way.
There are a lot of advantages to functional programming in terms of concurrency, using persistent data-structures makes it cheaper (as well as much simpler in terms of code) to maintain old copies of game state which is very important if your game is going to feature a multi-player component.
In functional programming, data cannot be stored in objects and it can only be transformed by creating functions. In object-oriented programming, data is stored in objects. The object-oriented programming is widely used by the programmers and successful also.
To repeat one of my favorite quotes
... takes in the state of the world and returns a new world, thus remaining pure.
This was talking about Clean, a cousin of Haskell but it's still related. The gist of it is, you're right, you need some kind of state, but it doesn't have to be mutable. Consider
myFun :: StateOfTheWorld -> a -> (StateOfTheWorld, b)
so we don't modify the state, we just produce a new one. This is referentially transparent, since given the same state of the world and the same action, you'll get the same thing back.
For you you might have something like
killPlayer :: Game -> Event -> Game
killPlayer g (Kill x) = g { isDead = x : isDead g }
which is just using functional updates for records. This is a bit klunky, so we might do something like
killPlayer :: Game -> Event -> Action
killPlayer (PlayerDamaged x amount) = if playerHealth g x <= amount
then KillPlayer x
else ReduceHealth x amount
So we just return the differences, not the full game state.
This works, but is ugly. So we prettify this with do
notation and Control.Monad.State. This sounds scary but it's exactly what we were doing above, just with a bit more syntactic abstraction. In fact, this is what IO
is on GHC as well. I don't know if you've learned about Monads, but the State monad is often the motivating example.
Finally to get back to games, many of the gameframeworks I've seen are like this: piles of things listening to events and then suggesting some small incremental change to the game state and returning the different, finally the framework itself makes the appropriate openGL calls or whatever to implement those changes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With