## Purely Functional Games How I built a game in Haskell - pure functional style
### What this talk is about I'd like to share with you my experience of building a game in Haskell. I'll describe my experience, share some key ideas, and try to dispel some myths about Haskell.
## Demo
Your browser does not support the video tag.
## What is Haskell? - General purpose programming language - Purely functional - Statically typed - Lazy - [Unique](https://www.snoyman.com/blog/2017/12/what-makes-haskell-unique)
## Hello world in Haskell ```hs main = putStrLn "Hello, world!" ```
## Pure functional programming - Separation between **effectful** code (mutation, IO, etc) and **uneffectful** code - The **type** of expressions that might use effects to produce a value is different from the type of expressions that don't - Referential transparency - `=` (equals sign) means both sides are **interchangeable**
# Benefits?
## Equational and local reasoning - Understand code in **isolation** without the context in which it is used. - Know when some code has no effects and is not effected by other code. - The output is determined only by the input and algorithm.
## Code reuse - Apply a function many times and in different contexts without worrying it might change something unintended. - Call a function from multiple locations - no side effects! - Refactor safely
#### These benefits are useful when making games as well And we can have them by writing a game in Haskell using pure functional style.
### Available libraries / Engines - gloss - limited vector graphics - Hickory / Helm - game engines of various quality - sdl2 - idiomatic bindings for SDL2 - a cross-platform development library - gl / gpipe - opengl bindings / opengl abstraction - apecs / ecstasy - ECS
### Custom Engine I chose to build a custom engine on top of the sdl2 bindings as they are quite thorough and stable and I didn't want to get stuck. But this means that I need to decide on the architecture myself.
### Game architecture? Many engines (such as Flash and Love2d) use **events** and **callbacks**.
### Type of Events - Ticks - Update - Render - Keyboard/Mouse Input - Collisions - Animation - Etc.
### Events and Event Handlers This design makes the program inherently **effectful**. **A function that returns `void` is all about effects!**
### A General Idea for Pure Functional Architecture - Effectful outer layer, Uneffectful core - Push effects to the outer layer
### Effects - what is needed in a video game? - Graphics - render every frame - Sound - async usually (fire and forget) - User input - sampled every frame, passed to the rest of the game - Assets loading - should be done before a section/level begins
### Effects - How to push to the outer layer The **Game loop** handles effects - Gets the **current state** of the game - Samples **events**, user input and results of requests - **Update**s the state and gets the new state and requests (This is uneffectful) - Run **requests** asyncly - **Render**s state on screen
### Game representation Let's focus on a few components: - Scenes - Game entities - Scripts
![Design](design.png)
### Our Toolbox - ADTs - Represent data, commands, EDSLs and state machines - Lenses - data access and manipulation - Functions and Closures - Pass state in, return updated state out
## Scenes - Bullet hell game level - Menus - Visual novel gameplay - End credits - Asset loader
## A Scene - Holds the relevant data for a scene - Describes how to update it - Describes how to render it
## A pattern? ```haskell data SceneF a = SceneF { state :: a , update :: Input -> a -> Result ([Request], (Command, a)) , render :: Renderer -> a -> IO () } ``` - But different scenes require different data - So we can't, for example, create a stack of `Scene`s of different data
### Existential Types ```hs {-# LANGUAGE ExistentialQuantification #-} -- Definition data Scene = forall s. Scene (SceneF s) -- Creation mkScene :: SceneF s -> Scene mkScene scene = Scene scene -- Usage: render the top scene on the stack renderer :: Renderer -> Stack Scene -> IO () renderer sdlRenderer scenes = case head scenes of Scene SceneF{render, state} -> render sdlRenderer state ``` - `Scene` does not know (nor care) about `s` - `SceneF` encodes the fact the `render`'s `s` and state's `s` are the same
## Game Entities - Player character - Bullet - Background screen - Enemies
### How are they represented in the game? - Bags of data (position component, size, texture, health, attack pattern) - Have an initialization, update function, and render function - Accessing and updating state is easy through lenses - map, fold, traverse works well
## Lenses First class (composable) getters and setters. Can be used to: - get component data - update nested records - abstract operations over data components polymorphically
### Get, Set, Update ```hs view (hitbox . size . x) character ``` ```hs set direction newDir character ``` ```hs over position (flip addPoint move) character ```
### Row Polymorphism We can talk about types that has the same components polymorphicaly: ```hs isTouchingCircleCircle :: (HasSize a Size, HasPos a IPoint) => (HasSize b Size, HasPos b IPoint) => a -> b -> Maybe (a, b) ```
### First class getters and setters You can pass a lens to a function: ```hs circleDistance circle rect = Point (dist x) (dist y) where dist axis = abs ( circle ^. pos . axis - (rect ^. hitbox . pos . axis) - (rect ^. hitbox . size . axis) `div` 2 ) ```
### Scripts - An EDSL for changing the game - A list of commands is enough for something linear - More approaches are available for something more complex (such as Free monads)
### Scripts EDSL ```hs data Command = Wait Actions Int | Spawn [Enemy] | LoadScene Scene | LoadTextBox Actions TB.TextBox | PlayMusic BS.ByteString | StopMusic | Shake | ... ```
## Dispelling a few myths about Haskell
### Games are inherently stateful and that's not a good fit for Haskell
- Haskell does not forbid you from using state, it just makes it explicit - Explicit state means more control - For example, you can easily decide if you want to update something or not - No need to worry about partial data update (frames being updated using partially updated data, order of update does not matter) - New data each frame translates well to games - Lenses works really well with nested record updates
### Haskell is too slow for games Can you write games in GC languages? Does Haskell have unacceptable pause times?
- There are many games written with GCed languages (Lua / ActionScript / Java / C# / Haxe) - For a simple game, Haskell is plenty fast enough - The garbage collector is very good at allocating and deleting from nursery
``` ➜ haskell-play git:(master) stack exec app -- +RTS -sstderr ... Tot time (elapsed) Avg pause Max pause Gen 0 0.082s 0.087s 0.0001s 0.0007s Gen 1 0.001s 0.001s 0.0002s 0.0003s INIT time 0.000s ( 0.002s elapsed) MUT time 7.862s ( 92.500s elapsed) GC time 0.083s ( 0.087s elapsed) EXIT time 0.000s ( 0.005s elapsed) Total time 7.945s ( 92.594s elapsed) %GC time 1.0% (0.1% elapsed) Alloc rate 81,352,956 bytes per MUT second Productivity 99.0% of total user, 99.9% of total elapsed ```
### In Haskell you have to design your whole program and only then implement it
- Incrementally building a program is available in any language - Expressive static typing makes it easier to change existing code - Write huge functions that does a lot of stuff and clean them up once you understand what you want/need - This is a strength of Haskell's idioms and type-system!
Your browser does not support the video tag.
### Conclusion You can also write a game in Haskell in purely functional style. - Push effects to an outer layer and keep the core effects-free - Keep it simple and don't abstract too early - Iterate and refactor
### Questions?
### Thank you! If you want to play the game, you can find it at: - [gilmi.me/nyx](https://gilmi.me/nyx)