The best way to learn new technologies is to make something interesting, fun, engaging and unconventional. In this tutorial I will walk you through a Mastermind game I built to show some of the most fundamental ideas behind the React library, ECMAScript 6 specification and Webpack module bundler.
ECMAScript 6 is the newest edition of the ECMAScript Language Specification, which standardises the JavaScript programming language. ES6 introduces a new syntax and features which we will take a closer look at later in this article. It’s worth mentioning that not all of the browsers support those fancy features so it is important to use a special compiler like Babel to make sure that our code written in ES6 will run correctly on older browsers.
React is a library made by Facebook which uses a concept of Virtual DOM
, which aims to apply as few mutations as possible. When the element’s state changes, React decides whether an actual DOM update is necessary by comparing changes in his Virtual DOM
with the actual DOM.
React’s philosophy also leans towards clean separation between components. One of the library’s features is a one-way data flow
which means that child components cannot directly affect parent components.
Before we start I want to let you know that the whole repository is available on Calanthe/mastermind.
Setup and initialization
As mentioned before, in order to run ES6 smoothly, we need to use Babel which will compile JavaScript ES6 syntax into ES5. We will also use JSX which is a JavaScript syntax extension, very similar to well known XML
. Before starting coding, we need to setup the project and configure compilers. Let’s use Webpack to build working JS files:
//install webpack globally
npm i -g webpack
//install necessary modules locally
npm i --save-dev webpack@^1.0.0 babel-loader babel-preset-es2015 babel-preset-react path
The whole Webpack configuration is specified in the webpack.config.js
file, which defines the inputs, the outputs of the project and types of transformations we want to perform. Some of the most important fragments are:
module.exports = {
entry: {
game: './game.js' //which file should be loaded on the page
},
output: {
path: path.resolve('./dist'), //where the compiled JavaScript file should be saved
filename: './game.js', //name of the compiled JavaScript file
},
module: {
loaders: [
{
test: /\.js?$/, //translate and compile on the fly ES6 with JSX into ES5
exclude: /node_modules/,
loader: 'babel',
query: { //query configuration passed to the loader
presets: ['react', 'es2015']
}
}
]
}
};
If you run the npm run watch
specified in the package.json
file:
"scripts": {
"watch": "webpack --progress --colors --watch --config ./webpack.config.js",
"build": "webpack --config ./webpack.config.js"
}
You will be able to load your file:///PATH_TO_FOLDER/dist/index.html
file in the browser.
As we specified in the configuration, our entry file is ./game.js
:
import React from 'react';
import ReactDOM from 'react-dom';
import Mastermind from './src/mastermind';
ReactDOM.render( //initialise game with specified codeLength and Map of colors properties
React.createElement(Mastermind, {codeLength: 4, colors: new Map([[0, 'zero'], [1, 'one'], [2, 'two'], [3, 'three'], [4, 'four'], [5, 'five']])}),
document.getElementById('mastermind')
)
As you can see at the top of the file, there are imported three modules: react
, react-dom
which serves as the entry point of the DOM-related rendering paths and our Mastermind code.
After importing modules, the Mastermind code is initialised and rendered into the DOM
within the element whose ID
is mastermind
, defined in the ./dist/index.html
file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="./styles.css">
<title>Mastermind in React and ES6</title>
</head>
<body>
<div id="mastermind"></div>
<script src="game.js"></script>
</body>
</html>
In the script
tag we include the game.js
file which should be in the same dist
folder as index.html
. If the Webpack’s entry and output points are configured correctly, the ./dist/game.js
file should be compiled based on the aforementioned ./game.js
source file.
Introduction to the React components
The project is setup so let’s focus on the most interesting part - programming the Mastermind game itself. The whole game module will be divided into a few components. A component is a React class, which should be responsible for one thing only. As mentioned before, React has unidirectional data flow
so it is important to keep as many of components as possible stateless
. As Facebook’s developers recommend:
A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via props. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
Let’s look at the top components I’ve created for the Mastermind game:
const Rules = React.createClass({...});
const DecodingBoard = React.createClass({...});
const CodePegs = React.createClass({...});
const EndGame = React.createClass({...});
const Mastermind = React.createClass({
(...)
render: function() {
return (
<div>
<Rules state={this.state} toggleRules={this.toggleRules}/>
<div className="clearfix">
<DecodingBoard state={this.state} activatePeg={this.activatePeg} submitPegs={this.submitPegs}/>
<CodePegs state={this.state} colors={this.props.colors} activatePeg={this.activatePeg}/>
</div>
<EndGame state={this.state} reloadGame={this.reloadGame}/>
</div>
);
}
});
Mastermind
is our stateful
component which passes state to its child components: Rules
, DecodingBoard
, CodePegs
and EndGame
. Of course those sub components can also encapsulate other child components, like DecodingBoard
and Row
in this example:
const SubmitButton = React.createClass({
render: function() {
return (
<button></button>
);
}
});
const Row = React.createClass({
render: function() {
(...)
return (
<div>
<div className='left'>
<DecodeRow (...)/>
</div>
<div className='left'>
<SubmitButton (...)/>
</div>
<div className='right'>
<HintsRow (...)/>
</div>
</div>
);
}
});
const DecodingBoard = React.createClass({
render: function() {
(...)
rows.push(<Row (...)/>);
(...)
return (
<div className="decoding-board left">
{rows}
</div>
);
}
});
The whole components’ hierarchy is more readable on the following diagram:
This is how those components look like in the Chrome’s React extension:
One of the first new features of ES6 I want to introduce to you are modules
. If you noticed earlier in the ./game.js
file, I imported three modules at the top of that file:
Modules are already known to the front-end community. There were implemented via different libraries like RequireJS or Browserify but fortunately now we can use the native solution built in ES6. One of the principal of modules is that they have to be specified in one dedicated file. Every module is a singleton, which means that there exists at most one instance of the module regardless of how many times it was imported. The whole module is executed once it is loaded and it’s content is encapsulated, preventing the pollution of the global namespace.
Exporting modules in ES6 is very easy and straightforward:
We have a few React classes defined in the ./src/mastermind
file but we want to expose only one value. The default export
syntax means that the other ES6 modules can only access that one particular class, and it can only be read without any modifications. The ./src/mastermind
module exports the Mastermind
class because it is a child module and can’t export modules that have been defined elsewhere.
Props versus states
Components in React are state machines, which means that the DOM
is updated only based on the new state.
Components have also predefined methods, called lifecycle methods
which are executed at specific points in a component’s lifecycle.
There is no point of reviewing all of them but it is worth keeping in mind that there is: a componentWillMount
, invoked just before rendering; a componentDidMount
invoked just after rendering; and a shouldComponentUpdate
where we can specify if there is a need to render particular component.
Let’s take a deeper look at the Mastermind
class itself. As mentioned before, it is the main React class which encapsulates sub classes and pass data to them. In React there are two kinds of properties: props
and states
and the difference between them is crucial to understand the whole philosophy behind React.
Both of them are plain JS objects and their changes trigger render()
update. Props
are passed to the child within the render method of the parent and are immutable in the child components. The child components should be as stateless
as possible and just render those props
values. The best example of such stateless
component in the Mastermind game is the SubmitButton
:
Based on props
passed by it’s parent module, it renders a <button>
element with proper attributes like class name and onClick
method. As you can see, those props
values are accessible here via this.props
object.
But how can we change those passed values? This is what states
are for. Contrary to child components, parent components are stateful
, which means that usually based on the user’s action, they can mutate the states
values and pass them down. That’s why props
and state
are related. The state
of one component will often become the props
of a child component. Like here:
The codeLength
and colors
are passed to the Mastermind
module and accessed there by this.props.colors
and this.props.codeLength
values:
The selectedPeg
state
is initialised in the predefined getInitialState()
function which is executed exactly once during the lifecycle of the component and sets up all state:
Despite the getInitialState
method, the Mastermind
module also contains other functions:
This is considered to be a good practice, because the stateful
Mastermind
component has all of the callback functions and pass the state down through props
.
When the callback functions call this.setState()
method and mutate states
, React takes care of re-rendering components.
As you probably noticed, each of the components have a render
method. It is a required method responsible only for returning the single child element. As you can see it always returns either HTML
tags or another child component which will eventually render to HTML.
JSX
As mentioned before, I used the JSX
syntax extension. It is possible to skip JSX
and use plain Javascript. Instead of:
return (
<div className="decoding-board left">
{rows}
</div>
);
I could write:
I prefer to use the JSX
extension, because it is more familiar syntax for defining tree structures with attributes.
I think that I covered all of the most important basics of React library. It will be easier now to understand how the Mastermind game works.
Generating the code to guess the peg colors
I assume that you already are familiar with the rules of the Mastermind game. If not, take a look at this useful Wikipedia description.
The first thing that needs to be done at the beginning of the game is create the auto generated code that the user will have to guess. We do that in the getCode
method:
The above code is straightforward once you understand the new ES6 arrow functions
. The expression generateCode = (i) => {i};
is a shorter syntax than generateCode = function (i) { return i };
.
It not only looks much better but it also lexically binds this
, so there is no need to use .bind()
or var that = this
, anymore.
I also started using the keywords const
and let
to declare a variable. One of the biggest drawbacks of ES5 is that var
creates a variable scoped within its nearest parent function. This leads to hoisting issues which sometimes requires using closures
to fix. Let
scopes the variable to the nearest block, which includes also loops and conditional statements. The main difference between let
and const
is that the const
declaration creates a read-only reference
to a value so once defined it cannot be re-assigned.
Despite not being fully supported by React I used Map to store data about code pegs and current guess made by the user. The Map
is a new data structure in ES6, which itself is just an object with a simple key-value map. The keys can be any type, including strings and objects. It is very easy to compare two Maps
, get its size and alter its values.
I just declare code as a new Map()
object and for each of the four code pegs, I generate a random number from 0-5 which is later on represented as a particular color. The generated values are stored in the Map
object by using a code.set()
method.
The getRandomArbitrary
function is an example of an ES6 function with a default parameter. This means that if no value is passed to the method this.getRandomArbitrary()
, the parameters min
and max
are initialized with default values function(min = 0, max = 5)
.
The times
method is a functional-ish method created to prevent me from using for var
to iterate n times. I found that solution really clean and useful.
Building the board and pegs
The decoding board located on the left consists of ten rows, where each of them includes:
DecodeRow
, where the user makes his/hers attempts,SubmitButton
, which verify the selected value,HintsRow
to indicate which pegs are chosen correctly.
The key
value passed down to each child component is a necessary value which helps React handle DOM
changes in a minimal way.
The classNames module originally was part of React, but now it stands up as an additional utility library. It is a really useful module which helps joining classNames
together.
Instead of:
I have now definitely more readable solution:
Some interesting stuff is happening in the DecodeRow
class. First of all, I didn’t want to update all of the already guessed rows. In such situations, the shouldComponentUpdate
method comes in handy:
The shouldComponentUpdate is very effective in situations when we are sure that the re-rendering of the component is redundant.
The nextProps
variable has access to the properties passed to this component and based on that we can calculate if there is a need to proceed with re-rendering.
The render
method of the DecodeRow
component looks like this:
The peg’s css class depends on the preselected pegs located on the right hand side, which I just called CodePegs
. This is the place where the user can select colors he/she want to use on the decoding board on the left.
Those selected values are stored in a Map
called currentGuess
and they can take any value predefined in the ./game.js
values: zero, one, two, three, four, five. It is important at this point to pass the correct value so the styles will be applied accordingly to the chosen pegs from the right.
The Peg
component is responsible for displaying markup based on the passed props
. This is another example of a stateless
component:
Every peg in the game (on both left and right sides) is represented as <input type='radio'/>
which is responsible for storing necessary values and corresponding <label>
element which is used for styling purposes only. This is how pegs are styled:
As you probably already figured out, the className
value sets the css class attribute. It is called className
instead of just class
, because JSX
gets translated to JS, where class
is already in use.
User actions and peg selection
Pegs on the decoding board located on the left change their state (which is represented as colors) based on the selected peg on the right. By default the first peg (orange one) is selected, which is marked with a darker border.
As mentioned before, the Peg
is the lowest module in the hierarchy. The user actions are handled by the onClick
attribute onClick={this.props.activatePeg}
.
The activatePeg
method is defined in the Mastermind
module:
The new selectedPeg
state
is set when one of the right pegs was selected. Every peg in the game is represented as an <input>
so its value is really easy to get.
After changing the selectedPeg
state, React takes care of the re-rendering and the selected peg is set as active.
You can see here the startsWith
ES6 method, which determines whether a string ‘peg’ begins with the characters of another string, in this example - name of the input on the CodePegs
board.
In the situation when the peg on the decoding board is selected, the currentGuess
state is updated: this.setState({ currentGuess: this.state.currentGuess.set(event.target.value - 1, this.state.selectedPeg) });
.
The currentGuess
is a Map
structure, where a proper value identified by a event.target.value - 1
key, has to be changed to the preselected peg: this.state.selectedPeg
.
Once again, after changing the state
, React renders the updated board.
Let’s take a guess!
After choosing four pegs, the user can submit a guess to check if they are correct. The submit button is visible only when all of the four pegs in one row are selected.
On selection, the this.props.submitPegs
is called:
First we need to copy the Map
object which represents a randomly generated color code (which - as a reminder - consists of 4 pegs). It is important to make a clone of the object, because later on we are going to delete some of its values and we don’t want to alter the main color code.
After initializing values, we do two important steps. First, we want to go through the selected pegs and compare their values and positions with the generated color code. If there are any pegs with the same position and color, we need to remove them from Maps
because we don’t want them in the next step of calculations. We also need to count those pegs whose position and values are the same, in order to show the hints circles as matching values (styled as black pegs).
Notice, that in order to loop through Map
objects, we need to use the for (let [key, value] of pegs) {}
syntax.
In the second pass, we need to find all of the selected pegs which are anywhere in the code and mark them later with a white color. To do that I used the simple implementation of the keyOf
function, which returns either -1
or the key of the item found.
I have to admit that the above algorithm wasn’t invented by me. I found this really interesting discussion and applied those findings to my game.
After the pegs’ validation, we need to increment the currentRow
value, reset the currentGuess
Map
, so the user can take another guess and update the exactMatches
and valueMatches
needed by the HintsRow
component.
HintsRow
provides feedback about selected pegs. The small black peg is placed for each code peg which is correct in both color and position. So there should be exactMatches
black pegs and valueMatches
white hint pegs:
If all of the pegs selected by the user are in the correct location, we have to update the endGame
status and inform the user about the success. If the current row is the last one and the user still didn’t guess the pattern, then the game is over.
Summary
I know that this is a rather long post and I really appreciate that you stayed with me this far :). I focused only on the most important parts of the game. The code related to enhanced functionality like reloading the game and toggling game rules I will leave as an exercise for you!
Let’s summarise what we’ve just learned about ECMAScript 6 and React library.
ES6:
- There are new
let
andconst
variable declarations, mostly to avoid hoisting. - The
str.startsWith()
is one of the newest string method, which determines whether a string begins with the characters of another string. - Maps are one of the new data structures which support a few handful methods like:
set
,get
,has
,delete
. - Use
for (var value of myArray) {}
to iterate throughMaps
. - Arrow functions
=>
are useful when you want to automatically bindthis
. - It is possible to create and import modules.
- The
default parameter
of the function allow parameters to be initialized with default values if no value is passed.
React:
- A component is a React class, which ideally is responsible for one thing only.
- Always return one DOM element which wraps child components or markup inside.
- Event handlers modify the state and
render()
reflects the current state. - The
className
keyword is used becauseclass
is taken in ES6. As wellhtmlFor
instead offor
. Props
are immutable in the child components.States
are mutable. After altering them, React takes care of re-rendering components.- Variables in
JSX
are passed in{}
. - Re-render components as little as possible, remember about the
shouldComponentUpdate
method.
I am aware that I barely touched the subject of ES6. I would highly recommend reading this well written online book about EcmaScript 6 by Dr. Axel Rauschmayer or just look into the Mozilla JavaScript documents if you are interested in going deeper into this topic.
In this particular example React may not be faster than standard native DOM operations. There are various discussions about the performance of that library. Despite that I would recommend to use it even for small games like this. At least you to get know the newest technology better in a fun, interesting way.
Big thanks to Rob Berry and Richard Nguyen for mentoring me while writing this game and article.