Spotted Arrow

2017-08-12

Let’s Build a Simon Game in PureScript Pt. 1

If you have read any of my other articles then you probably know I am getting into functional programming. It pulls in many concepts from the field of Maths and I have always enjoyed a good algorithm or interesting number fact. My path to PureScript was not a straight shot though. When I first saw a PureScript project I said to myself: “That’s a lot of modules! What does that operator do? What is going on!?” So for a few months, I worked on learning Elm, another functional programming language that compiles down to JavaScript.

Elm had nice error messages, a terse syntax, and a welcoming community. I was in my comfort zone. But after I finished creating a Simon Game in Elm, I felt the challenge was right there for the taking. To make the transition to PureScript a bit more seamless I am choosing a front end library in Pux that mimics Elm in many ways. Sorry, I wasn’t ready for you Halogen!

What is PureScript and Why Use It? λ

PureScript is a functional, strongly typed 💪, programming language that compiles down to JavaScript and/or Node. A language like PureScript allows me to write programs that are more declarative and expressive, all the while handling data in such a way that I don’t have to see undefined is not a function ever again. It forces you to know types well, to think about programs in greater abstractions, and to use those abstractions to build more safe and expressive code. But don’t take my word for it! 📚 🌈

User Stories 👱

So what does a Simon Game entail? User stories to the rescue!

*These user stories are adopted from the the freeCodeCamp challenge Build a Simon game which initially inspired this madness.

As a user:

  • I am presented with a random series of button presses
  • Each time I input a series of button presses correctly, I see the same series of button presses but with an additional step
  • I hear a sound that corresponds to each button when I click the button and when the series of buttons plays
  • If I press the wrong button, I am notified I have done so and the series of button presses start over
  • I can see how many steps are in the current series
  • If I want to restart, I can hit a button to do so, and I return to a single step
  • I can play strict mode where if I get a button press wrong, the game notifies me and the game restarts the current random series from one
  • I can win the game by getting a series of 20 steps correct. I am notified of my victory and I restart with a new random series of buttons presses

Sound like something you’re into? Well, if you are, come along further down this page and I can show you a world…where you make a Simon Game 😄.

Getting Started 💥

To get started on this project you will need PureScript and it’s build tool pulp. There are a few ways to get these but I think the easiest way to do this is npm i -g purescript pulp bower. Because pulp uses bower as its default to handle dependency management, bower is needed as well.

One of the nice things about PureScript’s pulp is you can get up and running with a project relatively easily. Just create a directory folder with simon in the name, cd into the folder and run pulp init. A project structure is scaffolded out so you don’t have to start from scratch like grandma’s biscuits 👵. The pulp CLI has a lot of great features. If you run pulp -h you can see all of the commands that are available to you. There are even more commands in the commands. Commands inception! To do this run pulp <command> -h. You’ll be glad you did!

Development Mode 💻

Let’s just get up and running to make sure things are working as expected. First, we will need an index.html file to render our script. Create an index.html file at the root of your directory. Add a script tag with a src of /app.js. To serve up your application in a development environment run pulp server. This command will run pulp build and serve up your application from localhost:1337. You can change the port by running pulp server --port 3000 if you feel you aren’t leet enough at this point 😄. But I think you are so let’s carry on. If you open up your developer tools in the console you should see Hello sailor!. You have successfully created a project in PureScript, okay, now let’s go home. Just kidding 😉.

What is a Monad? 🌯

Let’s have a look see in the Main.purs file. The main function, just like in Elm and Haskell, kicks off the whole program. But there are more interesting things in this file, like the weirdly long library names with words like monad in them. For example, there is the Control.Monad.Eff and Control.Monad.Eff.Console libraries. Like you maybe, when I saw these I just ran for the hills 🏔. I didn’t know what they meant nor did I want to know. It all looked like magic to me and I already was feeling like I knew nothing.

In reality, though these things are quite simple. A monad is a burrito 🌯. Got it? Cool. Actually, programs written in FP languages like Haskell which have side-effects that are handled outside of the program are taken care of in a special box called a monad. This may seem strange to you because you might be used to putting console.log all throughout your program like me. But in Haskell and PureScript, functions are pure. When I say pure I mean that a function given some input will have an expected output. Any side-effects explicitly handled outside of that function are done so within the safe confines of a box. Side-effects include console.log, an asynchronous request, interacting with the DOM, so on and so forth.

