Spotted Arrow

2017-08-19

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

To summarize, in the last post you learned about Pux, the Elm Architecture, and how to integrate the Pux architecture into your application. In this post, you will learn how to add animations to your game that simulate a user click.

View Layer 🖖

To get started you will need to update the view layer. Currently it demos counter functionality which is not what this series about at all. Put simply, you need 4 divs with the 4 colors of the Simon Game: red, blue, green, and yellow. To create the color styles you will need the CSS library. Let’s pull that in to get started.

bower install —save purescript-css

To make things a bit simpler in this process, and for what we are going to do in the future, create a file called Styles.purs in the App folder. This is where you are going to handle the styles for the button components. First pull in the necessary imports.

module App.Styles
  ( styledButton
  ) where

import Prelude

import CSS (Color, backgroundColor, height, width)
import CSS.Color (red, green, yellow, blue, black)
import CSS.Size (px)
import Pux.DOM.HTML.Attributes (style)
import Text.Smolder.Markup (Attribute)

You will learn more about what these imports do once you start to use them. Create a function styledButton that takes a String as an argument and returns an Attribute.

styledButton :: String -> Attribute
styledButton color =
  style do
    backgroundColor bgColor
    height $ px 200.0
    width $ px 200.0
    where bgColor = convertColor color

The styles are provided in a do block so that you can write multiple styles for a given element. You might notice there are a few things going on here that differ from regular ole’ CSS. The px acts as a function and takes a Number. Also, backgroundColor takes a value bgColor and in the where block that is where bgColor is defined. The function convertColor is not defined but if you look at the type definition for backgroundColor you will see that it expects a type of Color. That means I can’t just pass "red" because it is of type String, not of type Color. Therefore, I created a simple helper function to do this for you.

convertColor :: String -> Color
convertColor color =
  case color of
    "red" -> red
    "green" -> green
    "yellow" -> yellow
    "blue" -> blue
    \_ -> black

Pretty simple, right? Just give this convertColor a color of type String and you get the color of type Color back. That’s a lot of coloring 🖍. To make sure you account for all possible arguments, if you don’t get a color of the Simon Game, you simple pass back black.

With this in your arsenal you can make changes to the view. Create a few divs passing in the styledButton function and giving the div a color. To start let’s pull in the necessary imports.

import App.Styles (styledButton)
import Text.Smolder.Markup (text, (#!), (!)) -- added (!)

Now update the view function:

view :: State -> HTML Event
view state =
  div do
    div ! styledButton "red" $ text ""
    div ! styledButton "green" $ text ""
    div ! styledButton "blue" $ text ""
    div ! styledButton "yellow" $ text ""

Because styledButton is an Attribute you have to prepend it with a !. Also, I am probably doing this wrong, but I couldn’t find an easy way to create a div without appending $ text "" at the end for it to compile correctly. If you have an alternate solution, please share in the comments below!

For kicks and giggles run pulp server and you should see a bunch of different color divs in your browser 🎉.

You now want to work on simulating a user click. What that entails is when click on a button the button will make a sound and the color of the button will darken. I will just focus on the later part of that in this post, with the former being covered later on. To start, let’s name this type of Event in the Events.purs file while gutting all previous logic that was in there. You can call this event UserClick or anything else that makes sense to you.

data Event = UserClick String

The tag UserClick will take a string as a value, which will be the color of the button that is clicked. Let’s now add this to the view function.

div ! styledButton "red" #! onClick (const $ UserClick "red") $ text ""

This will be implemented for each button in the view.

Now you can update the foldp function to handle this Event and gut whatever logic was there before while you’re at it.

foldp :: Event -> State -> EffModel State Event AppEffects
foldp (UserClick color) state =
  { state: state
  , effects: \[\]
  }

So let’s walk through how this will actually work. The way in which I implemented this before was in two steps. First, update the state with the value of the button that the user clicked as being the currentColor. Then, with the help of an effect, delay a computation for 300 milliseconds and then after that time is up, update the state back to currentColor: "". There is quite a bit going on here so let’s first modify the new state record, then handle the update in the foldp function.

type State =
  { currentColor :: String
  }

Because you will need this later, create an init function that is the initialState of the application, export it in the module above, import it into Main.purs and add it as the initialState in the Pux setup.

init :: State
init =
  { currentColor: ""
  }

Now the foldp state will be updated when a button is clicked like so.

foldp (UserClick color) state =
  { state: state { currentColor = color }
  , effects: \[\]
  }

The state { currentColor = color } is the record update syntax for creating a new record with all the fields returned with the new updated field value.

Now based on the state you can change the value of the color of the button. Let’s update the view to pass in the currentColor to div elements.

div ! styledButton "red" currenColor #! onClick (const $ UserClick "red") $ text ""

\-- Note, make sure this is on 1 line

To do this I am destucturing the state value passed to the view function.

view { currentColor } =
  --- more stuff here

This is not going to work quite yet. That’s because you need to update the styledButton function to take another argument. That argument will determine the color of the button. It will look a little something like this:

\-- Added desaturate
import CSS.Color (red, green, yellow, blue, black, desaturate)

styledButton :: String -> String -> Attribute
styledButton color currentColor =
  let
    converted = convertColor color
    bgColor =
      if currentColor == color then desaturate 0.5 converted
      else converted
  in
   style do
      backgroundColor bgColor
      height $ px 200.0
      width $ px 200.0

Let’s run a build and see what this does. Run pulp server and interact with your buttons.

Nothing too crazy going on here. However, what you need it to do is actually change color and then have that color change back after about 300 milliseconds or so. Let’s implement that.

In Elm this is implemented as a side-effect from the result of a Cmd msg. In Pux this is handled as just an effect. In my previous implementation in Elm I used the elm-delay library, so when I shifted to PureScript and Pux I looked for a similar tool and found the delay function. This is what you will use to create the animation.

First, add another Event that declares a reset of the color in your Events.purs file.

data Event = UserClick String | ResetColor

Then add another pattern in your foldp function to match this action.

foldp ResetColor state = noEffects state { currentColor = "" }

Pux has a nice helper function noEffects that is short-hand for:

foldp ResetColor state =
  { state: state { currentColor = "" }
  , effects: \[\]
  }

The delay function takes a Milliseconds type. So let’s pull in a library that provides that type and import it.

bower install --save purescript-datetime
import Data.Time.Duration (Milliseconds)
import Control.Monad.Aff (delay) -- both in Updated.purs

Then inside of the UserClick event you can update the effects to first delay 300 milliseconds and then execute the ResetColor event.

foldp (UserClick color) state =
  { state: state { currentColor = color }
  , effects:
    \[ delay (Milliseconds 300.0) $> Just ResetColor \]
  }

Another operator! The $> is known as voidLeft. It says, hey, this computation to the left of me, just ignore that and return what is to the right. To the right you have Just ResetColor which is the correct return type of Maybe Event. Cool, huh?

Let’s run over to the browser and see what you get. Run pulp server and see what the output is.

Great, it works as expected!

Summary ✅

In this post you learned:

  • How to style views using PureScript
  • How to create a delay effect using the Aff monad
  • How to destructure values in a function
  • What the $> operator is

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. Also, check out all the library document on Pursuit. If you have any questions check out the Slack or Gitter channels. Until next time, keep hacking!

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