Building Eloquence game in ElmElm ·
I've decided to try out Elm language by implementing a game. At first glance, writing an article about it may appear to be as original as writing “Yet another tutorial about creating a Ruby on Rails blog”. Actually, it is 😆
I, strange though it may sound, consider Elm as a smooth introduction to Haskell and functional world. It combines such features as immutability and strong static typing (strong enough to ditch writing tests 😅) and “beginner-friendliness” by virtue of avoiding “ominous” abstractions for managing side effects. Still, it stays simple and easy to get the hang of.
As an exercise, what about building one of the games of the amazing Elevate App? The rules are simple: just provide as many as possible synonyms to the highlighted word in the sentence.
The source code is located here . I'm just going to highlight basic moments in the article.
Here’s how the complete app works:
Initialize the app's backbone
Create Elm App tool was used to initialize an application with no build configuration.
Standard functions are generated (
view, etc...) to be filled in the future.
Model is the state of the application. First and foremost, let’s specify its type. Since the whole state consists of three stages (before a round is played, playing the round, after the round has been played), it should have corresponding types:
It’s just a constant which identifies that the app is in the state of awaiting a round to be played.
The stage provides a structure for providing an experience of viewing a sentence and enter the synonyms for a specific word in the sentence.
- words - the correct synonyms entered by a user
- sentence - a record which contains a text to display, a target word and the allowed synonyms for the word
- elapsed - the number of seconds passed from the beginning of a round
- word - the current word being entered by a user
- wrongWord - a flag which identifies whether the current word is correct
The stage displays the number of correctly entered synonyms and a random synonym which could have been entered (if there is one).
If a user enters all the possible synonyms for a word, there won’t be a synonym,
that could be shown as a hint in the GameOver
state. All of a sudden hint won’t equal
null or undefined in that case. hint
Maybe String , which means that it can be either
Nothing . The type system requires processing every of
these cases, which is one of the reasons why we are not going to meet cool
Cannot read property 'a' of null in Elm.
The function which specifies the initial state of the application. In our case,
View is a way to view the state as HTML.
Since the function just renders a particular screen depending on the type of the model, I won’t put the source code in the article. It still can be found here .
The application needs to update the timer every second and change the state to GameOver after the 20 seconds from the start of a round is passed. That’s why a subscription needs to be defined.
Subscriptions is how your application can listen for external input.
That will call
update function every second with a Tick
update function is the most complicated part of the application,
which contains the decent part of the logic (at least in my case).
It is a way to update the state depending on the type of the message emitted as a
result of users’ interaction with the application.
StartRound and GameOver identically handle
the same types of messages, hence use the same function. A user should be able
to start (or restart) a round in these particular stages. That’s why if the
message passed to
update is of type StartGame
a hard-coded initial playing state is returned.
There are two things, that I would like to point out here:
A trick for setting a focus on a field is used here. As you see, it must be done
by sending a specific task/command to the update function (
( playingModel, focus )), not by
$(fieldId).focus()from an arbitrary place, which gives you control over side effects in the application.
let in, which I might overindulge in, lets you provide helper functions scoped to the given function. Right, the whole function became large, but it still easy to be read, because the logic separated into additional functions.
The function handles users’ interaction during a playing of a round.
There are two things, that I would like to explain here:
- AddWord either stores the correctly entered word or marks that the word is invalid (to paint the input field in red) and after 200 milliseconds clears the field (needs to be done via commands)
- Tick either updates
elapsedor ends a round by sending a command with a randomly generated number. Note, that generating a random number is also a side effect, which is, as expected, addressed by sending command.
What I really liked during my experience of building this game is that the development was led by the language itself. The Elm Architecture, type system, and even immutability was defining the way the application must have been built. It was also interesting to try these and other concepts (such as sum types) in action and see how they do something tangible👆