What is this post really about?
Making a game in Haskell is a pioneering process. Despite the fact that there’s a page on the wiki and a full subreddit dedicated to the purpose of making a game in this beautiful language, not many people have actually succeeded making anything close to what current game developers can already achieve. I hope to try and communicate my experiences in this post.
The most exciting thing I saw regarding Haskell in games development was Wayward Tide, a game made by Chucklefish that was made with Haskell. Unfortunately, the project appears to have been shelved, already giving off bad vibes around the viability of Haskell as a games language. There’s also Keera Studios who make games and solutions in Haskell, however in my honest opinion I don’t believe there’s enough there to prove anything regarding the power of Haskell in games.
Another thing that was exciting was John Carmack’s keynote at Quakecon 2013. John Carmack is the co-founder of id Software and was the lead programmer for games like Doom and Quake. When someone this important starts talking about functional programming in such a positive way, it does fire up. We have all read about the payoffs of using Haskell and so this validation feels great. I’ve embedded the keynote below just in case you missed the link I provided, it really is insightful.
So we have a few examples at best of what Haskell can do for the games industry, but the biggest question still remains at large: Where is everyone?. There are lots of libraries out there for games development in Haskell, such as Apecs. Why haven’t more people made games? We have bindings to SDL2 already available and lots of experienced programmers to help each other out.
Unsatisfied with not knowing and understanding the reasons behind the lack of development, I challenged myself to learn through doing. I tried converting my knowledge of how to make a game in using a framework into one compatible with Haskell. It was hard, but I was always convincing myself that the payoff would be fantastic. Naturally, using a high level language will result in less performance than that using something like C++, but once cracked, I believe that development will become significantly easier and more rewarding. That really pushed me to work on my game.
I will be using the commit history from my project FirstGameHS for the next few sections. Feel free to go through my commits and laugh at my mistakes as a beginner. Just be sure to remember that the take away here is that these were my thoughts coming from imperative games development, and if we’re going to make functional games development a viable thing then we need to stamp out these habits and iron the creases to get everyone thinking correctly when they enter this world.
I started small, I went through Lazyfoo’s SDL tutorials and tried to recall how every step was different. At this stage, the game loop looked very simple and didn’t contain anything interesting, so I immediately sought after something better. This commit was when things started becoming clearer. This commit contained a rectangle that could be controlled with the keyboard. Let’s dive in and take a look at some things.
Forgive me for not providing complete snippets, but you can guess what exactly
screenWidth means without me having to show the code. So in this area of our code, we begin to define the
World of our game. Because there’s only one thing changing, instead of
World being some sort of collection it is more of an alias for our character,
initialGuy is used to create a
Guy, he is placed in the middle of the screen with no velocity, simple.
Here’s how we are going to control our character. I have only posted a little portion of the full code which focuses on the right arrow key, but all the directions are covered. All we say here is that when you press a key, you add velocity in that direction, and when you release it the opposite happens. This function is going to be used when iterating through the payload of SDL events, meaning that everything that has happened since the last frame will go through this function, which will then check on whether the current event is relevant. If the player presses both the left and right arrow keys down, the velocity will cancel out, leaving you with either 0 or a constant velocity (
currentVelocity + walkingVelocity - walkingVelocity = currentVelocity). Nothing out of the ordinary here. Let’s look at something more interesting.
Okay, here’s something more intriguing. This function takes a
CDouble and our what will evaluate to our
Guy and it will produce another
Guy. This part is very important, as it’s how games in Haskell are going to work. In C++, state is held whether you like it or not. Every object in an Object Oriented environment will have a state. In Haskell, however, everything is just a transformation. This function takes a
Guy and produced a
Guy. I emphasise the a here as this function could not care less about the fact this is all a game. This function is the thing that links the current frame to the next, and everything that is going to happen to
Guy happens here. Right now, we simply use the
velocity value within
Guy to manipulate his
position. It then makes sure to keep him inside an arbitrary area.
Finally, here’s the game loop. We get an event payload and we work out how long it has been since the previous frame just like we would anywhere else. Let’s look into
newWorld though. Remember that
loop isn’t a keyword like
while, we are simply defining a function called
loop that takes some timing data in the variable
last and our
Guy in the variable
newWorld is what we call the thing that
world transforms into after using our
updateWorld functions as we have already shown. We then render things as normal below.
Looking at this game loop, lots of things strike me as challenges. Firstly, everything is in one file, and so we will need a reliable way for data such as
Guy to exist in it’s own module and to be rounded up when needed. This is a challenge every game will need to solve in any language. Secondly, the managing of a state becomes very important. Recall that
Guy is the only thing in our
World right now, which is why they are the same thing. Later,
World would contain everything, and so
updateWorld will have to change to accommodate everything. Going back to the first point, this would mean that
updateWorld will need to transform all of it’s internal data into new data (
Guy would then have to implement his own function
updateGuy. This sounds okay, as we could export
updateGuy from the module, and then our
Main.hs file will import it and call it whenever a
Guy needs updating every frame. The important thing to remember is that if something changes between frames, it will need to go inside of
World and it will need to be managed in some way.
Of Boxes and Threads
So what is with the edgy title? After the final paragraph of the last section, I think it’s time to get to the meat of this post. What exactly do I mean by boxes and threads in this context? Beginner programmers tend to imagine a variable as a ‘box’ with a number inside of it. A number then becomes a piece of data, and data then becomes anything the programming language is capable of evaluating — functions, classes, pointers, files, days of the week, bools. If you look at the gameloop above though, the value of
newWorld is only valid for a single frame. While in an imperative language like C++, you could argue that the value of a variable exists in the same way, but I’m trying to describe this a little differently. In C++, if you create a variable, it sticks around until it falls out of scope. This happens when the current code block ends, such as a function or loop. It is true that variables go out of scope in Haskell too, but every function is just a way of transforming data continuously, it doesn’t stick around, it can’t (this isn’t true, but for simplicity we’ll say it is).
To hopefully illustrate what I’m talking about, I made two diagrams. Don’t take them literally.
Here’s how I’m going to interpret the way traditional, imperative game structures work. In an Object oriented system, you’d probably have a game controller class containing everything in the game in some sort of structure. If you are using an Entity Component System, you will simply have a list of
GameObjects or something. These
GameObjects will contain data for your game which you will manipulate and then render as part of your game loop.
Let’s imagine that everything you use inside your game is placed inside of a box. You create the data, you put it into a box. You put that box into a collection of boxes. As long as this controller stays in scope, these boxes will exist and any
GameObjects can manipulate any other as long as functions are made public blah blah blah. The object itself doesn’t need to know about all possibilities, it only needs to export what it can do, and how it should manipulate the current object.
This might make more sense if I just insert the next diagram now, so you can compare:
Yes, we could have bundled up the data in the first diagram and the two would be extremely similar. What I’m trying to get at with the second diagram, is that the data travels with the flow of the program, whereas in imperative programming they exist in boxes irrespective of what happens in the game loop as long as something holds them. I imagine Haskell programs as a bundle of threads — where we can pack and unpack boxes of data, we can also inter-twine, unravel and tie threads together. These threads run parallel (don’t get confused with parallel computing!) with the flow of the game as they travel together, twisting, weaving and separating with each iteration. The boxes exist outside the loop and can interact and be interacted with. Threads, however, do not — any threads that aren’t passed to the next iteration are cut off and terminated, all data must be accounted for in the transition to the next frame of the game.
While it may sound very inefficient passing your entire game around with functions every single frame, Haskell is actually designed to handle this and it isn’t as bad as you think. Also, because the data here isn’t somewhere in memory but is simply an echo of the initial state twisted continuously, multiple copies are made with every function call and so you can parallelise (yes, parallel computing!) very easily. I won’t go into this as the information is out there.
Drawing diagrams is fun and all, but it’s time to talk about why I’m exactly writing this. After I got to grips with how state is managed in Haskell, I started separating my code into modules like every good programmer should. This commit was the next stepping stone for me, and looking at
Main.hs will show how the loop has changed. I now have a
GameState which actually contains a list of
GameState is also a nice way of streamlining the calculations for
deltaTime too, as that’s another element passed between frames, so why not put it into the state itself?
Every frame, each guy is updated using
updateGameState which transforms the
GameState into a new, updated
GameState by also fmapping
updateGuy onto every element inside the list of
Guys. This sounds really tidy, especially when all the code for
Guy is in his module and out the way (even the function to render him). The problem is that for something to change within our game, our function signature needs to look similar to
ClassToChange -> a -> ClassToChange, where
ClassToChange here is a
Guy. Another object will struggle to manipulate a guy in our collection in some way, as all of these functions are simply transformations. Unless you plan on transforming another class into a
Guy, interacting with things inside the collection is much harder than it was with imperative structures.
If items inside this collection cannot manipulate other items, then how do we get anything done? The solution is to weave all of the different things that would change your class into the update function. For instance, our
Guy takes a
CDouble that is
deltaTime. Instead, we could pass the entirety of the
GameState which contains
Guy can then sample data inside this state and make decisions on how to change based on all of this data. Because data is passed by copy, you can do whatever you like to it to make operating with
Guys easier, without any worries of impacting the original data or the performance of your game.
For simple games, this is fine. However, any complex RPG will be tough to code when you have to imagine how to feed in this data. This get’s even worse when you want to make exceptions (such as when coding cutscenes or tutorials), as you then have to figure out a way of streamlining these to make things easier. This was definitely my biggest struggle as I couldn’t get my head around how I should go about doing this in a sensible way.
Please note that I’m not saying that making a game in Haskell is impossible, I’m simply saying that there are indeed challenges to someone not used to this style of programming. I wasn’t able to completely understand everything, and these are the challenges that were presented to me. I am a true believer of Haskell being a great language to work with, and I really want someone to break through these walls.
Functional Reactive Programming
So I started looking into having dynamic data values to try and solve the challenges above. I found FRP, or Functional Reactive Programming and while I’m still unsure on whether it could be used to overcome this challenge, it was definitely something new for me to learn. I started with Reactive Banana in this commit, but later I moved on to using Reflex FRP in this commit — note that I moved my logic into
Game.hs to keep things tidy.
I’m not going to go into the details of FRP, but the gist is this: In Functional Programming, calling a function with the same arguments will always yield the same output. FRP grants access to a set of data types typically called
Events (and sometimes
Dynamics) that have values that vary over time. To put it simply, imagine these variables to be simply a
Data.Map with a time value as a key and the value as the type you want. This means that you can use these new types to create functions that fire when an
Event is triggered and that can sample
Behaviours at any point in time to get a value. A good example would be setting the location of the character to be the same position of the mouse whenever the mouse moves, or changing the orientation of an enemy’s gaze to face the player whenever the player’s position value has changed.
FRP does make things a little more complex though. If you look at Reflex’s quick reference sheet you will see that most of these FRP types rely on FRP. In terms of threads, you need to already have an existing thread before you can twist and weave it. Reflex-SDL2 is a ‘host’, which essentially sinks itself into SDL and creates some crucial
Events for your to manipulate. When an
Event fires, it contains information in context to the kind of
Event you are using. Functions like
getDeltaTickEvent will get you the number of milliseconds since the last frame tick, which means you can set up a function to render your game every time this event fires (every tick) using the information in the event.
FRP does change the process a lot, but the problem I had with regards to having objects in the world react to each other still stood — I ended up needing to create an
Event that fires every tick with the entire
GameState, so all in all it was the same problem as before, just written in a different way. It meant that when I defined the event that is triggered every tick that produces a new
Guy, I would also need to combine the tick
Event with any other
Event that would change my
The requirement of forward thinking does worry me a little bit, but it does make the game cleaner. It will definitely be easier to iron out bugs if you knew precisely all the conditions in which your character dies, but at the same time it means you can’t add new
GameObjects that call a
kill() function on a character to kill them without making your character know about specific situations. I will continue to work on
FirstGameHS and post here what I find. Forward thinking is usually a good thing, but I find that the ability to box up data and not have to worry about how they integrate into the system so much is much friendlier for games with a more intertwined world such as an RPG than it is for more simple games such as puzzlers.