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.
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!
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:
This article has Webmentions