Why is this even necessary, Adam? It removes the implicit nature of your program and forces you to be fully aware and conscious of what is going on and to handle it accordingly. At least that is what it does for me. Going through building the Simon Game forced me to think in greater detail about my program and what it actually does instead of thinking non-sensibly about what is going on.

Generate A Random Sequence 🔢

At the core of this game is a random sequence of 20 things. That is where we will start. This is not something that I was able to figure out on my own. When I first started I was stumped as to how I would do this in PureScript. In Elm, there are generators for this. The generators fit nice and neat into the Elm architecture. They generate a Cmd msg and the Elm run time handles it from there. So I turned to the PureScript community and got some incredible help from Christoph Hegemann. He even wrote a gist for goodness sake, look at this guy! Phil Freeman, the creator of PureScript chimed in as well, which is really cool. If you need help I recommend you hit up the functional programming group on Slack or Gitter.

To satisfy the user story, I am presented with a random series of button presses, you will create a function called generateRandoSequence. The definition for this function will be:

generateRandoSequence :: ∀ eff. Eff (random :: RANDOM | eff) (List Int)

Oh lord! What are you doing to me, Adam? And why is the first letter of your name upside-down? The is the forall symbol in Maths. There is no easy way to explain up above, I am sorry. But basically what it’s saying is, hey, for each random effect, and any other effect (eff), this function will return a List of Int. A List is a linked list, which works a bit differently and in some cases are more performant than Arrays. An Int, behaves like an actual Int in PureScript, whereas in JavaScript all numbers are floats even if they look like an Int.

The actual function will make use of the replicateA and the randomInt functions. You pass a range of values, inclusive, to randomInt that you want to choose from. In this case you want to choose between 1 and 4. Then you pass that monad, being that it is a side-effect, to replicateA along with the number of times you want that monad to be replicated. The A in replicateA I think stands for applicative. I don’t really want to explain what an applicative is because I can’t do it well so you might want to check out an article by Tom Harding to get a better idea. Just to point it out, in our case, the applicative is the randomInt monad. In summary, our function will look a little something like this:

generateRandoSequence :: ∀ e. Eff (random :: RANDOM | e) (List Int) generateRandoSequence = replicateA 20 (randomInt 1 4)

We are missing a few libraries to kick this off so let’s get those.

bower install --save purescript-lists purescript-unfoldable purescript-random

Then we can pull in those libraries to use in our Main.purs file.

import Control.Monad.Eff.Console (CONSOLE, logShow) — Added logShow import Control.Monad.Eff.Random (RANDOM, randomInt) import Data.List (List) import Data.Unfoldable (replicateA)

Let’s test run our new function to see what we get when we log it out.

main :: ∀ e. Eff (console :: CONSOLE, random :: RANDOM | e) Unit main = do result <- generateRandoSequence logShow $ result

If you’re lucky, when you run pulp run you should see a “random” list sequence in your terminal. You can also run pulp psci then type import Main and call the function generateRandoSequence and you should see the same thing. Cool, right?!

Summary ✅

Well, this concludes part 1 where you generated a random list of 20 integers. “How useful is a random series of integers?” You ask. That my friend is something we will delve into when we get to part 2.

In this post you learned quite a few things:

  • What a monad is (kinda)
  • What an applicative is (sorta)
  • What side-effects are and how they are handled
  • How to use pulp
  • How to install dependencies from bower
  • How to import modules
  • How to write a function and its signature
  • How to generate a random integer list
  • How to run a program and use the PureScript REPL

What you didn’t learn:

  • What is $
  • What is <- in a do block
  • What is | in a function signature
  • What the f is Eff

The things that I did not cover would be good for independent study. I recommend looking at the book PureScript by Example as well as the PureScript Documentation to get some background if you haven’t done so already. Until next time, keep hacking!

Part 1 of this project is tagged and can be found on Github here:

Friendly Tip: Throughout out the development process I was not getting the most up-to-date JavaScript bundle in my browser because Chrome kept caching it. That’s why I suggest using a Chrome extension like Cache Killer in order for you not to go through the pain I did.