2018-10-02
Let's Build a Simon Game in ReasonReact Pt. 1 Randos
UPDATE 02/08/2020 : This post has been updated to the latest ReasonML and JavaScript ecosystem versions.
Doing tutorials using the Simon Game as inspiration all started with FreeCodeCamp. But generally speaking, I think it’s a good place to start for someone like myself who enjoys building things that are moderately complex in my free time. What I like most about the Simon Game is the amount of state one has to control for and how asynchronicity plays such a big part. Also, it forces me to learn the more obscure APIs, for example Audio, I would otherwise not touch.
Code for this part of the project can be found here 👇
Reason React Simon Game Part 1
To get start👇ed locally check out the article you can either use a setup using webpack and bs-platform or the github
link above. After getting that up and running you should be ready for the next phase.
The latest version of reason-react
does not include the same kind of reducer as the old one. In order to reduce the number of changes in this article in relation to when this was last written, install reason-react-update
and it to your add to your bs.config.json
.
yarn add reason-react-update
With the help of reason-react-update
, you will be able to update state in the same way as you did using the previous reason-react
API. This way you can update the state with the sequence that is generated when the component mounts and update it when the user loses or wins the game.
Above your component declaration will be your state types. There will just be one property on state and it will be called sequence
. First, you’ll declare what a sequence is:
type sequence = array(int)
Here you are stating that sequence
is an array of integers. Pretty simple. Next let’s declare state and actions.
type state = { sequence }
type actions = | SetSequence(sequence)
The type state = { sequence }
is shorthand for type state = { sequence: sequence }
since you declared sequence
above.
You have one action, SetSequence(sequence)
, which will be used to update state with the generated random sequence.
Now that you have all of that out of the way, next you will create your component with its initial state. In this case, because you have to wait for the component to mount before a sequence is generated, the initial state will be an empty array.
[@react.component]
let make = () => {
let (state, send) =
ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
/* Wait for it... */
);
Then you will define how your reducer will handle each action. There is only one action at the moment so this is not too bad.
let (state, send) =
ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
switch (action) {
| SetSequence(array) => Update({sequence: array})
}
);
When the reducer receives the action SetSeqeunce
with the generated array
, the state will be updated with that sequence.
For now, let’s render the sequence
, which in this case won’t do a damn thing.
[@react.component]
let make = () => {
let (state, send) =
ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
switch (action) {
| SetSequence(array) => Update({sequence: array})
}
);
<div>
{state.sequence
|> Array.map(number =>
<span key={Js.Math.random() |> Js.Float.toString}>
{number |> Js.Int.toString |> React.string}
</span>
)
|> React.array}
</div>;
};
I am using the pipe operator, |>
, to push sequence
as the last argument into Array.map
. All functions in Reason are curried automatically. Along with the pipe operator, this allows you to write code that is a little easier to read.
I am using BuckleScript’s Js.Math.Random
function to generate a random number between 0 and 1, then using BuckleScript’s Js.Float.toString
to convert it to a string. This will be the key for the element. It’s probably a better idea to use a more unique identifier but this will do for now.
Each number in the sequence is converted to a string using Js.Int.toString
then made into a React
string in order to render it to the DOM correctly. Because I am mapping an array of integers I need to transform the array into a React
array to ensure the array type is properly rendered.
Like I said, not a damn thing is happening right now, but just you wait!
You now are at the point where you want to actually render some ish to the screen. You will use useEffect0
, an effect hook with no dependencies that triggers on mount. First though, let’s generate a sequence of numbers between 1 and 4 (inclusive).
let array =
Belt.Array.makeBy(20, _i =>
Js.Math.floor(Js.Math.random() *. 4.0 +. 1.0)
);
What are you doing here? 🤷♂️ Well, first you are using BuckleScript’s new standard library know as Belt
. This gives JS developers handy utilities they will commonly use for front-end development. At your disposal is a method that generates an array with a callback function that transforms each element. This function is makeBy
.
Inside of that function you are using BuckleScript’s Math
library, same one you used before. There is one slight difference you probably notice though, the *.
syntax. Now I am going to tell you a word that will make sound cool and smart: ad-hoc polymorphism.
An example of ad-hoc polymorphism can be found in Haskell. When you want to add/subtract/divide/multiply a float or integer in Haskell you use the same operators: +, -, /, *. This is because the operators are overloaded with multiple implementations and the compiler chooses which implementation based on the parameters that are provided at run-time. If the parameters are floats, it knows to add floats. If they are integers, it knows to add integers.
Unfortunately, or fortunately depending on how you view this, Ocaml does not have ad-hoc polymorphism, or a comparable implementation, and therefore has separate operators for adding/subtracting/dividing/multiplying/etc. floats and integers.
/* Adding Integers */
1 + 1
/* Adding Floats */
1 +. 1
There is some debate around whether Ocaml should change this or not. For now though, you will need to know both.
Lastly, you will notice I have an underscore before the i
. The _i
indicates the variable is not being used. The compiler will yell at you if you declare an unused variable. Shame on you!
Now that you have the randos now what? Update state!
send(SetSequence(array));
That wasn’t too hard, was it? Here is the rest of the code for completeness.
React.useEffect0(() => {
let array =
Belt.Array.makeBy(20, _i =>
Js.Math.floor(Js.Math.random() *. 4.0 +. 1.0)
);
send(SetSequence(array));
None;
});
There is a None
at the bottom there because this effect expects an Option
to be returned. A simple None
will work to satisfy this requirement.
/* App.re */
type sequence = array(int);
type state = {sequence};
type action =
| SetSequence(sequence);
[@react.component]
let make = () => {
let (state, send) =
ReactUpdate.useReducer({sequence: [||]}, (action, _state) =>
switch (action) {
| SetSequence(array) => Update({sequence: array})
}
);
React.useEffect0(() => {
let array =
Belt.Array.makeBy(20, _i =>
Js.Math.floor(Js.Math.random() *. 4.0 +. 1.0)
);
send(SetSequence(array));
None;
});
<div>
{state.sequence
|> Array.map(number =>
<span key={Js.Math.random() |> Js.Float.toString}>
{number |> Js.Int.toString |> React.string}
</span>
)
|> React.array}
</div>;
};
In this lesson you learned how to generate random numbers, declare types, and update state in reason-react
. This is a pretty good start if I do say so myself. In the next lesson, you will learn how to play the sequence and have the user interact with the game on the screen.
This article has Webmentions