Hi! before introducing this post let’s do some myth busting! 😎
Purely functional programming languages have no place in the browser and Haskell is a magical genius language that everyone talks about but no one really uses.
Truth is, haskell is not a difficult language to program in and it’s past reputation of never being used in the industry has always been uncalled for.
As for how we can use it to program applications for the web, there are some really cool projects that make this possible and this post will show you just that.
This tutorial assumes that you have basic knowledge of haskell or some other functional programming language. It also uses version 0.4 of the Haste compiler so you should choose to install this version if you plan to try out the code along. Or you may want to follow this code instead ported for version 0.5.
Rules of the game
- There are two paddles and one ball.
- When the ball hits any of the walls (right, left) or paddles, it should change its direction.
- When a ball hits the ceiling or floor, the game should end.
- Paddles should be controlled by the player’s mouse. (Not really a rule of the game but hey!)
First, we import some useful functions and datatypes from the Haste libraries. Create a file called pong.hs and add the following statements to the top of the file.
Step 1 - Defining Our Game state and Initial Declarations
We start by declaring a datatype
GameState. We’ll use it to describe the state of our game at any given moment, using values like the ball and paddle position, current score, speed of the ball. Add this code to pong.hs.
Note that the
paddlePos field has a single value. You might expect that we need to store the full dimensions of the paddles (x and y coordinates) but as you will see very soon, the other values never change and so are declared as constants instead of passing them around in our game state.
Now we define constants to be used throughout the program. These include the dimensions of the canvas and partial dimensions of paddles, radius of the ball etc.
Also define a state describing the initial values of our game.
Step 2 - Canvas, Paddles and Ball
Our animation is going to be on an HTML canvas but we can make life easier by abstracting the process of creating a canvas. We define a function
mkCanvas :: Double -> Double -> IO Elem
The type signature tells us in a nutshell that we give it two doubles (width and height of our canvas) and receive instructions for creating a HTML element as per Haskell monads. This is the body of the function.
This function creates a new canvas html element using the newElem function from the Haste.Graphics.Canvas library, sets its dimensions (height, width) and assigns some other housekeeping properties like color, border etc.
This library also gives us functions for creating basic shapes and pictures. Here are some we will be using
A Point is simply a pair of floats which may or may not represent x and y coordinates in pixels
Now back in our game, we can use the color function to define our color theme.
For our ball and paddles, we use a familiar technique of abstracting the creation process using factory functions. Add this to pong.hs.
All three functions are very similar to each other. Their type signature tells us they receive a set of values and return a Picture monad
Picture(), instructions for drawing their respective shapes on a canvas. the
drawText function writes text on the canvas.
Step 3 - Drawing on the canvas
At this point we have functions for creating our paddles, ball and canvas. It’s about time that we actually use them to well, create paddles, ball and a canvas.
We want to draw a picture of our game on the canvas and to do that we first need a picture of our game. This is where our
GameState type comes into play (Pun intended! 😒).
gamePicture function takes as an argument, a
GameState and returns a Picture monad. So it basically gives you a picture based on our given game state which we can the render on our canvas. So what’s going on here?
In a do block, we create 4 pictures (Picture monads to be technical) to draw on the canvas.\
- The ball. Using the ballPos field of the given state as our argument to the
- and 3. The top and bottom paddles. Using the
paddlefunction and their coordinates. Notice that we only needed the paddle’s x coordinate from the state, the rest can be inferred from the paddle’s orientation (Top or Bottom). The top paddle starts at height 0 while the bottom baddle starts right before the end of the canvas (height - paddleHeight).
- The text field showing the score on the canvas using the
gamePictureproduces the picture, the
renderStatefunction will do the actual rendering onto a canvas.
The render function is imported from haste and draws a given picture (or series of pictures using a do block) on the specified canvas.
We move on to (finally 😁) create our canvas.
We’ll create the canvas inside our
main function. The main function
main :: IO () in haskell, unlike any other function has to be called main and is the entry point of our program just like in C, Java etc.
At this point you can check that the screen is drawn as it should. Compile the program by running this command in a terminal.
Step 4 - Animation
We continue with ball animation. The
ballSpeed field of our
GameState type is useful for this purpose. Simply put, everytime the screen is redrawn, we want the ball to change its position on the canvas. Incrementing the x and y coordinates of the ball by a value everytime gives us this effect and these values are what we have as
ballSpeed in our
moveBall increments the x and y coordinates of the ball and returns a new
GameState with the new coordinates. Note that there is no mutation/side effects here as the function does indeed return a new value.
Our paddles will be controlled by the mouse so we need to listen for mouse events, specifically the mousemove event.
To add an event listener to our game, we go back to our
Now REMOVE the last statement
renderState canvas initialState from the
do block as we will no longer be needing it. Instead, add the following statements to the do block.
Remember that thing about Haskell being a purely functional language? Whoever said that didn’t finish the entire story it seems. Variables are immutable in Haskell but there are a few ways to create references whereby we can change what the reference points to.
We won’t be able to do a lot without interacting with the real world now that we need to process mouse events from the user, so we use the IORef data type to do the job. This is the first sighting of mutation in our code but don’t be alarmed if you haven’t seen this before.
Data.IORef makes it quite easy and safe to do this.
The first statement creates a reference object of type
IORef GameState using the
newIORef function. This creates a new
GameState reference with our initialState, allowing us to modify its contents throughout our code.
The second statement adds a mousemove event to the canvas element. The
onEvent function provided by Haste takes an
Elem and an
\mousePos -> do movePaddles mousePos stateRef receives the mouse position
mousePos and moves the paddles whenever the mousemove event fires.
Here is the code to move our paddles.
readIORef extracts the
GameState referenced by our state reference
atomicModifyIORef changes the referenced content using the extracted state. The
atomicModifyIORef function unlike the
modifyIORef mutates the variable atomically, preventing race conditions and the likes. Our state is simply updated by centering the position of the paddle around the mouse coordinates
Now we move on to define our primary animation function. Let’s call it
animate. It takes a Canvas, an
IORef GameState (for rendering onto canvas) and returns an IO monad
IO () .
animate updates that state with the update function, writes the updated state back to the state variable then waits 30 milliseconds before repeating the whole process so it runs in a loop.
Later on we’ll add a few more functions used to compose the update function but for now it consists only of the
atomicWriteIORef is similar to
atomicModifyIORef but overwrites the variable with a new state. This feels more efficient here since we can extract our pure state, pass it around various functions composed by the update function and then, only once do we need to commit the sinful act of changing the value referenced by our
IORef variable 😇.
Now add the following as the last line of our main function
animate canvas stateRef
Our main function should look like this
Step 5 - Detecting Collision
We’ve got our ball and paddles moving and what’s left is making the ball bounce.
The rules of our game requires the ball to bounce when it hits any of the walls or paddles. Let’s start with the paddles. Here’s our function
paddleHit to detect collision between the ball and the paddle
paddleHit checks if the ball coordinates are within the dimensions of the paddles and if so, returns a new
GameState wherein the ball now heads in the opposite direction. This is accomplished by simply negating the vertical speed value of the ball. We also increment the score for each time the ball hits the paddle.
Note that for the paddleHit function, the
and function short-circuit evaluates, so once an expression returns false, we simply return our state unchanged.
Detecting collision with walls is similar to the paddles.
We simply check if the ball crosses the right or left wall and if so, negate the horizontal speed of the ball.
Now we have our functions to detect collision with the walls and paddles. Let’s use them to compose our
update function. Go to the
animate function. Currently the update function defined within it consists only of
moveBall so add our two new functions to it by replacing the definition of update with this line of code.
The last rule of the game to be implemented is that the game should end when the ball hits the ceiling or the floor. That means we also need to check for ball collisions with the ceiling and floor of our canvas. Add this function to the code.
gameEnded returns a
Bool telling us if the game should end. That is, if the ball has collided with any of the vertical boundaries that isn’t the paddle. To make use of this function, we go back to our
animate function and make our decision on whether to stop animating or not, based on the reply from
animate function should now look like this
Wow! this is a really lengthy tutorial 😵. We were bound to reach this part at some point and good news is you now have written a game of pong in haskell.
You may have seen that I included some extra features in the live demo such as start, restart buttons and speed of the ball increasing during the game. Browse through the code to see how these were implemented, then try to implement them and add more features to your game or fork this one on github and continue from there. If you get stuck in implementation somewhere, leave me a message/comment and I’ll be sure to help you as much as I can.